Skip to content

Commit e9c7d6b

Browse files
Add extrinsic support to all constraints
1 parent ab64512 commit e9c7d6b

56 files changed

Lines changed: 6118 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

vesta_constraints/include/vesta_constraints/3d/absolute_orientation_3d_stamped_constraint.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include <vesta_core/fuse_macros.h>
4040
#include <vesta_core/serialization.h>
4141
#include <vesta_core/uuid.h>
42+
#include <vesta_variables/3d/extrinsic_3d_orientation.h>
43+
#include <vesta_variables/3d/extrinsic_3d_position.h>
4244
#include <vesta_variables/3d/orientation_3d_stamped.h>
4345

4446
#include <Eigen/Geometry>
@@ -102,6 +104,34 @@ class AbsoluteOrientation3DStampedConstraint : public vesta_core::Constraint
102104
const vesta_variables::Orientation3DStamped& orientation,
103105
const Eigen::Quaterniond& mean, const vesta_core::Matrix3d& covariance);
104106

107+
/**
108+
* @brief Constructor with extrinsic calibration
109+
*
110+
* This constructor accepts an extrinsic transform. The orientation variable represents the body
111+
* frame, and the extrinsic rotation is applied internally to compute the sensor-frame orientation.
112+
* The extrinsic translation is included for API consistency but does not affect orientation.
113+
*
114+
* @param[in] source The name of the sensor or motion model that generated this constraint
115+
* @param[in] orientation The variable representing the body-frame orientation
116+
* @param[in] ext_position The extrinsic translation (body-to-sensor), unused in math
117+
* @param[in] ext_orientation The extrinsic rotation (body-to-sensor)
118+
* @param[in] mean The measured/prior sensor-frame orientation (4x1 vector: w, x, y, z)
119+
* @param[in] covariance The measurement/prior covariance (3x3 matrix: qx, qy, qz)
120+
*/
121+
AbsoluteOrientation3DStampedConstraint(const std::string& source,
122+
const vesta_variables::Orientation3DStamped& orientation,
123+
const vesta_variables::Extrinsic3DPosition& ext_position,
124+
const vesta_variables::Extrinsic3DOrientation& ext_orientation,
125+
const vesta_core::Vector4d& mean, const vesta_core::Matrix3d& covariance);
126+
127+
/**
128+
* @brief Returns whether this constraint uses an extrinsic calibration.
129+
*/
130+
bool hasExtrinsic() const
131+
{
132+
return has_extrinsic_;
133+
}
134+
105135
/**
106136
* @brief Destructor
107137
*/
@@ -172,6 +202,7 @@ class AbsoluteOrientation3DStampedConstraint : public vesta_core::Constraint
172202

173203
vesta_core::Vector4d mean_; //!< The measured/prior mean vector for this variable
174204
vesta_core::Matrix3d sqrt_information_; //!< The square root information matrix
205+
bool has_extrinsic_{ false }; //!< Whether this constraint uses an extrinsic calibration
175206

176207
private:
177208
// Allow Boost Serialization access to private methods
@@ -192,6 +223,7 @@ class AbsoluteOrientation3DStampedConstraint : public vesta_core::Constraint
192223
archive& boost::serialization::base_object<vesta_core::Constraint>(*this);
193224
archive & mean_;
194225
archive & sqrt_information_;
226+
archive & has_extrinsic_;
195227
}
196228
};
197229

vesta_constraints/include/vesta_constraints/3d/absolute_orientation_3d_stamped_euler_constraint.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include <vesta_core/fuse_macros.h>
4040
#include <vesta_core/serialization.h>
4141
#include <vesta_core/uuid.h>
42+
#include <vesta_variables/3d/extrinsic_3d_orientation.h>
43+
#include <vesta_variables/3d/extrinsic_3d_position.h>
4244
#include <vesta_variables/3d/orientation_3d_stamped.h>
4345

4446
#include <boost/serialization/access.hpp>
@@ -94,6 +96,37 @@ class AbsoluteOrientation3DStampedEulerConstraint : public vesta_core::Constrain
9496
const vesta_core::VectorXd& mean, const vesta_core::MatrixXd& covariance,
9597
const std::vector<Euler>& axes);
9698

99+
/**
100+
* @brief Constructor with extrinsic calibration
101+
*
102+
* This constructor accepts an extrinsic transform. The orientation variable represents the body
103+
* frame, and the extrinsic rotation is applied internally to compute the sensor-frame orientation
104+
* before extracting Euler angles. The extrinsic translation is included for API consistency but
105+
* does not affect orientation.
106+
*
107+
* @param[in] source The name of the sensor or motion model that generated this constraint
108+
* @param[in] orientation The variable representing the body-frame orientation
109+
* @param[in] ext_position The extrinsic translation (body-to-sensor), unused in math
110+
* @param[in] ext_orientation The extrinsic rotation (body-to-sensor)
111+
* @param[in] mean The measured/prior Euler orientations in the order specified in \p axes
112+
* @param[in] covariance The measurement/prior covariance
113+
* @param[in] axes Used to specify which Euler axes to include in the constraint
114+
*/
115+
AbsoluteOrientation3DStampedEulerConstraint(const std::string& source,
116+
const vesta_variables::Orientation3DStamped& orientation,
117+
const vesta_variables::Extrinsic3DPosition& ext_position,
118+
const vesta_variables::Extrinsic3DOrientation& ext_orientation,
119+
const vesta_core::VectorXd& mean, const vesta_core::MatrixXd& covariance,
120+
const std::vector<Euler>& axes);
121+
122+
/**
123+
* @brief Returns whether this constraint uses an extrinsic calibration.
124+
*/
125+
bool hasExtrinsic() const
126+
{
127+
return has_extrinsic_;
128+
}
129+
97130
/**
98131
* @brief Destructor
99132
*/
@@ -166,6 +199,7 @@ class AbsoluteOrientation3DStampedEulerConstraint : public vesta_core::Constrain
166199
vesta_core::VectorXd mean_; //!< The measured/prior mean vector for this variable
167200
vesta_core::MatrixXd sqrt_information_; //!< The square root information matrix
168201
std::vector<Euler> axes_; //!< Which Euler angle axes we want to measure
202+
bool has_extrinsic_{ false }; //!< Whether this constraint uses an extrinsic calibration
169203

170204
private:
171205
// Allow Boost Serialization access to private methods
@@ -187,6 +221,7 @@ class AbsoluteOrientation3DStampedEulerConstraint : public vesta_core::Constrain
187221
archive & mean_;
188222
archive & sqrt_information_;
189223
archive & axes_;
224+
archive & has_extrinsic_;
190225
}
191226
};
192227

vesta_constraints/include/vesta_constraints/3d/absolute_pose_3d_stamped_constraint.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include <vesta_core/fuse_macros.h>
4040
#include <vesta_core/serialization.h>
4141
#include <vesta_core/uuid.h>
42+
#include <vesta_variables/3d/extrinsic_3d_orientation.h>
43+
#include <vesta_variables/3d/extrinsic_3d_position.h>
4244
#include <vesta_variables/3d/orientation_3d_stamped.h>
4345
#include <vesta_variables/3d/position_3d_stamped.h>
4446

@@ -96,6 +98,35 @@ class AbsolutePose3DStampedConstraint : public vesta_core::Constraint
9698
const vesta_variables::Orientation3DStamped& orientation,
9799
const vesta_core::Vector7d& mean, const vesta_core::Matrix6d& covariance);
98100

101+
/**
102+
* @brief Constructor with extrinsic calibration
103+
*
104+
* This constructor accepts an extrinsic transform T_body_sensor that maps points from sensor
105+
* frame to body frame. The position and orientation variables represent the body frame, and
106+
* the extrinsic is applied internally to compute the sensor-frame pose for the prior.
107+
*
108+
* @param[in] source The name of the sensor or motion model that generated this constraint
109+
* @param[in] position The variable representing the body-frame position
110+
* @param[in] orientation The variable representing the body-frame orientation
111+
* @param[in] ext_position The extrinsic translation (body-to-sensor)
112+
* @param[in] ext_orientation The extrinsic rotation (body-to-sensor)
113+
* @param[in] mean The measured/prior sensor-frame pose (7x1 vector: x, y, z, qw, qx, qy, qz)
114+
* @param[in] covariance The measurement/prior covariance (6x6 matrix: x, y, z, qx, qy, qz)
115+
*/
116+
AbsolutePose3DStampedConstraint(const std::string& source, const vesta_variables::Position3DStamped& position,
117+
const vesta_variables::Orientation3DStamped& orientation,
118+
const vesta_variables::Extrinsic3DPosition& ext_position,
119+
const vesta_variables::Extrinsic3DOrientation& ext_orientation,
120+
const vesta_core::Vector7d& mean, const vesta_core::Matrix6d& covariance);
121+
122+
/**
123+
* @brief Returns whether this constraint uses an extrinsic calibration.
124+
*/
125+
bool hasExtrinsic() const
126+
{
127+
return has_extrinsic_;
128+
}
129+
99130
/**
100131
* @brief Destructor
101132
*/
@@ -155,6 +186,7 @@ class AbsolutePose3DStampedConstraint : public vesta_core::Constraint
155186
protected:
156187
vesta_core::Vector7d mean_; //!< The measured/prior mean vector for this variable
157188
vesta_core::Matrix6d sqrt_information_; //!< The square root information matrix
189+
bool has_extrinsic_{ false }; //!< Whether this constraint uses an extrinsic calibration
158190

159191
private:
160192
// Allow Boost Serialization access to private methods
@@ -175,6 +207,7 @@ class AbsolutePose3DStampedConstraint : public vesta_core::Constraint
175207
archive& boost::serialization::base_object<vesta_core::Constraint>(*this);
176208
archive & mean_;
177209
archive & sqrt_information_;
210+
archive & has_extrinsic_;
178211
}
179212
};
180213

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
3+
/*
4+
* Software License Agreement (BSD License)
5+
*
6+
* Copyright (c) 2026, Vesta Contributors
7+
* All rights reserved.
8+
*
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions
11+
* are met:
12+
*
13+
* * Redistributions of source code must retain the above copyright
14+
* notice, this list of conditions and the following disclaimer.
15+
* * Redistributions in binary form must reproduce the above
16+
* copyright notice, this list of conditions and the following
17+
* disclaimer in the documentation and/or other materials provided
18+
* with the distribution.
19+
* * Neither the name of the copyright holder nor the names of its
20+
* contributors may be used to endorse or promote products derived
21+
* from this software without specific prior written permission.
22+
*
23+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27+
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34+
* POSSIBILITY OF SUCH DAMAGE.
35+
*/
36+
37+
#include <ceres/rotation.h>
38+
39+
namespace vesta_constraints
40+
{
41+
42+
/**
43+
* @brief Compute the sensor-frame pose in the world frame given a body pose and an extrinsic.
44+
*
45+
* The extrinsic T_body_sensor transforms points from the sensor frame to the body frame:
46+
* p_body = q_bs * p_sensor + t_bs
47+
*
48+
* Given the body pose in the world frame (p_wb, q_wb), the sensor pose in the world frame is:
49+
* q_ws = q_wb * q_bs
50+
* p_ws = p_wb + R_wb * t_bs
51+
*
52+
* @param[in] body_position Body position in world frame (3 elements: x, y, z)
53+
* @param[in] body_orientation Body orientation quaternion in world frame (4 elements: w, x, y, z)
54+
* @param[in] ext_position Extrinsic translation body-to-sensor (3 elements: x, y, z)
55+
* @param[in] ext_orientation Extrinsic rotation quaternion body-to-sensor (4 elements: w, x, y, z)
56+
* @param[out] sensor_position Sensor position in world frame (3 elements)
57+
* @param[out] sensor_orientation Sensor orientation quaternion in world frame (4 elements)
58+
*/
59+
template <typename T>
60+
inline void computeSensorPose(const T* const body_position, const T* const body_orientation,
61+
const T* const ext_position, const T* const ext_orientation, T* sensor_position,
62+
T* sensor_orientation)
63+
{
64+
// q_ws = q_wb * q_bs
65+
ceres::QuaternionProduct(body_orientation, ext_orientation, sensor_orientation);
66+
67+
// p_ws = p_wb + R_wb * t_bs
68+
T rotated_ext_position[3];
69+
ceres::QuaternionRotatePoint(body_orientation, ext_position, rotated_ext_position);
70+
sensor_position[0] = body_position[0] + rotated_ext_position[0];
71+
sensor_position[1] = body_position[1] + rotated_ext_position[1];
72+
sensor_position[2] = body_position[2] + rotated_ext_position[2];
73+
}
74+
75+
} // namespace vesta_constraints
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#pragma once
2+
3+
/*
4+
* Software License Agreement (BSD License)
5+
*
6+
* Copyright (c) 2026, Vesta Contributors
7+
* All rights reserved.
8+
*
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions
11+
* are met:
12+
*
13+
* * Redistributions of source code must retain the above copyright
14+
* notice, this list of conditions and the following disclaimer.
15+
* * Redistributions in binary form must reproduce the above
16+
* copyright notice, this list of conditions and the following
17+
* disclaimer in the documentation and/or other materials provided
18+
* with the distribution.
19+
* * Neither the name of the copyright holder nor the names of its
20+
* contributors may be used to endorse or promote products derived
21+
* from this software without specific prior written permission.
22+
*
23+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27+
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34+
* POSSIBILITY OF SUCH DAMAGE.
35+
*/
36+
37+
#include <vesta_core/eigen.h>
38+
#include <vesta_core/fuse_macros.h>
39+
40+
#include <ceres/rotation.h>
41+
#include <Eigen/Core>
42+
43+
namespace vesta_constraints
44+
{
45+
46+
/**
47+
* @brief Cost function for the difference between two 3D orientations with an extrinsic calibration.
48+
*
49+
* This extends NormalDeltaOrientation3DCostFunctor to accept body-frame orientation variables
50+
* and an extrinsic transform. The sensor orientations are computed as q_sensor = q_body * q_ext,
51+
* and the delta orientation residual is computed on the sensor orientations.
52+
*
53+
* The ext_position parameter block is present for graph consistency but is unused in the math,
54+
* since translation does not affect orientation.
55+
*
56+
* Parameter blocks: body_orientation1 (4), body_orientation2 (4), ext_position (3), ext_orientation (4)
57+
*
58+
* Residuals: 3 (qx, qy, qz)
59+
*/
60+
class NormalDeltaOrientation3DWithExtrinsicCostFunctor
61+
{
62+
public:
63+
VESTA_MAKE_ALIGNED_OPERATOR_NEW();
64+
65+
/**
66+
* @brief Construct a cost function instance
67+
*
68+
* @param[in] A The residual weighting matrix (3x3), typically the square root information matrix
69+
* @param[in] b The measured orientation change as a quaternion (4x1: w, x, y, z)
70+
*/
71+
NormalDeltaOrientation3DWithExtrinsicCostFunctor(const vesta_core::Matrix3d& A, const vesta_core::Vector4d& b)
72+
: A_(A), b_(b)
73+
{
74+
}
75+
76+
/**
77+
* @brief Evaluate the cost function. Used by the Ceres optimization engine.
78+
*/
79+
template <typename T>
80+
bool operator()(const T* const body_orientation1, const T* const body_orientation2,
81+
const T* const /* ext_position */, const T* const ext_orientation, T* residuals) const
82+
{
83+
// Compute sensor orientations: q_sensor = q_body * q_ext
84+
T sensor_orientation1[4];
85+
ceres::QuaternionProduct(body_orientation1, ext_orientation, sensor_orientation1);
86+
87+
T sensor_orientation2[4];
88+
ceres::QuaternionProduct(body_orientation2, ext_orientation, sensor_orientation2);
89+
90+
// Compute the delta: q1^-1 * q2
91+
T orientation1_inverse[4] = { sensor_orientation1[0], -sensor_orientation1[1], -sensor_orientation1[2],
92+
-sensor_orientation1[3] };
93+
94+
T observation_inverse[4] = { T(b_(0)), T(-b_(1)), T(-b_(2)), T(-b_(3)) };
95+
96+
T difference[4];
97+
ceres::QuaternionProduct(orientation1_inverse, sensor_orientation2, difference);
98+
T error[4];
99+
ceres::QuaternionProduct(observation_inverse, difference, error);
100+
ceres::QuaternionToAngleAxis(error, residuals);
101+
102+
// Scale the residuals by the square root information matrix to account for
103+
// the measurement uncertainty.
104+
Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, 1>> residuals_map(residuals, A_.rows());
105+
residuals_map.applyOnTheLeft(A_.template cast<T>());
106+
107+
return true;
108+
}
109+
110+
private:
111+
vesta_core::Matrix3d A_; //!< The residual weighting matrix
112+
vesta_core::Vector4d b_; //!< The measured difference between orientation1 and orientation2
113+
};
114+
115+
} // namespace vesta_constraints

0 commit comments

Comments
 (0)