NVIDIA Holoscan SDK v3.4.0

Program Listing for File so3.hpp

Return to documentation for file (include/holoscan/pose_tree/math/so3.hpp)

Copy
Copied!
            

/* * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * 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 HOLOSCAN_POSE_TREE_MATH_SO3_HPP #define HOLOSCAN_POSE_TREE_MATH_SO3_HPP #include <cmath> #include "holoscan/pose_tree/math/so2.hpp" #include "holoscan/pose_tree/math/types.hpp" namespace holoscan { template <typename K> class SO3 { public: using Scalar = K; static constexpr int kDimension = 3; SO3() {} static SO3 identity() { return SO3(Quaternion<K>::Identity()); } static SO3 from_scaled_axis(const Vector3<K>& axis_angle) { const K norm = axis_angle.norm(); if (is_almost_zero(norm)) { return SO3::identity(); } else { return SO3(Quaternion<K>(Eigen::AngleAxis<K>(norm, axis_angle / norm))); } } static SO3 from_axis_angle(const Vector3<K>& axis, K angle) { return SO3(Quaternion<K>(Eigen::AngleAxis<K>(angle, axis.normalized()))); } static SO3 from_angle_axis(K angle, const Vector3<K>& axis) { return SO3(Quaternion<K>(Eigen::AngleAxis<K>(angle, axis.normalized()))); } static SO3 from_quaternion(const Quaternion<K>& quaternion) { return SO3(quaternion.normalized()); } static SO3 from_normalized_quaternion(const Quaternion<K>& quaternion) { return SO3(quaternion); } static SO3 from_so2_xy(const SO2<K>& rotation) { return from_axis_angle({K(0), K(0), K(1)}, rotation.angle()); } static SO3 from_matrix(const Matrix<K, 3, 3>& matrix) { return SO3(Quaternion<K>(matrix).normalized()); } Vector3<K> axis() const { return quaternion_.coeffs().head(3).normalized(); } K angle() const { return K(2) * std::atan2(quaternion_.coeffs().head(3).norm(), quaternion_.coeffs().w()); } const Quaternion<K>& quaternion() const { return quaternion_; } Matrix3<K> matrix() const { return quaternion_.toRotationMatrix(); } Vector3<K> euler_angles_rpy() const { Vector3d euler_angles = quaternion_.toRotationMatrix().eulerAngles(0, 1, 2); // Make sure the roll is in range [-Pi/2, Pi/2] if (std::abs(euler_angles[0]) > M_PI * K(0.5)) { euler_angles[0] -= M_PI; euler_angles[1] = M_PI - euler_angles[1]; euler_angles[2] += M_PI; // TODO(bbutin): make sure we are in the range [-Pi, Pi] } return euler_angles; } SO3 inverse() const { // conjugate == inverse iff quaternion_.norm() == 1 return SO3(quaternion_.conjugate()); } template <typename S, typename std::enable_if_t<!std::is_same<S, K>::value, int> = 0> SO3<S> cast() const { // We need to re-normalize in the new type return SO3<S>::from_quaternion(quaternion().template cast<S>()); } template <typename S, typename std::enable_if_t<std::is_same<S, K>::value, int> = 0> const SO3& cast() const { // Nothing to do as the type does not change return *this; } SO2<K> to_so2_xy() const { // 2D rotation matrix: // cos(a) -sin(a) // sin(a) cos(a) // Quaternion to 3D rotation matrix: // 1 - 2*(qy^2 + qz^2) 2*(qx*qy - qz*qw) ... // 2*(qx*qy + qz*qw) 1 - 2*(qx^2 + qz^2) ... // It follows (modulo re-normalization): // cos(a) = 1 - (qx^2 + qy^2 + 2*qz^2) // sin(a) = 2*qz*qw // These formulas correspond to the half-angle formulas for sin/cos. const K qx = quaternion_.x(); const K qy = quaternion_.y(); const K qz = quaternion_.z(); const K qw = quaternion_.w(); const K cos_a = K(1) - (qx * qx + qy * qy + K(2) * qz * qz); const K sin_a = K(2) * qz * qw; return SO2<K>::from_direction(cos_a, sin_a); } friend SO3 operator*(const SO3& lhs, const SO3& rhs) { return from_quaternion(lhs.quaternion_ * rhs.quaternion_); } friend Vector3<K> operator*(const SO3& rot, const Vector3<K>& vec) { // TODO: faster implementation return (rot.quaternion_ * Quaternion<K>(K(0), vec.x(), vec.y(), vec.z()) * rot.quaternion_.conjugate()) .coeffs() .head(3); } static SO3 from_euler_angles_rpy(K roll_angle, K pitch_angle, K yaw_angle) { SO3 roll = SO3::from_angle_axis(roll_angle, {K(1), K(0), K(0)}); SO3 pitch = SO3::from_angle_axis(pitch_angle, {K(0), K(1), K(0)}); SO3 yaw = SO3::from_angle_axis(yaw_angle, {K(0), K(0), K(1)}); return roll * pitch * yaw; } static SO3 from_euler_angles_rpy(const Vector3d& roll_pitch_yaw) { return from_euler_angles_rpy(roll_pitch_yaw[0], roll_pitch_yaw[1], roll_pitch_yaw[2]); } Matrix<K, 3, 4> vector_rotation_jacobian(const Vector3<K>& n) const { // Ref: https://www.weizmann.ac.il/sci-tea/benari/sites/sci-tea.benari/files/uploads/ // softwareAndLearningMaterials/quaternion-tutorial-2-0-1.pdf // Rotation Matrix in Quaternion (R): // [w^2 + x^2 - y^2 - z^2 2xy - 2wz 2wy + 2xz] // [2wz + 2xy w^2 - x^2 + y^2 - z^2 2yz - 2wx] // [2xz - 2wy 2wx + 2yz w^2 - x^2 - y^2 + z^2] const K qx = quaternion_.x(); const K qy = quaternion_.y(); const K qz = quaternion_.z(); const K qw = quaternion_.w(); Matrix<K, 3, 4> result; result << K(2) * (qw * n[0] - qz * n[1] + qy * n[2]), K(2) * (qx * n[0] + qy * n[1] + qz * n[2]), K(-2) * (qy * n[0] - qx * n[1] - qw * n[2]), K(-2) * (qz * n[0] + qw * n[1] - qx * n[2]), K(2) * (qz * n[0] + qw * n[1] - qx * n[2]), K(2) * (qy * n[0] - qx * n[1] - qw * n[2]), K(2) * (qx * n[0] + qy * n[1] + qz * n[2]), K(2) * (qw * n[0] - qz * n[1] + qy * n[2]), K(-2) * (qy * n[0] - qx * n[1] - qw * n[2]), K(2) * (qz * n[0] + qw * n[1] - qx * n[2]), K(-2) * (qw * n[0] - qz * n[1] + qy * n[2]), K(2) * (qx * n[0] + qy * n[1] + qz * n[2]); return result; } private: explicit SO3(const Quaternion<K>& quaternion) : quaternion_(quaternion) {} Quaternion<K> quaternion_; }; using SO3d = SO3<double>; using SO3f = SO3<float>; } // namespace holoscan #endif/* HOLOSCAN_POSE_TREE_MATH_SO3_HPP */

© Copyright 2022-2025, NVIDIA. Last updated on Jul 1, 2025.