Program Listing for File tf2_sophus.hpp

Return to documentation for file (include/beluga_ros/tf2_sophus.hpp)

// Copyright 2022-2023 Ekumen, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef BELUGA_ROS_TF2_SOPHUS_HPP
#define BELUGA_ROS_TF2_SOPHUS_HPP

#include <tf2/convert.h>
#include <tf2/utils.h>
#include <beluga/algorithm/unscented_transform.hpp>
#include <beluga/eigen_compatibility.hpp>
#include <cmath>
#include <sophus/common.hpp>

#if BELUGA_ROS_VERSION == 2
#include <tf2_eigen/tf2_eigen.hpp>
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
#elif BELUGA_ROS_VERSION == 1
#include <tf2_eigen/tf2_eigen.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#else
#error BELUGA_ROS_VERSION is not defined or invalid
#endif

#include <sophus/se2.hpp>
#include <sophus/se3.hpp>
#include <sophus/types.hpp>

#include <beluga_ros/messages.hpp>

namespace detail {
template <typename Scalar>
inline Eigen::Vector<Scalar, 6> tangential_space_to_xyz_rpy(const Eigen::Vector<Scalar, 6>& tangent) {
  // ROS covariances use extrinsic RPY parametrization, we need to convert to it from our tangent space
  // representation. We use an unscented transform to apply such transform as it's non linear.
  // See https://www.ros.org/reps/rep-0103.html#covariance-representation .
  const auto se3 = Sophus::SE3<Scalar>::exp(tangent);
  // Eigen's eulerAngles uses intrinsic rotations, but XYZ extrinsic rotation is the same as ZYX intrinsic rotation.
  // See https://pages.github.berkeley.edu/EECS-106/fa21-site/assets/discussions/D1_Rotations_soln.pdf
  // This gives (extrinsic) yaw, pitch, roll in that order.
  const Eigen::Vector<Scalar, 3> euler_angles = se3.so3().matrix().eulerAngles(2, 1, 0);
  Eigen::Vector<Scalar, 6> ret;
  ret.template head<3>() = se3.translation();
  ret[3] = euler_angles.z();
  ret[4] = euler_angles.y();
  ret[5] = euler_angles.x();
  return ret;
}
}  // namespace detail

namespace tf2 {


template <class Scalar>
inline beluga_ros::msg::Pose& toMsg(const Sophus::SE2<Scalar>& in, beluga_ros::msg::Pose& out) {
  const double theta = in.so2().log();
  out.position.x = in.translation().x();
  out.position.y = in.translation().y();
  out.position.z = 0;
  out.orientation.w = std::cos(theta / 2.);
  out.orientation.x = 0;
  out.orientation.y = 0;
  out.orientation.z = std::sin(theta / 2.);
  return out;
}


template <class Scalar>
inline beluga_ros::msg::Pose& toMsg(const Sophus::SE3<Scalar>& in, beluga_ros::msg::Pose& out) {
  out.position.x = in.translation().x();
  out.position.y = in.translation().y();
  out.position.z = in.translation().z();
  out.orientation.w = in.so3().unit_quaternion().w();
  out.orientation.x = in.so3().unit_quaternion().x();
  out.orientation.y = in.so3().unit_quaternion().y();
  out.orientation.z = in.so3().unit_quaternion().z();
  return out;
}


template <class Scalar>
inline beluga_ros::msg::Transform toMsg(const Sophus::SE2<Scalar>& in) {
  auto msg = beluga_ros::msg::Transform{};
  const double theta = in.so2().log();
  msg.translation.x = in.translation().x();
  msg.translation.y = in.translation().y();
  msg.translation.z = 0;
  msg.rotation.w = std::cos(theta / 2.);
  msg.rotation.x = 0;
  msg.rotation.y = 0;
  msg.rotation.z = std::sin(theta / 2.);
  return msg;
}


template <class Scalar>
inline beluga_ros::msg::Transform toMsg(const Sophus::SE3<Scalar>& in) {
  auto msg = beluga_ros::msg::Transform{};
  msg.translation.x = in.translation().x();
  msg.translation.y = in.translation().y();
  msg.translation.z = in.translation().z();
  msg.rotation.w = in.so3().unit_quaternion().w();
  msg.rotation.x = in.so3().unit_quaternion().x();
  msg.rotation.y = in.so3().unit_quaternion().y();
  msg.rotation.z = in.so3().unit_quaternion().z();
  return msg;
}


template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Transform& msg, Sophus::SE2<Scalar>& out) {
  out.translation() = Sophus::Vector2<Scalar>{
      msg.translation.x,
      msg.translation.y,
  };
  out.so2() = Sophus::SO2<Scalar>{static_cast<Scalar>(tf2::getYaw(msg.rotation))};
}


template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Transform& msg, Sophus::SE3<Scalar>& out) {
  out.translation() = Sophus::Vector3<Scalar>{
      static_cast<Scalar>(msg.translation.x),
      static_cast<Scalar>(msg.translation.y),
      static_cast<Scalar>(msg.translation.z),
  };
  out.so3() = Sophus::SO3<Scalar>{Eigen::Quaternion<Scalar>{
      static_cast<Scalar>(msg.rotation.w),
      static_cast<Scalar>(msg.rotation.x),
      static_cast<Scalar>(msg.rotation.y),
      static_cast<Scalar>(msg.rotation.z),
  }};
}


template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Pose& msg, Sophus::SE2<Scalar>& out) {
  out.translation() = Sophus::Vector2<Scalar>{
      static_cast<Scalar>(msg.position.x),
      static_cast<Scalar>(msg.position.y),
  };
  out.so2() = Sophus::SO2<Scalar>{static_cast<Scalar>(tf2::getYaw(msg.orientation))};
}


template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Pose& msg, Sophus::SE3<Scalar>& out) {
  out.translation() = Sophus::Vector3<Scalar>{
      static_cast<Scalar>(msg.position.x),
      static_cast<Scalar>(msg.position.y),
      static_cast<Scalar>(msg.position.z),
  };
  out.so3() = Sophus::SO3<Scalar>{Eigen::Quaternion<Scalar>{
      static_cast<Scalar>(msg.orientation.w),
      static_cast<Scalar>(msg.orientation.x),
      static_cast<Scalar>(msg.orientation.y),
      static_cast<Scalar>(msg.orientation.z),
  }};
}


template <template <typename, std::size_t> class Array, typename Scalar>
inline Array<Scalar, 36>& covarianceEigenToRowMajor(const Sophus::Matrix3<Scalar>& in, Array<Scalar, 36>& out) {
  out.fill(0);
  out[0] = in.coeff(0, 0);
  out[1] = in.coeff(0, 1);
  out[5] = in.coeff(0, 2);
  out[6] = in.coeff(1, 0);
  out[7] = in.coeff(1, 1);
  out[11] = in.coeff(1, 2);
  out[30] = in.coeff(2, 0);
  out[31] = in.coeff(2, 1);
  out[35] = in.coeff(2, 2);
  return out;
}


template <template <typename, std::size_t> class Array, typename Scalar>
inline Sophus::Matrix3<Scalar>& covarianceRowMajorToEigen(const Array<Scalar, 36>& in, Sophus::Matrix3<Scalar>& out) {
  out.coeffRef(0, 0) = in[0];
  out.coeffRef(0, 1) = in[1];
  out.coeffRef(0, 2) = in[5];
  out.coeffRef(1, 0) = in[6];
  out.coeffRef(1, 1) = in[7];
  out.coeffRef(1, 2) = in[11];
  out.coeffRef(2, 0) = in[30];
  out.coeffRef(2, 1) = in[31];
  out.coeffRef(2, 2) = in[35];
  return out;
}

template <typename Scalar>
inline beluga_ros::msg::PoseWithCovariance toMsg(
    const Sophus::SE3<Scalar>& in,
    const Eigen::Matrix<Scalar, 6, 6>& covariance) {
  beluga_ros::msg::PoseWithCovariance out;
  tf2::toMsg(in, out.pose);

  // ROS covariances use extrinsic RPY parametrization, we need to convert to it from our tangent space
  // representation. We use an unscented transform to apply such transform as it's non linear.
  // See https://www.ros.org/reps/rep-0103.html#covariance-representation .

  const auto& [base_pose_in_map_xyz_rpy, base_pose_covariance_xyz_rpy] = beluga::unscented_transform(
      in.log(), covariance, detail::tangential_space_to_xyz_rpy<Scalar>, std::nullopt,
      [](const std::vector<Eigen::Vector<Scalar, 6>>& samples, const std::vector<Scalar>& weights) {
        Eigen::Vector<Scalar, 6> out = Eigen::Vector<Scalar, 6>::Zero();
        Eigen::Vector<Scalar, 2> roll_aux = Eigen::Vector<Scalar, 2>::Zero();
        Eigen::Vector<Scalar, 2> pitch_aux = Eigen::Vector<Scalar, 2>::Zero();
        Eigen::Vector<Scalar, 2> yaw_aux = Eigen::Vector<Scalar, 2>::Zero();

        for (const auto& [s, w] : ranges::views::zip(samples, weights)) {
          out.template head<3>() += s.template head<3>() * w;
          roll_aux.x() = std::sin(s[3]) * w;
          roll_aux.y() = std::cos(s[3]) * w;
          pitch_aux.x() = std::sin(s[4]) * w;
          pitch_aux.y() = std::cos(s[4]) * w;
          yaw_aux.x() = std::sin(s[5]) * w;
          yaw_aux.y() = std::cos(s[5]) * w;
        }

        out[3] = std::atan2(roll_aux.x(), roll_aux.y());
        out[4] = std::atan2(pitch_aux.x(), pitch_aux.y());
        out[5] = std::atan2(yaw_aux.x(), yaw_aux.y());
        return out;
      },
      [](const Eigen::Vector<Scalar, 6>& sample, const Eigen::Vector<Scalar, 6>& mean) {
        Eigen::Vector<Scalar, 6> out;
        const Sophus::SO3<Scalar> sample_so3(
            Sophus::SO3<Scalar>::rotZ(sample[5]) * Sophus::SO3<Scalar>::rotY(sample[4]) *
            Sophus::SO3<Scalar>::rotX(sample[3]));

        const Sophus::SO3<Scalar> mean_so3(
            Sophus::SO3<Scalar>::rotZ(mean[5]) * Sophus::SO3<Scalar>::rotY(mean[4]) *
            Sophus::SO3<Scalar>::rotX(mean[3]));

        const Sophus::SO3<Scalar> delta = mean_so3.inverse() * sample_so3;
        const Eigen::AngleAxis<Scalar> angle_axis{delta.unit_quaternion()};
        out.template head<3>() = sample.template head<3>() - mean.template head<3>();
        out.template tail<3>() = angle_axis.axis() * angle_axis.angle();
        return out;
      });

  // ROS covariance elements type is always double, and they're in RowMajor order.
  Eigen::Map<Eigen::Matrix<double, 6, 6, Eigen::RowMajor>>(out.covariance.data()) =
      base_pose_covariance_xyz_rpy.template cast<double>();
  return out;
}

}  // namespace tf2

namespace Sophus {  // NOLINT(readability-identifier-naming)

template <class Scalar>
inline beluga_ros::msg::Transform toMsg(const Sophus::SE2<Scalar>& pose) {
  return tf2::toMsg(pose);
}

template <class Scalar>
inline beluga_ros::msg::Transform toMsg(const Sophus::SE3<Scalar>& in) {
  return tf2::toMsg(in);
}

template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Transform& message, Sophus::SE2<Scalar>& pose) {
  tf2::fromMsg(message, pose);
}

template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Transform& message, Sophus::SE3<Scalar>& pose) {
  tf2::fromMsg(message, pose);
}

template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Pose& message, Sophus::SE2<Scalar>& pose) {
  tf2::fromMsg(message, pose);
}

template <class Scalar>
inline void fromMsg(const beluga_ros::msg::Pose& message, Sophus::SE3<Scalar>& pose) {
  tf2::fromMsg(message, pose);
}

}  // namespace Sophus

#endif  // BELUGA_ROS_TF2_SOPHUS_HPP