blob: bdf82582e9ca2b4ee46e28233776d319055ec9ff [file] [log] [blame]
#include "frc971/vision/vision_util_lib.h"
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
#include "aos/util/math.h"
namespace frc971::vision {
// For now, just testing extracting camera number from channel name
TEST(VisionUtilsTest, CameraNumberFromChannel) {
ASSERT_EQ(CameraNumberFromChannel("/camera0").value(), 0);
ASSERT_EQ(CameraNumberFromChannel("/camera1").value(), 1);
ASSERT_EQ(CameraNumberFromChannel("/camera"), std::nullopt);
ASSERT_EQ(CameraNumberFromChannel("/orin1/camera0").value(), 0);
ASSERT_EQ(CameraNumberFromChannel("/orin1/camera1").value(), 1);
ASSERT_EQ(CameraNumberFromChannel("/orin1"), std::nullopt);
}
namespace {
constexpr double kToleranceRadians = 0.05;
// Conversions between euler angles and quaternion result in slightly-off
// doubles
constexpr double kOrientationEqTolerance = 1e-10;
} // namespace
// Angles normalized by aos::math::NormalizeAngle()
#define EXPECT_NORMALIZED_ANGLES_NEAR(theta1, theta2, tolerance) \
{ \
double delta = std::abs(aos::math::DiffAngle(theta1, theta2)); \
/* Have to check delta - 2pi for the case that one angle is very */ \
/* close to -pi, and the other is very close to +pi */ \
EXPECT_TRUE(delta < tolerance || std::abs(aos::math::DiffAngle( \
delta, 2.0 * M_PI)) < tolerance); \
}
#define EXPECT_POSE_NEAR(pose1, pose2) \
{ \
for (size_t i = 0; i < 3; i++) { \
EXPECT_NEAR(pose1.p(i), pose2.p(i), kToleranceMeters); \
} \
auto rpy_1 = PoseUtils::QuaternionToEulerAngles(pose1.q); \
auto rpy_2 = PoseUtils::QuaternionToEulerAngles(pose2.q); \
for (size_t i = 0; i < 3; i++) { \
SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
rpy_1(i), i, rpy_2(i))); \
EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), kToleranceRadians) \
} \
}
#define EXPECT_POSE_EQ(pose1, pose2) \
EXPECT_EQ(pose1.p, pose2.p); \
EXPECT_EQ(pose1.q, pose2.q);
#define EXPECT_QUATERNION_NEAR(q1, q2) \
EXPECT_NEAR(q1.x(), q2.x(), kOrientationEqTolerance) << q1 << " != " << q2; \
EXPECT_NEAR(q1.y(), q2.y(), kOrientationEqTolerance) << q1 << " != " << q2; \
EXPECT_NEAR(q1.z(), q2.z(), kOrientationEqTolerance) << q1 << " != " << q2; \
EXPECT_NEAR(q1.w(), q2.w(), kOrientationEqTolerance) << q1 << " != " << q2;
// Expects same roll, pitch, and yaw values (not equivalent rotation)
#define EXPECT_RPY_EQ(rpy_1, rpy_2) \
{ \
for (size_t i = 0; i < 3; i++) { \
SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
rpy_1(i), i, rpy_2(i))); \
EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), \
kOrientationEqTolerance) \
} \
}
#define EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(roll, pitch, yaw) \
{ \
auto rpy = Eigen::Vector3d(roll, pitch, yaw); \
auto converted_rpy = PoseUtils::QuaternionToEulerAngles( \
PoseUtils::EulerAnglesToQuaternion(rpy)); \
EXPECT_RPY_EQ(converted_rpy, rpy); \
}
// Both confidence matrixes should have the same dimensions and be square
#define EXPECT_CONFIDENCE_GT(confidence1, confidence2) \
{ \
ASSERT_EQ(confidence1.rows(), confidence2.rows()); \
ASSERT_EQ(confidence1.rows(), confidence1.cols()); \
ASSERT_EQ(confidence2.rows(), confidence2.cols()); \
for (size_t i = 0; i < confidence1.rows(); i++) { \
EXPECT_GT(confidence1(i, i), confidence2(i, i)); \
} \
}
TEST(PoseUtilsTest, EulerAnglesAndQuaternionConversions) {
// Make sure that the conversions are consistent back and forth.
// These angles shouldn't get changed to a different, equivalent roll pitch
// yaw.
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, M_PI);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, -M_PI);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, M_PI_2);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, -M_PI_2);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, 0.0);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, M_PI_4, 0.0);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, -M_PI_4, 0.0);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, -M_PI_4, M_PI_4);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(M_PI_4, -M_PI_4, M_PI_4);
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(-M_PI_2, -M_PI_4, M_PI_4);
// Now, do a sweep of roll, pitch, and yaws in the normalized
// range.
// - roll: (-pi/2, pi/2)
// - pitch: (-pi/2, pi/2)
// - yaw: [-pi, pi)
constexpr double kThetaMaxRoll = M_PI_2 - kToleranceRadians;
constexpr double kThetaMaxPitch = M_PI_2 - kToleranceRadians;
constexpr double kThetaMaxYaw = M_PI;
constexpr double kDeltaTheta = M_PI / 16;
for (double roll = -kThetaMaxRoll; roll < kThetaMaxRoll;
roll += kDeltaTheta) {
for (double pitch = -kThetaMaxPitch; pitch < kThetaMaxPitch;
pitch += kDeltaTheta) {
for (double yaw = -kThetaMaxYaw; yaw < kThetaMaxYaw; yaw += kDeltaTheta) {
SCOPED_TRACE(
absl::StrFormat("roll: %f, pitch: %f, yaw: %f", roll, pitch, yaw));
EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(roll, pitch, yaw);
}
}
}
}
} // namespace frc971::vision