blob: bdf82582e9ca2b4ee46e28233776d319055ec9ff [file] [log] [blame]
Jim Ostrowskib974cca2024-01-28 15:07:50 -08001#include "frc971/vision/vision_util_lib.h"
2
Jim Ostrowski6d242d52024-04-03 20:39:03 -07003#include "absl/strings/str_format.h"
Jim Ostrowskib974cca2024-01-28 15:07:50 -08004#include "gtest/gtest.h"
5
Jim Ostrowski6d242d52024-04-03 20:39:03 -07006#include "aos/util/math.h"
7
Jim Ostrowskib974cca2024-01-28 15:07:50 -08008namespace frc971::vision {
9// For now, just testing extracting camera number from channel name
10TEST(VisionUtilsTest, CameraNumberFromChannel) {
11 ASSERT_EQ(CameraNumberFromChannel("/camera0").value(), 0);
12 ASSERT_EQ(CameraNumberFromChannel("/camera1").value(), 1);
13 ASSERT_EQ(CameraNumberFromChannel("/camera"), std::nullopt);
14 ASSERT_EQ(CameraNumberFromChannel("/orin1/camera0").value(), 0);
15 ASSERT_EQ(CameraNumberFromChannel("/orin1/camera1").value(), 1);
16 ASSERT_EQ(CameraNumberFromChannel("/orin1"), std::nullopt);
17}
Jim Ostrowski6d242d52024-04-03 20:39:03 -070018
19namespace {
20constexpr double kToleranceRadians = 0.05;
21// Conversions between euler angles and quaternion result in slightly-off
22// doubles
23constexpr double kOrientationEqTolerance = 1e-10;
24} // namespace
25
26// Angles normalized by aos::math::NormalizeAngle()
27#define EXPECT_NORMALIZED_ANGLES_NEAR(theta1, theta2, tolerance) \
28 { \
29 double delta = std::abs(aos::math::DiffAngle(theta1, theta2)); \
30 /* Have to check delta - 2pi for the case that one angle is very */ \
31 /* close to -pi, and the other is very close to +pi */ \
32 EXPECT_TRUE(delta < tolerance || std::abs(aos::math::DiffAngle( \
33 delta, 2.0 * M_PI)) < tolerance); \
34 }
35
36#define EXPECT_POSE_NEAR(pose1, pose2) \
37 { \
38 for (size_t i = 0; i < 3; i++) { \
39 EXPECT_NEAR(pose1.p(i), pose2.p(i), kToleranceMeters); \
40 } \
41 auto rpy_1 = PoseUtils::QuaternionToEulerAngles(pose1.q); \
42 auto rpy_2 = PoseUtils::QuaternionToEulerAngles(pose2.q); \
43 for (size_t i = 0; i < 3; i++) { \
44 SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
45 rpy_1(i), i, rpy_2(i))); \
46 EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), kToleranceRadians) \
47 } \
48 }
49
50#define EXPECT_POSE_EQ(pose1, pose2) \
51 EXPECT_EQ(pose1.p, pose2.p); \
52 EXPECT_EQ(pose1.q, pose2.q);
53
54#define EXPECT_QUATERNION_NEAR(q1, q2) \
55 EXPECT_NEAR(q1.x(), q2.x(), kOrientationEqTolerance) << q1 << " != " << q2; \
56 EXPECT_NEAR(q1.y(), q2.y(), kOrientationEqTolerance) << q1 << " != " << q2; \
57 EXPECT_NEAR(q1.z(), q2.z(), kOrientationEqTolerance) << q1 << " != " << q2; \
58 EXPECT_NEAR(q1.w(), q2.w(), kOrientationEqTolerance) << q1 << " != " << q2;
59
60// Expects same roll, pitch, and yaw values (not equivalent rotation)
61#define EXPECT_RPY_EQ(rpy_1, rpy_2) \
62 { \
63 for (size_t i = 0; i < 3; i++) { \
64 SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
65 rpy_1(i), i, rpy_2(i))); \
66 EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), \
67 kOrientationEqTolerance) \
68 } \
69 }
70
71#define EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(roll, pitch, yaw) \
72 { \
73 auto rpy = Eigen::Vector3d(roll, pitch, yaw); \
74 auto converted_rpy = PoseUtils::QuaternionToEulerAngles( \
75 PoseUtils::EulerAnglesToQuaternion(rpy)); \
76 EXPECT_RPY_EQ(converted_rpy, rpy); \
77 }
78
79// Both confidence matrixes should have the same dimensions and be square
80#define EXPECT_CONFIDENCE_GT(confidence1, confidence2) \
81 { \
82 ASSERT_EQ(confidence1.rows(), confidence2.rows()); \
83 ASSERT_EQ(confidence1.rows(), confidence1.cols()); \
84 ASSERT_EQ(confidence2.rows(), confidence2.cols()); \
85 for (size_t i = 0; i < confidence1.rows(); i++) { \
86 EXPECT_GT(confidence1(i, i), confidence2(i, i)); \
87 } \
88 }
89
90TEST(PoseUtilsTest, EulerAnglesAndQuaternionConversions) {
91 // Make sure that the conversions are consistent back and forth.
92 // These angles shouldn't get changed to a different, equivalent roll pitch
93 // yaw.
94 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, M_PI);
95 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, -M_PI);
96 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, M_PI_2);
97 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, -M_PI_2);
98 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, 0.0, 0.0);
99 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, M_PI_4, 0.0);
100 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, -M_PI_4, 0.0);
101 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(0.0, -M_PI_4, M_PI_4);
102 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(M_PI_4, -M_PI_4, M_PI_4);
103 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(-M_PI_2, -M_PI_4, M_PI_4);
104
105 // Now, do a sweep of roll, pitch, and yaws in the normalized
106 // range.
107 // - roll: (-pi/2, pi/2)
108 // - pitch: (-pi/2, pi/2)
109 // - yaw: [-pi, pi)
110 constexpr double kThetaMaxRoll = M_PI_2 - kToleranceRadians;
111 constexpr double kThetaMaxPitch = M_PI_2 - kToleranceRadians;
112 constexpr double kThetaMaxYaw = M_PI;
113 constexpr double kDeltaTheta = M_PI / 16;
114
115 for (double roll = -kThetaMaxRoll; roll < kThetaMaxRoll;
116 roll += kDeltaTheta) {
117 for (double pitch = -kThetaMaxPitch; pitch < kThetaMaxPitch;
118 pitch += kDeltaTheta) {
119 for (double yaw = -kThetaMaxYaw; yaw < kThetaMaxYaw; yaw += kDeltaTheta) {
120 SCOPED_TRACE(
121 absl::StrFormat("roll: %f, pitch: %f, yaw: %f", roll, pitch, yaw));
122 EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(roll, pitch, yaw);
123 }
124 }
125 }
126}
127
Jim Ostrowskib974cca2024-01-28 15:07:50 -0800128} // namespace frc971::vision