diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 12ba01777409f49bb84afea831d368d0bb59dd0f..d1243c205eaf2cf8711cd6012f5c5dbdc20b710b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,10 @@
+workflow:
+  rules:
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
+      when: never
+    - if: '$CI_COMMIT_BRANCH'
+
 stages:
   - license
   - build_and_test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 685499123554ca81439d4b6c9ee70575bd656186..01bb44f1c27e19e25777a4a6978c47a6c835bd04 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -108,12 +108,13 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/internal/config.h.in "${WOLF_CONFIG_D
 # ============ HEADERS ============ 
 SET(HDRS_CAPTURE
   include/${PROJECT_NAME}/capture/capture_base.h
+  include/${PROJECT_NAME}/capture/capture_diff_drive.h
+  include/${PROJECT_NAME}/capture/capture_landmarks_external.h
   include/${PROJECT_NAME}/capture/capture_motion.h
   include/${PROJECT_NAME}/capture/capture_odom_2d.h
   include/${PROJECT_NAME}/capture/capture_odom_3d.h
   include/${PROJECT_NAME}/capture/capture_pose.h
   include/${PROJECT_NAME}/capture/capture_void.h
-  include/${PROJECT_NAME}/capture/capture_diff_drive.h
   )
 SET(HDRS_COMMON
   include/${PROJECT_NAME}/common/factory.h
@@ -133,11 +134,15 @@ SET(HDRS_FACTOR
   include/${PROJECT_NAME}/factor/factor_pose_2d.h
   include/${PROJECT_NAME}/factor/factor_pose_3d.h
   include/${PROJECT_NAME}/factor/factor_quaternion_absolute.h
+  include/${PROJECT_NAME}/factor/factor_pose_3d_with_extrinsics.h
   include/${PROJECT_NAME}/factor/factor_relative_pose_2d.h
   include/${PROJECT_NAME}/factor/factor_relative_pose_2d_with_extrinsics.h
   include/${PROJECT_NAME}/factor/factor_relative_pose_3d.h
-  include/${PROJECT_NAME}/factor/factor_pose_3d_with_extrinsics.h
   include/${PROJECT_NAME}/factor/factor_relative_pose_3d_with_extrinsics.h
+  include/${PROJECT_NAME}/factor/factor_relative_position_2d.h
+  include/${PROJECT_NAME}/factor/factor_relative_position_2d_with_extrinsics.h
+  include/${PROJECT_NAME}/factor/factor_relative_position_3d.h
+  include/${PROJECT_NAME}/factor/factor_relative_position_3d_with_extrinsics.h
   include/${PROJECT_NAME}/factor/factor_velocity_local_direction_3d.h
   )
 SET(HDRS_FEATURE
@@ -185,6 +190,7 @@ SET(HDRS_PROCESSOR
   include/${PROJECT_NAME}/processor/processor_pose.h
   include/${PROJECT_NAME}/processor/processor_tracker.h
   include/${PROJECT_NAME}/processor/processor_tracker_feature.h
+  include/${PROJECT_NAME}/processor/processor_tracker_feature_landmark_external.h
   include/${PROJECT_NAME}/processor/processor_tracker_landmark.h
   include/${PROJECT_NAME}/processor/track_matrix.h
   )
@@ -244,12 +250,13 @@ SET(HDRS_YAML
 # ============ SOURCES ============ 
 SET(SRCS_CAPTURE
   src/capture/capture_base.cpp
+  src/capture/capture_diff_drive.cpp
+  src/capture/capture_landmarks_external.cpp
   src/capture/capture_motion.cpp
   src/capture/capture_odom_2d.cpp
   src/capture/capture_odom_3d.cpp
   src/capture/capture_pose.cpp
   src/capture/capture_void.cpp
-  src/capture/capture_diff_drive.cpp
   )
 SET(SRCS_COMMON
   src/common/node_base.cpp
@@ -294,6 +301,7 @@ SET(SRCS_PROCESSOR
   src/processor/processor_pose.cpp
   src/processor/processor_tracker.cpp
   src/processor/processor_tracker_feature.cpp
+  src/processor/processor_tracker_feature_landmark_external.cpp
   src/processor/processor_tracker_landmark.cpp
   src/processor/track_matrix.cpp
   )
diff --git a/include/core/capture/capture_landmarks_external.h b/include/core/capture/capture_landmarks_external.h
new file mode 100644
index 0000000000000000000000000000000000000000..27af193e1f73de46ec3f934c0f89b0ff0f2a9750
--- /dev/null
+++ b/include/core/capture/capture_landmarks_external.h
@@ -0,0 +1,61 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#pragma once
+
+//Wolf includes
+#include "core/capture/capture_base.h"
+
+namespace wolf {
+
+struct LandmarkDetection
+{
+    int id;                      // id of landmark
+    Eigen::VectorXd measure;     // either pose or position
+    Eigen::MatrixXd covariance;  // covariance of the measure
+    double quality;              // [0, 1] quality of the detection
+};
+
+WOLF_PTR_TYPEDEFS(CaptureLandmarksExternal);
+
+//class CaptureLandmarksExternal
+class CaptureLandmarksExternal : public CaptureBase
+{
+    protected:
+        std::vector<LandmarkDetection> detections_;
+
+    public:
+
+        CaptureLandmarksExternal(const TimeStamp& _ts, 
+                                 SensorBasePtr _sensor_ptr, 
+                                 const std::vector<int>& _ids = {},
+                                 const std::vector<Eigen::VectorXd>& _detections = {}, 
+                                 const std::vector<Eigen::MatrixXd>& _covs = {}, 
+                                 const std::vector<double>& _qualities = {});
+
+        ~CaptureLandmarksExternal() override;
+
+        std::vector<LandmarkDetection> getDetections() const {return detections_;};
+
+        void addDetection(const int& _id, const Eigen::VectorXd& _detection, const Eigen::MatrixXd& _cov, const double& quality);
+};
+
+} //namespace wolf
\ No newline at end of file
diff --git a/include/core/factor/factor_relative_pose_2d.h b/include/core/factor/factor_relative_pose_2d.h
index 4a608feb01c29b413b19608987767853eecf5407..d156213cd912cb8b82bef159804e184cac5018f6 100644
--- a/include/core/factor/factor_relative_pose_2d.h
+++ b/include/core/factor/factor_relative_pose_2d.h
@@ -142,11 +142,11 @@ class FactorRelativePose2d : public FactorAnalytic
          *
          **/
         void evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
-                                       std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
-                                       const std::vector<bool>& _compute_jacobian) const override;
+                               std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
+                               const std::vector<bool>& _compute_jacobian) const override;
         void evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
-                                       std::vector<Eigen::MatrixXd>& jacobians,
-                                       const std::vector<bool>& _compute_jacobian) const override;
+                               std::vector<Eigen::MatrixXd>& jacobians,
+                               const std::vector<bool>& _compute_jacobian) const override;
 
         /** \brief Returns the pure jacobians (without measurement noise) evaluated in the state blocks values
          * \param jacobians is an output vector of mapped eigen matrices that sould contain the jacobians w.r.t each state block
@@ -175,8 +175,8 @@ inline Eigen::VectorXd FactorRelativePose2d::evaluateResiduals(
 }
 
 inline void FactorRelativePose2d::evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd> >& _st_vector,
-                                                        std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
-                                                        const std::vector<bool>& _compute_jacobian) const
+                                                    std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
+                                                    const std::vector<bool>& _compute_jacobian) const
 {
     assert(jacobians.size() == 4);
     assert(_st_vector.size() == 4);
@@ -211,8 +211,8 @@ inline void FactorRelativePose2d::evaluateJacobians(const std::vector<Eigen::Map
 }
 
 inline void FactorRelativePose2d::evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
-                                                        std::vector<Eigen::MatrixXd>& jacobians,
-                                                        const std::vector<bool>& _compute_jacobian) const
+                                                    std::vector<Eigen::MatrixXd>& jacobians,
+                                                    const std::vector<bool>& _compute_jacobian) const
 {
     assert(jacobians.size() == 4);
     assert(_st_vector.size() == 4);
diff --git a/include/core/factor/factor_relative_pose_2d_with_extrinsics.h b/include/core/factor/factor_relative_pose_2d_with_extrinsics.h
index b0eea0f2b8f91f00a9a2bc8a7bda6ce1be66dc45..f0f70a5c15341c48f03e1b36ce86eb6e1e1415db 100644
--- a/include/core/factor/factor_relative_pose_2d_with_extrinsics.h
+++ b/include/core/factor/factor_relative_pose_2d_with_extrinsics.h
@@ -65,6 +65,8 @@ class FactorRelativePose2dWithExtrinsics : public FactorAutodiff<FactorRelativeP
                          const T* const _p_sensor,
                          const T* const _o_sensor,
                          T* _residuals) const;
+
+        Eigen::Vector3d residual() const;
 };
 
 inline FactorRelativePose2dWithExtrinsics::FactorRelativePose2dWithExtrinsics(const FeatureBasePtr& _ftr_ptr,
@@ -132,31 +134,49 @@ inline bool FactorRelativePose2dWithExtrinsics::operator ()(const T* const _p_re
     Eigen::Map<const Eigen::Matrix<T,2,1> > p_ref(_p_ref);
     Eigen::Map<const Eigen::Matrix<T,2,1> > p_target(_p_target);
     Eigen::Map<const Eigen::Matrix<T,2,1> > p_sensor(_p_sensor);
-    T o_ref = _o_ref[0];
-    T o_target = _o_target[0];
-    T o_sensor = _o_sensor[0];
+    const T& o_ref = _o_ref[0];
+    const T& o_target = _o_target[0];
+    const T& o_sensor = _o_sensor[0];
 
     Eigen::Matrix<T, 3, 1> expected_measurement;
     Eigen::Matrix<T, 3, 1> er; // error
 
-    // Expected measurement
-    // r1_p_r1_s1 = ps
-    // r2_p_r2_s2 = ps
-    // r1_R_s1 = R(os)
-    // r2_R_s2 = R(os)
-    // w_R_r1 = R(o1)
-    // w_R_r2 = R(o2)
-    // ----------------------------------------
-
-    // s1_p_s1_s2 = s1_R_r1*r1_p_s1_r1 +
-    //              s1_R_r1*r1_R_w*w_p_r1_w +
-    //              s1_R_r1*r1_R_w*w_p_w_r2 +
-    //              s1_R_r1*r1_R_w*w_R_r2*r2_p_r2_s2
-
-    // s1_p_s1_s2 = s1_R_r1*(r1_p_s1_r1 + r1_R_w*(w_p_r1_w + w_p_w_r2 + w_R_r2*r2_p_r2_s2))
-
-    expected_measurement.head(2) = Eigen::Rotation2D<T>(-o_sensor)*(-p_sensor + Eigen::Rotation2D<T>(-o_ref)*(-p_ref + p_target + Eigen::Rotation2D<T>(o_target)*p_sensor));
-    expected_measurement(2) = o_target - o_ref;
+    // FRAME CASE
+    if (not getFrameOtherList().empty())
+    {
+        // Expected measurement
+        // r1_p_s1 = p_sensor
+        // r2_p_s2 = p_sensor
+        // r1_R_s1 = R(o_sensor)
+        // r2_R_s2 = R(o_sensor)
+        // w_p_r1 = p_ref
+        // w_p_r2 = p_target
+        // w_R_r1 = R(o_ref)
+        // w_R_r2 = R(o_target)
+        // ----------------------------------------
+        // s1_p_s2 = s1_R_r1*(r1_R_w*(w_p_r2 + w_R_r2*r2_p_s2 - w_p_r1) - r1_p_s1)
+
+        expected_measurement.head(2) = Eigen::Rotation2D<T>(-o_sensor)*(-p_sensor + Eigen::Rotation2D<T>(-o_ref)*(-p_ref + p_target + Eigen::Rotation2D<T>(o_target)*p_sensor));
+        expected_measurement(2) = o_target - o_ref;
+    }
+    // LANDMARK CASE
+    else if (not getLandmarkOtherList().empty())
+    {
+        // Expected measurement
+        // r1_p_s1 = p_sensor
+        // r2_p_s2 = p_sensor
+        // r1_R_s1 = R(o_sensor)
+        // r2_R_s2 = R(o_sensor)
+        // w_p_r1 = p_ref
+        // w_p_r2 = p_target
+        // w_R_r1 = R(o_ref)
+        // w_R_r2 = R(o_target)
+        // ----------------------------------------
+        // s1_p_s2 = s1_R_r1*(r1_R_w*(w_p_r2 - w_p_r1) - r1_p_s1)
+
+        expected_measurement.head(2) = Eigen::Rotation2D<T>(-o_sensor)*(-p_sensor + Eigen::Rotation2D<T>(-o_ref)*(-p_ref + p_target));
+        expected_measurement(2) = o_target - o_ref - o_sensor;
+    }
 
     // Error
     er = expected_measurement - getMeasurement().cast<T>();
@@ -186,6 +206,34 @@ inline bool FactorRelativePose2dWithExtrinsics::operator ()(const T* const _p_re
     return true;
 }
 
+inline Eigen::Vector3d FactorRelativePose2dWithExtrinsics::residual() const
+{
+    Eigen::Vector3d res;
+    Eigen::VectorXd p_sensor, o_sensor, p_ref, o_ref, p_target, o_target;
+    p_sensor = getCapture()->getSensorP()->getState();
+    o_sensor = getCapture()->getSensorO()->getState();
+    // FRAME CASE
+    if (not getFrameOtherList().empty())
+    {
+        p_ref    = getFrameOther()->getP()->getState();
+        o_ref    = getFrameOther()->getO()->getState();
+        p_target = getCapture()->getFrame()->getP()->getState();
+        o_target = getCapture()->getFrame()->getO()->getState();
+    }
+    // LANDMARK CASE
+    else if (not getLandmarkOtherList().empty())
+    {
+        p_ref    = getCapture()->getFrame()->getP()->getState();
+        o_ref    = getCapture()->getFrame()->getO()->getState();
+        p_target = getLandmarkOther()->getP()->getState();
+        o_target = getLandmarkOther()->getO()->getState();
+    }
+
+    operator() (p_ref.data(), o_ref.data(), p_target.data(), o_target.data(), p_sensor.data(), o_sensor.data(), res.data());
+
+    return res;
+}
+
 } // namespace wolf
 
 #endif
diff --git a/include/core/factor/factor_relative_pose_3d.h b/include/core/factor/factor_relative_pose_3d.h
index 6003c446732399f1abe6c4127dfb257543c82c2c..4ea300d5c13d35aed9ff1c60ba57c822cd4386c6 100644
--- a/include/core/factor/factor_relative_pose_3d.h
+++ b/include/core/factor/factor_relative_pose_3d.h
@@ -19,12 +19,6 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 //--------LICENSE_END--------
-/*
- * factor_relative_pose_3d.h
- *
- *  Created on: Oct 7, 2016
- *      Author: jsola
- */
 
 #ifndef FACTOR_RELATIVE_POSE_3D_H_
 #define FACTOR_RELATIVE_POSE_3D_H_
@@ -48,24 +42,31 @@ class FactorRelativePose3d : public FactorAutodiff<FactorRelativePose3d,6,3,4,3,
                              const FactorTopology& _top,
                              FactorStatus _status = FAC_ACTIVE);
 
+        FactorRelativePose3d(const FeatureBasePtr& _ftr_current_ptr,
+                             const LandmarkBasePtr& _landmark_ptr,
+                             const ProcessorBasePtr& _processor_ptr,
+                             bool _apply_loss_function,
+                             const FactorTopology& _top,
+                             FactorStatus _status = FAC_ACTIVE);
+
         ~FactorRelativePose3d() override = default;
 
         template<typename T>
-                bool operator ()(const T* const _p_current,
-                                 const T* const _q_current,
-                                 const T* const _p_past,
-                                 const T* const _q_past,
+                bool operator ()(const T* const _p_target,
+                                 const T* const _q_target,
+                                 const T* const _p_ref,
+                                 const T* const _q_ref,
                                  T* _residuals) const;
 
         template<typename T>
-                bool expectation(const T* const _p_current,
-                                 const T* const _q_current,
-                                 const T* const _p_past,
-                                 const T* const _q_past,
+                bool expectation(const T* const _p_target,
+                                 const T* const _q_target,
+                                 const T* const _p_ref,
+                                 const T* const _q_ref,
                                  T* _expectation_dp,
                                  T* _expectation_dq) const;
 
-        Eigen::VectorXd expectation() const;
+        Eigen::Vector7d expectation() const;
 
     private:
         template<typename T>
@@ -118,39 +119,64 @@ inline FactorRelativePose3d::FactorRelativePose3d(const FeatureBasePtr& _ftr_cur
     MatrixSizeCheck<6,6>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper());
 }
 
+inline FactorRelativePose3d::FactorRelativePose3d(const FeatureBasePtr& _ftr_current_ptr,
+                                                  const LandmarkBasePtr& _lmk_ptr,
+                                                  const ProcessorBasePtr& _processor_ptr,
+                                                  bool _apply_loss_function,
+                                                  const FactorTopology& _top,
+                                                  FactorStatus _status) :
+        FactorAutodiff<FactorRelativePose3d, 6, 3, 4, 3, 4>("FactorRelativePose3d",     // type
+                                                            _top, // topology
+                                                            _ftr_current_ptr,   // feature
+                                                            nullptr,            // frame other
+                                                            nullptr,            // capture other
+                                                            nullptr,            // feature other
+                                                            _lmk_ptr,           // landmark other
+                                                            _processor_ptr,     // processor
+                                                            _apply_loss_function,
+                                                            _status,
+                                                            _lmk_ptr->getP(), // landmark P
+                                                            _lmk_ptr->getO(), // landmark Q
+                                                            _ftr_current_ptr->getFrame()->getP(), // current frame P
+                                                            _ftr_current_ptr->getFrame()->getO()) // current frame Q
+{
+    MatrixSizeCheck<7,1>::check(_ftr_current_ptr->getMeasurement());
+    MatrixSizeCheck<6,6>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper());
+}
+
 template<typename T>
-inline bool FactorRelativePose3d::expectation(const T* const _p_current,
-                                      const T* const _q_current,
-                                      const T* const _p_past,
-                                      const T* const _q_past,
-                                      T* _expectation_dp,
-                                      T* _expectation_dq) const
+inline bool FactorRelativePose3d::expectation(const T* const _p_target,
+                                              const T* const _q_target,
+                                              const T* const _p_ref,
+                                              const T* const _q_ref,
+                                              T* _expectation_dp,
+                                              T* _expectation_dq) const
 {
-    Eigen::Map<const Eigen::Matrix<T,3,1> > p_current(_p_current);
-    Eigen::Map<const Eigen::Quaternion<T> > q_current(_q_current);
-    Eigen::Map<const Eigen::Matrix<T,3,1> > p_past(_p_past);
-    Eigen::Map<const Eigen::Quaternion<T> > q_past(_q_past);
+    Eigen::Map<const Eigen::Matrix<T,3,1> > p_target(_p_target);
+    Eigen::Map<const Eigen::Quaternion<T> > q_target(_q_target);
+    Eigen::Map<const Eigen::Matrix<T,3,1> > p_ref(_p_ref);
+    Eigen::Map<const Eigen::Quaternion<T> > q_ref(_q_ref);
     Eigen::Map<Eigen::Matrix<T,3,1> > expectation_dp(_expectation_dp);
     Eigen::Map<Eigen::Quaternion<T> > expectation_dq(_expectation_dq);
 
-//     std::cout << "p_current: " << p_current(0) << " "
-//                                << p_current(1) << " "
-//                                << p_current(2) << "\n";
-//     std::cout << "q_current: " << q_current.coeffs()(0) << " "
-//                                << q_current.coeffs()(1) << " "
-//                                << q_current.coeffs()(2) << " "
-//                                << q_current.coeffs()(3) << "\n";
-//     std::cout << "p_past: " << p_past(0) << " "
-//                             << p_past(1) << " "
-//                             << p_past(2) << "\n";
-//     std::cout << "q_past: " << q_past.coeffs()(0) << " "
-//                             << q_past.coeffs()(1) << " "
-//                             << q_past.coeffs()(2) << " "
-//                             << q_past.coeffs()(3) << "\n";
+//     std::cout << "p_target: " << p_target(0) << " "
+//                               << p_target(1) << " "
+//                               << p_target(2) << "\n";
+//     std::cout << "q_target: " << q_target.coeffs()(0) << " "
+//                               << q_target.coeffs()(1) << " "
+//                               << q_target.coeffs()(2) << " "
+//                               << q_target.coeffs()(3) << "\n";
+//     std::cout << "p_ref: " << p_ref(0) << " "
+//                            << p_ref(1) << " "
+//                            << p_ref(2) << "\n";
+//     std::cout << "q_ref: " << q_ref.coeffs()(0) << " "
+//                            << q_ref.coeffs()(1) << " "
+//                            << q_ref.coeffs()(2) << " "
+//                            << q_ref.coeffs()(3) << "\n";
 
     // estimate motion increment, dp, dq;
-    expectation_dp = q_past.conjugate() * (p_current - p_past);
-    expectation_dq = q_past.conjugate() * q_current;
+    expectation_dp = q_ref.conjugate() * (p_target - p_ref);
+    expectation_dq = q_ref.conjugate() * q_target;
 
 //    std::cout << "exp_p: " << expectation_dp(0) << " "
 //                           << expectation_dp(1) << " "
@@ -163,34 +189,38 @@ inline bool FactorRelativePose3d::expectation(const T* const _p_current,
     return true;
 }
 
-inline Eigen::VectorXd FactorRelativePose3d::expectation() const
+inline Eigen::Vector7d FactorRelativePose3d::expectation() const
 {
-    Eigen::VectorXd exp(7);
+    Eigen::Vector7d exp;
     auto frm_current = getFeature()->getFrame();
     auto frm_past = getFrameOther();
-    const Eigen::VectorXd frame_current_pos  = frm_current->getP()->getState();
-    const Eigen::VectorXd frame_current_ori  = frm_current->getO()->getState();
-    const Eigen::VectorXd frame_past_pos     = frm_past->getP()->getState();
-    const Eigen::VectorXd frame_past_ori     = frm_past->getO()->getState();
-
-//    std::cout << "frame_current_pos: " << frm_current->getP()->getState().transpose() << std::endl;
-//    std::cout << "frame_past_pos: " << frm_past->getP()->getState().transpose() << std::endl;
-
-    expectation(frame_current_pos.data(),
-                frame_current_ori.data(),
-                frame_past_pos.data(),
-                frame_past_ori.data(),
+    auto lmk = getLandmarkOther();
+
+    const Eigen::VectorXd target_pos  = ( frm_past ? frm_current->getP()->getState() : lmk->getP()->getState());
+    const Eigen::VectorXd target_ori  = ( frm_past ? frm_current->getO()->getState() : lmk->getO()->getState());
+    const Eigen::VectorXd ref_pos     = ( frm_past ? frm_past->getP()->getState() : frm_current->getP()->getState());
+    const Eigen::VectorXd ref_ori     = ( frm_past ? frm_past->getO()->getState() : frm_current->getO()->getState());
+
+//    std::cout << "target_pos: " << target_pos.transpose() << std::endl;
+//    std::cout << "target_ori: " << target_ori.coeffs().transpose() << std::endl;
+//    std::cout << "ref_pos: " << ref_pos.transpose() << std::endl;
+//    std::cout << "ref_ori: " << ref_ori.coeffs().transpose() << std::endl;
+
+    expectation(target_pos.data(),
+                target_ori.data(),
+                ref_pos.data(),
+                ref_ori.data(),
                 exp.data(),
                 exp.data()+3);
     return exp;
 }
 
 template<typename T>
-inline bool FactorRelativePose3d::operator ()(const T* const _p_current,
-                                      const T* const _q_current,
-                                      const T* const _p_past,
-                                      const T* const _q_past,
-                                      T* _residuals) const
+inline bool FactorRelativePose3d::operator ()(const T* const _p_target,
+                                              const T* const _q_target,
+                                              const T* const _p_ref,
+                                              const T* const _q_ref,
+                                              T* _residuals) const
 {
 
     /* Residual expression
@@ -232,7 +262,7 @@ inline bool FactorRelativePose3d::operator ()(const T* const _p_current,
     Eigen::Map<Eigen::Matrix<T,6,1> > residuals(_residuals);
 
     Eigen::Matrix<T, Eigen::Dynamic, 1> expected(7) ;
-    expectation(_p_current, _q_current, _p_past, _q_past, expected.data(), expected.data()+3);
+    expectation(_p_target, _q_target, _p_ref, _q_ref, expected.data(), expected.data()+3);
 
     // measured motion increment, dp_m, dq_m
     // Eigen::Matrix<T,3,1> dp_m = getMeasurement().head<3>().cast<T>();
diff --git a/include/core/factor/factor_relative_pose_3d_with_extrinsics.h b/include/core/factor/factor_relative_pose_3d_with_extrinsics.h
index 19245f6bcf630f737e4a01aa78b4a11030d8264a..66468bbcafb2b4480df5077ca6b2a16d6ff61839 100644
--- a/include/core/factor/factor_relative_pose_3d_with_extrinsics.h
+++ b/include/core/factor/factor_relative_pose_3d_with_extrinsics.h
@@ -58,7 +58,7 @@ class FactorRelativePose3dWithExtrinsics : public FactorAutodiff<FactorRelativeP
 
         /** \brief Class Destructor
          */
-        ~FactorRelativePose3dWithExtrinsics() override;
+        ~FactorRelativePose3dWithExtrinsics() override = default;
  
         /** brief : compute the residual from the state blocks being iterated by the solver.
          **/
@@ -116,6 +116,8 @@ inline FactorRelativePose3dWithExtrinsics::FactorRelativePose3dWithExtrinsics(co
                    _feature_ptr->getCapture()->getSensorP(),
                    _feature_ptr->getCapture()->getSensorO())
 {
+    assert(_feature_ptr->getCapture()->getSensorP() != nullptr && "No extrinsics found!");
+    assert(_feature_ptr->getCapture()->getSensorO() != nullptr && "No extrinsics found!");
 }
 
 inline FactorRelativePose3dWithExtrinsics::FactorRelativePose3dWithExtrinsics(const FeatureBasePtr& _feature_ptr,
@@ -143,45 +145,59 @@ inline FactorRelativePose3dWithExtrinsics::FactorRelativePose3dWithExtrinsics(co
 {
 }
 
-inline FactorRelativePose3dWithExtrinsics::~FactorRelativePose3dWithExtrinsics()
-{
-    //
-}
-
 template<typename T>
 inline bool FactorRelativePose3dWithExtrinsics::operator ()(const T* const _p_ref,
-                                                     const T* const _o_ref,
-                                                     const T* const _p_target,
-                                                     const T* const _o_target,
-                                                     const T* const _p_sensor,
-                                                     const T* const _o_sensor,
-                                                     T* _residuals) const
+                                                            const T* const _o_ref,
+                                                            const T* const _p_target,
+                                                            const T* const _o_target,
+                                                            const T* const _p_sensor,
+                                                            const T* const _o_sensor,
+                                                            T* _residuals) const
 {
     // Maps
-    Eigen::Map<const Eigen::Matrix<T,3,1>> p_r_c(_p_sensor);
-    Eigen::Map<const Eigen::Quaternion<T>> q_r_c(_o_sensor);
+    Eigen::Map<const Eigen::Matrix<T,3,1>> p_r_s(_p_sensor);
+    Eigen::Map<const Eigen::Quaternion<T>> q_r_s(_o_sensor);
     Eigen::Map<const Eigen::Matrix<T,3,1>> p_w_r(_p_ref);
     Eigen::Map<const Eigen::Quaternion<T>> q_w_r(_o_ref);
-    Eigen::Map<const Eigen::Matrix<T,3,1>> p_w_l(_p_target);
-    Eigen::Map<const Eigen::Quaternion<T>> q_w_l(_o_target);
+    Eigen::Map<const Eigen::Matrix<T,3,1>> p_w_t(_p_target);
+    Eigen::Map<const Eigen::Quaternion<T>> q_w_t(_o_target);
     Eigen::Map<Eigen::Matrix<T,6,1>> residuals(_residuals);
 
+    // WOLF_INFO("p_sensor: ", p_r_s.transpose());
+    // WOLF_INFO("o_sensor: ", q_r_s.coeffs().transpose());
+    // WOLF_INFO("p_ref:    ", p_w_r.transpose());
+    // WOLF_INFO("o_ref:    ", q_w_r.coeffs().transpose());
+    // WOLF_INFO("p_target: ", p_w_t.transpose());
+    // WOLF_INFO("o_target: ", q_w_t.coeffs().transpose());
+
     // Expected measurement
-    Eigen::Quaternion<T> q_c_w = (q_w_r * q_r_c).conjugate();
-    Eigen::Quaternion<T> q_c_l = q_c_w * q_w_l;
-    Eigen::Matrix<T,3,1> p_c_l = q_c_w * (-(p_w_r + q_w_r * p_r_c) + p_w_l);
+    Eigen::Quaternion<T> expected_q;
+    Eigen::Matrix<T,3,1> expected_p;
+    // FRAME CASE
+    if (not getFrameOtherList().empty())
+    {
+        expected_p = q_r_s.conjugate() * (q_w_r.conjugate() * (p_w_t + q_w_t * p_r_s - p_w_r) - p_r_s);
+        expected_q = q_r_s.conjugate() * q_w_r.conjugate() * q_w_t * q_r_s;
+    }
+    // LANDMARK CASE
+    else if (not getLandmarkOtherList().empty())
+    {
+        expected_p = q_r_s.conjugate() * (q_w_r.conjugate() * (p_w_t - p_w_r) - p_r_s);
+        expected_q = q_r_s.conjugate() * q_w_r.conjugate() * q_w_t;
+    }
 
     // Measurement
-    Eigen::Vector3d      p_c_l_meas(getMeasurement().head<3>());
+    Eigen::Vector3d      p_meas(getMeasurement().head<3>());
     Eigen::Quaterniond   q_c_l_meas(getMeasurement().data() + 3 );
     Eigen::Quaternion<T> q_l_c_meas = q_c_l_meas.conjugate().cast<T>();
     //Eigen::Matrix<T,3,1> p_l_c_meas = -q_l_c_meas * p_c_l_meas.cast<T>();
 
     // Error
     Eigen::Matrix<T, 6, 1> err;
-    err.head(3) = q_l_c_meas * (p_c_l_meas.cast<T>() - p_c_l);
-    //err.tail(3) = wolf::log_q(q_l_c_meas * q_c_l);  // true error function for which the covariance should be computed
-    err.tail(3) = T(2)*(q_l_c_meas * q_c_l).vec();  // 1rst order approximation of sin function ( 2*sin(aa/2) ~ aa )
+    // err.head(3) = q_l_c_meas * (p_meas.cast<T>() - expected_p);
+    err.head(3) = (p_meas - expected_p);
+    err.tail(3) = wolf::log_q(q_l_c_meas * expected_q);  // true error function for which the covariance should be computed
+    //err.tail(3) = T(2)*(q_l_c_meas * expected_q).vec();  // 1rst order approximation of sin function ( 2*sin(aa/2) ~ aa )
 
     // Residual
     residuals = getMeasurementSquareRootInformationUpper().cast<T>() * err;
@@ -192,15 +208,27 @@ inline bool FactorRelativePose3dWithExtrinsics::operator ()(const T* const _p_re
 inline Eigen::Vector6d FactorRelativePose3dWithExtrinsics::residual() const
 {
     Eigen::Vector6d res;
-    double * p_camera, * o_camera, * p_ref, * o_ref, * p_target, * o_target;
-    p_camera = getCapture()->getSensorP()->getState().data();
-    o_camera = getCapture()->getSensorO()->getState().data();
-    p_ref    = getCapture()->getFrame()->getP()->getState().data();
-    o_ref    = getCapture()->getFrame()->getO()->getState().data();
-    p_target = getLandmarkOther()->getP()->getState().data();
-    o_target = getLandmarkOther()->getO()->getState().data();
-
-    operator() (p_camera, o_camera, p_ref, o_ref, p_target, o_target, res.data());
+    Eigen::VectorXd p_sensor, o_sensor, p_ref, o_ref, p_target, o_target;
+    p_sensor = getCapture()->getSensorP()->getState();
+    o_sensor = getCapture()->getSensorO()->getState();
+    // FRAME CASE
+    if (not getFrameOtherList().empty())
+    {
+        p_ref    = getFrameOther()->getP()->getState();
+        o_ref    = getFrameOther()->getO()->getState();
+        p_target = getCapture()->getFrame()->getP()->getState();
+        o_target = getCapture()->getFrame()->getO()->getState();
+    }
+    // LANDMARK CASE
+    else if (not getLandmarkOtherList().empty())
+    {
+        p_ref    = getCapture()->getFrame()->getP()->getState();
+        o_ref    = getCapture()->getFrame()->getO()->getState();
+        p_target = getLandmarkOther()->getP()->getState();
+        o_target = getLandmarkOther()->getO()->getState();
+    }
+
+    operator() (p_ref.data(), o_ref.data(), p_target.data(), o_target.data(), p_sensor.data(), o_sensor.data(), res.data());
 
     return res;
 }
diff --git a/include/core/factor/factor_relative_position_2d.h b/include/core/factor/factor_relative_position_2d.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a2c7a0e52a7b58f8cda42586c56abff31793e79
--- /dev/null
+++ b/include/core/factor/factor_relative_position_2d.h
@@ -0,0 +1,258 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#pragma once
+
+//Wolf includes
+#include "core/factor/factor_analytic.h"
+#include "core/landmark/landmark_base.h"
+#include "core/math/rotations.h"
+#include <Eigen/StdVector>
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(FactorRelativePosition2d);
+    
+//class
+class FactorRelativePosition2d : public FactorAnalytic
+{
+    public:
+
+        /** \brief Constructor of category FAC_FRAME
+         **/
+        FactorRelativePosition2d(const FeatureBasePtr& _ftr_ptr,
+                                 const FrameBasePtr& _frame_other_ptr,
+                                 const ProcessorBasePtr& _processor_ptr,
+                                 bool _apply_loss_function,
+                                 const FactorTopology& _top,
+                                 FactorStatus _status = FAC_ACTIVE) :
+            FactorAnalytic("FactorRelativePosition2d",
+                           _top,
+                           _ftr_ptr,
+                           _frame_other_ptr,
+                           nullptr,
+                           nullptr,
+                           nullptr,
+                           _processor_ptr,
+                           _apply_loss_function,
+                           _status,
+                           _frame_other_ptr->getP(),
+                           _frame_other_ptr->getO(),
+                           _ftr_ptr->getFrame()->getP())
+        {
+            //
+        }
+
+        /** \brief Constructor of category FAC_FEATURE
+         **/
+        FactorRelativePosition2d(const FeatureBasePtr& _ftr_ptr,
+                                 const FeatureBasePtr& _ftr_other_ptr,
+                                 const ProcessorBasePtr& _processor_ptr,
+                                 bool _apply_loss_function,
+                                 const FactorTopology& _top,
+                                 FactorStatus _status = FAC_ACTIVE) :
+            FactorAnalytic("FactorRelativePosition2d",
+                           _top,
+                           _ftr_ptr,
+                           nullptr,
+                           nullptr,
+                           _ftr_other_ptr,
+                           nullptr,
+                           _processor_ptr,
+                           _apply_loss_function,
+                           _status,
+                           _ftr_ptr->getFrame()->getP(),
+                           _ftr_ptr->getFrame()->getO(),
+                           _ftr_other_ptr->getFrame()->getP() )
+        {
+            //
+        }
+
+        /** \brief Constructor of category FAC_LANDMARK
+         **/
+        FactorRelativePosition2d(const FeatureBasePtr& _ftr_ptr,
+                                 const LandmarkBasePtr& _landmark_other_ptr,
+                                 const ProcessorBasePtr& _processor_ptr,
+                                 bool _apply_loss_function,
+                                 const FactorTopology& _top,
+                                 FactorStatus _status = FAC_ACTIVE) :
+            FactorAnalytic("FactorRelativePosition2d",
+                           _top,
+                           _ftr_ptr,
+                           nullptr,
+                           nullptr,
+                           nullptr,
+                           _landmark_other_ptr,
+                           _processor_ptr,
+                           _apply_loss_function,
+                           _status,
+                           _ftr_ptr->getFrame()->getP(),
+                           _ftr_ptr->getFrame()->getO(),
+                           _landmark_other_ptr->getP())
+        {
+            //
+        }
+
+        ~FactorRelativePosition2d() override = default;
+
+        /** \brief Returns the factor residual size
+         **/
+        unsigned int getSize() const override
+        {
+            return 2;
+        }
+
+        /** \brief Returns the residual evaluated in the states provided
+         *
+         * Returns the residual evaluated in the states provided in a std::vector of mapped Eigen::VectorXd
+         **/
+        Eigen::VectorXd evaluateResiduals(
+                const std::vector<Eigen::Map<const Eigen::VectorXd> >& _st_vector) const override;
+
+        /** \brief Returns the jacobians evaluated in the states provided
+         *
+         * Returns the jacobians evaluated in the states provided in std::vector of mapped Eigen::VectorXd.
+         * IMPORTANT: only fill the jacobians of the state blocks specified in _compute_jacobian.
+         *
+         * \param _st_vector is a vector containing the mapped eigen vectors of all state blocks involved in the factor
+         * \param jacobians is an output vector of mapped eigen matrices that sould contain the jacobians w.r.t each state block
+         * \param _compute_jacobian is a vector that specifies whether the ith jacobian sould be computed or not
+         *
+         **/
+        void evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
+                                       std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
+                                       const std::vector<bool>& _compute_jacobian) const override;
+        void evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
+                                       std::vector<Eigen::MatrixXd>& jacobians,
+                                       const std::vector<bool>& _compute_jacobian) const override;
+
+        /** \brief Returns the pure jacobians (without measurement noise) evaluated in the state blocks values
+         * \param jacobians is an output vector of mapped eigen matrices that sould contain the jacobians w.r.t each state block
+         *
+         **/
+        void evaluatePureJacobians(std::vector<Eigen::MatrixXd>& jacobians) const override;
+
+};
+
+/// IMPLEMENTATION ///
+
+inline Eigen::VectorXd FactorRelativePosition2d::evaluateResiduals(
+        const std::vector<Eigen::Map<const Eigen::VectorXd> >& _st_vector) const
+{
+    Eigen::VectorXd residual(2);
+    Eigen::VectorXd expected_measurement(2);
+    // Expected measurement
+    Eigen::Matrix2d R = Eigen::Rotation2D<double>(-_st_vector[1](0)).matrix();
+    expected_measurement = R * (_st_vector[2] - _st_vector[0]); // rotar menys l'angle de primer (-_o1)
+    // Residual
+    residual = expected_measurement - getMeasurement();
+    residual = getMeasurementSquareRootInformationUpper() * residual;
+    return residual;
+}
+
+inline void FactorRelativePosition2d::evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd> >& _st_vector,
+                                                        std::vector<Eigen::Map<Eigen::MatrixRowXd> >& jacobians,
+                                                        const std::vector<bool>& _compute_jacobian) const
+{
+    assert(jacobians.size() == 3);
+    assert(_st_vector.size() == 3);
+    assert(_compute_jacobian.size() == 3);
+
+    if (_compute_jacobian[0])
+    {
+        jacobians[0] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,2) <<
+                        -cos(_st_vector[1](0)), -sin(_st_vector[1](0)),
+                        sin(_st_vector[1](0)), -cos(_st_vector[1](0))).finished();
+    }
+    if (_compute_jacobian[1])
+    {
+        jacobians[1] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,1) <<
+                        -(_st_vector[2](0) - _st_vector[0](0)) * sin(_st_vector[1](0)) + 
+                         (_st_vector[2](1) - _st_vector[0](1)) * cos(_st_vector[1](0)),
+                        -(_st_vector[2](0) - _st_vector[0](0)) * cos(_st_vector[1](0)) - 
+                         (_st_vector[2](1) - _st_vector[0](1)) * sin(_st_vector[1](0))).finished();
+    }
+    if (_compute_jacobian[2])
+    {
+        jacobians[2] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,2) <<
+                        cos(_st_vector[1](0)), sin(_st_vector[1](0)),
+                        -sin(_st_vector[1](0)), cos(_st_vector[1](0))).finished();
+    }
+}
+
+inline void FactorRelativePosition2d::evaluateJacobians(const std::vector<Eigen::Map<const Eigen::VectorXd>>& _st_vector,
+                                                        std::vector<Eigen::MatrixXd>& jacobians,
+                                                        const std::vector<bool>& _compute_jacobian) const
+{
+    assert(jacobians.size() == 3);
+    assert(_st_vector.size() == 3);
+    assert(_compute_jacobian.size() == 3);
+
+    if (_compute_jacobian[0])
+    {
+        jacobians[0] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,2) <<
+                        -cos(_st_vector[1](0)), -sin(_st_vector[1](0)),
+                        sin(_st_vector[1](0)), -cos(_st_vector[1](0))).finished();
+    }
+    if (_compute_jacobian[1])
+    {
+        jacobians[1] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,1) <<
+                        -(_st_vector[2](0) - _st_vector[0](0)) * sin(_st_vector[1](0)) + 
+                         (_st_vector[2](1) - _st_vector[0](1)) * cos(_st_vector[1](0)),
+                        -(_st_vector[2](0) - _st_vector[0](0)) * cos(_st_vector[1](0)) - 
+                         (_st_vector[2](1) - _st_vector[0](1)) * sin(_st_vector[1](0))).finished();
+    }
+    if (_compute_jacobian[2])
+    {
+        jacobians[2] = getMeasurementSquareRootInformationUpper() * (Eigen::MatrixRowXd(2,2) <<
+                        cos(_st_vector[1](0)), sin(_st_vector[1](0)),
+                        -sin(_st_vector[1](0)), cos(_st_vector[1](0))).finished();
+    }
+}
+
+inline void FactorRelativePosition2d::evaluatePureJacobians(std::vector<Eigen::MatrixXd>& jacobians) const
+{
+    assert(jacobians.size() == 3);
+
+    jacobians[0] = (Eigen::MatrixXd(2,2) <<
+                    -cos(getStateBlockPtrVector()[1]->getState()(0)), 
+                    -sin(getStateBlockPtrVector()[1]->getState()(0)),
+                     sin(getStateBlockPtrVector()[1]->getState()(0)), 
+                    -cos(getStateBlockPtrVector()[1]->getState()(0))).finished();
+
+    jacobians[1] = (Eigen::MatrixXd(2,1) <<
+                    -(getStateBlockPtrVector()[2]->getState()(0)
+                        - getStateBlockPtrVector()[0]->getState()(0)) * sin(getStateBlockPtrVector()[1]->getState()(0))
+                        + (getStateBlockPtrVector()[2]->getState()(1)
+                        - getStateBlockPtrVector()[0]->getState()(1)) * cos(getStateBlockPtrVector()[1]->getState()(0)),
+                    -(getStateBlockPtrVector()[2]->getState()(0)
+                        - getStateBlockPtrVector()[0]->getState()(0)) * cos(getStateBlockPtrVector()[1]->getState()(0))
+                        - (getStateBlockPtrVector()[2]->getState()(1)
+                        - getStateBlockPtrVector()[0]->getState()(1)) * sin(getStateBlockPtrVector()[1]->getState()(0))).finished();
+
+    jacobians[2] = (Eigen::MatrixXd(2,2) <<
+                     cos(getStateBlockPtrVector()[1]->getState()(0)),
+                     sin(getStateBlockPtrVector()[1]->getState()(0)),
+                    -sin(getStateBlockPtrVector()[1]->getState()(0)),
+                     cos(getStateBlockPtrVector()[1]->getState()(0))).finished();
+}
+
+} // namespace wolf
\ No newline at end of file
diff --git a/include/core/factor/factor_relative_position_2d_with_extrinsics.h b/include/core/factor/factor_relative_position_2d_with_extrinsics.h
new file mode 100644
index 0000000000000000000000000000000000000000..fd36867f4a93e08fe671f0491a07276ed00b6624
--- /dev/null
+++ b/include/core/factor/factor_relative_position_2d_with_extrinsics.h
@@ -0,0 +1,128 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#pragma once
+
+//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(FactorRelativePosition2dWithExtrinsics);
+
+//class
+class FactorRelativePosition2dWithExtrinsics : public FactorAutodiff<FactorRelativePosition2dWithExtrinsics, 2, 2, 1, 2, 2, 1>
+{
+    public:
+
+        /** \brief Class constructor Frame-Landmark
+         */
+        FactorRelativePosition2dWithExtrinsics(const FeatureBasePtr& _ftr_ptr,
+                                               const LandmarkBasePtr& _lmk_other_ptr,
+                                               const ProcessorBasePtr& _processor_ptr,
+                                               bool _apply_loss_function,
+                                               const FactorTopology& _top = TOP_LMK,
+                                               FactorStatus _status = FAC_ACTIVE);
+
+        ~FactorRelativePosition2dWithExtrinsics() override = default;
+
+        template<typename T>
+        bool operator ()(const T* const _p_ref,
+                         const T* const _o_ref,
+                         const T* const _p_target,
+                         const T* const _p_sensor,
+                         const T* const _o_sensor,
+                         T* _residuals) const;
+};
+
+inline FactorRelativePosition2dWithExtrinsics::FactorRelativePosition2dWithExtrinsics(const FeatureBasePtr& _ftr_ptr,
+                                                                                      const LandmarkBasePtr& _lmk_other_ptr,
+                                                                                      const ProcessorBasePtr& _processor_ptr,
+                                                                                      bool _apply_loss_function,
+                                                                                      const FactorTopology& _top,
+                                                                                      FactorStatus _status) :
+     FactorAutodiff("FactorRelativePosition2dWithExtrinsics",
+                    _top,
+                    _ftr_ptr,
+                    nullptr, nullptr, nullptr, _lmk_other_ptr,
+                    _processor_ptr,
+                    _apply_loss_function,
+                    _status,
+                    _ftr_ptr->getFrame()->getP(),
+                    _ftr_ptr->getFrame()->getO(),
+                    _lmk_other_ptr->getP(),
+                    _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!");
+    //
+}
+
+template<typename T>
+inline bool FactorRelativePosition2dWithExtrinsics::operator ()(const T* const _p_ref,
+                                                                const T* const _o_ref,
+                                                                const T* const _p_target,
+                                                                const T* const _p_sensor,
+                                                                const T* const _o_sensor,
+                                                                T* _residuals) const
+{
+    // MAPS
+    Eigen::Map<Eigen::Matrix<T,2,1> > res(_residuals);
+    Eigen::Map<const Eigen::Matrix<T,2,1> > p_ref(_p_ref);
+    Eigen::Map<const Eigen::Matrix<T,2,1> > p_target(_p_target);
+    Eigen::Map<const Eigen::Matrix<T,2,1> > p_sensor(_p_sensor);
+    T o_ref = _o_ref[0];
+    T o_sensor = _o_sensor[0];
+
+    // Expected measurement
+    Eigen::Matrix<T, 2, 1> expected_measurement = Eigen::Rotation2D<T>(-o_sensor) * 
+                                                  (-p_sensor + Eigen::Rotation2D<T>(-o_ref) * (p_target - p_ref));
+
+    // Residuals
+    res = getMeasurementSquareRootInformationUpper() * (expected_measurement - getMeasurement());
+
+    ////////////////////////////////////////////////////////
+    // print Jacobian. Uncomment this as you wish (remember to uncomment #include "ceres/jet.h" above):
+//    using ceres::Jet;
+//    Eigen::MatrixXs J(3,6);
+//    J.row(0) = ((Jet<Scalar, 6>)(er(0))).v;
+//    J.row(1) = ((Jet<Scalar, 6>)(er(1))).v;
+//    J.row(2) = ((Jet<Scalar, 6>)(er(2))).v;
+//    J.row(0) = ((Jet<Scalar, 6>)(res(0))).v;
+//    J.row(1) = ((Jet<Scalar, 6>)(res(1))).v;
+//    J.row(2) = ((Jet<Scalar, 6>)(res(2))).v;
+//    if (sizeof(er(0)) != sizeof(double))
+//    {
+//        std::cout << "FactorRelativePosition2dWithExtrinsics::Jacobian(c" << id() << ") = \n " << J << std::endl;
+//        std::cout << "FactorRelativePosition2dWithExtrinsics::Weighted Jacobian(c" << id() << ") = \n " << J << std::endl;
+//        std::cout << "FactorRelativePosition2dWithExtrinsics::Sqrt Info(c" << id() << ") = \n" << getMeasurementSquareRootInformationUpper() << std::endl;
+//    }
+    ////////////////////////////////////////////////////////
+
+    return true;
+}
+
+} // namespace wolf
\ No newline at end of file
diff --git a/include/core/factor/factor_relative_position_3d.h b/include/core/factor/factor_relative_position_3d.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d76dcb3406059f37218d0ace894ec1b3f9bc474
--- /dev/null
+++ b/include/core/factor/factor_relative_position_3d.h
@@ -0,0 +1,154 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#pragma once
+
+#include "core/factor/factor_autodiff.h"
+
+namespace wolf
+{
+
+WOLF_PTR_TYPEDEFS(FactorRelativePosition3d);
+    
+//class
+class FactorRelativePosition3d : public FactorAutodiff<FactorRelativePosition3d,3,3,4,3>
+{
+    public:
+        FactorRelativePosition3d(const FeatureBasePtr& _ftr_current_ptr,
+                                 const FrameBasePtr& _frame_past_ptr,
+                                 const ProcessorBasePtr& _processor_ptr,
+                                 bool _apply_loss_function,
+                                 const FactorTopology& _top,
+                                 FactorStatus _status = FAC_ACTIVE);
+
+        FactorRelativePosition3d(const FeatureBasePtr& _ftr_current_ptr,
+                                 const LandmarkBasePtr& _landmark_ptr,
+                                 const ProcessorBasePtr& _processor_ptr,
+                                 bool _apply_loss_function,
+                                 const FactorTopology& _top,
+                                 FactorStatus _status = FAC_ACTIVE);
+
+        ~FactorRelativePosition3d() override = default;
+
+        template<typename T>
+        bool operator ()(const T* const _p_ref,
+                         const T* const _q_ref,
+                         const T* const _p_target,
+                         T* _residuals) const;
+
+    private:
+        template<typename T>
+        void printRes(const Eigen::Matrix<T, 3, 1>& r) const;
+};
+
+template<typename T>
+inline void FactorRelativePosition3d::printRes(const Eigen::Matrix<T, 3, 1>& r) const
+{
+    std::cout << "Jet residual = " << std::endl;
+    std::cout << r(0) << std::endl;
+    std::cout << r(1) << std::endl;
+    std::cout << r(2) << std::endl;
+}
+
+template<>
+inline void FactorRelativePosition3d::printRes (const  Eigen::Matrix<double,3,1> & r) const
+{
+    std::cout << "Scalar residual = " << std::endl;
+    std::cout << r.transpose() << std::endl;
+}
+
+inline FactorRelativePosition3d::FactorRelativePosition3d(const FeatureBasePtr& _ftr_current_ptr,
+                                                          const FrameBasePtr& _frame_past_ptr,
+                                                          const ProcessorBasePtr& _processor_ptr,
+                                                          bool _apply_loss_function,
+                                                          const FactorTopology& _top,
+                                                          FactorStatus _status) :
+        FactorAutodiff("FactorRelativePosition3d",     // type
+                       _top, // topology
+                       _ftr_current_ptr,   // feature
+                       _frame_past_ptr,    // frame other
+                       nullptr,            // capture other
+                       nullptr,            // feature other
+                       nullptr,            // landmark other
+                       _processor_ptr,     // processor
+                       _apply_loss_function,
+                       _status,
+                       _frame_past_ptr->getP(), // past frame P
+                       _frame_past_ptr->getO(), // past frame Q
+                       _ftr_current_ptr->getFrame()->getP()) // current frame P
+{
+    MatrixSizeCheck<3,1>::check(_ftr_current_ptr->getMeasurement());
+    MatrixSizeCheck<3,3>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper());
+}
+
+inline FactorRelativePosition3d::FactorRelativePosition3d(const FeatureBasePtr& _ftr_current_ptr,
+                                                  const LandmarkBasePtr& _lmk_ptr,
+                                                  const ProcessorBasePtr& _processor_ptr,
+                                                  bool _apply_loss_function,
+                                                  const FactorTopology& _top,
+                                                  FactorStatus _status) :
+        FactorAutodiff("FactorRelativePosition3d",     // type
+                       _top, // topology
+                       _ftr_current_ptr,   // feature
+                       nullptr,            // frame other
+                       nullptr,            // capture other
+                       nullptr,            // feature other
+                       _lmk_ptr,           // landmark other
+                       _processor_ptr,     // processor
+                       _apply_loss_function,
+                       _status,
+                       _ftr_current_ptr->getFrame()->getP(), // frame P
+                       _ftr_current_ptr->getFrame()->getO(), // frame Q
+                       _lmk_ptr->getP())                     // landmark P
+{
+    MatrixSizeCheck<3,1>::check(_ftr_current_ptr->getMeasurement());
+    MatrixSizeCheck<3,3>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper());
+}
+
+template<typename T>
+inline bool FactorRelativePosition3d::operator ()(const T* const _p_ref,
+                                                  const T* const _q_ref,
+                                                  const T* const _p_target,
+                                                  T* _residuals) const
+{
+    Eigen::Map<const Eigen::Matrix<T,3,1> > p_target(_p_target);
+    Eigen::Map<const Eigen::Matrix<T,3,1> > p_ref(_p_ref);
+    Eigen::Map<const Eigen::Quaternion<T> > q_ref(_q_ref);
+    Eigen::Map<Eigen::Matrix<T,3,1> > residuals(_residuals);
+
+//     std::cout << "p_ref: " << p_ref(0) << " "
+//                            << p_ref(1) << " "
+//                            << p_ref(2) << "\n";
+//     std::cout << "q_ref: " << q_ref.coeffs()(0) << " "
+//                            << q_ref.coeffs()(1) << " "
+//                            << q_ref.coeffs()(2) << " "
+//                            << q_ref.coeffs()(3) << "\n";
+//     std::cout << "p_target: " << p_target(0) << " "
+//                               << p_target(1) << " "
+//                               << p_target(2) << "\n";
+
+    residuals = getMeasurementSquareRootInformationUpper() * (getMeasurement() - (q_ref.conjugate() * (p_target - p_ref)));
+
+    return true;
+}
+
+} /* namespace wolf */
\ No newline at end of file
diff --git a/include/core/factor/factor_relative_position_3d_with_extrinsics.h b/include/core/factor/factor_relative_position_3d_with_extrinsics.h
new file mode 100644
index 0000000000000000000000000000000000000000..f892ac25a4adcb6c31f43f76b4fc3022a62b56ae
--- /dev/null
+++ b/include/core/factor/factor_relative_position_3d_with_extrinsics.h
@@ -0,0 +1,149 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#pragma once
+
+//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(FactorRelativePosition3dWithExtrinsics);
+
+//class
+class FactorRelativePosition3dWithExtrinsics : public FactorAutodiff<FactorRelativePosition3dWithExtrinsics, 3, 3, 4, 3, 3, 4>
+{
+    public:
+
+        /** \brief Class constructor Frame-Landmark
+         */
+        FactorRelativePosition3dWithExtrinsics(const FeatureBasePtr& _ftr_ptr,
+                                               const LandmarkBasePtr& _lmk_other_ptr,
+                                               const ProcessorBasePtr& _processor_ptr,
+                                               bool _apply_loss_function,
+                                               const FactorTopology& _top = TOP_LMK,
+                                               FactorStatus _status = FAC_ACTIVE);
+
+        ~FactorRelativePosition3dWithExtrinsics() override = default;
+
+        template<typename T>
+        bool operator ()(const T* const _p_ref,
+                         const T* const _o_ref,
+                         const T* const _p_target,
+                         const T* const _p_sensor,
+                         const T* const _o_sensor,
+                         T* _residuals) const;
+
+        Eigen::Vector3d residual() const;
+        double cost() const;
+};
+
+inline FactorRelativePosition3dWithExtrinsics::FactorRelativePosition3dWithExtrinsics(const FeatureBasePtr& _ftr_ptr,
+                                                                                      const LandmarkBasePtr& _lmk_other_ptr,
+                                                                                      const ProcessorBasePtr& _processor_ptr,
+                                                                                      bool _apply_loss_function,
+                                                                                      const FactorTopology& _top,
+                                                                                      FactorStatus _status) :
+     FactorAutodiff("FactorRelativePosition3dWithExtrinsics",
+                    _top,
+                    _ftr_ptr,
+                    nullptr, nullptr, nullptr, _lmk_other_ptr,
+                    _processor_ptr,
+                    _apply_loss_function,
+                    _status,
+                    _ftr_ptr->getFrame()->getP(),
+                    _ftr_ptr->getFrame()->getO(),
+                    _lmk_other_ptr->getP(),
+                    _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!");
+}
+
+template<typename T>
+inline bool FactorRelativePosition3dWithExtrinsics::operator ()(const T* const _p_ref,
+                                                                const T* const _o_ref,
+                                                                const T* const _p_target,
+                                                                const T* const _p_sensor,
+                                                                const T* const _o_sensor,
+                                                                T* _residuals) const
+{
+    // Maps
+    Eigen::Map<const Eigen::Matrix<T,3,1>> p_r_s(_p_sensor);
+    Eigen::Map<const Eigen::Quaternion<T>> q_r_s(_o_sensor);
+    Eigen::Map<const Eigen::Matrix<T,3,1>> p_w_r(_p_ref);
+    Eigen::Map<const Eigen::Quaternion<T>> q_w_r(_o_ref);
+    Eigen::Map<const Eigen::Matrix<T,3,1>> p_w_t(_p_target);
+    Eigen::Map<Eigen::Matrix<T,3,1>> residuals(_residuals);
+
+    // WOLF_INFO("p_sensor: ", p_r_s.transpose());
+    // WOLF_INFO("o_sensor: ", q_r_s.coeffs().transpose());
+    // WOLF_INFO("p_ref:    ", p_w_r.transpose());
+    // WOLF_INFO("o_ref:    ", q_w_r.coeffs().transpose());
+    // WOLF_INFO("p_target: ", p_w_t.transpose());
+
+    // Expected measurement
+    Eigen::Matrix<T,3,1> expected_p;
+    expected_p = q_r_s.conjugate() * (q_w_r.conjugate() * (p_w_t - p_w_r) - p_r_s);
+
+    // Residual
+    residuals = getMeasurementSquareRootInformationUpper().cast<T>() * (getMeasurement() - expected_p);
+
+    return true;
+}
+
+inline Eigen::Vector3d FactorRelativePosition3dWithExtrinsics::residual() const
+{
+    Eigen::Vector3d res;
+    Eigen::VectorXd p_sensor, o_sensor, p_ref, o_ref, p_target;
+    p_sensor = getCapture()->getSensorP()->getState();
+    o_sensor = getCapture()->getSensorO()->getState();
+    // FRAME CASE
+    if (not getFrameOtherList().empty())
+    {
+        p_ref    = getFrameOther()->getP()->getState();
+        o_ref    = getFrameOther()->getO()->getState();
+        p_target = getCapture()->getFrame()->getP()->getState();
+    }
+    // LANDMARK CASE
+    else if (not getLandmarkOtherList().empty())
+    {
+        p_ref    = getCapture()->getFrame()->getP()->getState();
+        o_ref    = getCapture()->getFrame()->getO()->getState();
+        p_target = getLandmarkOther()->getP()->getState();
+    }
+
+    operator() (p_ref.data(), o_ref.data(), p_target.data(), p_sensor.data(), o_sensor.data(), res.data());
+
+    return res;
+}
+
+inline double FactorRelativePosition3dWithExtrinsics::cost() const
+{
+    return residual().squaredNorm();
+}
+
+} // namespace wolf
\ No newline at end of file
diff --git a/include/core/map/map_base.h b/include/core/map/map_base.h
index af09f86002c697ad026ad9942d218026808555c4..da7113fa583bd7838bda5ee9a96c610731814dcd 100644
--- a/include/core/map/map_base.h
+++ b/include/core/map/map_base.h
@@ -113,7 +113,9 @@ class MapBase : public NodeBase, public std::enable_shared_from_this<MapBase>
     public:
         LandmarkBaseConstPtrList getLandmarkList() const;
         LandmarkBasePtrList getLandmarkList();
-        
+        LandmarkBaseConstPtr getLandmark(const unsigned int& _id) const;
+        LandmarkBasePtr getLandmark(const unsigned int& _id);
+
         void load(const std::string& _map_file_yaml);
         void save(const std::string& _map_file_yaml, const std::string& _map_name = "Map automatically saved by Wolf");
 
diff --git a/include/core/math/SE2.h b/include/core/math/SE2.h
index 95631a8a6fb82ac22595b64d3e1d5b4fe7849cb4..39f67c0358fc1f3c127adc1ec96844d005011588 100644
--- a/include/core/math/SE2.h
+++ b/include/core/math/SE2.h
@@ -320,6 +320,13 @@ inline void compose(const VectorComposite& _x1,
     _c['O'] = Matrix1d( pi2pi(a1 + a2) ) ;
 }
 
+inline VectorComposite compose(const VectorComposite& x1, const VectorComposite& x2)
+{
+    VectorComposite c("PO", {2,1});
+    compose(x1, x2, c);
+    return c;
+}
+
 inline void compose(const VectorComposite& _x1,
              const VectorComposite& _x2,
              VectorComposite& _c,
diff --git a/include/core/problem/problem.h b/include/core/problem/problem.h
index 46500c6e225e5f2f07928c2656d950a1462749ad..d2de44d0b38a58d5313a8f07574755e4866260d6 100644
--- a/include/core/problem/problem.h
+++ b/include/core/problem/problem.h
@@ -62,6 +62,12 @@ struct PriorOptions
 };
 WOLF_STRUCT_PTR_TYPEDEFS(PriorOptions);
 
+struct Reference
+{
+    StateBlockPtr p_ptr;
+    StateBlockPtr o_ptr;
+};
+
 /** \brief Wolf problem node element in the Wolf Tree
  */
 class Problem : public std::enable_shared_from_this<Problem>
@@ -90,6 +96,8 @@ class Problem : public std::enable_shared_from_this<Problem>
         VectorComposite  transformation_;
         mutable std::mutex mut_transform_;
 
+        Reference local_reference_;
+
       private:  // CAUTION: THESE METHODS ARE PRIVATE, DO NOT MAKE THEM PUBLIC !!
         Problem(const std::string& _frame_structure, SizeEigen _dim, MapBasePtr _map); // USE create() below !!
         void setup();
@@ -105,6 +113,10 @@ class Problem : public std::enable_shared_from_this<Problem>
     public:
         SizeEigen getDim() const;
         const StateStructure& getFrameStructure() const;
+        StateBlockPtr getLocalReferenceP();
+        StateBlockConstPtr getLocalReferenceP() const;
+        StateBlockPtr getLocalReferenceO();
+        StateBlockConstPtr getLocalReferenceO() const;
 
     protected:
         void appendToStructure(const StateStructure& _structure);
@@ -453,6 +465,26 @@ class Problem : public std::enable_shared_from_this<Problem>
 namespace wolf
 {
 
+inline StateBlockPtr Problem::getLocalReferenceP()
+{
+    return local_reference_.p_ptr;
+}
+
+inline StateBlockConstPtr Problem::getLocalReferenceP() const
+{
+    return local_reference_.p_ptr;
+}
+
+inline StateBlockPtr Problem::getLocalReferenceO()
+{
+    return local_reference_.o_ptr;
+}
+
+inline StateBlockConstPtr Problem::getLocalReferenceO() const
+{
+    return local_reference_.o_ptr;
+}
+
 inline TreeManagerBaseConstPtr Problem::getTreeManager() const
 {
     return tree_manager_;
diff --git a/include/core/processor/processor_tracker_feature_landmark_external.h b/include/core/processor/processor_tracker_feature_landmark_external.h
new file mode 100644
index 0000000000000000000000000000000000000000..b16168db2d60f618401a09848de6fb368f711db3
--- /dev/null
+++ b/include/core/processor/processor_tracker_feature_landmark_external.h
@@ -0,0 +1,176 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#pragma once
+
+#include "core/common/wolf.h"
+#include "core/processor/processor_tracker_feature.h"
+
+namespace wolf
+{
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorTrackerFeatureLandmarkExternal);
+
+struct ParamsProcessorTrackerFeatureLandmarkExternal : public ParamsProcessorTrackerFeature
+{
+    double filter_quality_th;             ///< min quality to consider the detection
+    double filter_dist_th;                ///< for considering tracked detection: distance threshold to previous detection
+    unsigned int filter_track_length_th;  ///< length of the track necessary to consider the detection
+    double time_span;                     ///< for keyframe voting: time span since last frame
+
+    ParamsProcessorTrackerFeatureLandmarkExternal() = default;
+    ParamsProcessorTrackerFeatureLandmarkExternal(std::string _unique_name,
+                                       const wolf::ParamsServer & _server):
+        ParamsProcessorTrackerFeature(_unique_name, _server)
+    {
+        filter_quality_th       = _server.getParam<double>      (prefix + _unique_name + "/filter_quality_th");
+        filter_dist_th          = _server.getParam<double>      (prefix + _unique_name + "/filter_dist_th");
+        filter_track_length_th  = _server.getParam<unsigned int>(prefix + _unique_name + "/filter_track_length_th");
+        time_span               = _server.getParam<double>      (prefix + _unique_name + "/keyframe_vote/time_span");
+    }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorTrackerFeatureLandmarkExternal);
+
+//Class
+class ProcessorTrackerFeatureLandmarkExternal : public ProcessorTrackerFeature
+{
+    public:
+        ProcessorTrackerFeatureLandmarkExternal(ParamsProcessorTrackerFeatureLandmarkExternalPtr _params_tracker_feature);
+        ~ProcessorTrackerFeatureLandmarkExternal() override;
+
+        // Factory method for high level API
+        WOLF_PROCESSOR_CREATE(ProcessorTrackerFeatureLandmarkExternal, ParamsProcessorTrackerFeatureLandmarkExternal);
+
+        void configure(SensorBasePtr _sensor) override { };
+
+    protected:
+
+        ParamsProcessorTrackerFeatureLandmarkExternalPtr params_tfle_;
+        std::set<FeatureBasePtr> unmatched_detections_incoming_, unmatched_detections_last_;
+
+        /** \brief Track provided features in \b _capture
+         * \param _features_in input list of features in \b last to track
+         * \param _capture the capture in which the _features_in should be searched
+         * \param _features_out returned list of features found in \b _capture
+         * \param _feature_correspondences returned map of correspondences: _feature_correspondences[feature_out_ptr] = feature_in_ptr
+         *
+         * IMPORTANT: The features in _features_out should be emplaced. Don't use `make_shared`, use `FeatureBase::emplace` instead.
+         * Then, they will be already linked to the _capture.
+         *
+         * \return the number of features tracked
+         */
+        unsigned int trackFeatures(const FeatureBasePtrList& _features_in,
+                                   const CaptureBasePtr& _capture,
+                                   FeatureBasePtrList& _features_out,
+                                   FeatureMatchMap& _feature_correspondences) override;
+
+        /** \brief Correct the drift in incoming feature by re-comparing against the corresponding feature in origin.
+         * \param _last_feature input feature in last capture tracked
+         * \param _incoming_feature input/output feature in incoming capture to be corrected
+         * \return false if the the process discards the correspondence with origin's feature
+         */
+        bool correctFeatureDrift(const FeatureBasePtr _origin_feature,
+                                 const FeatureBasePtr _last_feature,
+                                 FeatureBasePtr _incoming_feature) override;
+
+        /** \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;
+
+        /** \brief Detect new Features
+         * \param _max_features maximum number of features detected (-1: unlimited. 0: none)
+         * \param _capture The capture in which the new features should be detected.
+         * \param _features_out The list of detected Features in _capture.
+         * \return The number of detected Features.
+         *
+         * This function detects Features that do not correspond to known Features/Landmarks in the system.
+         *
+         * IMPORTANT: The features in _features_out should be emplaced. Don't use `make_shared`, use `FeatureBase::emplace` instead.
+         * Then, they will be already linked to the _capture.
+         *
+         * The function is called in ProcessorTrackerFeature::processNew() to set the member new_features_last_,
+         * the list of newly detected features of the capture last_ptr_.
+         */
+        unsigned int detectNewFeatures(const int& _max_new_features,
+                                       const CaptureBasePtr& _capture,
+                                       FeatureBasePtrList& _features_out) override;
+        /** \brief Emplaces a new factor
+         * \param _feature_ptr pointer to the parent Feature
+         * \param _feature_other_ptr pointer to the other feature constrained.
+         *
+         * This function emplaces a factor of the appropriate type for the derived processor.
+         */
+        FactorBasePtr emplaceFactor(FeatureBasePtr _feature_ptr,
+                                    FeatureBasePtr _feature_other_ptr) override;
+
+        /** Pre-process incoming Capture
+         *
+         * This is called by process() just after assigning incoming_ptr_ to a valid Capture.
+         *
+         * extract the detections and fill detections_incoming_ (FeaturePtrList)
+         */
+        void preProcess() override;
+
+        /** Post-process
+         *
+         * This is called by process() after finishing the processing algorithm.
+         *
+         * It does nothing for now
+         */
+        void postProcess() override {};
+
+        void advanceDerived() override;
+        void resetDerived() override;
+
+        double detectionDistance(FeatureBasePtr _ftr1,
+                                 FeatureBasePtr _ftr2,
+                                 const VectorComposite& _pose1,
+                                 const VectorComposite& _pose2,
+                                 const VectorComposite& _pose_sen) const;
+};
+
+inline ProcessorTrackerFeatureLandmarkExternal::ProcessorTrackerFeatureLandmarkExternal(ParamsProcessorTrackerFeatureLandmarkExternalPtr _params_tfle) :
+        ProcessorTrackerFeature("ProcessorTrackerFeatureLandmarkExternal", "PO", 0, _params_tfle),
+        params_tfle_(_params_tfle)
+{
+    //
+}
+
+inline ProcessorTrackerFeatureLandmarkExternal::~ProcessorTrackerFeatureLandmarkExternal()
+{
+    //
+}
+
+inline bool ProcessorTrackerFeatureLandmarkExternal::correctFeatureDrift(const FeatureBasePtr _origin_feature,
+                                                                         const FeatureBasePtr _last_feature,
+                                                                         FeatureBasePtr _incoming_feature)
+{
+    return true;
+}
+
+} // namespace wolf
\ No newline at end of file
diff --git a/include/core/processor/processor_tracker_landmark.h b/include/core/processor/processor_tracker_landmark.h
index bbccccf950aa02f499d215a21f5488a724330435..ab109ffdbf4302cfeb517be5e184ae3499d77fa2 100644
--- a/include/core/processor/processor_tracker_landmark.h
+++ b/include/core/processor/processor_tracker_landmark.h
@@ -88,7 +88,7 @@ WOLF_PTR_TYPEDEFS(ProcessorTrackerLandmark);
  *     - createLandmark() : creates a Landmark using a new Feature                  <=== IMPLEMENT
  *     - findLandmarks() : find the new Landmarks again in \b incoming              <=== IMPLEMENT
  *   - establishFactors() : which calls the pure virtual:
- *     - createFactor() : create a Feature-Landmark factor of the correct derived type <=== IMPLEMENT
+ *     - emplaceFactor() : create a Feature-Landmark factor of the correct derived type <=== IMPLEMENT
  *
  * Should you need extra functionality for your derived types, you can overload these two methods,
  *
diff --git a/include/core/utils/utils_gtest.h b/include/core/utils/utils_gtest.h
index b89b9102ab64073b729744ec5d6c7d4d8098b143..9c46dd20c96535308e5f851e218641498787a631 100644
--- a/include/core/utils/utils_gtest.h
+++ b/include/core/utils/utils_gtest.h
@@ -19,16 +19,7 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 //--------LICENSE_END--------
-/**
- * \file utils_gtest.h
- * \brief Some utils for gtest
- * \author Jeremie Deray
- *  Created on: 26/09/2016
- *  Eigen macros extension by: Joan Sola on 26/04/2017
- */
-
-#ifndef WOLF_UTILS_GTEST_H
-#define WOLF_UTILS_GTEST_H
+#pragma once
 
 #include <gtest/gtest.h>
 
@@ -144,24 +135,58 @@ TEST(Test, Foo)
                }, \
                C_expect, C_actual);
 
-#define EXPECT_QUATERNION_APPROX(C_expect, C_actual, precision) EXPECT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
+#define EXPECT_QUATERNION_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   return Eigen::Quaterniond(Eigen::Vector4d(lhs)).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs))) < precision; \
+               }, \
+               Quaterniond(C_expect).coeffs(), Quaterniond(C_actual).coeffs());
 
-#define ASSERT_QUATERNION_APPROX(C_expect, C_actual, precision) ASSERT_MATRIX_APPROX((C_expect).coeffs(), (C_actual).coeffs(), precision)
+#define ASSERT_QUATERNION_APPROX(C_expect, C_actual, precision) ASSERT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   return Eigen::Quaterniond(Eigen::Vector4d(lhs)).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs))) < precision; \
+               }, \
+               Quaterniond(C_expect).coeffs(), Quaterniond(C_actual).coeffs());
 
-#define EXPECT_POSE2d_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXd lhs, const Eigen::MatrixXd rhs) { \
-                   MatrixXd er = lhs - rhs; \
+#define EXPECT_QUATERNION_VECTOR_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   return Eigen::Quaterniond(Eigen::Vector4d(lhs)).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs))) < precision; \
+               }, \
+               C_expect, C_actual);
+
+#define ASSERT_QUATERNION_VECTOR_APPROX(C_expect, C_actual, precision) ASSERT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   return Eigen::Quaterniond(Eigen::Vector4d(lhs)).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs))) < precision; \
+               }, \
+               C_expect, C_actual);
+
+#define EXPECT_POSE2d_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   assert(lhs.size() == 3 and rhs.size() == 3); \
+                   Eigen::VectorXd er = lhs - rhs; \
                    er(2) = pi2pi((double)er(2)); \
                    return er.isMuchSmallerThan(1, precision); \
                }, \
                C_expect, C_actual);
 
-#define ASSERT_POSE2d_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::MatrixXd lhs, const Eigen::MatrixXd rhs) { \
-                   MatrixXd er = lhs - rhs; \
+#define ASSERT_POSE2d_APPROX(C_expect, C_actual, precision) ASSERT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   assert(lhs.size() == 3 and rhs.size() == 3); \
+                   Eigen::VectorXd er = lhs - rhs; \
                    er(2) = pi2pi((double)er(2)); \
                    return er.isMuchSmallerThan(1, precision); \
                }, \
                C_expect, C_actual);
 
-} // namespace testing
+#define EXPECT_POSE3d_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   assert(lhs.size() == 7 and rhs.size() == 7); \
+                   Eigen::Vector4d er; \
+                   er.head(3) = lhs.head(3) - rhs.head(3); \
+                   er(3) = Eigen::Quaterniond(Eigen::Vector4d(lhs.tail(4))).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs.tail(4)))); \
+                   return er.isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
+
+#define ASSERT_POSE3d_APPROX(C_expect, C_actual, precision) EXPECT_PRED2([](const Eigen::VectorXd lhs, const Eigen::VectorXd rhs) { \
+                   assert(lhs.size() == 7 and rhs.size() == 7); \
+                   Eigen::Vector4d er; \
+                   er.head(3) = lhs.head(3) - rhs.head(3); \
+                   er(3) = Eigen::Quaterniond(Eigen::Vector4d(lhs.tail(4))).angularDistance(Eigen::Quaterniond(Eigen::Vector4d(rhs.tail(4)))); \
+                   return er.isMuchSmallerThan(1, precision); \
+               }, \
+               C_expect, C_actual);
 
-#endif /* WOLF_UTILS_GTEST_H */
+} // namespace testing
\ No newline at end of file
diff --git a/src/capture/capture_landmarks_external.cpp b/src/capture/capture_landmarks_external.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..154dcc83f488b39a91c69f0906a0f59e2fdd56d7
--- /dev/null
+++ b/src/capture/capture_landmarks_external.cpp
@@ -0,0 +1,53 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#include "core/capture/capture_landmarks_external.h"
+
+namespace wolf{
+
+CaptureLandmarksExternal::CaptureLandmarksExternal(const TimeStamp& _ts, 
+                                                   SensorBasePtr _sensor_ptr, 
+                                                   const std::vector<int>& _ids,
+                                                   const std::vector<Eigen::VectorXd>& _detections, 
+                                                   const std::vector<Eigen::MatrixXd>& _covs, 
+                                                   const std::vector<double>& _qualities) :
+	CaptureBase("CaptureLandmarksExternal", _ts, _sensor_ptr)
+{
+    if (_ids.size() != _detections.size() or 
+        _ids.size() != _covs.size() or 
+        _ids.size() != _qualities.size())
+        throw std::runtime_error("CaptureLandmarksExternal constructor: '_ids', '_detections', '_covs', '_qualities' should have the same size.");
+    
+    for (auto i = 0; i < _detections.size(); i++)
+        addDetection(_ids.at(i), _detections.at(i), _covs.at(i), _qualities.at(i));
+}
+
+CaptureLandmarksExternal::~CaptureLandmarksExternal()
+{
+	//
+}
+
+void CaptureLandmarksExternal::addDetection(const int& _id, const Eigen::VectorXd& _detection, const Eigen::MatrixXd& _cov, const double& _quality)
+{
+    detections_.push_back(LandmarkDetection{_id, _detection, _cov, _quality});
+}
+
+} // namespace wolf
diff --git a/src/map/map_base.cpp b/src/map/map_base.cpp
index 66367305a0b9ad82e9ef5987c3bdacb9e9a1eeea..0c002934af723fd242babeb22cabd4de15823e9f 100644
--- a/src/map/map_base.cpp
+++ b/src/map/map_base.cpp
@@ -65,21 +65,46 @@ void MapBase::removeLandmark(LandmarkBasePtr _landmark_ptr)
     landmark_list_.remove(_landmark_ptr);
 }
 
-void MapBase::load(const std::string& _map_file_dot_yaml)
+LandmarkBaseConstPtr MapBase::getLandmark(const unsigned int& _id) const
 {
-    YAML::Node map = YAML::LoadFile(_map_file_dot_yaml);
+    auto lmk_it = std::find_if(landmark_list_.begin(),
+                               landmark_list_.end(),
+                               [&](LandmarkBaseConstPtr lmk)
+                               {
+                                   return lmk->id() == _id;
+                               }); // lambda function for the find_if
+
+    if (lmk_it == landmark_list_.end())
+        return nullptr;
+
+    return (*lmk_it);
+}
+
+LandmarkBasePtr MapBase::getLandmark(const unsigned int& _id)
+{
+    auto lmk_it = std::find_if(landmark_list_.begin(),
+                               landmark_list_.end(),
+                               [&](LandmarkBasePtr lmk)
+                               {
+                                   return lmk->id() == _id;
+                               }); // lambda function for the find_if
 
-    unsigned int nlandmarks = map["nlandmarks"].as<unsigned int>();
+    if (lmk_it == landmark_list_.end())
+        return nullptr;
 
-    assert(nlandmarks == map["landmarks"].size() && "Number of landmarks in map file does not match!");
+    return (*lmk_it);
+}
 
-    for (unsigned int i = 0; i < nlandmarks; i++)
+void MapBase::load(const std::string& _map_file_dot_yaml)
+{
+    YAML::Node map = YAML::LoadFile(_map_file_dot_yaml);
+
+    for (unsigned int i = 0; i < map["landmarks"].size(); i++)
     {
         YAML::Node lmk_node = map["landmarks"][i];
         LandmarkBasePtr lmk_ptr = FactoryLandmark::create(lmk_node["type"].as<std::string>(), lmk_node);
         lmk_ptr->link(shared_from_this());
     }
-
 }
 
 void MapBase::save(const std::string& _map_file_yaml, const std::string& _map_name)
@@ -87,10 +112,8 @@ void MapBase::save(const std::string& _map_file_yaml, const std::string& _map_na
     YAML::Emitter emitter;
 
     emitter << YAML::BeginMap;
-    emitter << "map name"   << _map_name;
-    emitter << "date-time" << dateTimeNow(); // Get date and time for archiving purposes
-
-    emitter << "nlandmarks" << getLandmarkList().size();
+    emitter << "map_name"   << _map_name;
+    emitter << "date_time" << dateTimeNow(); // Get date and time for archiving purposes
 
     emitter << "landmarks"  << YAML::BeginSeq;
 
diff --git a/src/processor/processor_tracker_feature.cpp b/src/processor/processor_tracker_feature.cpp
index d55a2666cc00b3c8cdc53854ab43f7903f0b9d6e..c269ff41137f33cb6cfd14158b54228346782f28 100644
--- a/src/processor/processor_tracker_feature.cpp
+++ b/src/processor/processor_tracker_feature.cpp
@@ -200,11 +200,11 @@ void ProcessorTrackerFeature::establishFactors()
 
         auto fac_ptr  = emplaceFactor(feature_in_last, feature_in_origin);
 
-        assert(fac_ptr->getFeature() != nullptr && "not emplaced factor returned by emplaceFactor()");
+        assert((fac_ptr == nullptr or fac_ptr->getFeature() != nullptr) && "not emplaced factor returned by emplaceFactor()");
 
-        WOLF_DEBUG( "Factor: track: " , feature_in_last->trackId(),
-                    " origin: "       , feature_in_origin->id() ,
-                    " from last: "    , feature_in_last->id() );
+        WOLF_DEBUG_COND(fac_ptr, "New factor: track: " , feature_in_last->trackId(),
+                                 " origin: "       , feature_in_origin->id() ,
+                                 " from last: "    , feature_in_last->id() );
     }
 }
 
diff --git a/src/processor/processor_tracker_feature_landmark_external.cpp b/src/processor/processor_tracker_feature_landmark_external.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..50a1730c2d3bf08c7020022448913ca7c857cf3c
--- /dev/null
+++ b/src/processor/processor_tracker_feature_landmark_external.cpp
@@ -0,0 +1,399 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#include "core/processor/processor_tracker_feature_landmark_external.h"
+#include "core/capture/capture_landmarks_external.h"
+#include "core/feature/feature_base.h"
+#include "core/factor/factor_relative_pose_2d_with_extrinsics.h"
+#include "core/factor/factor_relative_position_2d_with_extrinsics.h"
+#include "core/factor/factor_relative_pose_3d_with_extrinsics.h"
+#include "core/factor/factor_relative_position_3d_with_extrinsics.h"
+#include "core/landmark/landmark_base.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_quaternion.h"
+#include "core/state_block/state_angle.h"
+#include "core/math/SE2.h"
+#include "core/math/SE3.h"
+
+using namespace Eigen;
+
+namespace wolf
+{
+
+void ProcessorTrackerFeatureLandmarkExternal::preProcess()
+{
+    assert(unmatched_detections_incoming_.empty());
+
+    auto cap_landmarks = std::dynamic_pointer_cast<CaptureLandmarksExternal>(incoming_ptr_);
+    if (not cap_landmarks)
+        throw std::runtime_error("ProcessorTrackerFeatureLandmarkExternal::preProcess incoming_ptr_ should be of type 'CaptureLandmarksExternal'");
+
+    auto landmark_detections = cap_landmarks->getDetections();
+    std::set<int> ids;
+    for (auto detection : landmark_detections)
+    {
+        WOLF_WARN_COND(ids.count(detection.id), "ProcessorTrackerFeatureLandmarkExternal::preProcess: detection with repeated id, discarding...");
+
+        if (detection.quality < params_tfle_->filter_quality_th
+            or ids.count(detection.id))
+            continue;
+
+        FeatureBasePtr ftr = FeatureBase::emplace<FeatureBase>(cap_landmarks,
+                                                               "FeatureLandmarkExternal",
+                                                               detection.measure,
+                                                               detection.covariance);
+        ftr->setLandmarkId(detection.id);
+
+        if (detection.id != -1 and detection.id != 0)
+            ids.insert(detection.id);
+
+        unmatched_detections_incoming_.insert(ftr);
+    }
+}
+
+unsigned int ProcessorTrackerFeatureLandmarkExternal::trackFeatures(const FeatureBasePtrList& _features_in,
+                                                                    const CaptureBasePtr& _capture,
+                                                                    FeatureBasePtrList& _features_out,
+                                                                    FeatureMatchMap& _feature_correspondences)
+{
+    if (_capture != incoming_ptr_)
+        throw std::runtime_error("ProcessorTrackerFeatureLandmarkExternal::trackFeatures should be called with incoming capture");
+    if (not _features_in.empty() and _features_in.front()->getCapture() != last_ptr_)
+        throw std::runtime_error("ProcessorTrackerFeatureLandmarkExternal::trackFeatures should be called with features in not from last");
+
+    auto pose_sen = getSensor()->getState("PO");
+    auto pose_in  = getProblem()->getState(last_ptr_->getTimeStamp(), "PO");
+    auto pose_out = getProblem()->getState(incoming_ptr_->getTimeStamp(), "PO");
+
+    // Track features given by ProcessorTrackerFeature
+    for (auto feat_in : _features_in)
+    {
+        for (auto feat_candidate : unmatched_detections_incoming_)
+        {
+            if (feat_candidate->landmarkId() == feat_in->landmarkId() and 
+                detectionDistance(feat_in, feat_candidate, pose_in, pose_out, pose_sen) < params_tfle_->filter_dist_th)
+            {
+                // add match
+                _features_out.push_back(feat_candidate);
+                _feature_correspondences[_features_out.back()] = std::make_shared<FeatureMatch>(FeatureMatch({feat_in,0}));
+
+                // remove from unmatched
+                unmatched_detections_incoming_.erase(feat_candidate);
+                break;
+            }
+        }
+    }
+
+    // Track features in last not matched yet
+    auto feat_last_it = unmatched_detections_last_.begin();
+    while (feat_last_it != unmatched_detections_last_.end())
+    {
+        auto feat_in = *feat_last_it;
+        bool found = false;
+        for (auto feat_candidate : unmatched_detections_incoming_)
+        {
+            if (feat_candidate->landmarkId() == feat_in->landmarkId() and 
+                detectionDistance(feat_in, feat_candidate, pose_in, pose_out, pose_sen) < params_tfle_->filter_dist_th)
+            {
+                // add track
+                track_matrix_.newTrack(feat_in);
+
+                // add match
+                _features_out.push_back(feat_candidate);
+                _feature_correspondences[_features_out.back()] = std::make_shared<FeatureMatch>(FeatureMatch({feat_in,0}));
+
+                // remove from unmatched
+                unmatched_detections_incoming_.erase(feat_candidate);
+                found = true;
+                break;
+            }
+        }
+        if (found)
+            feat_last_it = unmatched_detections_last_.erase(feat_last_it);
+        else
+            feat_last_it++;
+    }
+
+    return _features_out.size();
+}
+
+double ProcessorTrackerFeatureLandmarkExternal::detectionDistance(FeatureBasePtr _ftr1,
+                                                                  FeatureBasePtr _ftr2,
+                                                                  const VectorComposite& _pose1,
+                                                                  const VectorComposite& _pose2,
+                                                                  const VectorComposite& _pose_sen) const
+{
+    // Any not available info of poses, assume identity
+    if (not _pose1.includesStructure("PO") or not _pose2.includesStructure("PO") or not _pose_sen.includesStructure("PO"))
+    {
+        if (getProblem()->getDim() == 2)
+            return (_ftr1->getMeasurement().head<2>() - _ftr2->getMeasurement().head<2>()).norm();
+        else
+            return (_ftr1->getMeasurement().head<3>() - _ftr2->getMeasurement().head<3>()).norm();
+    }
+    else
+    {
+        if (getProblem()->getDim() == 2)
+        {
+            auto pose_s1 = SE2::compose(_pose1, _pose_sen);
+            auto pose_s2 = SE2::compose(_pose2, _pose_sen);
+            auto p1 = pose_s1.at('P') + Eigen::Rotation2Dd(pose_s1.at('O')(0)) * _ftr1->getMeasurement().head<2>();
+            auto p2 = pose_s2.at('P') + Eigen::Rotation2Dd(pose_s2.at('O')(0)) * _ftr2->getMeasurement().head<2>();
+            return (p1-p2).norm();
+        }
+        else
+        {
+            auto pose_s1 = SE3::compose(_pose1, _pose_sen);
+            auto pose_s2 = SE3::compose(_pose2, _pose_sen);
+            auto p1 = pose_s1.at('P') + Eigen::Quaterniond(Eigen::Vector4d(pose_s1.at('O'))) * _ftr1->getMeasurement().head<3>();
+            auto p2 = pose_s2.at('P') + Eigen::Quaterniond(Eigen::Vector4d(pose_s2.at('O'))) * _ftr2->getMeasurement().head<3>();
+            return (p1-p2).norm();
+        }
+    }
+}
+
+bool ProcessorTrackerFeatureLandmarkExternal::voteForKeyFrame() const
+{
+    auto matches_origin_last = track_matrix_.matches(origin_ptr_, last_ptr_);
+
+    // no tracks longer than filter_track_length_th
+    auto n_tracks = 0;
+    for (auto match : matches_origin_last)
+    {
+        if (track_matrix_.track(match.first).size() >= params_tfle_->filter_track_length_th)
+            n_tracks++;
+    }
+    WOLF_INFO("Nbr. of active feature tracks: " , n_tracks );
+    if (n_tracks == 0)
+        return false;
+
+    bool vote = false;// = n_tracks < params_tracker_feature_->min_features_for_keyframe;
+
+    if (origin_ptr_)
+    {
+        WOLF_INFO("Time span since last frame: " , 
+                  last_ptr_->getTimeStamp() - origin_ptr_->getTimeStamp() , 
+                  " (should be greater than ",
+                  params_tfle_->time_span,
+                  ")");
+        vote = vote or last_ptr_->getTimeStamp() - origin_ptr_->getTimeStamp() > params_tfle_->time_span;
+    }
+
+    WOLF_INFO( (vote ? "Vote ": "Do not vote ") , "for KF" );
+
+    return vote;
+}
+
+unsigned int ProcessorTrackerFeatureLandmarkExternal::detectNewFeatures(const int& _max_new_features,
+                                                                        const CaptureBasePtr& _capture,
+                                                                        FeatureBasePtrList& _features_out)
+{
+    // NOT NECESSARY SINCE ALL FEATURES ARE TRACKED IN trackFeatures(...)
+    return 0;
+}
+
+FactorBasePtr ProcessorTrackerFeatureLandmarkExternal::emplaceFactor(FeatureBasePtr _feature_ptr,
+                                                                     FeatureBasePtr _feature_other_ptr)
+{
+    assert(_feature_ptr);
+    assert(_feature_other_ptr);
+    assert(_feature_ptr->getCapture());
+    assert(_feature_ptr->getCapture()->getFrame());
+
+    assert(getProblem());
+    assert(getProblem()->getMap());
+
+    // Check track length
+    if (params_tfle_->filter_track_length_th > 1)
+    {
+        auto snapshot = track_matrix_.snapshot(_feature_ptr->getCapture());
+        const auto& it = std::find_if(snapshot.begin(), snapshot.end(),
+                                      [_feature_ptr](const std::pair<SizeStd, FeatureBasePtr>& pair)
+                                      {
+                                        return pair.second == _feature_ptr;
+                                      });
+        assert(it != snapshot.end());
+        if (track_matrix_.track(it->first).size() < params_tfle_->filter_track_length_th)
+            return nullptr;
+    }
+
+    // Get landmark
+    LandmarkBasePtr lmk = getProblem()->getMap()->getLandmark(_feature_ptr->landmarkId());
+    
+    // 2D - Relative Position
+    if (getProblem()->getDim() == 2 and _feature_ptr->getMeasurement().size() == 2)
+    {
+        // no landmark, create it
+        if (not lmk)
+        {
+            Vector2d frm_p = _feature_ptr->getCapture()->getFrame()->getP()->getState();
+            Vector2d sen_p = _feature_ptr->getCapture()->getSensorP()->getState();
+            double frm_o   = _feature_ptr->getCapture()->getFrame()->getO()->getState()(0);
+            double sen_o   = _feature_ptr->getCapture()->getSensorO()->getState()(0);
+            
+            Vector2d lmk_p = frm_p + Rotation2Dd(frm_o) * (sen_p + Rotation2Dd(sen_o) * _feature_ptr->getMeasurement());
+
+            lmk = LandmarkBase::emplace<LandmarkBase>(getProblem()->getMap(),
+                                                      "LandmarkRelativePosition2d",
+                                                      std::make_shared<StatePoint2d>(lmk_p), 
+                                                      nullptr);
+            lmk->setId(_feature_ptr->landmarkId());
+        }
+
+        // emplace factor
+        return FactorBase::emplace<FactorRelativePosition2dWithExtrinsics>(_feature_ptr, 
+                                                                           _feature_ptr,
+                                                                           lmk,
+                                                                           shared_from_this(),
+                                                                           params_tfle_->apply_loss_function);
+    }
+    // 2D - Relative Pose
+    else if (getProblem()->getDim() == 2 and _feature_ptr->getMeasurement().size() == 3)
+    {
+        // no landmark, create it
+        if (not lmk)
+        {
+            Vector2d frm_p = _feature_ptr->getCapture()->getFrame()->getP()->getState();
+            Vector2d sen_p = _feature_ptr->getCapture()->getSensorP()->getState();
+            double frm_o   = _feature_ptr->getCapture()->getFrame()->getO()->getState()(0);
+            double sen_o   = _feature_ptr->getCapture()->getSensorO()->getState()(0);
+
+            Vector2d lmk_p = frm_p + Rotation2Dd(frm_o) * (sen_p + Rotation2Dd(sen_o) * _feature_ptr->getMeasurement().head<2>());
+            double lmk_o   = frm_o + sen_o + _feature_ptr->getMeasurement()(2);
+
+            lmk = LandmarkBase::emplace<LandmarkBase>(getProblem()->getMap(),
+                                                      "LandmarkRelativePose2d",
+                                                      std::make_shared<StatePoint2d>(lmk_p),
+                                                      std::make_shared<StateAngle>(lmk_o));
+            lmk->setId(_feature_ptr->landmarkId());
+        }
+        // no orientation, add it
+        else if (not lmk->getO())
+        {
+            double frm_o = _feature_ptr->getCapture()->getFrame()->getO()->getState()(0);
+            double sen_o = _feature_ptr->getCapture()->getSensorO()->getState()(0);
+
+            double lmk_o = frm_o + sen_o + _feature_ptr->getMeasurement()(2);
+
+            lmk->addStateBlock('O', std::make_shared<StateAngle>(lmk_o), getProblem());
+        }
+
+        // emplace factor
+        return FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(_feature_ptr,
+                                                                       _feature_ptr,
+                                                                       lmk, 
+                                                                       shared_from_this(),
+                                                                       params_tfle_->apply_loss_function);
+    }
+    // 3D - Relative Position
+    else if (getProblem()->getDim() == 3 and _feature_ptr->getMeasurement().size() == 3)
+    {
+        // no landmark, create it
+        if (not lmk)
+        {
+            Vector3d frm_p    = _feature_ptr->getCapture()->getFrame()->getP()->getState();
+            Vector3d sen_p    = _feature_ptr->getCapture()->getSensorP()->getState();
+            Quaterniond frm_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getFrame()->getO()->getState()));
+            Quaterniond sen_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getSensorO()->getState()));
+
+            Vector3d lmk_p = frm_p + frm_o * (sen_p + sen_o * _feature_ptr->getMeasurement());
+
+            lmk = LandmarkBase::emplace<LandmarkBase>(getProblem()->getMap(),
+                                                      "LandmarkRelativePosition3d",
+                                                      std::make_shared<StatePoint3d>(lmk_p),
+                                                      nullptr);
+            lmk->setId(_feature_ptr->landmarkId());
+        }
+
+        // emplace factor
+        return FactorBase::emplace<FactorRelativePosition3dWithExtrinsics>(_feature_ptr, 
+                                                                           _feature_ptr,
+                                                                           lmk,
+                                                                           shared_from_this(),
+                                                                           params_tfle_->apply_loss_function);
+    }
+    // 3D - Relative Pose
+    else if (getProblem()->getDim() == 3 and _feature_ptr->getMeasurement().size() == 7)
+    {
+        // no landmark, create it
+        if (not lmk)
+        {
+            Vector3d frm_p    = _feature_ptr->getCapture()->getFrame()->getP()->getState();
+            Vector3d sen_p    = _feature_ptr->getCapture()->getSensorP()->getState();
+            Quaterniond frm_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getFrame()->getO()->getState()));
+            Quaterniond sen_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getSensorO()->getState()));
+
+            Vector3d lmk_p    = frm_p + frm_o * (sen_p + sen_o * _feature_ptr->getMeasurement().head<3>());
+            Quaterniond lmk_o = frm_o * sen_o * Quaterniond(_feature_ptr->getMeasurement().tail<4>());
+
+            lmk = LandmarkBase::emplace<LandmarkBase>(getProblem()->getMap(),
+                                                      "LandmarkRelativePose3d",
+                                                      std::make_shared<StatePoint3d>(lmk_p),
+                                                      std::make_shared<StateQuaternion>(lmk_o));
+            lmk->setId(_feature_ptr->landmarkId());
+        }
+        // no orientation, add it
+        else if (not lmk->getO())
+        {
+            Quaterniond frm_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getFrame()->getO()->getState()));
+            Quaterniond sen_o = Quaterniond(Vector4d(_feature_ptr->getCapture()->getSensorO()->getState()));
+
+            Quaterniond lmk_o = frm_o * sen_o * Quaterniond(_feature_ptr->getMeasurement().tail<4>());
+
+            lmk->addStateBlock('O', std::make_shared<StateQuaternion>(lmk_o), getProblem());
+        }
+
+        // emplace factor
+        return FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(_feature_ptr, 
+                                                                       _feature_ptr, 
+                                                                       lmk, 
+                                                                       shared_from_this(),
+                                                                       params_tfle_->apply_loss_function);
+    }
+    else 
+        throw std::runtime_error("ProcessorTrackerFeatureLandmarkExternal::emplaceFactor unknown case");
+
+    // not reachable
+    return nullptr;
+}
+
+void ProcessorTrackerFeatureLandmarkExternal::advanceDerived()
+{
+    ProcessorTrackerFeature::advanceDerived();
+
+    unmatched_detections_last_ = std::move(unmatched_detections_incoming_);
+}
+void ProcessorTrackerFeatureLandmarkExternal::resetDerived()
+{
+    ProcessorTrackerFeature::resetDerived();
+
+    unmatched_detections_last_ = std::move(unmatched_detections_incoming_);
+}
+
+} // namespace wolf
+
+// Register in the FactoryProcessor
+#include "core/processor/factory_processor.h"
+namespace wolf {
+WOLF_REGISTER_PROCESSOR(ProcessorTrackerFeatureLandmarkExternal)
+WOLF_REGISTER_PROCESSOR_AUTO(ProcessorTrackerFeatureLandmarkExternal)
+} // namespace wolf
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6fa50395a7ee2af50d290b32e543b18bac534869..261438924ee686d0799f738278b5f1186630a48e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -173,6 +173,18 @@ wolf_add_gtest(gtest_factor_relative_pose_3d gtest_factor_relative_pose_3d.cpp)
 # FactorRelativePose3dWithExtrinsics class test
 wolf_add_gtest(gtest_factor_relative_pose_3d_with_extrinsics gtest_factor_relative_pose_3d_with_extrinsics.cpp)
 
+# FactorRelativePosition2d class test
+wolf_add_gtest(gtest_factor_relative_position_2d gtest_factor_relative_position_2d.cpp)
+
+# FactorRelativePosition2dWithExtrinsics class test
+wolf_add_gtest(gtest_factor_relative_position_2d_with_extrinsics gtest_factor_relative_position_2d_with_extrinsics.cpp)
+
+# FactorRelativePosition3d class test
+wolf_add_gtest(gtest_factor_relative_position_3d gtest_factor_relative_position_3d.cpp)
+
+# FactorRelativePosition3dWithExtrinsics class test
+wolf_add_gtest(gtest_factor_relative_position_3d_with_extrinsics gtest_factor_relative_position_3d_with_extrinsics.cpp)
+
 # FactorVelocityLocalDirection3d class test
 wolf_add_gtest(gtest_factor_velocity_local_direction_3d gtest_factor_velocity_local_direction_3d.cpp)
 
@@ -185,6 +197,9 @@ wolf_add_gtest(gtest_map_yaml gtest_map_yaml.cpp)
 # Parameter prior test
 wolf_add_gtest(gtest_param_prior gtest_param_prior.cpp)
 
+# ProcessorTrackerFeatureLandmarkExternal class test
+wolf_add_gtest(gtest_processor_tracker_feature_landmark_external gtest_processor_tracker_feature_landmark_external.cpp)
+
 # ProcessorFixedWingModel class test
 wolf_add_gtest(gtest_processor_fixed_wing_model gtest_processor_fixed_wing_model.cpp)
 
diff --git a/test/gtest_SE3.cpp b/test/gtest_SE3.cpp
index a876103795f7afe0401c4a5db48290dd95c3775c..614d6f7f4cc04430d4a595209a94039bbbacd5cb 100644
--- a/test/gtest_SE3.cpp
+++ b/test/gtest_SE3.cpp
@@ -19,19 +19,10 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 //--------LICENSE_END--------
-/**
- * \file gtest_SE3.cpp
- *
- *  Created on: Feb 2, 2019
- *      \author: jsola
- */
-
 
 #include "core/math/SE3.h"
 #include "core/utils/utils_gtest.h"
 
-
-
 using namespace Eigen;
 using namespace wolf;
 using namespace SE3;
@@ -150,7 +141,6 @@ TEST(SE3, inverseComposite)
     VectorComposite pose_vc_out_bis = inverse(pose_vc);
     ASSERT_MATRIX_APPROX(pose_vc_out_bis.at('P'), pi_true, 1e-8);
     ASSERT_MATRIX_APPROX(pose_vc_out_bis.at('O'), qi_true.coeffs(), 1e-8);
-
 }
 
 TEST(SE3, composeBlocks)
@@ -194,7 +184,6 @@ TEST(SE3, composeEigenVectors)
 
     compose(p1, q1, p2, q2, pc, qc); // tested in composeVectorBlocks
 
-
     Vector7d x1; x1 << p1, q1.coeffs();
     Vector7d x2; x2 << p2, q2.coeffs();
     Vector7d xc, xc_true; xc_true << pc, qc.coeffs();
@@ -287,7 +276,6 @@ TEST(SE3, exp_0_Composite)
 
     ASSERT_MATRIX_APPROX(x.at('P'), Vector3d::Zero(), 1e-8);
     ASSERT_MATRIX_APPROX(x.at('O'), Quaterniond::Identity().coeffs(), 1e-8);
-
 }
 
 TEST(SE3, plus_0_Composite)
diff --git a/test/gtest_factor_relative_pose_2d.cpp b/test/gtest_factor_relative_pose_2d.cpp
index cc4030a9e51617399f2b256047f24eb30a93f5df..e317161887bc11dfbb4937b47e9ceb5b4c2e6c5e 100644
--- a/test/gtest_factor_relative_pose_2d.cpp
+++ b/test/gtest_factor_relative_pose_2d.cpp
@@ -25,6 +25,8 @@
 #include "core/factor/factor_relative_pose_2d.h"
 #include "core/capture/capture_odom_2d.h"
 #include "core/math/rotations.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_angle.h"
 
 
 using namespace Eigen;
@@ -32,6 +34,12 @@ using namespace wolf;
 using std::cout;
 using std::endl;
 
+
+int N = 1e2;
+
+// Vectors
+Vector3d delta, x_0, x_1, x_f, x_l;
+
 // Input odometry data and covariance
 Matrix3d data_cov = 0.1*Matrix3d::Identity();
 
@@ -43,93 +51,164 @@ SolverCeres solver(problem_ptr);
 FrameBasePtr frm0 = problem_ptr->emplaceFrame(0.0, Vector3d::Zero());
 FrameBasePtr frm1 = problem_ptr->emplaceFrame(1.0, Vector3d::Zero());
 
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                          "LandmarkPose2d",
+                                                          std::make_shared<StatePoint2d>(Vector2d::Zero()),
+                                                          std::make_shared<StateAngle>(Vector1d::Zero()));
+
 // Capture from frm1 to frm0
 auto cap1 = CaptureBase::emplace<CaptureOdom2d>(frm1, 1, nullptr, Vector3d::Zero(), data_cov);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePose2dPtr fac = nullptr;
 
+void generateRandomProblemFrame()
+{
+    // Random delta
+    delta = Vector3d::Random() * 1e2;
+    delta(2) = pi2pi(delta(2));
+
+    // Random frame 0 pose
+    x_0 = Vector3d::Random() * 1e2;
+    x_0(2) = pi2pi(x_0(2));
+
+    // Frame 1 pose
+    x_1(2) = pi2pi(x_0(2) + delta(2));
+    x_1.head<2>() = x_0.head<2>() + Rotation2Dd(x_0(2)) * delta.head<2>();
+
+    // Set states
+    frm0->setState(x_0);
+    frm1->setState(x_1);
+    cap1->setData(delta);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose2d>(fea, fea, frm0, nullptr, false, TOP_MOTION); // create and add
+}
+
+void generateRandomProblemLandmark()
+{
+    // Random delta
+    delta = Vector3d::Random() * 1e2;
+    delta(2) = pi2pi(delta(2));
+
+    // Random frame 0 pose
+    x_f = Vector3d::Random() * 1e2;
+    x_f(2) = pi2pi(x_f(2));
+
+    // landmark pose
+    x_l(2) = pi2pi(x_f(2) + delta(2));
+    x_l.head<2>() = x_f.head<2>() + Rotation2Dd(x_f(2)) * delta.head<2>();
+
+    // Set states
+    frm1->setState(x_f);
+    lmk->setState(x_l);
+    cap1->setData(delta);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureLandmarkPose2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose2d>(fea, fea, lmk, nullptr, false, TOP_LMK); // create and add
+}
+
+////////////// TESTS /////////////////////////////////////////////////////////////////////
 TEST(FactorRelativePose2d, check_tree)
 {
     ASSERT_TRUE(problem_ptr->check(0));
 }
 
-TEST(FactorRelativePose2d, fix_0_solve)
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePose2d, frame_solve_frame1)
 {
-    for (int i = 0; i < 1e3; i++)
+    for (int i = 0; i < N; i++)
     {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
-
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
-
-        // x1 groundtruth
-        Vector3d x1;
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * delta.head<2>();
-        x1(2) = pi2pi(x0(2) + delta(2));
-
-        // Set states and measurement
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
+        // random setup
+        generateRandomProblemFrame();
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2d>(fea1, fea1, frm0, nullptr, false, TOP_MOTION);
-
-        // Fix frm0, perturb frm1
+        // Perturb frm1, fix the rest
         frm0->fix();
         frm1->unfix();
-        frm1->perturb(5);
+        frm1->perturb(1);
 
         // solve for frm1
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x1, 1e-6);
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_1, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
-TEST(FactorRelativePose2d, fix_1_solve)
+TEST(FactorRelativePose2d, frame_solve_frame0)
 {
-    for (int i = 0; i < 1e3; i++)
+    for (int i = 0; i < N; i++)
     {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
-
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
+        // random setup
+        generateRandomProblemFrame();
+        
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->unfix();
+        frm0->perturb(1);
 
-        // x1 groundtruth
-        Vector3d x1;
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * delta.head<2>();
-        x1(2) = pi2pi(x0(2) + delta(2));
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        // Set states and measurement
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
+        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x_0, 1e-3);
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2d>(fea1, fea1, frm0, nullptr, false, TOP_MOTION);
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-        // Fix frm1, perturb frm0
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePose2d, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb landmark, fix the rest
         frm1->fix();
-        frm0->unfix();
-        frm0->perturb(5);
+        lmk->unfix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePose2d, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm1->unfix();
+        lmk->fix();
+        frm1->perturb(1);
 
         // solve for frm0
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x0, 1e-6);
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_f, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
diff --git a/test/gtest_factor_relative_pose_2d_with_extrinsics.cpp b/test/gtest_factor_relative_pose_2d_with_extrinsics.cpp
index ee2bc076da1c48876250197c2fa1179ede489306..b5b4c9d0f36318408b0e056a7bdc3b17def589e7 100644
--- a/test/gtest_factor_relative_pose_2d_with_extrinsics.cpp
+++ b/test/gtest_factor_relative_pose_2d_with_extrinsics.cpp
@@ -25,8 +25,9 @@
 #include "core/factor/factor_relative_pose_2d_with_extrinsics.h"
 #include "core/capture/capture_odom_2d.h"
 #include "core/sensor/sensor_odom_2d.h"
-#include "core/processor/processor_odom_2d.h"
 #include "core/math/rotations.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_angle.h"
 
 
 using namespace Eigen;
@@ -36,6 +37,11 @@ using std::endl;
 
 std::string wolf_root = _WOLF_ROOT_DIR;
 
+int N = 1e2;
+
+// Vectors
+Vector3d delta, x_0, x_1, x_f, x_l, x_s;
+
 // Input odometry data and covariance
 Matrix3d data_cov = 0.1*Matrix3d::Identity();
 
@@ -44,212 +50,307 @@ ProblemPtr problem_ptr = Problem::create("PO", 2);
 SolverCeres solver(problem_ptr);
 
 // Sensor
-auto sensor_odom2d = problem_ptr->installSensor("SensorOdom2d", "odom", Vector3d(0,0,0), wolf_root + "/test/yaml/sensor_odom_2d.yaml");
-auto processor_odom2d = problem_ptr->installProcessor("ProcessorOdom2d", "odom", sensor_odom2d, std::make_shared<ParamsProcessorOdom2d>());
+auto sensor = problem_ptr->installSensor("SensorOdom2d", "odom", Vector3d(0,0,0), wolf_root + "/test/yaml/sensor_odom_2d.yaml");
 
 // Two frames
 FrameBasePtr frm0 = problem_ptr->emplaceFrame(0, Vector3d::Zero() );
 FrameBasePtr frm1 = problem_ptr->emplaceFrame(1, Vector3d::Zero() );
 
-// Capture from frm1 to frm0
-auto cap1 = CaptureBase::emplace<CaptureOdom2d>(frm1, 1, sensor_odom2d, Vector3d::Zero(), data_cov);
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                          "LandmarkPose2d",
+                                                          std::make_shared<StatePoint2d>(Vector2d::Zero()),
+                                                          std::make_shared<StateAngle>(Vector1d::Zero()));
 
-TEST(FactorRelativePose2dWithExtrinsics, check_tree)
+// Capture
+auto cap1 = CaptureBase::emplace<CaptureOdom2d>(frm1, 1, sensor, Vector3d::Zero(), data_cov);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePose2dWithExtrinsicsPtr fac = nullptr;
+
+void generateRandomProblemFrame()
 {
-    ASSERT_TRUE(problem_ptr->check(0));
+    // Random delta
+    delta = Vector3d::Random() * 1e2;
+    delta(2) = pi2pi(delta(2));
+
+    // Random frame 0 pose
+    x_0 = Vector3d::Random() * 1e2;
+    x_0(2) = pi2pi(x_0(2));
+
+    // Random extrinsics
+    x_s = Vector3d::Random() * 1e2;
+    x_s(2) = pi2pi(x_s(2));
+
+    // Frame 1 pose
+    x_1(2) = pi2pi(x_0(2) + delta(2));
+    x_1.head<2>() = x_0.head<2>() + Rotation2Dd(x_0(2)) * (x_s.head<2>() + Rotation2Dd(x_s(2)) * delta.head<2>()) - Rotation2Dd(x_1(2)) * x_s.head<2>();
+
+    // Set states
+    frm0->setState(x_0);
+    frm1->setState(x_1);
+    cap1->setData(delta);
+    sensor->setState(x_s);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea, fea, frm0, nullptr, false, TOP_MOTION); // create and add
 }
 
-TEST(FactorRelativePose2dWithExtrinsics, fix_0_solve)
+void generateRandomProblemLandmark()
 {
-    for (int i = 0; i < 1e2; i++)
-    {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
-
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
+    // Random delta
+    delta = Vector3d::Random() * 1e2;
+    delta(2) = pi2pi(delta(2));
+
+    // Random frame pose
+    x_f = Vector3d::Random() * 1e2;
+    x_f(2) = pi2pi(x_f(2));
+
+    // Random extrinsics
+    x_s = Vector3d::Random() * 1e2;
+    x_s(2) = pi2pi(x_s(2));
+
+    // landmark pose
+    x_l(2) = pi2pi(x_f(2) + x_s(2) + delta(2));
+    x_l.head<2>() = x_f.head<2>() + Rotation2Dd(x_f(2)) * (x_s.head<2>() + Rotation2Dd(x_s(2)) * delta.head<2>());
+
+    // Set states
+    frm1->setState(x_f);
+    lmk->setState(x_l);
+    cap1->setData(delta);
+    sensor->setState(x_s);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureLandmarkPose2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea, fea, lmk, nullptr, false, TOP_LMK); // create and add
+}
 
-        // Random extrinsics
-        Vector3d xs = Vector3d::Random();
-        pi2pi(xs(2));
+////////////// TESTS /////////////////////////////////////////////////////////////////////
 
-        // x1 groundtruth
-        Vector3d x1;
-        x1(2) = pi2pi(x0(2) + delta(2));
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * (xs.head<2>() + Rotation2Dd(xs(2)) * delta.head<2>()) - Rotation2Dd(x1(2)) * xs.head<2>();
+TEST(FactorRelativePose2dWithExtrinsics, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
 
-        // Set states
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
-        sensor_odom2d->setState(xs);
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePose2dWithExtrinsics, frame_solve_frame1)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea1, fea1, frm0, processor_odom2d, false, TOP_MOTION); // create and add
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
         // Perturb frm1, fix the rest
         frm0->fix();
         frm1->unfix();
-        sensor_odom2d->getP()->fix();
-        sensor_odom2d->getO()->fix();
-        frm1->perturb(5);
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm1->perturb(1);
 
         // solve for frm1
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x1, 1e-6);
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_1, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
-TEST(FactorRelativePose2dWithExtrinsics, fix_1_solve)
+TEST(FactorRelativePose2dWithExtrinsics, frame_solve_frame0)
 {
-    for (int i = 0; i < 1e2; i++)
+    for (int i = 0; i < N; i++)
     {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm0->perturb(1);
 
-        // Random extrinsics
-        Vector3d xs = Vector3d::Random();
-        pi2pi(xs(2));
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        // x1 groundtruth
-        Vector3d x1;
-        x1(2) = pi2pi(x0(2) + delta(2));
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * (xs.head<2>() + Rotation2Dd(xs(2)) * delta.head<2>()) - Rotation2Dd(x1(2)) * xs.head<2>();
+        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x_0, 1e-3);
 
-        // Set states
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
-        sensor_odom2d->setState(xs);
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea1, fea1, frm0, processor_odom2d, false, TOP_MOTION); // create and add
+// JV: The position extrinsics in case of pair of frames is not observable
+TEST(FactorRelativePose2dWithExtrinsics, frame_solve_extrinsics_p)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
-        // Perturb frm0, fix the rest
+        // Perturb sensor P, fix the rest
         frm1->fix();
-        frm0->unfix();
-        sensor_odom2d->getP()->fix();
-        sensor_odom2d->getO()->fix();
-        frm0->perturb(5);
+        frm0->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
 
         // solve for frm0
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
 
-        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x0, 1e-6);
+        //ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
-TEST(FactorRelativePose2dWithExtrinsics, extrinsics_p_solve)
+TEST(FactorRelativePose2dWithExtrinsics, frame_solve_extrinsics_o)
 {
-    for (int i = 0; i < 1e2; i++)
+    for (int i = 0; i < N; i++)
     {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
+        // Perturb sensor O, fix the rest
+        frm1->fix();
+        frm0->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
 
-        // Random extrinsics
-        Vector3d xs = Vector3d::Random();
-        pi2pi(xs(2));
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        // x1 groundtruth
-        Vector3d x1;
-        x1(2) = pi2pi(x0(2) + delta(2));
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * (xs.head<2>() + Rotation2Dd(xs(2)) * delta.head<2>()) - Rotation2Dd(x1(2)) * xs.head<2>();
+        ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
 
-        // Set states
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
-        sensor_odom2d->setState(xs);
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea1, fea1, frm0, processor_odom2d, false, TOP_MOTION); // create and add
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePose2dWithExtrinsics, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
-        // Perturb sensor P, fix the rest
+        // Perturb landmark, fix the rest
         frm1->fix();
-        frm0->fix();
-        sensor_odom2d->getP()->unfix();
-        sensor_odom2d->getO()->fix();
-        sensor_odom2d->getP()->perturb(1);
+        lmk->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePose2dWithExtrinsics, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
+
+        // Perturb frm0, fix the rest
+        frm1->unfix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm1->perturb(1);
 
         // solve for frm0
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        ASSERT_POSE2d_APPROX(sensor_odom2d->getStateVector(), xs, 1e-6);
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_f, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
-TEST(FactorRelativePose2dWithExtrinsics, extrinsics_o_solve)
+TEST(FactorRelativePose2dWithExtrinsics, landmark_extrinsics_p_solve)
 {
-    for (int i = 0; i < 1e2; i++)
+    for (int i = 0; i < N; i++)
     {
-        // Random delta
-        Vector3d delta = Vector3d::Random() * 10;
-        pi2pi(delta(2));
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
-        // Random initial pose
-        Vector3d x0 = Vector3d::Random() * 10;
-        pi2pi(x0(2));
+        // Perturb sensor P, fix the rest
+        frm1->fix();
+        lmk->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
 
-        // Random extrinsics
-        Vector3d xs = Vector3d::Random();
-        pi2pi(xs(2));
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        // x1 groundtruth
-        Vector3d x1;
-        x1(2) = pi2pi(x0(2) + delta(2));
-        x1.head<2>() = x0.head<2>() + Rotation2Dd(x0(2)) * (xs.head<2>() + Rotation2Dd(xs(2)) * delta.head<2>()) - Rotation2Dd(x1(2)) * xs.head<2>();
+        ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
 
-        // Set states
-        frm0->setState(x0);
-        frm1->setState(x1);
-        cap1->setData(delta);
-        sensor_odom2d->setState(xs);
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-        // feature & factor with delta measurement
-        auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom2d", delta, data_cov);
-        FactorBase::emplace<FactorRelativePose2dWithExtrinsics>(fea1, fea1, frm0, processor_odom2d, false, TOP_MOTION); // create and add
+TEST(FactorRelativePose2dWithExtrinsics, landmark_extrinsics_o_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(3), Constants::EPS);
 
         // Perturb sensor O, fix the rest
         frm1->fix();
-        frm0->fix();
-        sensor_odom2d->getP()->fix();
-        sensor_odom2d->getO()->unfix();
-        sensor_odom2d->getO()->perturb(.2);
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
 
-        //std::cout << "Sensor O (perturbed): " << sensor_odom2d->getO()->getState().transpose() << std::endl;
+        //std::cout << "Sensor O (perturbed): " << sensor->getO()->getState().transpose() << std::endl;
 
         // solve for frm0
-        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-        ASSERT_POSE2d_APPROX(sensor_odom2d->getStateVector(), xs, 1e-6);
+        ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
 
         // remove feature (and factor) for the next loop
-        fea1->remove();
+        fea->remove();
     }
 }
 
diff --git a/test/gtest_factor_relative_pose_3d.cpp b/test/gtest_factor_relative_pose_3d.cpp
index 9369c090efecad4b74435214230fca2b3b01f959..3b7f25c8fba43f0d56f5146fd2d5168e83733631 100644
--- a/test/gtest_factor_relative_pose_3d.cpp
+++ b/test/gtest_factor_relative_pose_3d.cpp
@@ -19,18 +19,14 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 //--------LICENSE_END--------
-/**
- * \file gtest_factor_relative_pose_3d.cpp
- *
- *  Created on: Mar 30, 2017
- *      \author: jsola
- */
 
 #include "core/utils/utils_gtest.h"
 
 #include "core/ceres_wrapper/solver_ceres.h"
 #include "core/factor/factor_relative_pose_3d.h"
-#include "core/capture/capture_motion.h"
+#include "core/capture/capture_odom_3d.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_quaternion.h"
 
 
 using namespace Eigen;
@@ -38,72 +34,189 @@ using namespace wolf;
 using std::cout;
 using std::endl;
 
-Vector7d data2delta(Vector6d _data)
-{
-    return (Vector7d() << _data.head<3>() , v2q(_data.tail<3>()).coeffs()).finished();
-}
+std::string wolf_root = _WOLF_ROOT_DIR;
 
-// Input odometry data and covariance
-Vector6d data(Vector6d::Random());
-Vector7d delta = data2delta(data);
-Vector6d data_var((Vector6d() << 0.2,0.2,0.2,0.2,0.2,0.2).finished());
-Matrix6d data_cov = data_var.asDiagonal();
+int N = 1e2;
 
-// perturbated priors
-Vector7d x0 = data2delta(Vector6d::Random()*0.25);
-Vector7d x1 = data2delta(data + Vector6d::Random()*0.25);
+// Vectors
+Vector7d delta, x_0, x_1, x_f, x_l;
+
+// Input odometry data and covariance
+Matrix6d data_cov = (Vector6d() << 1e-3, 1e-3, 1e-3, 1e-6, 1e-6, 1e-6).finished().asDiagonal();
 
 // Problem and solver
 ProblemPtr problem_ptr = Problem::create("PO", 3);
+
 SolverCeres solver(problem_ptr);
 
 // Two frames
-FrameBasePtr frm0 = problem_ptr->emplaceFrame(TimeStamp(0), problem_ptr->stateZero());
-FrameBasePtr frm1 = problem_ptr->emplaceFrame(TimeStamp(1), delta);
-
-// Capture, feature and factor from frm1 to frm0
-auto cap1 = CaptureBase::emplace<CaptureMotion>(frm1, "CaptureOdom3d", 1, nullptr, delta, nullptr);
-auto fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom3d", delta, data_cov);
-FactorRelativePose3dPtr ctr1 = FactorBase::emplace<FactorRelativePose3d>(fea1, fea1, frm0, nullptr, false, TOP_MOTION); // create and add
+FrameBasePtr frm0 = problem_ptr->emplaceFrame(0, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+FrameBasePtr frm1 = problem_ptr->emplaceFrame(1, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                        "LandmarkPose3d",
+                                                        std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                        std::make_shared<StateQuaternion>(Quaterniond::Identity().coeffs()));
+
+// Capture
+auto cap1 = CaptureBase::emplace<CaptureOdom3d>(frm1, 1, nullptr, Vector7d::Zero(), data_cov);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePose3dPtr fac = nullptr;
 
 ////////////////////////////////////////////////////////
 
-TEST(FactorRelativePose3d, check_tree)
+void generateRandomProblemFrame()
 {
-    ASSERT_TRUE(problem_ptr->check(0));
+    // Random delta
+    delta = Vector7d::Random() * 1e2;
+    delta.tail<4>().normalize();
+    auto q_delta = Quaterniond(delta.tail<4>());
+
+    // Random frame 0 pose
+    x_0 = Vector7d::Random() * 1e2;
+    x_0.tail<4>().normalize();
+    auto q_0 = Quaterniond(x_0.tail<4>());
+
+    // Frame 1 pose
+    x_1.head<3>() = x_0.head<3>() + q_0 * delta.head<3>();
+    x_1.tail<4>() = (q_0 * q_delta).coeffs();
+
+    // Set states
+    frm0->setState(x_0);
+    frm1->setState(x_1);
+    cap1->setData(delta);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom3d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose3d>(fea, fea, frm0, nullptr, false, TOP_MOTION); // create and add
 }
 
-TEST(FactorRelativePose3d, expectation)
+void generateRandomProblemLandmark()
 {
-    ASSERT_MATRIX_APPROX(ctr1->expectation() , delta, 1e-6);
+    // Random delta
+    delta = Vector7d::Random() * 10;
+    delta.tail<4>().normalize();
+    auto q_delta = Quaterniond(delta.tail<4>());
+
+    // Random frame pose
+    x_f = Vector7d::Random() * 10;
+    x_f.tail<4>().normalize();
+    auto q_f = Quaterniond(x_f.tail<4>());
+
+    // Landmark pose
+    x_l.head<3>() = x_f.head<3>() + q_f * delta.head<3>();
+    x_l.tail<4>() = (q_f * q_delta).coeffs();
+
+    // Set states
+    frm1->setState(x_f);
+    lmk->setState(x_l);
+    cap1->setData(delta);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureLandmarkPose", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose3d>(fea, fea, lmk, nullptr, false, TOP_LMK); // create and add
 }
 
-TEST(FactorRelativePose3d, fix_0_solve)
+TEST(FactorRelativePose3d, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
+
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePose3d, frame_solve_frame1)
 {
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
 
-    // Fix frame 0, perturb frm1
-    frm0->fix();
-    frm1->unfix();
-    frm1->setState(x1);
+        // Perturb frm1, fix the rest
+        frm0->fix();
+        frm1->unfix();
+        frm1->perturb(1);
 
-    // solve for frm1
-    std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+        // solve for frm1
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-    ASSERT_MATRIX_APPROX(frm1->getStateVector(), delta, 1e-6);
+        ASSERT_POSE3d_APPROX(frm1->getStateVector(), x_1, 1e-3);
 
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
-TEST(FactorRelativePose3d, fix_1_solve)
+TEST(FactorRelativePose3d, frame_solve_frame0)
 {
-    // Fix frame 1, perturb frm0
-    frm0->unfix();
-    frm1->fix();
-    frm0->setState(x0);
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->unfix();
+        frm0->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm0->getStateVector(), x_0, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-    // solve for frm0
-    std::string brief_report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePose3d, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb landmark, fix the rest
+        frm1->fix();
+        lmk->unfix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-    ASSERT_MATRIX_APPROX(frm0->getStateVector(), problem_ptr->stateZero().vector("PO"), 1e-6);
+TEST(FactorRelativePose3d, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm1->unfix();
+        lmk->fix();
+        frm1->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm1->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
 int main(int argc, char **argv)
diff --git a/test/gtest_factor_relative_pose_3d_with_extrinsics.cpp b/test/gtest_factor_relative_pose_3d_with_extrinsics.cpp
index f4755e11139f07ba42d537e0fc4e070c300cf9a3..2e99b1d2b765998d11c7eba96f7241e29977a7c4 100644
--- a/test/gtest_factor_relative_pose_3d_with_extrinsics.cpp
+++ b/test/gtest_factor_relative_pose_3d_with_extrinsics.cpp
@@ -19,308 +19,353 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 //--------LICENSE_END--------
-#include <core/factor/factor_relative_pose_3d_with_extrinsics.h>
-#include <core/utils/utils_gtest.h>
-
-#include "core/common/wolf.h"
-#include "core/utils/logging.h"
+#include "core/utils/utils_gtest.h"
 
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_relative_pose_3d_with_extrinsics.h"
+#include "core/capture/capture_odom_3d.h"
+#include "core/sensor/sensor_odom_3d.h"
+#include "core/math/rotations.h"
 #include "core/state_block/state_block_derived.h"
 #include "core/state_block/state_quaternion.h"
-#include "core/ceres_wrapper/solver_ceres.h"
-#include "core/capture/capture_pose.h"
-#include "core/feature/feature_pose.h"
-
 
 
 using namespace Eigen;
 using namespace wolf;
-using std::static_pointer_cast;
-
-
-
-// Use the following in case you want to initialize tests with predefines variables or methods.
-class FactorRelativePose3dWithExtrinsics_class : public testing::Test{
-    public:
-        Vector3d    pos_camera,   pos_robot,   pos_landmark;
-        Vector3d    euler_camera, euler_robot, euler_landmark;
-        Quaterniond quat_camera,  quat_robot,  quat_landmark;
-        Vector4d    vquat_camera, vquat_robot, vquat_landmark; // quaternions as vectors
-        Vector7d    pose_camera,  pose_robot,  pose_landmark;
-
-        ProblemPtr     problem;
-        SolverCeresPtr solver;
-
-        SensorBasePtr   S;
-        FrameBasePtr    F1;
-        CapturePosePtr  c1;
-        FeaturePosePtr  f1;
-        LandmarkBasePtr lmk1;
-        FactorRelativePose3dWithExtrinsicsPtr fac;
-
-        Eigen::Matrix6d meas_cov;
-
-        void SetUp() override
-        {
-            // configuration
-
-             /* We have three poses to take into account:
-             *  - pose of the sensor (extrinsincs)
-             *  - pose of the landmark
-             *  - pose of the robot (Keyframe)
-             *
-             * The measurement provides the pose of the landmark wrt sensor's current pose in the world.
-             * Camera's current pose in World is the composition of the robot pose with the sensor extrinsics.
-             *
-             * The robot has a sensor looking forward
-             *   S: pos = (0,0,0), ori = (0, 0, 0)
-             *
-             * There is a point at the origin
-             *   P: pos = (0,0,0)
-             *
-             * Therefore, P projects exactly at the origin of the sensor,
-             *   f: p = (0,0)
-             *
-             * We form a Wolf tree with 1 frames F1, 1 capture C1,
-             * 1 feature f1 (measurement), 1 landmark l and 1 relative kf landmark constraint c1:
-             *
-             *   Frame F1, Capture C1, feature f1, landmark l1, constraint c1
-             *
-             * The frame pose F1, and the sensor pose S
-             * in the robot frame are variables subject to optimization
-             *
-             * We perform a number of tests based on this configuration.*/
-
-
-            // sensor is at origin of robot and looking forward
-            // robot is at (0,0,0)
-            // landmark is right in front of sensor. Its orientation wrt sensor is identity.
-            pos_camera      << 0,0,0;
-            pos_robot       << 0,0,0; //robot is at origin
-            pos_landmark    << 0,1,0;
-            euler_camera    << 0,0,0;
-            //euler_camera    << -M_PI_2, 0, -M_PI_2;
-            euler_robot     << 0,0,0;
-            euler_landmark  << 0,0,0;
-            quat_camera     = e2q(euler_camera);
-            quat_robot      = e2q(euler_robot);
-            quat_landmark   = e2q(euler_landmark);
-            vquat_camera    = quat_camera.coeffs();
-            vquat_robot     = quat_robot.coeffs();
-            vquat_landmark  = quat_landmark.coeffs();
-            pose_camera     << pos_camera, vquat_camera;
-            pose_robot      << pos_robot, vquat_robot;
-            pose_landmark   << pos_landmark, vquat_landmark;
-
-            // Build problem
-            problem = Problem::create("PO", 3);
-            solver = std::make_shared<SolverCeres>(problem);
-
-            // Create sensor to be able to initialize (a camera for instance)
-            S = SensorBase::emplace<SensorBase>(problem->getHardware(), "SensorPose", 
-                                                std::make_shared<StatePoint3d>(pos_camera, true), 
-                                                std::make_shared<StateQuaternion>(vquat_camera, true), 
-                                                std::make_shared<StateParams4>(Vector4d::Zero(), false),  // fixed
-                                                Vector1d::Zero());
-
-            // ParamsSensorCameraPtr params_camera = std::make_shared<ParamsSensorCamera>();
-            // S      = problem->installSensor("SensorCamera", "canonical", pose_camera, params_camera);
-            // camera = std::static_pointer_cast<SensorCamera>(S);
-
-
-            // F1 is be origin KF
-            VectorComposite x0(pose_robot, "PO", {3,4});
-            VectorComposite s0("PO", {Vector3d(1,1,1), Vector3d(1,1,1)});
-            F1 = problem->setPriorFactor(x0, s0, 0.0);
-
-
-            // the sensor is at origin as well as the robot. The measurement matches with the pose of the tag wrt sensor (and also wrt robot and world)
-            meas_cov = Eigen::Matrix6d::Identity();
-            meas_cov.topLeftCorner(3,3)     *= 1e-2;
-            meas_cov.bottomRightCorner(3,3) *= 1e-3;
-
-            //emplace feature and landmark
-            c1 = std::static_pointer_cast<CapturePose>(CaptureBase::emplace<CapturePose>(F1, 0, S, pose_landmark, meas_cov));
-            f1 = std::static_pointer_cast<FeaturePose>(FeatureBase::emplace<FeaturePose>(c1, pose_landmark, meas_cov));
-            lmk1 = LandmarkBase::emplace<LandmarkBase>(problem->getMap(), "LandmarkPose",  
-                                                       std::make_shared<StatePoint3d>(pos_landmark), 
-                                                       std::make_shared<StateQuaternion>(Quaterniond(vquat_landmark)));
-        }
-};
-
-
-TEST_F(FactorRelativePose3dWithExtrinsics_class, Constructor)
-{
-    auto factor = std::make_shared<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                       lmk1,
-                                                                       nullptr,
-                                                                       false);
+using std::cout;
+using std::endl;
 
-    ASSERT_TRUE(factor->getType() == "FactorRelativePose3dWithExtrinsics");
-}
+std::string wolf_root = _WOLF_ROOT_DIR;
+
+int N = 1e2;
+
+// Vectors
+Vector7d delta, x_0, x_1, x_f, x_l, x_s;
+
+// Input odometry data and covariance
+Matrix6d data_cov = (Vector6d() << 1e-3, 1e-3, 1e-3, 1e-6, 1e-6, 1e-6).finished().asDiagonal();
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 3);
+
+SolverCeres solver(problem_ptr);
+
+// Sensor
+auto sensor = problem_ptr->installSensor("SensorOdom3d", "odom", (Vector7d() << 0,0,0,0,0,0,1).finished(), wolf_root + "/test/yaml/sensor_odom_3d.yaml");
+
+// Two frames
+FrameBasePtr frm0 = problem_ptr->emplaceFrame(0, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+FrameBasePtr frm1 = problem_ptr->emplaceFrame(1, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                        "LandmarkPose3d",
+                                                        std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                        std::make_shared<StateQuaternion>(Quaterniond::Identity().coeffs()));
 
-/////////////////////////////////////////////
-// Tree not ok with this gtest implementation
-/////////////////////////////////////////////
-TEST_F(FactorRelativePose3dWithExtrinsics_class, Check_tree)
+// Capture
+auto cap1 = CaptureBase::emplace<CaptureOdom3d>(frm1, 1, sensor, Vector7d::Zero(), data_cov);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePose3dWithExtrinsicsPtr fac = nullptr;
+
+void generateRandomProblemFrame()
 {
-    ASSERT_TRUE(problem->check(1));
-
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
-    ASSERT_TRUE(problem->check(1));
+    // Random delta
+    delta = Vector7d::Random() * 1e2;
+    delta.tail<4>().normalize();
+    auto q_delta = Quaterniond(delta.tail<4>());
+
+    // Random frame 0 pose
+    x_0 = Vector7d::Random() * 1e2;
+    x_0.tail<4>().normalize();
+    auto q_0 = Quaterniond(x_0.tail<4>());
+
+    // Random extrinsics
+    x_s = Vector7d::Random() * 1e2;
+    x_s.tail<4>().normalize();
+    auto q_s = Quaterniond(x_s.tail<4>());
+
+    // Frame 1 pose
+    auto q_1 = q_0 * q_s * q_delta * q_s.conjugate();
+    x_1.head<3>() = x_0.head<3>() + q_0 * (x_s.head<3>() + q_s * delta.head<3>()) - q_1 *  x_s.head<3>();
+    x_1.tail<4>() = q_1.coeffs();
+
+    // WOLF_INFO("x_0:   ", x_0.transpose());
+    // WOLF_INFO("x_s:   ", x_s.transpose());
+    // WOLF_INFO("delta: ", delta.transpose());
+    // WOLF_INFO("x_1:   ", x_1.transpose());
+
+    // Set states
+    frm0->setState(x_0);
+    frm1->setState(x_1);
+    cap1->setData(delta);
+    sensor->setState(x_s);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureOdom3d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(fea, fea, frm0, nullptr, false, TOP_MOTION); // create and add
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, solve_F1_P_perturbated)
+void generateRandomProblemLandmark()
 {
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
-
-    // unfix F1, perturbate state
-    F1->unfix();
-    F1->getP()->perturb();
-
-    std::string report = solver->solve(SolverManager::ReportVerbosity::QUIET); // 0: nothing, 1: BriefReport, 2: FullReport
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), pose_robot, 1e-6);
+    // Random delta
+    delta = Vector7d::Random() * 10;
+    delta.tail<4>().normalize();
+    auto q_delta = Quaterniond(delta.tail<4>());
+
+    // Random frame pose
+    x_f = Vector7d::Random() * 10;
+    x_f.tail<4>().normalize();
+    auto q_f = Quaterniond(x_f.tail<4>());
+
+    // Random extrinsics
+    x_s = Vector7d::Random() * 10;
+    x_s.tail<4>().normalize();
+    auto q_s = Quaterniond(x_s.tail<4>());
+
+    // Landmark pose
+    x_l.head<3>() = x_f.head<3>() + q_f * (x_s.head<3>() + q_s * delta.head<3>());
+    x_l.tail<4>() = (q_f * q_s * q_delta).coeffs();
+
+    // Set states
+    frm1->setState(x_f);
+    lmk->setState(x_l);
+    cap1->setData(delta);
+    sensor->setState(x_s);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap1, "FeatureLandmarkPose", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(fea, fea, lmk, nullptr, false, TOP_LMK); // create and add
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, solve_F1_O_perturbated)
+TEST(FactorRelativePose3dWithExtrinsics, check_tree)
 {
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
-
-    // unfix F1, perturbate state
-    F1->unfix();
-    F1->getO()->perturb();
-
-    std::string report = solver->solve(SolverManager::ReportVerbosity::QUIET); // 0: nothing, 1: BriefReport, 2: FullReport
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), pose_robot, 1e-6);
+    ASSERT_TRUE(problem_ptr->check(0));
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, Check_initialization)
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePose3dWithExtrinsics, frame_solve_frame1)
 {
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb frm1, fix the rest
+        frm0->fix();
+        frm1->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm1->perturb(1);
+
+        // solve for frm1
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
 
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), pose_robot, 1e-6);
-    ASSERT_MATRIX_APPROX(f1->getMeasurement(), pose_landmark, 1e-6);
-    ASSERT_MATRIX_APPROX(lmk1->getState().vector("PO"), pose_landmark, 1e-6);
+        ASSERT_POSE3d_APPROX(frm1->getStateVector(), x_1, 1e-3);
 
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, solve_L1_P_perturbated)
+TEST(FactorRelativePose3dWithExtrinsics, frame_solve_frame0)
 {
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
-
-    // unfix lmk1, perturbate state
-    lmk1->unfix();
-    lmk1->getP()->perturb();
-
-    std::string report = solver->solve(SolverManager::ReportVerbosity::QUIET); // 0: nothing, 1: BriefReport, 2: FullReport
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), pose_robot, 1e-6);
-    ASSERT_MATRIX_APPROX(lmk1->getState().vector("PO"), pose_landmark, 1e-6);
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm0->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm0->getStateVector(), x_0, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, solve_L1_O_perturbated)
+// JV: The position extrinsics in case of pair of frames is not observable
+TEST(FactorRelativePose3dWithExtrinsics, frame_solve_extrinsics_p)
 {
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb sensor P, fix the rest
+        frm1->fix();
+        frm0->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        //ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-    // unfix F1, perturbate state
-    lmk1->unfix();
-    lmk1->getO()->perturb();
+TEST(FactorRelativePose3dWithExtrinsics, frame_solve_extrinsics_o)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb sensor O, fix the rest
+        frm1->fix();
+        frm0->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
-    std::string report = solver->solve(SolverManager::ReportVerbosity::QUIET); // 0: nothing, 1: BriefReport, 2: FullReport
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), pose_robot, 1e-6);
-    ASSERT_MATRIX_APPROX(lmk1->getState().vector("PO"), pose_landmark, 1e-6);
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePose3dWithExtrinsics, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb landmark, fix the rest
+        frm1->fix();
+        lmk->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
+TEST(FactorRelativePose3dWithExtrinsics, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb frm0, fix the rest
+        frm1->unfix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm1->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm1->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
-TEST_F(FactorRelativePose3dWithExtrinsics_class, solve_L1_PO_perturbated)
+TEST(FactorRelativePose3dWithExtrinsics, landmark_extrinsics_p_solve)
 {
-    // Change setup
-    Vector3d p_w_r, p_r_c, p_c_l, p_w_l;
-    Quaterniond q_w_r, q_r_c, q_c_l, q_w_l;
-    p_w_r << 1, 2, 3;
-    p_r_c << 4, 5, 6;
-    p_c_l << 7, 8, 9;
-    q_w_r.coeffs() << 1, 2, 3, 4; q_w_r.normalize();
-    q_r_c.coeffs() << 4, 5, 6, 7; q_r_c.normalize();
-    q_c_l.coeffs() << 7, 8, 9, 0; q_c_l.normalize();
-
-    q_w_l = q_w_r * q_r_c * q_c_l;
-    p_w_l = p_w_r + q_w_r * (p_r_c + q_r_c * p_c_l);
-
-    // Change feature (remove and emplace)
-    Vector7d meas;
-    meas << p_c_l, q_c_l.coeffs();
-    f1->remove();
-    f1 = std::static_pointer_cast<FeaturePose>(FeatureBase::emplace<FeaturePose>(c1, meas, meas_cov));
-
-    // emplace factor
-    auto factor = FactorBase::emplace<FactorRelativePose3dWithExtrinsics>(f1,
-                                                                          f1,
-                                                                          lmk1,
-                                                                          nullptr,
-                                                                          false);
-
-    // Change Landmark
-    lmk1->getP()->setState(p_w_l);
-    lmk1->getO()->setState(q_w_l.coeffs());
-    ASSERT_TRUE(lmk1->getP()->stateUpdated());
-    ASSERT_TRUE(lmk1->getO()->stateUpdated());
-
-    // Change Frame
-    F1->getP()->setState(p_w_r);
-    F1->getO()->setState(q_w_r.coeffs());
-    F1->fix();
-    ASSERT_TRUE(F1->getP()->stateUpdated());
-    ASSERT_TRUE(F1->getO()->stateUpdated());
-
-    // Change sensor extrinsics
-    S->getP()->setState(p_r_c);
-    S->getO()->setState(q_r_c.coeffs());
-    ASSERT_TRUE(S->getP()->stateUpdated());
-    ASSERT_TRUE(S->getO()->stateUpdated());
-
-    Vector7d t_w_r, t_w_l;
-    t_w_r << p_w_r, q_w_r.coeffs();
-    t_w_l << p_w_l, q_w_l.coeffs();
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO"), t_w_r, 1e-6);
-    ASSERT_MATRIX_APPROX(lmk1->getState().vector("PO"), t_w_l, 1e-6);
-
-    // unfix LMK, perturbate state
-    lmk1->unfix();
-    lmk1->perturb();
-
-    std::string report = solver->solve(SolverManager::ReportVerbosity::QUIET); // 0: nothing, 1: BriefReport, 2: FullReport
-    ASSERT_MATRIX_APPROX(F1->getState().vector("PO").transpose(), t_w_r.transpose(), 1e-6);
-    ASSERT_MATRIX_APPROX(lmk1->getState().vector("PO").transpose(), t_w_l.transpose(), 1e-6);
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb sensor P, fix the rest
+        frm1->fix();
+        lmk->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
 
+TEST(FactorRelativePose3dWithExtrinsics, landmark_extrinsics_o_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac->residual(), VectorXd::Zero(6), Constants::EPS);
+
+        // Perturb sensor O, fix the rest
+        frm1->fix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
 }
 
 int main(int argc, char **argv)
 {
-    testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
+testing::InitGoogleTest(&argc, argv);
+return RUN_ALL_TESTS();
 }
-
diff --git a/test/gtest_factor_relative_position_2d.cpp b/test/gtest_factor_relative_position_2d.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e91d0fb436646ea92401d64e5bde466e5989c6d9
--- /dev/null
+++ b/test/gtest_factor_relative_position_2d.cpp
@@ -0,0 +1,268 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#include "core/utils/utils_gtest.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_relative_position_2d.h"
+#include "core/capture/capture_void.h"
+#include "core/sensor/sensor_odom_2d.h"
+#include "core/math/rotations.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_angle.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using std::cout;
+using std::endl;
+
+std::string wolf_root = _WOLF_ROOT_DIR;
+
+int N = 1;//1e2
+
+// Vectors
+Vector3d x_0, x_f, x_1;
+Vector2d delta, x_l;
+
+// Input odometry data and covariance
+Matrix2d data_cov = 0.1*Matrix2d::Identity();
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 2);
+SolverCeres solver(problem_ptr);
+
+// Frames
+FrameBasePtr frm0 = problem_ptr->emplaceFrame(0, Vector3d::Zero() );
+FrameBasePtr frm1 = problem_ptr->emplaceFrame(1, Vector3d::Zero() );
+
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                          "LandmarkPosition2d",
+                                                          std::make_shared<StatePoint2d>(Vector2d::Zero()),
+                                                          nullptr);
+
+// Capture
+auto cap = CaptureBase::emplace<CaptureVoid>(frm1, 1, nullptr);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePosition2dPtr fac = nullptr;
+
+void generateRandomProblemFrame()
+{
+    // Random delta
+    delta = Vector2d::Random() * 1e2;
+
+    // Random frame 0 pose
+    x_0 = Vector3d::Random() * 1e2;
+    x_0(2) = pi2pi(x_0(2));
+
+    // Frame 1 position
+    x_1.head<2>() = x_0.head<2>() + Rotation2Dd(x_0(2)) * delta;
+
+    // Set states
+    frm0->setState(x_0);
+    frm1->setState(x_1);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureFramePosition2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePosition2d>(fea, fea, frm0, nullptr, false, TOP_MOTION);
+}
+
+void generateRandomProblemLandmark()
+{
+    // Random delta
+    delta = Vector2d::Random() * 1e2;
+
+    // Random frame pose
+    x_f = Vector3d::Random() * 1e2;
+    x_f(2) = pi2pi(x_f(2));
+
+    // landmark position
+    x_l = x_f.head<2>() + Rotation2Dd(x_f(2)) * delta;
+
+    // Set states
+    frm1->setState(x_f);
+    lmk->setState(x_l);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureLandmarkPosition2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePosition2d>(fea, fea, lmk, nullptr, false, TOP_LMK);
+}
+
+////////////// TESTS /////////////////////////////////////////////////////////////////////
+
+TEST(FactorRelativePosition2d, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
+
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePosition2d, frame_solve_frame1_p)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+
+        // Perturb frm1, fix the rest
+        frm0->fix();
+        frm1->getP()->unfix();
+        frm1->getO()->fix();
+        frm1->getP()->perturb(1);
+
+        // solve for frm1
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_1, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2d, frame_solve_frame0_p)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->getP()->unfix();
+        frm0->getO()->fix();
+        frm0->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x_0, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2d, frame_solve_frame0_o)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        // Perturb frm0, fix the rest
+        frm1->fix();
+        frm0->getP()->fix();
+        frm0->getO()->unfix();
+        frm0->getO()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm0->getStateVector(), x_0, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePosition2d, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb landmark, fix the rest
+        frm1->fix();
+        lmk->unfix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2d, landmark_solve_frame_p)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm1->getP()->unfix();
+        frm1->getO()->fix();
+        lmk->fix();
+        frm1->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2d, landmark_solve_frame_o)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm1->getP()->fix();
+        frm1->getO()->unfix();
+        lmk->fix();
+        frm1->getO()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm1->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_factor_relative_position_2d_with_extrinsics.cpp b/test/gtest_factor_relative_position_2d_with_extrinsics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..87bb8f07080f83760be2fe1896904c8f64e2bd92
--- /dev/null
+++ b/test/gtest_factor_relative_position_2d_with_extrinsics.cpp
@@ -0,0 +1,239 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#include "core/utils/utils_gtest.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_relative_position_2d_with_extrinsics.h"
+#include "core/capture/capture_void.h"
+#include "core/sensor/sensor_odom_2d.h"
+#include "core/math/rotations.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_angle.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using std::cout;
+using std::endl;
+
+std::string wolf_root = _WOLF_ROOT_DIR;
+
+int N = 1;//1e2
+
+// Vectors
+Vector3d x_f, x_s;
+Vector2d delta, x_l;
+
+// Input odometry data and covariance
+Matrix2d data_cov = 0.1*Matrix2d::Identity();
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 2);
+SolverCeres solver(problem_ptr);
+
+// Sensor
+auto sensor = problem_ptr->installSensor("SensorOdom2d", "odom", Vector3d(0,0,0), wolf_root + "/test/yaml/sensor_odom_2d.yaml");
+
+// Frame
+FrameBasePtr frm = problem_ptr->emplaceFrame(0, Vector3d::Zero() );
+
+// Landmark
+LandmarkBasePtr lmk = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                          "LandmarkPosition2d",
+                                                          std::make_shared<StatePoint2d>(Vector2d::Zero()),
+                                                          nullptr);
+
+// Capture
+auto cap = CaptureBase::emplace<CaptureVoid>(frm, 1, sensor);
+// Feature
+FeatureBasePtr fea = nullptr;
+// Factor
+FactorRelativePosition2dWithExtrinsicsPtr fac = nullptr;
+
+void generateRandomProblemLandmark()
+{
+    // Random delta
+    delta = Vector2d::Random() * 1e2;
+
+    // Random frame pose
+    x_f = Vector3d::Random() * 1e2;
+    x_f(2) = pi2pi(x_f(2));
+
+    // Random extrinsics
+    x_s = Vector3d::Random() * 1e2;
+    x_s(2) = pi2pi(x_s(2));
+
+    // landmark position
+    x_l = x_f.head<2>() + Rotation2Dd(x_f(2)) * (x_s.head<2>() + Rotation2Dd(x_s(2)) * delta);
+
+    // Set states
+    frm->setState(x_f);
+    lmk->setState(x_l);
+    sensor->setState(x_s);
+
+    // feature & factor with delta measurement
+    fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureLandmarkPosition2d", delta, data_cov);
+    fac = FactorBase::emplace<FactorRelativePosition2dWithExtrinsics>(fea, fea, lmk, nullptr, false, TOP_LMK);
+}
+
+////////////// TESTS /////////////////////////////////////////////////////////////////////
+
+TEST(FactorRelativePosition2dWithExtrinsics, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
+
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePosition2dWithExtrinsics, landmark_solve_lmk)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb landmark, fix the rest
+        frm->fix();
+        lmk->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        lmk->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(lmk->getStateVector(), x_l, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2dWithExtrinsics, landmark_solve_frame_p)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm->getP()->unfix();
+        frm->getO()->fix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2dWithExtrinsics, landmark_solve_frame_o)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm->getP()->fix();
+        frm->getO()->unfix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm->getO()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(frm->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2dWithExtrinsics, landmark_extrinsics_p_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb sensor P, fix the rest
+        frm->fix();
+        lmk->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+TEST(FactorRelativePosition2dWithExtrinsics, landmark_extrinsics_o_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb sensor O, fix the rest
+        frm->fix();
+        lmk->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
+
+        //std::cout << "Sensor O (perturbed): " << sensor->getO()->getState().transpose() << std::endl;
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE2d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea->remove();
+    }
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_factor_relative_position_3d.cpp b/test/gtest_factor_relative_position_3d.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e928a49baf2f61b7c23eb663830a7a0d63c78a47
--- /dev/null
+++ b/test/gtest_factor_relative_position_3d.cpp
@@ -0,0 +1,280 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#include "core/utils/utils_gtest.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_relative_position_3d.h"
+#include "core/capture/capture_odom_3d.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_quaternion.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using std::cout;
+using std::endl;
+
+std::string wolf_root = _WOLF_ROOT_DIR;
+
+int N = 1e2;
+
+// Vectors
+Vector7d x_f;
+Vector3d delta1, delta2, delta3, x_1, x_2, x_3;
+
+// Input odometry data and covariance
+Matrix3d data_cov = (Vector3d() << 1e-3, 1e-3, 1e-3).finished().asDiagonal();
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 3);
+
+SolverCeres solver(problem_ptr);
+
+// Two frames
+FrameBasePtr frm  = problem_ptr->emplaceFrame(0, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+FrameBasePtr frm1 = problem_ptr->emplaceFrame(1, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+FrameBasePtr frm2 = problem_ptr->emplaceFrame(2, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+FrameBasePtr frm3 = problem_ptr->emplaceFrame(3, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+
+// Landmarks
+LandmarkBasePtr lmk1 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+LandmarkBasePtr lmk2 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+LandmarkBasePtr lmk3 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+// Captures
+CaptureBasePtr cap1, cap2, cap3;
+// Features
+FeatureBasePtr fea1, fea2, fea3;
+// Factors
+FactorRelativePosition3dPtr fac1, fac2, fac3;
+
+////////////////////////////////////////////////////////
+void generateRandomProblemFrame()
+{
+    // Random delta
+    delta1 = Vector3d::Random() * 1e2;
+    delta2 = Vector3d::Random() * 1e2;
+    delta3 = Vector3d::Random() * 1e2;
+
+    // Random frame pose
+    x_f = Vector7d::Random() * 1e2;
+    x_f.tail<4>().normalize();
+    auto q_f = Quaterniond(x_f.tail<4>());
+
+    // Frames position
+    x_1 = x_f.head<3>() + q_f * delta1;
+    x_2 = x_f.head<3>() + q_f * delta2;
+    x_3 = x_f.head<3>() + q_f * delta3;
+
+    // Set states
+    frm->setState(x_f);
+    frm1->getP()->setState(x_1);
+    frm2->getP()->setState(x_2);
+    frm3->getP()->setState(x_3);
+
+    // capture & feature & factor with delta measurementCaptureBase(const std::string& _type,
+    cap1 = CaptureBase::emplace<CaptureBase>(frm1, "CaptureFramePosition3d", 1);
+    cap2 = CaptureBase::emplace<CaptureBase>(frm2, "CaptureFramePosition3d", 2);
+    cap3 = CaptureBase::emplace<CaptureBase>(frm3, "CaptureFramePosition3d", 3);
+    fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureFramePosition3d", delta1, data_cov);
+    fea2 = FeatureBase::emplace<FeatureBase>(cap2, "FeatureFramePosition3d", delta2, data_cov);
+    fea3 = FeatureBase::emplace<FeatureBase>(cap3, "FeatureFramePosition3d", delta3, data_cov);
+    fac1 = FactorBase::emplace<FactorRelativePosition3d>(fea1, fea1, frm, nullptr, false, TOP_MOTION);
+    fac2 = FactorBase::emplace<FactorRelativePosition3d>(fea2, fea2, frm, nullptr, false, TOP_MOTION);
+    fac3 = FactorBase::emplace<FactorRelativePosition3d>(fea3, fea3, frm, nullptr, false, TOP_MOTION);
+}
+
+void generateRandomProblemLandmark()
+{
+    // Random delta
+    delta1 = Vector3d::Random() * 1e2;
+    delta2 = Vector3d::Random() * 1e2;
+    delta3 = Vector3d::Random() * 1e2;
+
+    // Random frame pose
+    x_f = Vector7d::Random() * 1e2;
+    x_f.tail<4>().normalize();
+    auto q_f = Quaterniond(x_f.tail<4>());
+
+    // Landmarks positions
+    x_1 = x_f.head<3>() + q_f * delta1;
+    x_2 = x_f.head<3>() + q_f * delta2;
+    x_3 = x_f.head<3>() + q_f * delta3;
+
+    // Set states
+    frm->setState(x_f);
+    lmk1->setState(x_1);
+    lmk2->setState(x_2);
+    lmk3->setState(x_3);
+
+    // feature & factor with delta measurement
+    cap1 = CaptureBase::emplace<CaptureBase>(frm, "CaptureLandmarkPosition3d", 1);
+    cap2 = CaptureBase::emplace<CaptureBase>(frm, "CaptureLandmarkPosition3d", 1);
+    cap3 = CaptureBase::emplace<CaptureBase>(frm, "CaptureLandmarkPosition3d", 1);
+    fea1 = FeatureBase::emplace<FeatureBase>(cap1, "FeatureLandmarkPosition3d", delta1, data_cov);
+    fea2 = FeatureBase::emplace<FeatureBase>(cap2, "FeatureLandmarkPosition3d", delta2, data_cov);
+    fea3 = FeatureBase::emplace<FeatureBase>(cap3, "FeatureLandmarkPosition3d", delta3, data_cov);
+    fac1 = FactorBase::emplace<FactorRelativePosition3d>(fea1, fea1, lmk1, nullptr, false, TOP_LMK);
+    fac2 = FactorBase::emplace<FactorRelativePosition3d>(fea2, fea2, lmk2, nullptr, false, TOP_LMK);
+    fac3 = FactorBase::emplace<FactorRelativePosition3d>(fea3, fea3, lmk3, nullptr, false, TOP_LMK);
+}
+
+TEST(FactorRelativePosition3d, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
+
+// FRAME TO FRAME =========================================================================
+TEST(FactorRelativePosition3d, frame_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+
+        // Perturb frm, fix the rest
+        frm->unfix();
+        frm1->fix();
+        frm2->fix();
+        frm3->fix();
+        frm->perturb(1);
+
+        // solve for frm1
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm->getStateVector(), x_f, 1e-3);
+
+        // remove captures (and features and factors) for the next loop
+        cap1->remove();
+        cap2->remove();
+        cap3->remove();
+    }
+}
+
+TEST(FactorRelativePosition3d, frame_solve_frames)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemFrame();
+        
+        // Perturb frm0, fix the rest
+        frm->fix();
+        frm1->unfix();
+        frm2->unfix();
+        frm3->unfix();
+        frm1->getP()->perturb(1);
+        frm2->getP()->perturb(1);
+        frm3->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(frm1->getStateVector("P"), x_1, 1e-3);
+        ASSERT_MATRIX_APPROX(frm2->getStateVector("P"), x_2, 1e-3);
+        ASSERT_MATRIX_APPROX(frm3->getStateVector("P"), x_3, 1e-3);
+
+        // remove captures (and features and factors) for the next loop
+        cap1->remove();
+        cap2->remove();
+        cap3->remove();
+    }
+}
+
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePosition3d, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb frm0, fix the rest
+        frm->unfix();
+        lmk1->fix();
+        lmk2->fix();
+        lmk3->fix();
+        frm->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm->getStateVector(), x_f, 1e-3);
+
+        // remove captures (and features and factors) for the next loop
+        cap1->remove();
+        cap2->remove();
+        cap3->remove();
+    }
+}
+
+TEST(FactorRelativePosition3d, landmark_solve_lmks)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        // Perturb landmark, fix the rest
+        frm->fix();
+        lmk1->unfix();
+        lmk2->unfix();
+        lmk3->unfix();
+        lmk1->perturb(1);
+        lmk2->perturb(1);
+        lmk3->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(lmk1->getStateVector("P"), x_1, 1e-3);
+        ASSERT_MATRIX_APPROX(lmk2->getStateVector("P"), x_2, 1e-3);
+        ASSERT_MATRIX_APPROX(lmk3->getStateVector("P"), x_3, 1e-3);
+
+        // remove captures (and features and factors) for the next loop
+        cap1->remove();
+        cap2->remove();
+        cap3->remove();
+    }
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_factor_relative_position_3d_with_extrinsics.cpp b/test/gtest_factor_relative_position_3d_with_extrinsics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3917e5d85f021c0f950b18fa7ab4d191fb835379
--- /dev/null
+++ b/test/gtest_factor_relative_position_3d_with_extrinsics.cpp
@@ -0,0 +1,267 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+#include "core/utils/utils_gtest.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_relative_position_3d_with_extrinsics.h"
+#include "core/capture/capture_odom_3d.h"
+#include "core/sensor/sensor_odom_3d.h"
+#include "core/math/rotations.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_quaternion.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+using std::cout;
+using std::endl;
+
+std::string wolf_root = _WOLF_ROOT_DIR;
+
+int N = 1e2;
+
+// Vectors
+Vector7d x_f, x_s;
+Vector3d delta1, delta2, delta3, x_l1, x_l2, x_l3;
+
+// Input odometry data and covariance
+Matrix3d data_cov = (Vector3d() << 1e-3, 1e-3, 1e-3).finished().asDiagonal();
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 3);
+
+SolverCeres solver(problem_ptr);
+
+// Sensor
+auto sensor = problem_ptr->installSensor("SensorOdom3d", "odom", (Vector7d() << 0,0,0,0,0,0,1).finished(), wolf_root + "/test/yaml/sensor_odom_3d.yaml");
+
+// Frame
+FrameBasePtr frm = problem_ptr->emplaceFrame(0, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+
+// Landmarks
+LandmarkBasePtr lmk1 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+LandmarkBasePtr lmk2 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+LandmarkBasePtr lmk3 = LandmarkBase::emplace<LandmarkBase>(problem_ptr->getMap(), 
+                                                           "LandmarkPosition3d",
+                                                           std::make_shared<StatePoint3d>(Vector3d::Zero()),
+                                                           nullptr);
+
+// Capture
+auto cap = CaptureBase::emplace<CaptureOdom3d>(frm, 1, sensor, Vector3d::Zero(), data_cov);
+// Features
+FeatureBasePtr fea1, fea2, fea3;
+// Factors
+FactorRelativePosition3dWithExtrinsicsPtr fac1, fac2, fac3;
+
+void generateRandomProblemLandmark()
+{
+    // Random deltas
+    delta1 = Vector3d::Random() * 10;
+    delta2 = Vector3d::Random() * 10;
+    delta3 = Vector3d::Random() * 10;
+
+    // Random frame pose
+    x_f = Vector7d::Random() * 10;
+    x_f.tail<4>().normalize();
+    auto q_f = Quaterniond(x_f.tail<4>());
+
+    // Random extrinsics
+    x_s = Vector7d::Random() * 10;
+    x_s.tail<4>().normalize();
+    auto q_s = Quaterniond(x_s.tail<4>());
+
+    // Landmarks poses
+    x_l1 = x_f.head<3>() + q_f * (x_s.head<3>() + q_s * delta1);
+    x_l2 = x_f.head<3>() + q_f * (x_s.head<3>() + q_s * delta2);
+    x_l3 = x_f.head<3>() + q_f * (x_s.head<3>() + q_s * delta3);
+
+    // Set states
+    frm->setState(x_f);
+    lmk1->setState(x_l1);
+    lmk2->setState(x_l2);
+    lmk3->setState(x_l3);
+    sensor->setState(x_s);
+
+    // features & factors with deltas measurements
+    fea1 = FeatureBase::emplace<FeatureBase>(cap, "FeatureLandmarkPose", delta1, data_cov);
+    fea2 = FeatureBase::emplace<FeatureBase>(cap, "FeatureLandmarkPose", delta2, data_cov);
+    fea3 = FeatureBase::emplace<FeatureBase>(cap, "FeatureLandmarkPose", delta3, data_cov);
+    fac1 = FactorBase::emplace<FactorRelativePosition3dWithExtrinsics>(fea1, fea1, lmk1, nullptr, false, TOP_LMK);
+    fac2 = FactorBase::emplace<FactorRelativePosition3dWithExtrinsics>(fea2, fea2, lmk2, nullptr, false, TOP_LMK);
+    fac3 = FactorBase::emplace<FactorRelativePosition3dWithExtrinsics>(fea3, fea3, lmk3, nullptr, false, TOP_LMK);
+}
+
+TEST(FactorRelativePosition3dWithExtrinsics, check_tree)
+{
+    ASSERT_TRUE(problem_ptr->check(0));
+}
+
+// FRAME TO LANDMARK =========================================================================
+TEST(FactorRelativePosition3dWithExtrinsics, landmark_solve_lmks)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac1->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac2->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac3->residual(), VectorXd::Zero(3), Constants::EPS);
+
+        // Perturb landmark, fix the rest
+        frm->fix();
+        lmk1->unfix();
+        lmk2->unfix();
+        lmk3->unfix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        lmk1->perturb(1);
+        lmk2->perturb(1);
+        lmk3->perturb(1);
+
+        // solve for landmark
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(lmk1->getStateVector(), x_l1, 1e-3);
+        ASSERT_MATRIX_APPROX(lmk2->getStateVector(), x_l2, 1e-3);
+        ASSERT_MATRIX_APPROX(lmk3->getStateVector(), x_l3, 1e-3);
+
+        // remove features (and factors) for the next loop
+        fea1->remove();
+        fea2->remove();
+        fea3->remove();
+    }
+}
+
+TEST(FactorRelativePosition3dWithExtrinsics, landmark_solve_frame)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac1->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac2->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac3->residual(), VectorXd::Zero(3), Constants::EPS);
+
+        // Perturb frm0, fix the rest
+        frm->unfix();
+        lmk1->fix();
+        lmk2->fix();
+        lmk3->fix();
+        sensor->getP()->fix();
+        sensor->getO()->fix();
+        frm->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(frm->getStateVector(), x_f, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea1->remove();
+        fea2->remove();
+        fea3->remove();
+    }
+}
+
+TEST(FactorRelativePosition3dWithExtrinsics, landmark_extrinsics_p_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac1->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac2->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac3->residual(), VectorXd::Zero(3), Constants::EPS);
+
+        // Perturb sensor P, fix the rest
+        frm->fix();
+        lmk1->fix();
+        lmk2->fix();
+        lmk3->fix();
+        sensor->getP()->unfix();
+        sensor->getO()->fix();
+        sensor->getP()->perturb(1);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea1->remove();
+        fea2->remove();
+        fea3->remove();
+    }
+}
+
+TEST(FactorRelativePosition3dWithExtrinsics, landmark_extrinsics_o_solve)
+{
+    for (int i = 0; i < N; i++)
+    {
+        // random setup
+        generateRandomProblemLandmark();
+        
+        ASSERT_MATRIX_APPROX(fac1->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac2->residual(), VectorXd::Zero(3), Constants::EPS);
+        ASSERT_MATRIX_APPROX(fac3->residual(), VectorXd::Zero(3), Constants::EPS);
+
+        // Perturb sensor O, fix the rest
+        frm->fix();
+        lmk1->fix();
+        lmk2->fix();
+        lmk3->fix();
+        sensor->getP()->fix();
+        sensor->getO()->unfix();
+        sensor->getO()->perturb(.2);
+
+        // solve for frm0
+        std::string report = solver.solve(SolverManager::ReportVerbosity::FULL);
+        //WOLF_INFO(report);
+
+        ASSERT_POSE3d_APPROX(sensor->getStateVector(), x_s, 1e-3);
+
+        // remove feature (and factor) for the next loop
+        fea1->remove();
+        fea2->remove();
+        fea3->remove();
+    }
+}
+
+int main(int argc, char **argv)
+{
+testing::InitGoogleTest(&argc, argv);
+return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_map_yaml.cpp b/test/gtest_map_yaml.cpp
index 9ca7544ee6c9f28013f4037642de45504eca0b92..f0957a5abd90436d96d6e3a4e2788ad2d5c54faa 100644
--- a/test/gtest_map_yaml.cpp
+++ b/test/gtest_map_yaml.cpp
@@ -40,6 +40,9 @@
 #include <iostream>
 using namespace wolf;
 
+std::string wolf_root = _WOLF_ROOT_DIR;
+std::string map_path  = wolf_root + "/test/yaml/maps";
+
 TEST(MapYaml, save_2d)
 {
     ProblemPtr problem = Problem::create("PO", 2);
@@ -62,9 +65,6 @@ TEST(MapYaml, save_2d)
     LandmarkBase::emplace<LandmarkBase>(problem->getMap(), "LandmarkBase", p2_sb, o2_sb);
     LandmarkBase::emplace<LandmarkBase>(problem->getMap(), "LandmarkBase", p3_sb, o3_sb);
 
-    std::string wolf_root = _WOLF_ROOT_DIR;
-    std::string map_path  = wolf_root + "/test/yaml";
-
     // Check Problem functions
     std::string filename  = map_path + "/map_2d_problem.yaml";
     std::cout << "Saving map to file: " << filename << std::endl;
@@ -80,89 +80,54 @@ TEST(MapYaml, load_2d)
 {
     ProblemPtr problem = Problem::create("PO", 2);
 
-    std::string wolf_root = _WOLF_ROOT_DIR;
-    std::string map_path  = wolf_root + "/test/yaml";
-
-    // Check Problem functions
-    std::string filename  = map_path + "/map_2d_problem.yaml";
-    std::cout << "Loading map to file: " << filename << std::endl;
-    problem->loadMap(filename);
-
-    ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
+    std::vector<std::string> maps{"map_2d_map", "map_2d_problem", "map_2d_manual"};
 
-    for (auto lmk : problem->getMap()->getLandmarkList())
+    for (auto map : maps)
     {
-        if (lmk->id() == 1)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() == nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<1,2).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-        }
-        else if (lmk->id() == 2)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<3,4).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<2).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-            ASSERT_FALSE(lmk->getO()->isFixed());
-        }
-        else if (lmk->id() == 3)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<5,6).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<3).finished(), 1e-12);
-            ASSERT_TRUE(lmk->getP()->isFixed());
-            ASSERT_TRUE(lmk->getO()->isFixed());
-        }
-    }
-
-    // empty the map
-    {
-        auto lmk_list = problem->getMap()->getLandmarkList();
-        for (auto lmk : lmk_list)
-            lmk->remove();
-    }
-    ASSERT_TRUE(problem->getMap()->getLandmarkList().empty());
+        std::string filename  = map_path + "/" + map + ".yaml";
+        std::cout << "Loading map to file: " << filename << std::endl;
+        problem->loadMap(filename);
 
-    // Check Map functions
-    filename  = map_path + "/map_2d_map.yaml";
-    std::cout << "Loading map to file: " << filename << std::endl;
-    problem->getMap()->load(filename);
+        ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
 
-    ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
-
-    for (auto lmk : problem->getMap()->getLandmarkList())
-    {
-        if (lmk->id() == 1)
+        for (auto lmk : problem->getMap()->getLandmarkList())
         {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() == nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<1,2).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
+            if (lmk->id() == 1)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() == nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<1,2).finished(), 1e-12);
+                ASSERT_FALSE(lmk->getP()->isFixed());
+            }
+            else if (lmk->id() == 2)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() != nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<3,4).finished(), 1e-12);
+                ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<2).finished(), 1e-12);
+                ASSERT_FALSE(lmk->getP()->isFixed());
+                ASSERT_FALSE(lmk->getO()->isFixed());
+            }
+            else if (lmk->id() == 3)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() != nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<5,6).finished(), 1e-12);
+                ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<3).finished(), 1e-12);
+                ASSERT_TRUE(lmk->getP()->isFixed());
+                ASSERT_TRUE(lmk->getO()->isFixed());
+            }
         }
-        else if (lmk->id() == 2)
+        // empty the map
         {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<3,4).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<2).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-            ASSERT_FALSE(lmk->getO()->isFixed());
-        }
-        else if (lmk->id() == 3)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector2d()<<5,6).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector1d()<<3).finished(), 1e-12);
-            ASSERT_TRUE(lmk->getP()->isFixed());
-            ASSERT_TRUE(lmk->getO()->isFixed());
+            auto lmk_list = problem->getMap()->getLandmarkList();
+            for (auto lmk : lmk_list)
+                lmk->remove();
         }
+        ASSERT_TRUE(problem->getMap()->getLandmarkList().empty());
     }
 }
+
 TEST(MapYaml, save_3d)
 {
     ProblemPtr problem = Problem::create("PO", 3);
@@ -185,9 +150,6 @@ TEST(MapYaml, save_3d)
     LandmarkBase::emplace<LandmarkBase>(problem->getMap(), "LandmarkBase", p2_sb, q2_sb);
     LandmarkBase::emplace<LandmarkBase>(problem->getMap(), "LandmarkBase", p3_sb, q3_sb);
 
-    std::string wolf_root = _WOLF_ROOT_DIR;
-    std::string map_path  = wolf_root + "/test/yaml";
-
     // Check Problem functions
     std::string filename  = map_path + "/map_3d_problem.yaml";
     std::cout << "Saving map to file: " << filename << std::endl;
@@ -203,99 +165,60 @@ TEST(MapYaml, load_3d)
 {
     ProblemPtr problem = Problem::create("PO", 3);
 
-    std::string wolf_root = _WOLF_ROOT_DIR;
-    std::string map_path  = wolf_root + "/test/yaml";
-
-    // Check Problem functions
-    std::string filename  = map_path + "/map_3d_problem.yaml";
-    std::cout << "Loading map to file: " << filename << std::endl;
-    problem->loadMap(filename);
-
-    ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
-
-    for (auto lmk : problem->getMap()->getLandmarkList())
-    {
-        if (lmk->id() == 1)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() == nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<1,2,3).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-        }
-        else if (lmk->id() == 2)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<4,5,6).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,1,0,0).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-            ASSERT_FALSE(lmk->getO()->isFixed());
-            ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
-        }
-        else if (lmk->id() == 3)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<7,8,9).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,0,1,0).finished(), 1e-12);
-            ASSERT_TRUE(lmk->getP()->isFixed());
-            ASSERT_TRUE(lmk->getO()->isFixed());
-            ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
-        }
-    }
+    std::vector<std::string> maps{"map_3d_map", "map_3d_problem", "map_3d_manual"};
 
-    // empty the map
+    for (auto map : maps)
     {
-        auto lmk_list = problem->getMap()->getLandmarkList();
-        for (auto lmk : lmk_list)
-            lmk->remove();
-    }
-    ASSERT_TRUE(problem->getMap()->getLandmarkList().empty());
+        std::string filename  = map_path + "/" + map + ".yaml";
 
-    // Check Map functions
-    filename  = map_path + "/map_3d_map.yaml";
-    std::cout << "Loading map to file: " << filename << std::endl;
-    problem->getMap()->load(filename);
+        std::cout << "Loading map to file: " << filename << std::endl;
+        problem->loadMap(filename);
 
-    ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
+        ASSERT_EQ(problem->getMap()->getLandmarkList().size(), 3);
 
-    for (auto lmk : problem->getMap()->getLandmarkList())
-    {
-        if (lmk->id() == 1)
-        {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() == nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<1,2,3).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-        }
-        else if (lmk->id() == 2)
+        for (auto lmk : problem->getMap()->getLandmarkList())
         {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<4,5,6).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,1,0,0).finished(), 1e-12);
-            ASSERT_FALSE(lmk->getP()->isFixed());
-            ASSERT_FALSE(lmk->getO()->isFixed());
-            ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
+            if (lmk->id() == 1)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() == nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<1,2,3).finished(), 1e-12);
+                ASSERT_FALSE(lmk->getP()->isFixed());
+            }
+            else if (lmk->id() == 2)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() != nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<4,5,6).finished(), 1e-12);
+                ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,1,0,0).finished(), 1e-12);
+                ASSERT_FALSE(lmk->getP()->isFixed());
+                ASSERT_FALSE(lmk->getO()->isFixed());
+                ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
+            }
+            else if (lmk->id() == 3)
+            {
+                ASSERT_TRUE(lmk->getP() != nullptr);
+                ASSERT_TRUE(lmk->getO() != nullptr);
+                ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<7,8,9).finished(), 1e-12);
+                ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,0,1,0).finished(), 1e-12);
+                ASSERT_TRUE(lmk->getP()->isFixed());
+                ASSERT_TRUE(lmk->getO()->isFixed());
+                ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
+            }
         }
-        else if (lmk->id() == 3)
+
+        // empty the map
         {
-            ASSERT_TRUE(lmk->getP() != nullptr);
-            ASSERT_TRUE(lmk->getO() != nullptr);
-            ASSERT_MATRIX_APPROX(lmk->getP()->getState(), (Eigen::Vector3d()<<7,8,9).finished(), 1e-12);
-            ASSERT_MATRIX_APPROX(lmk->getO()->getState(), (Eigen::Vector4d()<<0,0,1,0).finished(), 1e-12);
-            ASSERT_TRUE(lmk->getP()->isFixed());
-            ASSERT_TRUE(lmk->getO()->isFixed());
-            ASSERT_TRUE(lmk->getO()->hasLocalParametrization());
+            auto lmk_list = problem->getMap()->getLandmarkList();
+            for (auto lmk : lmk_list)
+                lmk->remove();
         }
+        ASSERT_TRUE(problem->getMap()->getLandmarkList().empty());
     }
 }
 
 TEST(MapYaml, remove_map_files)
 {
-    std::string wolf_root = _WOLF_ROOT_DIR;
-    std::string map_path  = wolf_root + "/test/yaml";
-
     std::string filename  = map_path + "/map_2d_problem.yaml";
     ASSERT_TRUE(std::remove(filename.c_str()) == 0);
     filename  = map_path + "/map_2d_map.yaml";
diff --git a/test/gtest_processor_tracker_feature_landmark_external.cpp b/test/gtest_processor_tracker_feature_landmark_external.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a82e385f35739d3065749cdfc67b43b07c71589d
--- /dev/null
+++ b/test/gtest_processor_tracker_feature_landmark_external.cpp
@@ -0,0 +1,524 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#include "core/utils/utils_gtest.h"
+#include "core/problem/problem.h"
+#include "core/capture/capture_landmarks_external.h"
+#include "core/landmark/landmark_base.h"
+#include "core/sensor/sensor_odom_2d.h"
+#include "core/sensor/sensor_odom_3d.h"
+#include "core/processor/processor_odom_2d.h"
+#include "core/processor/processor_odom_3d.h"
+#include "core/state_block/state_block_derived.h"
+#include "core/state_block/state_angle.h"
+#include "core/state_block/state_quaternion.h"
+#include "core/ceres_wrapper/solver_ceres.h"
+
+#include "core/processor/processor_tracker_feature_landmark_external.h"
+
+// STL
+#include <iterator>
+#include <iostream>
+
+using namespace wolf;
+using namespace Eigen;
+
+class ProcessorTrackerFeatureLandmarkExternalTest : public testing::Test
+{
+    protected:
+        int dim;
+        bool orientation;
+        TimeStamp t;
+        double dt;
+
+        ProblemPtr problem;
+        SolverManagerPtr solver;
+        SensorBasePtr sensor, sensor_odom;
+        ProcessorTrackerFeatureLandmarkExternalPtr processor;
+        ProcessorMotionPtr processor_motion;
+        std::vector<LandmarkBasePtr> landmarks;
+
+        // Groundtruth states
+        VectorComposite state_robot, state_sensor;
+        std::vector<VectorComposite> state_landmarks;
+
+        virtual void SetUp() {};
+        void initProblem(int _dim,
+                         bool _orientation,
+                         double _quality_th, 
+                         double _dist_th, 
+                         unsigned int _track_length_th,
+                         double _time_span,
+                         bool _add_landmarks);
+        void randomStep();
+        CaptureLandmarksExternalPtr computeCaptureLandmarks() const;
+        void testConfiguration(int dim, 
+                               bool orientation, 
+                               double quality_th, 
+                               double dist_th, 
+                               int track_length, 
+                               double time_span, 
+                               bool add_landmarks);
+        void assertVectorComposite(const VectorComposite& v1, const VectorComposite& v2) const;
+};
+
+void ProcessorTrackerFeatureLandmarkExternalTest::initProblem(int _dim, 
+                                                              bool _orientation,
+                                                              double _quality_th, 
+                                                              double _dist_th, 
+                                                              unsigned int _track_length_th,
+                                                              double _time_span,
+                                                              bool _add_landmarks)
+{
+    dim = _dim;
+    orientation = _orientation;
+    t = TimeStamp(0);
+    dt = 0.1;
+
+    problem = Problem::create("PO", dim);
+    solver = std::make_shared<SolverCeres>(problem);
+
+    // Sensors
+    if (dim == 2)
+    {
+        sensor = SensorBase::emplace<SensorBase>(problem->getHardware(),
+                                                 "SensorBase",
+                                                 std::make_shared<StatePoint2d>(Vector2d::Random()),
+                                                 std::make_shared<StateAngle>(Vector1d::Random() * M_PI),
+                                                 nullptr,
+                                                 2);
+        sensor_odom = SensorBase::emplace<SensorOdom2d>(problem->getHardware(),
+                                                        Vector3d::Zero(),
+                                                        ParamsSensorOdom2d());
+    
+    }
+    else
+    {
+        sensor = SensorBase::emplace<SensorBase>(problem->getHardware(),
+                                                 "SensorBase",
+                                                 std::make_shared<StatePoint3d>(Vector3d::Random()),
+                                                 std::make_shared<StateQuaternion>(Vector4d::Random().normalized()),
+                                                 nullptr,
+                                                 3);
+        sensor_odom = SensorBase::emplace<SensorOdom3d>(problem->getHardware(),
+                                                        (Vector7d() << 0,0,0,0,0,0,1).finished(),
+                                                        ParamsSensorOdom3d());
+    }
+
+    // Processors
+    ParamsProcessorTrackerFeatureLandmarkExternalPtr params = std::make_shared<ParamsProcessorTrackerFeatureLandmarkExternal>();
+    params->time_tolerance = dt/2;
+    params->min_features_for_keyframe = 0;
+    params->max_new_features = -1;
+    params->voting_active = true;
+    params->apply_loss_function = false;
+    params->filter_quality_th = _quality_th;
+    params->filter_dist_th = _dist_th;
+    params->filter_track_length_th = _track_length_th;
+    params->time_span = _time_span;
+    processor = ProcessorBase::emplace<ProcessorTrackerFeatureLandmarkExternal>(sensor, params);
+
+    if (dim == 2)
+    {
+        auto params_odom = std::make_shared<ParamsProcessorOdom2d>();
+        params_odom->state_getter = true;
+        params_odom->voting_active = false;
+        processor_motion = std::static_pointer_cast<ProcessorMotion>(ProcessorBase::emplace<ProcessorOdom2d>(sensor_odom, 
+                                                                                                             params_odom));
+    }
+    else
+    {
+        auto params_odom = std::make_shared<ParamsProcessorOdom3d>();
+        params_odom->state_getter = true;
+        params_odom->voting_active = false;
+        processor_motion = std::static_pointer_cast<ProcessorMotion>(ProcessorBase::emplace<ProcessorOdom3d>(sensor_odom, 
+                                                                                                             params_odom));
+    }
+
+    // Emplace frame
+    auto F0 = problem->emplaceFrame(t, (dim == 2 ? VectorXd::Zero(3) : (VectorXd(7) << 0,0,0,0,0,0,1).finished()));
+    F0->fix();
+    processor->keyFrameCallback(F0);
+    processor_motion->setOrigin(F0);
+
+    // Emplace 3 random landmarks
+    for (auto i = 0; i < 3; i++)
+    {
+        LandmarkBasePtr lmk;
+        if (dim == 2)
+            lmk = LandmarkBase::emplace<LandmarkBase>(_add_landmarks ? problem->getMap() : nullptr, 
+                                                      "LandmarkExternal",
+                                                      std::make_shared<StatePoint2d>(Vector2d::Random() * 10),
+                                                      (orientation ? 
+                                                        std::make_shared<StateAngle>(Vector1d::Random() * M_PI) : 
+                                                        nullptr));
+    
+        else 
+            lmk = LandmarkBase::emplace<LandmarkBase>(_add_landmarks ? problem->getMap() : nullptr, 
+                                                      "LandmarkExternal",
+                                                      std::make_shared<StatePoint3d>(Vector3d::Random() * 10),
+                                                      (orientation ? 
+                                                        std::make_shared<StateQuaternion>(Vector4d::Random().normalized()) : 
+                                                        nullptr));
+        lmk->setId(i);
+        landmarks.push_back(lmk);
+        state_landmarks.push_back(lmk->getState());
+    }
+
+    // Store groundtruth poses
+    state_robot  = F0->getState("PO");
+    state_sensor = sensor->getState("PO");
+}
+
+void ProcessorTrackerFeatureLandmarkExternalTest::randomStep()
+{
+    // compute delta
+    VectorXd delta;
+    MatrixXd delta_cov;
+    if (dim == 2)
+    {
+        delta = Vector2d::Random() * 0.1;
+        delta_cov = Matrix2d::Identity() * 0.1;
+    }
+    else
+    {
+        delta = Vector7d::Random() * 0.1;
+        delta.tail<4>().normalize();
+        delta_cov = Matrix6d::Identity() * 0.1;
+    }
+
+    // Process odometry
+    auto cap_odom = std::make_shared<CaptureMotion>("CaptureOdom", t, sensor_odom, delta, delta_cov, nullptr);
+    cap_odom->process();
+
+    // update groundtruth pose with random movement
+    state_robot = processor_motion->getState("PO");
+}
+
+CaptureLandmarksExternalPtr ProcessorTrackerFeatureLandmarkExternalTest::computeCaptureLandmarks() const
+{
+    // Detections
+    auto cap = std::make_shared<CaptureLandmarksExternal>(t, sensor);
+    for (auto i = 0; i < landmarks.size(); i++)
+    {
+        // compute detection
+        VectorXd meas(orientation?(dim==2?3:7):(dim==2?2:3));
+        if (dim == 2)
+        {
+            meas.head(2) = Rotation2Dd(-state_sensor.at('O')(0))*(Rotation2Dd(-state_robot.at('O')(0))*(state_landmarks.at(i).at('P') - state_robot.at('P')) - state_sensor.at('P'));
+            if (orientation)
+                meas(2) = state_landmarks.at(i).at('O')(0) - state_robot.at('O')(0) - state_sensor.at('O')(0);
+        }
+        else
+        {
+            auto q_sensor = Quaterniond(Vector4d(state_sensor.at('O')));
+            auto q_robot  = Quaterniond(Vector4d(state_robot.at('O')));
+
+            meas.head(3) = q_sensor.conjugate() * (q_robot.conjugate() * (state_landmarks.at(i).at('P') - state_robot.at('P')) - state_sensor.at('P'));
+            if (orientation)
+                meas.tail(4) = (q_sensor.conjugate() * q_robot.conjugate() * Quaterniond(Vector4d(state_landmarks.at(i).at('O')))).coeffs();
+        }
+        // cov
+        MatrixXd cov = MatrixXd::Identity(meas.size(), meas.size());
+        if (orientation and dim != 2)
+            cov = MatrixXd::Identity(6, 6);
+
+        // quality
+        double quality = (double) i / (double) (landmarks.size()-1); // increasing from 0 to 1
+
+        // add detection
+        cap->addDetection(landmarks.at(i)->id(), meas, cov, quality );
+    }
+
+    return cap;
+}
+
+void ProcessorTrackerFeatureLandmarkExternalTest::testConfiguration(int dim, 
+                                                                    bool orientation, 
+                                                                    double quality_th, 
+                                                                    double dist_th, 
+                                                                    int track_length, 
+                                                                    double time_span, 
+                                                                    bool add_landmarks)
+{
+    initProblem(dim, orientation, quality_th, dist_th, track_length, time_span, add_landmarks);  
+
+    ASSERT_TRUE(problem->check());
+
+    for (auto i = 0; i<10; i++)
+    {
+        WOLF_INFO("\n================= STEP ", i, " t = ", t, " =================");
+
+        // detection of landmarks
+        auto cap = computeCaptureLandmarks();
+        ASSERT_TRUE(problem->check());
+
+        // process detections
+        cap->process();
+        ASSERT_TRUE(problem->check());
+        problem->print(4,1,1,1);
+
+        // CHECKS
+        FactorBasePtrList fac_list;
+        problem->getTrajectory()->getFactorList(fac_list);
+        // should emplace KF in last?
+        bool any_active_track = quality_th <=1 and i >= track_length-1;
+        bool should_emplace_KF = t-dt > time_span and any_active_track;
+        WOLF_INFO("should emplace KF: ", should_emplace_KF, " t-dt = ", t-dt, " time_span = ", time_span, " track_length-1 = ", track_length-1);
+
+        if (should_emplace_KF)
+        {   
+            // voted for keyframe
+            ASSERT_TRUE(problem->getTrajectory()->size() > 1);
+            
+            // emplaced factors
+            ASSERT_FALSE(fac_list.empty());
+            
+            // factors' type
+            if (should_emplace_KF)
+                for (auto fac : fac_list)
+                {
+                    if (fac->getProcessor() == processor)
+                    {
+                        ASSERT_EQ(fac->getType(), std::string("FactorRelative") + 
+                                                  (orientation ? "Pose" : "Position") + 
+                                                  (dim == 2 ? "2d" : "3d") + 
+                                                  "WithExtrinsics");
+                    }
+                }
+            // landmarks created by processor
+            if (not add_landmarks)
+            {
+                auto landmarks_map = problem->getMap()->getLandmarkList();
+                // amount of landmarks due to quality filtering of detections
+                auto n_landmarks = (quality_th <= 0 ? 3 : (quality_th <= 0.5 ? 2 : (quality_th <= 1 ? 1 : 0)));
+                ASSERT_EQ(landmarks_map.size(), n_landmarks);
+
+                // check state correctly initialized
+                for (auto lmk_map : landmarks_map)
+                {
+                    ASSERT_TRUE(lmk_map->id() < landmarks.size());
+                    ASSERT_EQ(lmk_map->id(), landmarks.at(lmk_map->id())->id());
+                    assertVectorComposite(lmk_map->getState(), state_landmarks.at(lmk_map->id()));
+                }
+            }
+        }
+        else
+        {
+            // didn't vote for keyframe
+            ASSERT_FALSE(problem->getTrajectory()->size() > 1);
+            // no factors emplaced
+            ASSERT_TRUE(fac_list.empty());
+            // no landmarks created yet (if not added by the test)
+            ASSERT_EQ(problem->getMap()->getLandmarkList().empty(), not add_landmarks);
+        }
+
+        // step with random movement
+        t+=dt;
+        randomStep();
+
+        // solve
+        if (should_emplace_KF)
+        {
+            // perturb landmarks
+            for (auto lmk : problem->getMap()->getLandmarkList())
+                lmk->perturb();
+
+            // fix frame and extrinsics
+            sensor->fix();
+            problem->getLastFrame()->fix();
+
+            // solve
+            auto report = solver->solve(SolverManager::ReportVerbosity::FULL);
+            WOLF_INFO(report);
+
+            // check values                    
+            //assertVectorComposite(sensor->getState("PO"), state_sensor);
+            //assertVectorComposite(problem->getState("PO"), state_robot);
+            for (auto lmk_map : problem->getMap()->getLandmarkList())
+            {
+                assertVectorComposite(lmk_map->getState(), state_landmarks.at(lmk_map->id()));
+            }
+        }
+    }
+}
+
+void ProcessorTrackerFeatureLandmarkExternalTest::assertVectorComposite(const VectorComposite& v1, const VectorComposite& v2) const
+{
+    if (v1.includesStructure("PO") and v2.includesStructure("PO"))
+    {
+        if (dim == 2)
+        {
+            ASSERT_POSE2d_APPROX(v1.vector("PO"), v2.vector("PO"), Constants::EPS);
+        }
+        else
+        {
+            ASSERT_POSE3d_APPROX(v1.vector("PO"), v2.vector("PO"), Constants::EPS);
+        }
+    }
+    else if (v1.includesStructure("P") and v2.includesStructure("P"))
+    {
+        ASSERT_MATRIX_APPROX(v1.vector("P"), v2.vector("P"), Constants::EPS);
+    }
+    else
+        throw std::runtime_error("wrong vector composite");
+}
+
+// / TESTS //////////////////////////////////////////
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_2d_existing_lmks)
+{
+    testConfiguration(2,      //int dim
+                      false,  //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      6,      //int track_length
+                      4.5*dt, //double time_span
+                      true);  //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_2d_no_lmks)
+{
+    testConfiguration(2,      //int dim
+                      false,  //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      2,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_2d_no_lmks_quality)
+{
+    testConfiguration(2,      //int dim
+                      false,  //bool orientation
+                      0.3,    //double quality_th
+                      1e6,    //double dist_th
+                      6,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_2d_existing_lmks)
+{
+    testConfiguration(2,      //int dim
+                      true,   //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      3,      //int track_length
+                      4.5*dt, //double time_span
+                      true);  //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_2d_no_lmks)
+{
+    testConfiguration(2,      //int dim
+                      true,   //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      1,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_2d_no_lmks_quality)
+{
+    testConfiguration(2,      //int dim
+                      true,   //bool orientation
+                      0.3,    //double quality_th
+                      1e6,    //double dist_th
+                      0,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_3d_existing_lmks)
+{
+    testConfiguration(3,      //int dim
+                      false,  //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      7,      //int track_length
+                      4.5*dt, //double time_span
+                      true);  //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_3d_no_lmks)
+{
+    testConfiguration(3,      //int dim
+                      false,  //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      53,     //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, P_3d_no_lmks_quality)
+{
+    testConfiguration(3,      //int dim
+                      false,  //bool orientation
+                      0.3,    //double quality_th
+                      1e6,    //double dist_th
+                      2,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_3d_existing_lmks)
+{
+    testConfiguration(3,      //int dim
+                      true,   //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      1,      //int track_length
+                      4.5*dt, //double time_span
+                      true);  //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_3d_no_lmks)
+{
+    testConfiguration(3,      //int dim
+                      true,   //bool orientation
+                      0,      //double quality_th
+                      1e6,    //double dist_th
+                      4,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+TEST_F(ProcessorTrackerFeatureLandmarkExternalTest, PO_3d_no_lmks_quality)
+{
+    testConfiguration(3,      //int dim
+                      true,   //bool orientation
+                      0.2,    //double quality_th
+                      1e6,    //double dist_th
+                      5,      //int track_length
+                      4.5*dt, //double time_span
+                      false); //bool add_landmarks
+}
+
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/yaml/maps/map_2d_manual.yaml b/test/yaml/maps/map_2d_manual.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4a2276ad153b4adfbae3679d42b1aba801f15bb3
--- /dev/null
+++ b/test/yaml/maps/map_2d_manual.yaml
@@ -0,0 +1,13 @@
+map_name: 2d map saved from Map::save()
+date_time: 21/09/2022 at 12:00:59
+useless_parameter: [1, 6, 3]
+landmarks:
+  - 
+    id: 1
+    type: LandmarkBase
+    position: [1, 2]
+    position fixed: false
+    transformable: true
+    another_useless_param: "wtf"
+  - {id: 2, type: LandmarkBase, position: [3, 4], position fixed: false, orientation: [2], orientation fixed: false, transformable: true}
+  - {id: 3, type: LandmarkBase, position: [5, 6], position fixed: true, orientation: [3], orientation fixed: true, transformable: true}
\ No newline at end of file
diff --git a/test/yaml/maps/map_3d_manual.yaml b/test/yaml/maps/map_3d_manual.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4b66ed90f51224d03b70cf484ac333da74e777f4
--- /dev/null
+++ b/test/yaml/maps/map_3d_manual.yaml
@@ -0,0 +1,25 @@
+#map_name: 3d map saved from Problem::saveMap()
+#date_time: 21/09/2022 at 12:00:59
+landmarks:
+  - 
+    id: 10
+    type: LandmarkBase
+    position: [1, 2, 3]
+    position fixed: false
+    transformable: true
+  - 
+    id: 11
+    type: LandmarkBase
+    position: [4, 5, 6]
+    position fixed: false
+    orientation: [0, 1, 0, 0]
+    orientation fixed: false
+    transformable: true
+  - 
+    id: 12
+    type: LandmarkBase
+    position: [7, 8, 9]
+    position fixed: true
+    orientation: [0, 0, 1, 0]
+    orientation fixed: true
+    transformable: true
\ No newline at end of file