diff --git a/CMakeLists.txt b/CMakeLists.txt
index f5f3ab9b4fdcd65fa8184b1a2db3cd6fc5cffe61..16e11c7d14548787af08849a911d5821df4c3e06 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -132,9 +132,11 @@ SET(HDRS_MATH
 SET(HDRS_UTILS
   )
 SET(HDRS_CAPTURE
+  include/imu/capture/capture_compass.h
   include/imu/capture/capture_imu.h
   )
 SET(HDRS_FACTOR
+  include/imu/factor/factor_compass_3d.h
   include/imu/factor/factor_imu.h
   )
 SET(HDRS_FEATURE
@@ -143,9 +145,11 @@ SET(HDRS_FEATURE
 SET(HDRS_LANDMARK
   )
 SET(HDRS_PROCESSOR
+  include/imu/processor/processor_compass.h
   include/imu/processor/processor_imu.h
   )
 SET(HDRS_SENSOR
+  include/imu/sensor/sensor_compass.h
   include/imu/sensor/sensor_imu.h
   )
 SET(HDRS_SOLVER
@@ -174,10 +178,12 @@ SET(SRCS_FEATURE
 SET(SRCS_LANDMARK
   )
 SET(SRCS_PROCESSOR
+  src/processor/processor_compass.cpp
   src/processor/processor_imu.cpp
   test/processor_imu_UnitTester.cpp
 )
 SET(SRCS_SENSOR
+  src/sensor/sensor_compass.cpp
   src/sensor/sensor_imu.cpp
   )
 SET(SRCS_DTASSC
diff --git a/include/imu/capture/capture_compass.h b/include/imu/capture/capture_compass.h
new file mode 100644
index 0000000000000000000000000000000000000000..1a77f7ce5282fbaf6b615c8cea1950eaa7cbe274
--- /dev/null
+++ b/include/imu/capture/capture_compass.h
@@ -0,0 +1,63 @@
+#ifndef CAPTURE_COMPASS_H_
+#define CAPTURE_COMPASS_H_
+
+//Wolf includes
+#include "core/capture/capture_base.h"
+#include "imu/sensor/sensor_compass.h"
+
+//std includes
+//
+
+namespace wolf {
+
+WOLF_PTR_TYPEDEFS(CaptureCompass);
+
+//class CaptureCompass
+class CaptureCompass : public CaptureBase
+{
+    protected:
+        Eigen::Vector3d data_;              ///< Raw data (magnetic field).
+        Eigen::Matrix3d data_covariance_;   ///< Noise of the data.
+
+    public:
+
+        CaptureCompass(const TimeStamp& _ts,
+                       SensorBasePtr _sensor_ptr,
+                       const Eigen::Vector3d& _data) :
+            CaptureBase("CaptureCompass", _ts, _sensor_ptr),
+            data_(_data)
+        {
+            auto sensor_compass = std::dynamic_pointer_cast<SensorCompass>(_sensor_ptr);
+            assert(sensor_compass != nullptr && "CaptureCompass: Sensor should be of type SensorCompass");
+            data_covariance_ = sensor_compass->computeMeasurementCovariance(data_);
+        }
+
+        CaptureCompass(const TimeStamp& _ts,
+                       SensorBasePtr _sensor_ptr,
+                       const Eigen::Vector3d& _data,
+                       const Eigen::Matrix3d& _data_covariance) :
+            CaptureBase("CaptureCompass", _ts, _sensor_ptr),
+            data_(_data),
+            data_covariance_(_data_covariance)
+        {
+            assert((_sensor_ptr == nullptr or std::dynamic_pointer_cast<SensorCompass>(_sensor_ptr) != nullptr) && "CaptureCompass: Sensor should be of type SensorCompass");
+        }
+
+        ~CaptureCompass() override
+        {
+
+        }
+
+        const Eigen::Vector3d& getData() const
+        {
+            return data_;
+        }
+
+        const Eigen::Matrix3d& getDataCovariance() const
+        {
+            return data_covariance_;
+        }
+};
+
+} //namespace wolf
+#endif
diff --git a/include/imu/factor/factor_compass_3d.h b/include/imu/factor/factor_compass_3d.h
new file mode 100644
index 0000000000000000000000000000000000000000..6bb877776b886236d74f69abb1ec8062dad0bec5
--- /dev/null
+++ b/include/imu/factor/factor_compass_3d.h
@@ -0,0 +1,62 @@
+#ifndef FACTOR_COMPASS_3D_H_
+#define FACTOR_COMPASS_3D_H_
+
+#include "core/factor/factor_autodiff.h"
+
+namespace wolf
+{
+
+WOLF_PTR_TYPEDEFS(FactorCompass3d);
+
+class FactorCompass3d : public FactorAutodiff<FactorCompass3d, 3, 4, 4, 3, 3>
+{
+    public:
+        FactorCompass3d(const FeatureBasePtr&   _feat,
+                        const ProcessorBasePtr& _processor_ptr,
+                        bool                    _apply_loss_function,
+                        FactorStatus            _status = FAC_ACTIVE) :
+            FactorAutodiff("FactorCompass3d",
+                           TOP_ABS,
+                           _feat,
+                           nullptr,
+                           nullptr,
+                           nullptr,
+                           nullptr,
+                           _processor_ptr,
+                           _apply_loss_function,
+                           _status,
+                           _feat->getFrame()->getO(),
+                           _feat->getCapture()->getSensor()->getO(),
+                           _feat->getCapture()->getSensor()->getIntrinsic(),
+                           _feat->getCapture()->getSensor()->getStateBlock('F'))
+        {
+        }
+
+        ~FactorCompass3d() override { /* nothing */ }
+
+        template<typename T>
+        bool operator () (const T* const _frm_q,
+                          const T* const _sen_q,
+                          const T* const _sen_bias,
+                          const T* const _field,
+                          T* _residual) const
+        {
+            using namespace Eigen;
+
+            Map<const Quaternion<T>> frm_q(_frm_q);
+            Map<const Quaternion<T>> sen_q(_sen_q);
+            Map<const Matrix<T,3,1>> sen_bias(_sen_bias);
+            Map<const Matrix<T,3,1>> field(_field);
+            Map<Matrix<T,3,1>> res(_residual);
+
+            Matrix<T,3,1> exp = sen_q.conjugate() * frm_q.conjugate() * field + sen_bias;
+
+            res  = getMeasurementSquareRootInformationUpper() * (getMeasurement() - exp);
+
+            return true;
+        }
+};
+
+} /* namespace wolf */
+
+#endif /* FACTOR_COMPASS_3D_H_ */
diff --git a/include/imu/processor/processor_compass.h b/include/imu/processor/processor_compass.h
new file mode 100644
index 0000000000000000000000000000000000000000..690755716de4029d257ae0807bf947bc2b511acb
--- /dev/null
+++ b/include/imu/processor/processor_compass.h
@@ -0,0 +1,55 @@
+#ifndef INCLUDE_IMU_PROCESSOR_PROCESSORCOMPASS_H_
+#define INCLUDE_IMU_PROCESSOR_PROCESSORCOMPASS_H_
+
+#include "core/processor/processor_base.h"
+
+namespace wolf {
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorCompass);
+
+struct ParamsProcessorCompass : public ParamsProcessorBase
+{
+    ParamsProcessorCompass() = default;
+    ParamsProcessorCompass(std::string _unique_name, const ParamsServer& _server):
+        ParamsProcessorBase(_unique_name, _server)
+    {
+        //
+    }
+    std::string print() const override
+    {
+        return "\n" + ParamsProcessorBase::print();
+    }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorCompass);
+
+//class
+class ProcessorCompass : public ProcessorBase
+{
+    protected:
+        ParamsProcessorCompassPtr params_compass_;
+
+    public:
+        ProcessorCompass(ParamsProcessorCompassPtr);
+        ~ProcessorCompass() override;
+        void configure(SensorBasePtr) override { };
+
+        WOLF_PROCESSOR_CREATE(ProcessorCompass, ParamsProcessorCompass);
+
+    protected:
+        void processCapture(CaptureBasePtr) override;
+        void processKeyFrame(FrameBasePtr, const double&) override;
+        void processMatch(FrameBasePtr, CaptureBasePtr);
+
+        bool triggerInCapture(CaptureBasePtr _cap) const override { return true;};
+        bool triggerInKeyFrame(FrameBasePtr _frm, const double& _time_tol) const override { return true;};
+
+        bool storeKeyFrame(FrameBasePtr _frm) override { return false;};
+        bool storeCapture(CaptureBasePtr _cap) override { return false;};
+
+        bool voteForKeyFrame() const override { return false;};
+};
+
+} /* namespace wolf */
+
+#endif /* INCLUDE_IMU_PROCESSOR_PROCESSORCOMPASS_H_ */
diff --git a/include/imu/sensor/sensor_compass.h b/include/imu/sensor/sensor_compass.h
new file mode 100644
index 0000000000000000000000000000000000000000..e21bb028d1daf45e662698e4e21b2055fa5c69df
--- /dev/null
+++ b/include/imu/sensor/sensor_compass.h
@@ -0,0 +1,80 @@
+#ifndef SENSOR_SENSOR_COMPASS_H_
+#define SENSOR_SENSOR_COMPASS_H_
+
+//wolf includes
+#include "core/sensor/sensor_base.h"
+#include "core/utils/params_server.h"
+#include <iostream>
+
+namespace wolf {
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsSensorCompass);
+
+struct ParamsSensorCompass : public ParamsSensorBase
+{
+    double stdev_noise;
+
+    std::string     field_prior_mode;
+    Eigen::Vector3d field_prior_state;
+    Eigen::Vector3d field_prior_sigma;
+
+    std::string     bias_prior_mode;
+    Eigen::Vector3d bias_prior_state;
+    Eigen::Vector3d bias_prior_sigma;
+
+    ParamsSensorCompass() = default;
+    ParamsSensorCompass(std::string _unique_name, const wolf::ParamsServer& _server)
+      : ParamsSensorBase(_unique_name, _server)
+    {
+        stdev_noise         = _server.getParam<double>          (prefix + _unique_name + "/stdev_noise");
+
+        field_prior_mode    = _server.getParam<std::string>     (prefix + _unique_name + "/field_prior_mode");
+        field_prior_state   = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/field_prior_state");
+        if (field_prior_mode == "factor")
+            field_prior_sigma = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/field_prior_sigma");
+
+        bias_prior_mode    = _server.getParam<std::string>     (prefix + _unique_name + "/bias_prior_mode");
+        bias_prior_state   = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/bias_prior_state");
+        if (bias_prior_mode == "factor")
+            bias_prior_sigma = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/bias_prior_sigma");
+    }
+    ~ParamsSensorCompass() override = default;
+    std::string print() const override
+    {
+        return ParamsSensorBase::print()  + "\n"
+        + "stdev_noise: "       + std::to_string(stdev_noise)       + "\n"
+        + "field_prior_mode: "  + field_prior_mode                  + "\n"
+        + "field_prior_state: print not implemented. \n"
+        + "field_prior_sigma: print not implemented. \n"
+        + "bias_prior_mode: "   + bias_prior_mode                   + "\n"
+        + "bias_prior_state:  print not implemented. \n"
+        + "bias_prior_sigma:  print not implemented. \n";
+    }
+};
+
+WOLF_PTR_TYPEDEFS(SensorCompass);
+
+class SensorCompass : public SensorBase
+{
+    public:
+        SensorCompass(const Eigen::VectorXd& _extrinsics, ParamsSensorCompassPtr _params);
+        SensorCompass(const Eigen::VectorXd& _extrinsics, const ParamsSensorCompass& _params);
+        WOLF_SENSOR_CREATE(SensorCompass, ParamsSensorCompass, 7);
+
+        ~SensorCompass() override;
+        ParamsSensorCompassConstPtr getParams() const;
+
+        Eigen::Matrix3d computeMeasurementCovariance(const Eigen::Vector3d& _field) const;
+
+    protected:
+        ParamsSensorCompassPtr params_compass_;
+};
+
+inline ParamsSensorCompassConstPtr SensorCompass::getParams() const
+{
+    return params_compass_;
+}
+
+} /* namespace wolf */
+
+#endif /* SENSOR_SENSOR_DIFF_DRIVE_H_ */
diff --git a/include/imu/sensor/sensor_imu.h b/include/imu/sensor/sensor_imu.h
index 5ffd061841374ea942f8cdb44793a2a5ce223e9d..e875fde93d4d5ed446e7c433e142b2f8c917cdb7 100644
--- a/include/imu/sensor/sensor_imu.h
+++ b/include/imu/sensor/sensor_imu.h
@@ -116,4 +116,4 @@ inline double SensorImu::getAbRateStdev() const
 
 } // namespace wolf
 
-#endif // SENSOR_Imu_H
+#endif // SENSOR_IMU_H
diff --git a/src/processor/processor_compass.cpp b/src/processor/processor_compass.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7abc25ff089774bc670a163dd41993566f0b3f2f
--- /dev/null
+++ b/src/processor/processor_compass.cpp
@@ -0,0 +1,75 @@
+#include "imu/processor/processor_compass.h"
+#include "imu/capture/capture_compass.h"
+#include "imu/factor/factor_compass_3d.h"
+
+namespace wolf {
+
+ProcessorCompass::ProcessorCompass(ParamsProcessorCompassPtr _params) :
+        ProcessorBase("ProcessorCompass", 3, _params)
+{
+    //
+}
+
+ProcessorCompass::~ProcessorCompass()
+{
+    //
+}
+
+void ProcessorCompass::processCapture(CaptureBasePtr _capture)
+{
+    // Search for any stored frame within time tolerance of capture
+    auto frame_pack = buffer_pack_kf_.select(_capture->getTimeStamp(), params_->time_tolerance);
+    if (frame_pack)
+    {
+        processMatch(frame_pack->key_frame, _capture);
+
+        // remove the frame and older frames
+        buffer_pack_kf_.removeUpTo(frame_pack->key_frame->getTimeStamp());
+    }
+    // Otherwise: store capture
+    // Note that more than one processor can be emplacing frames, so an older frame can arrive later than this one.
+    buffer_capture_.add(_capture->getTimeStamp(), _capture);
+}
+
+void ProcessorCompass::processKeyFrame(FrameBasePtr _frame, const double& _time_tolerance)
+{
+    // Search for any stored capture within time tolerance of frame
+    auto capture = buffer_capture_.select(_frame->getTimeStamp(), _time_tolerance);
+    if (capture)
+    {
+        processMatch(_frame, capture);
+
+        // remove (very) old captures
+        buffer_capture_.removeUpTo(_frame->getTimeStamp() - 10);
+    }
+    // Otherwise: If frame is more recent than any capture in buffer -> store frame to be processed later in processCapture
+    else if (buffer_capture_.selectLastAfter(_frame->getTimeStamp(), _time_tolerance) == nullptr)
+    {
+        // store frame
+        buffer_pack_kf_.add(_frame, _time_tolerance);
+    }
+    // Otherwise: There are captures more recent than the frame but none that match with it -> discard frame
+}
+
+void ProcessorCompass::processMatch(FrameBasePtr _frame, CaptureBasePtr _capture)
+{
+    auto cap_compass = std::dynamic_pointer_cast<CaptureCompass>(_capture);
+    assert(cap_compass && "ProcessorCompass::processMatch capture not of type CaptureCompass");
+
+    // link capture to the frame
+    _capture->link(_frame);
+
+    // emplace feature
+    auto feat = FeatureBase::emplace<FeatureBase>(cap_compass,
+                                                  "FeatureCompass",
+                                                  cap_compass->getData(),
+                                                  cap_compass->getDataCovariance());
+
+    // emplace factor
+    FactorBase::emplace<FactorCompass3d>(feat,
+                                         feat,
+                                         shared_from_this(),
+                                         params_->apply_loss_function);
+}
+
+} /* namespace wolf */
diff --git a/src/sensor/sensor_compass.cpp b/src/sensor/sensor_compass.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d81f52926e3633cc31d248a88e8f119bc6f2012b
--- /dev/null
+++ b/src/sensor/sensor_compass.cpp
@@ -0,0 +1,85 @@
+#include "imu/sensor/sensor_compass.h"
+#include "core/state_block/state_block.h"
+#include "core/state_block/state_quaternion.h"
+
+namespace wolf
+{
+
+SensorCompass::SensorCompass(const Eigen::VectorXd& _extrinsics,
+                             ParamsSensorCompassPtr _params) :
+        SensorBase("SensorCompass",
+                   std::make_shared<StateBlock>(_extrinsics.head(3), true),
+                   std::make_shared<StateQuaternion>(_extrinsics.tail(4), true),
+                   std::make_shared<StateBlock>(_params->bias_prior_state,
+                                                _params->bias_prior_mode == "fix"),
+                   3,
+                   false, // P static
+                   false, // O static
+                   false),// I static
+        params_compass_(_params)
+{
+    // Bias prior
+    if (params_compass_->bias_prior_mode == "factor")
+        addPriorIntrinsics(params_compass_->bias_prior_state,
+                           (params_compass_->bias_prior_sigma.array() * params_compass_->bias_prior_sigma.array()).matrix().asDiagonal());
+
+    // Global Magnetic Field 'F' state block
+    addStateBlock('F',
+                  std::make_shared<StateBlock>(params_compass_->field_prior_state,
+                                               params_compass_->field_prior_mode == "fix"));
+
+    if (params_compass_->field_prior_mode == "factor")
+        addPriorParameter('F',
+                          params_compass_->field_prior_state,
+                          params_compass_->field_prior_sigma.cwiseAbs2().asDiagonal());
+}
+
+SensorCompass::SensorCompass(const Eigen::VectorXd& _extrinsics, const ParamsSensorCompass& _params) :
+        SensorCompass(_extrinsics, std::make_shared<ParamsSensorCompass>(_params))
+{
+
+}
+
+SensorCompass::~SensorCompass()
+{
+    // Auto-generated destructor stub
+}
+
+Eigen::Matrix3d SensorCompass::computeMeasurementCovariance(const Eigen::Vector3d& _field) const
+{
+    Eigen::Vector3d v1(_field.normalized());
+
+    // Find auxiliar vector not "too parallel" to v1
+    Eigen::Vector3d aux(Eigen::Vector3d::Random().normalized());
+    while (v1.dot(aux) > 0.9)
+        aux = Eigen::Vector3d::Random().normalized();
+
+    // Find v2 and v3 orthogonals to v1
+    Eigen::Vector3d v2, v3;
+    v2 = v1.cross(aux).normalized();
+    v3 = v1.cross(v2).normalized();
+
+    // Take v1, v2 and v3 as covariance eigenvectors
+    Eigen::Matrix3d eigen_vectors;
+    eigen_vectors.col(0) = v1;
+    eigen_vectors.col(1) = v2;
+    eigen_vectors.col(2) = v3;
+
+    // Set the covariance eigenvalues
+    Eigen::Matrix3d eigen_values(Eigen::Matrix3d::Zero());
+    eigen_values(0,0) = 1e6; // High covariance along field measurement direction to avoid gain problems
+    eigen_values(1,1) = pow(params_compass_->stdev_noise,2);
+    eigen_values(2,2) = pow(params_compass_->stdev_noise,2);
+
+    return eigen_vectors * eigen_values * eigen_vectors.inverse();
+}
+
+} /* namespace wolf */
+
+// Register in the FactorySensor
+#include "core/sensor/factory_sensor.h"
+namespace wolf {
+WOLF_REGISTER_SENSOR(SensorCompass);
+WOLF_REGISTER_SENSOR_AUTO(SensorCompass);
+} // namespace wolf
+
diff --git a/src/sensor/sensor_imu.cpp b/src/sensor/sensor_imu.cpp
index a0cec34a6166f872a14210cf1e55c32612006c71..488378b4f1f60b1baf670bcb80b22f17180d1f52 100644
--- a/src/sensor/sensor_imu.cpp
+++ b/src/sensor/sensor_imu.cpp
@@ -11,7 +11,12 @@ SensorImu::SensorImu(const Eigen::VectorXd& _extrinsics, ParamsSensorImuPtr _par
 }
 
 SensorImu::SensorImu(const Eigen::VectorXd& _extrinsics, const ParamsSensorImu& _params) :
-        SensorBase("SensorImu", std::make_shared<StateBlock>(_extrinsics.head(3), true), std::make_shared<StateQuaternion>(_extrinsics.tail(4), true), std::make_shared<StateBlock>(6, false, nullptr), (Eigen::Vector6d()<<_params.a_noise,_params.a_noise,_params.a_noise,_params.w_noise,_params.w_noise,_params.w_noise).finished(), false, false, true),
+        SensorBase("SensorImu",
+                   std::make_shared<StateBlock>(_extrinsics.head(3), true),
+                   std::make_shared<StateQuaternion>(_extrinsics.tail(4), true),
+                   std::make_shared<StateBlock>(6, false, nullptr),
+                   (Eigen::Vector6d()<<_params.a_noise,_params.a_noise,_params.a_noise,_params.w_noise,_params.w_noise,_params.w_noise).finished(),
+                   false, false, true),
         a_noise(_params.a_noise),
         w_noise(_params.w_noise),
         ab_initial_stdev(_params.ab_initial_stdev),
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index f05894d954d230b2168c35243162417df664b678..42bce1a6d1364c4ea6af8943a55efcbc88345aad 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -31,6 +31,9 @@ target_link_libraries(gtest_feature_imu ${PLUGIN_NAME} ${wolf_LIBRARY})
 wolf_add_gtest(gtest_imu_mocap_fusion gtest_imu_mocap_fusion.cpp)
 target_link_libraries(gtest_imu_mocap_fusion ${PLUGIN_NAME} ${wolf_LIBRARY})
 
+wolf_add_gtest(gtest_factor_compass_3d gtest_factor_compass_3d.cpp)
+target_link_libraries(gtest_factor_compass_3d ${PLUGIN_NAME} ${wolf_LIBRARY})
+
 # Has been excluded from tests for god knows how long, so thing is broken.
 # Maybe call an archeologist to fix this thing?
 # wolf_add_gtest(gtest_factor_imu gtest_factor_imu.cpp)
@@ -38,3 +41,6 @@ target_link_libraries(gtest_imu_mocap_fusion ${PLUGIN_NAME} ${wolf_LIBRARY})
 
 wolf_add_gtest(gtest_processor_motion_intrinsics_update gtest_processor_motion_intrinsics_update.cpp)
 target_link_libraries(gtest_processor_motion_intrinsics_update ${PLUGIN_NAME} ${wolf_LIBRARY})
+
+wolf_add_gtest(gtest_sensor_compass gtest_sensor_compass.cpp)
+target_link_libraries(gtest_sensor_compass ${PLUGIN_NAME} ${wolf_LIBRARY})
\ No newline at end of file
diff --git a/test/gtest_factor_compass_3d.cpp b/test/gtest_factor_compass_3d.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..362e8b0748dfe724e6747377d19833a71c5c37cd
--- /dev/null
+++ b/test/gtest_factor_compass_3d.cpp
@@ -0,0 +1,214 @@
+#include "core/utils/utils_gtest.h"
+
+#include "imu/internal/config.h"
+#include "imu/factor/factor_compass_3d.h"
+#include "imu/capture/capture_compass.h"
+#include "imu/sensor/sensor_compass.h"
+#include "imu/processor/processor_compass.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/math/rotations.h"
+
+using namespace Eigen;
+using namespace wolf;
+using std::cout;
+using std::endl;
+
+std::string wolf_root = _WOLF_IMU_ROOT_DIR;
+
+// Problem and solver
+ProblemPtr problem_ptr = Problem::create("PO", 3);
+SolverCeres solver(problem_ptr);
+
+// Sensor
+auto sen = std::static_pointer_cast<SensorCompass>(problem_ptr->installSensor("SensorCompass",
+                                                                              "magnetometer",
+                                                                              (Vector7d() << 0,0,0,0,0,0,1).finished(),
+                                                                              std::make_shared<ParamsSensorCompass>()));
+
+// Frame
+FrameBasePtr frm = problem_ptr->emplaceFrame(0, (Vector7d() << 0,0,0,0,0,0,1).finished() );
+
+// Pointers
+CaptureCompassPtr cap;
+
+// Auxliary states
+Vector3d frm_p, sen_p, sen_bias, field, meas;
+Quaterniond frm_q, sen_q;
+
+void generateRandomSetup()
+{
+    // remove capture (and feature and factor) just in case
+    if (cap)
+        cap->remove();
+
+    // Random states
+    frm_p = Vector3d::Random() * 10;
+    frm_q = Quaterniond(Vector4d::Random().normalized());
+    sen_p = Vector3d::Random() * 10;
+    sen_q = Quaterniond(Vector4d::Random().normalized());
+    sen_bias = Vector3d::Random();
+    field = Vector3d::Random().normalized() * 50;
+
+    // measurement
+    meas = sen_q.conjugate() * frm_q.conjugate() * field + sen_bias;
+
+    // Set states
+    frm->getP()->setState(frm_p);
+    frm->getO()->setState(frm_q.coeffs());
+    sen->getP()->setState(sen_p);
+    sen->getO()->setState(sen_q.coeffs());
+    sen->getIntrinsic()->setState(sen_bias);
+    sen->getStateBlock('F')->setState(field);
+
+    WOLF_DEBUG("Random setup:",
+               "\n\tfrm: P = ", frm_p.transpose(), " | O = ", frm_q.coeffs().transpose(),
+               "\n\tsen: P = ", sen_p.transpose(), " | O = ", sen_q.coeffs().transpose(), " | I = ", sen_bias.transpose(),
+               "\n\tfield: F = ", field.transpose(),
+               "\n\tmeasurement = ", meas.transpose());
+
+    // Capture
+    cap = CaptureBase::emplace<CaptureCompass>(frm,
+                                               1,
+                                               sen,
+                                               meas,
+                                               Matrix3d::Identity());
+
+    // feature & factor with delta measurement
+    auto fea = FeatureBase::emplace<FeatureBase>(cap,
+                                                 "FeatureCompass",
+                                                 meas,
+                                                 Eigen::Matrix3d::Identity());
+
+    FactorBase::emplace<FactorCompass3d>(fea,
+                                         fea,
+                                         nullptr,
+                                         false);
+
+    // Fix all
+    frm->getP()->fix();
+    frm->getO()->fix();
+    sen->getP()->fix();
+    sen->getO()->fix();
+    sen->getIntrinsic()->fix();
+    sen->getStateBlock('F')->fix();
+}
+
+TEST(FactorCompass3d, solve_frm_q)
+{
+    for (int i = 0; i < 1e2; i++)
+    {
+        // Random setup
+        generateRandomSetup();
+
+        // solve
+        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(frm->getO()->getState(), frm_q.coeffs(), 1e-6);
+
+        // Unfix and perturb: frame orientation
+        frm->getO()->unfix();
+        frm->getO()->perturb();
+
+        // solve
+        report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        // check measurement and field are aligned
+        Vector3d exp_meas = sen_q.conjugate() * frm_q.conjugate() * field + sen_bias;
+
+        ASSERT_MATRIX_APPROX(meas, exp_meas, 1e-6);
+
+        // remove capture (and feature and factor) for the next loop
+        cap->remove();
+    }
+}
+
+
+TEST(FactorCompass3d, solve_sen_q)
+{
+    for (int i = 0; i < 1e2; i++)
+    {
+        // Random setup
+        generateRandomSetup();
+
+        // solve
+        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(frm->getO()->getState(), frm_q.coeffs(), 1e-6);
+
+        // Unfix and perturb: frame orientation
+        sen->getO()->unfix();
+        sen->getO()->perturb();
+
+        // solve
+        report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        // check measurement and field are aligned
+        Vector3d exp_meas = sen_q.conjugate() * frm_q.conjugate() * field + sen_bias;
+
+        ASSERT_MATRIX_APPROX(meas, exp_meas, 1e-6);
+
+        // remove capture (and feature and factor) for the next loop
+        cap->remove();
+    }
+}
+
+TEST(FactorCompass3d, solve_bias)
+{
+    for (int i = 0; i < 1e2; i++)
+    {
+        // Random setup
+        generateRandomSetup();
+
+        // Unfix and perturb: bias
+        sen->getIntrinsic()->unfix();
+        sen->getIntrinsic()->perturb(1);
+
+        // solve
+        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(sen->getIntrinsic()->getState(), sen_bias, 1e-6);
+
+        // remove capture (and feature and factor) for the next loop
+        cap->remove();
+    }
+}
+
+TEST(FactorCompass3d, solve_field)
+{
+    for (int i = 0; i < 1e2; i++)
+    {
+        // Random setup
+        generateRandomSetup();
+
+        // Unfix and perturb: bias
+        sen->getStateBlock('F')->unfix();
+        sen->getStateBlock('F')->perturb(10);
+
+        // solve
+        std::string report = solver.solve(SolverManager::ReportVerbosity::BRIEF);
+
+        //WOLF_INFO(report);
+
+        ASSERT_MATRIX_APPROX(sen->getStateBlock('F')->getState(), field, 1e-6);
+
+        // remove capture (and feature and factor) for the next loop
+        cap->remove();
+    }
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_sensor_compass.cpp b/test/gtest_sensor_compass.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b404d2ad88ea63c0d6a6beac32f2a7b31377299
--- /dev/null
+++ b/test/gtest_sensor_compass.cpp
@@ -0,0 +1,244 @@
+#include "core/utils/utils_gtest.h"
+
+#include "imu/internal/config.h"
+#include "imu/capture/capture_compass.h"
+#include "imu/sensor/sensor_compass.h"
+
+#include "core/math/covariance.h"
+
+#include <cstdio>
+
+using namespace wolf;
+using namespace Eigen;
+
+std::string wolf_root = _WOLF_IMU_ROOT_DIR;
+
+TEST(SensorCompass, constructor)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    ASSERT_NEAR(extr.tail<4>().norm(), 1, 1e-6);
+
+    auto sen = std::make_shared<SensorCompass>(extr, params);
+
+    ASSERT_NE(sen, nullptr);
+
+    ASSERT_MATRIX_APPROX(sen->getP()->getState(), extr.head(3), 1e-12);
+    ASSERT_MATRIX_APPROX(sen->getO()->getState(), extr.tail(4), 1e-12);
+}
+
+TEST(SensorCompass, getParams)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    params->stdev_noise = 0.1;
+    params->field_prior_mode = "fix";
+    params->field_prior_state = Eigen::Vector3d(1, 2, 3);
+    params->field_prior_sigma = Eigen::Vector3d(4, 5, 6);
+
+    params->bias_prior_mode = "factor";
+    params->bias_prior_state = Eigen::Vector3d(3, 2, 1);
+    params->bias_prior_sigma = Eigen::Vector3d(6, 5, 4);
+
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    ASSERT_NEAR(extr.tail<4>().norm(), 1, 1e-6);
+
+    auto sen = std::make_shared<SensorCompass>(extr, params);
+
+    ASSERT_NE(sen->getParams(), nullptr);
+
+    ASSERT_NEAR(sen->getParams()->stdev_noise, 0.1, 1e-9);
+
+    ASSERT_EQ(sen->getParams()->field_prior_mode, "fix");
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_state, Eigen::Vector3d(1, 2, 3), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_sigma, Eigen::Vector3d(4, 5, 6), 1e-9);
+
+    ASSERT_EQ(sen->getParams()->bias_prior_mode, "factor");
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_state, Eigen::Vector3d(3, 2, 1), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_sigma, Eigen::Vector3d(6, 5, 4), 1e-9);
+
+    ASSERT_FALSE(sen->getIntrinsic()->isFixed());
+    ASSERT_TRUE(sen->getStateBlock('F')->isFixed());
+}
+
+TEST(SensorCompass, create)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    params->stdev_noise = 0.1;
+    params->field_prior_mode = "fix";
+    params->field_prior_state = Eigen::Vector3d(1, 2, 3);
+    params->field_prior_sigma = Eigen::Vector3d(4, 5, 6);
+
+    params->bias_prior_mode = "factor";
+    params->bias_prior_state = Eigen::Vector3d(3, 2, 1);
+    params->bias_prior_sigma = Eigen::Vector3d(6, 5, 4);
+
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    auto sen_base = SensorCompass::create("name", extr, params);
+
+    auto sen = std::static_pointer_cast<SensorCompass>(sen_base);
+
+    ASSERT_NE(sen->getParams(), nullptr);
+
+    ASSERT_NEAR(sen->getParams()->stdev_noise, 0.1, 1e-9);
+
+    ASSERT_EQ(sen->getParams()->field_prior_mode, "fix");
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_state, Eigen::Vector3d(1, 2, 3), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_sigma, Eigen::Vector3d(4, 5, 6), 1e-9);
+
+    ASSERT_EQ(sen->getParams()->bias_prior_mode, "factor");
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_state, Eigen::Vector3d(3, 2, 1), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_sigma, Eigen::Vector3d(6, 5, 4), 1e-9);
+
+    ASSERT_FALSE(sen->getIntrinsic()->isFixed());
+    ASSERT_TRUE(sen->getStateBlock('F')->isFixed());
+}
+
+TEST(SensorCompass, install)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    params->stdev_noise = 0.1;
+    params->field_prior_mode = "fix";
+    params->field_prior_state = Eigen::Vector3d(1, 2, 3);
+    params->field_prior_sigma = Eigen::Vector3d(4, 5, 6);
+
+    params->bias_prior_mode = "factor";
+    params->bias_prior_state = Eigen::Vector3d(3, 2, 1);
+    params->bias_prior_sigma = Eigen::Vector3d(6, 5, 4);
+
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    // Problem
+    ProblemPtr problem_ptr = Problem::create("PO", 3);
+
+    // Install sensor
+    auto sen_base = problem_ptr->installSensor("SensorCompass",
+                                               "magnetometer",
+                                               extr,
+                                               params);
+
+    auto sen = std::static_pointer_cast<SensorCompass>(sen_base);
+
+    ASSERT_NE(sen->getParams(), nullptr);
+
+    ASSERT_NEAR(sen->getParams()->stdev_noise, 0.1, 1e-9);
+
+    ASSERT_EQ(sen->getParams()->field_prior_mode, "fix");
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_state, Eigen::Vector3d(1, 2, 3), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->field_prior_sigma, Eigen::Vector3d(4, 5, 6), 1e-9);
+
+    ASSERT_EQ(sen->getParams()->bias_prior_mode, "factor");
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_state, Eigen::Vector3d(3, 2, 1), 1e-9);
+    ASSERT_MATRIX_APPROX(sen->getParams()->bias_prior_sigma, Eigen::Vector3d(6, 5, 4), 1e-9);
+
+    ASSERT_FALSE(sen->getIntrinsic()->isFixed());
+    ASSERT_TRUE(sen->getStateBlock('F')->isFixed());
+}
+
+TEST(SensorCompass, computeMeasurementCovariance)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    params->stdev_noise = 0.1;
+    params->field_prior_mode = "fix";
+    params->field_prior_state = Eigen::Vector3d(1, 2, 3);
+    params->field_prior_sigma = Eigen::Vector3d(4, 5, 6);
+
+    params->bias_prior_mode = "factor";
+    params->bias_prior_state = Eigen::Vector3d(3, 2, 1);
+    params->bias_prior_sigma = Eigen::Vector3d(6, 5, 4);
+
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    auto sen_base = SensorCompass::create("name", extr, params);
+
+    auto sen = std::static_pointer_cast<SensorCompass>(sen_base);
+
+    for (auto i = 0; i < 3; i++)
+    {
+        Vector3d field(Vector3d::Zero());
+        field(i) = 10;
+        WOLF_INFO("field = ", field.transpose());
+
+        Matrix3d cov = sen->computeMeasurementCovariance(field);
+        WOLF_INFO("cov = \n", cov);
+
+        Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(cov, Eigen::ComputeEigenvectors);
+        EXPECT_TRUE(eigensolver.info() == Eigen::Success);
+        WOLF_INFO("eigenvalues: ", eigensolver.eigenvalues().transpose());
+        WOLF_INFO("eigenvectors: \n", eigensolver.eigenvectors());
+
+        // covariance is created from the direction of field:
+        // large covariance: 1e6 m² in the field direction
+        // small covariance: params->stdev_noise² in the other directions.
+
+        // field should be aligned with its weighted vector by the covariance
+        Vector3d field_w = cov * field;
+        WOLF_INFO("field = ", field.transpose());
+        WOLF_INFO("field_w = ", field_w.transpose());
+        EXPECT_NEAR(field.norm(), field_w.norm() * 1e-6, 1e-6);
+        EXPECT_LE(acos(field.dot(field_w) / (field.norm() * field_w.norm())), 0.1);
+    }
+}
+
+TEST(SensorCompass, computeMeasurementCovarianceRandom)
+{
+    auto params = std::make_shared<ParamsSensorCompass>();
+    params->stdev_noise = 0.1;
+    params->field_prior_mode = "fix";
+    params->field_prior_state = Eigen::Vector3d(1, 2, 3);
+    params->field_prior_sigma = Eigen::Vector3d(4, 5, 6);
+
+    params->bias_prior_mode = "factor";
+    params->bias_prior_state = Eigen::Vector3d(3, 2, 1);
+    params->bias_prior_sigma = Eigen::Vector3d(6, 5, 4);
+
+    Vector7d extr(Vector7d::Random());
+    extr.tail<4>().normalize();
+
+    auto sen_base = SensorCompass::create("name", extr, params);
+
+    auto sen = std::static_pointer_cast<SensorCompass>(sen_base);
+
+    for (auto i = 0; i < 1e2; i++)
+    {
+        Vector3d field = Vector3d::Random().normalized() * 10;
+        WOLF_INFO("field = ", field.transpose());
+
+        Matrix3d cov = sen->computeMeasurementCovariance(field);
+        WOLF_INFO("cov = \n", cov);
+
+        Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(cov, Eigen::ComputeEigenvectors);
+        EXPECT_TRUE(eigensolver.info() == Eigen::Success);
+        WOLF_INFO("eigenvalues: ", eigensolver.eigenvalues().transpose());
+        WOLF_INFO("eigenvectors: \n", eigensolver.eigenvectors());
+
+        // covariance is created from the direction of field:
+        // large covariance: 1e6 m² in the field direction
+        // small covariance: params->stdev_noise² in the other directions.
+
+        // field should be aligned with its weighted vector by the covariance
+        Vector3d field_w = cov * field;
+        WOLF_INFO("field = ", field.transpose());
+        WOLF_INFO("field_w = ", field_w.transpose());
+        WOLF_INFO("field.norm() * field_w.norm() = ", field.norm() * field_w.norm());
+        WOLF_INFO("field.dot(field_w) = ", field.dot(field_w));
+        WOLF_INFO("field.dot(field_w) / (field.norm() * field_w.norm()) = ", field.dot(field_w) / (field.norm() * field_w.norm()));
+        WOLF_INFO("acos(min(1.0,field.dot(field_w) / (field.norm() * field_w.norm()))) = ", acos(std::min(1.0,field.dot(field_w) / (field.norm() * field_w.norm()))));
+        //EXPECT_NEAR(field.norm(), field_w.norm() * 1e-6, 1e-6);
+        EXPECT_LE(acos(std::min(1.0,field.dot(field_w) / (field.norm() * field_w.norm()))), 0.1);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
+
diff --git a/test/yaml/sensor_compass.yaml b/test/yaml/sensor_compass.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5d069063e53589bdad76cc972696d00fb227db2d
--- /dev/null
+++ b/test/yaml/sensor_compass.yaml
@@ -0,0 +1,11 @@
+type: "SensorCompass"
+
+stdev_noise: 0.02 # m
+
+field_prior_mode: "fix" # "fix", "factor" or "initial_guess"
+field_prior_state: [1, 2, 3]
+#field_prior_sigma: [0.1, 0.1, 0.1] #only mandatory in mode "factor" 
+
+bias_prior_mode: "factor" # "fix", "factor" or "initial_guess"
+bias_prior_state: [3, 2, 1]
+bias_prior_sigma: [0.1, 0.1, 0.1] #only mandatory in mode "factor"