/* ---------------------------------------------------------------------------- * GTSAM Copyright 2010, Georgia Tech Research Corporation, * Atlanta, Georgia 30332-0415 * All Rights Reserved * Authors: Frank Dellaert, et al. (see THANKS for the full author list) * See LICENSE for the license information * -------------------------------------------------------------------------- */ #pragma once #include #include namespace gtsam { /** * Factor to estimate rotation of a Pose2 or Pose3 given a magnetometer reading. * This version uses the measurement model bM = scale * bRn * direction + bias, * where bRn is the rotation of the body in the nav frame, and scale, direction, * and bias are assumed to be known. If the factor is constructed with a * body_P_sensor, then the magnetometer measurements and bias should be * expressed in the sensor frame. */ template class MagPoseFactor: public NoiseModelFactor1 { private: using This = MagPoseFactor; using Base = NoiseModelFactor1; using Point = typename POSE::Translation; ///< Could be a Vector2 or Vector3 depending on POSE. using Rot = typename POSE::Rotation; const Point measured_; ///< The measured magnetometer data in the body frame. const Point nM_; ///< Local magnetic field (mag output units) in the nav frame. const Point bias_; ///< The bias vector (mag output units) in the body frame. boost::optional body_P_sensor_; ///< The pose of the sensor in the body frame. static const int MeasDim = Point::RowsAtCompileTime; static const int PoseDim = traits::dimension; static const int RotDim = traits::dimension; /// Shorthand for a smart pointer to a factor. using shared_ptr = boost::shared_ptr>; /// Concept check by type. GTSAM_CONCEPT_TESTABLE_TYPE(POSE) GTSAM_CONCEPT_POSE_TYPE(POSE) public: ~MagPoseFactor() override {} /// Default constructor - only use for serialization. MagPoseFactor() {} /** * Construct the factor. * @param pose_key of the unknown pose nPb in the factor graph * @param measured magnetometer reading, a Point2 or Point3 * @param scale by which a unit vector is scaled to yield a magnetometer reading * @param direction of the local magnetic field, see e.g. http://www.ngdc.noaa.gov/geomag-web/#igrfwmm * @param bias of the magnetometer, modeled as purely additive (after scaling) * @param model of the additive Gaussian noise that is assumed * @param body_P_sensor an optional transform of the magnetometer in the body frame */ MagPoseFactor(Key pose_key, const Point& measured, double scale, const Point& direction, const Point& bias, const SharedNoiseModel& model, const boost::optional& body_P_sensor) : Base(model, pose_key), measured_(body_P_sensor ? body_P_sensor->rotation() * measured : measured), nM_(scale * direction.normalized()), bias_(body_P_sensor ? body_P_sensor->rotation() * bias : bias), body_P_sensor_(body_P_sensor) {} /// @return a deep copy of this factor. NonlinearFactor::shared_ptr clone() const override { return boost::static_pointer_cast( NonlinearFactor::shared_ptr(new This(*this))); } /// Implement functions needed for Testable. // Print out the factor. void print(const std::string& s = "", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override { Base::print(s, keyFormatter); gtsam::print(Vector(nM_), "local field (nM): "); gtsam::print(Vector(measured_), "measured field (bM): "); gtsam::print(Vector(bias_), "magnetometer bias: "); } /// Equals function. bool equals(const NonlinearFactor& expected, double tol=1e-9) const override { const This *e = dynamic_cast (&expected); return e != nullptr && Base::equals(*e, tol) && gtsam::equal_with_abs_tol(this->measured_, e->measured_, tol) && gtsam::equal_with_abs_tol(this->nM_, e->nM_, tol) && gtsam::equal_with_abs_tol(this->bias_, e->bias_, tol); } /// Implement functions needed to derive from Factor. /** * Return the factor's error h(x) - z, and the optional Jacobian. Note that * the measurement error is expressed in the body frame. */ Vector evaluateError(const POSE& nPb, boost::optional H = boost::none) const override { // Predict the measured magnetic field h(x) in the *body* frame. // If body_P_sensor was given, bias_ will have been rotated into the body frame. Matrix H_rot = Matrix::Zero(MeasDim, RotDim); const Point hx = nPb.rotation().unrotate(nM_, H_rot, boost::none) + bias_; if (H) { // Fill in the relevant part of the Jacobian (just rotation columns). *H = Matrix::Zero(MeasDim, PoseDim); const size_t rot_col0 = nPb.rotationInterval().first; (*H).block(0, rot_col0, MeasDim, RotDim) = H_rot; } return (hx - measured_); } private: /// Serialization function. friend class boost::serialization::access; template void serialize(ARCHIVE & ar, const unsigned int /*version*/) { ar & boost::serialization::make_nvp("NoiseModelFactor1", boost::serialization::base_object(*this)); ar & BOOST_SERIALIZATION_NVP(measured_); ar & BOOST_SERIALIZATION_NVP(nM_); ar & BOOST_SERIALIZATION_NVP(bias_); ar & BOOST_SERIALIZATION_NVP(body_P_sensor_); } }; // \class MagPoseFactor } /// namespace gtsam