blob: 62f969c5d24d3a578e68a0d30720c58ce03f65ef [file] [log] [blame]
Milind Upadhyay7c205222022-11-16 18:20:58 -08001#include "frc971/vision/target_mapper.h"
2
3#include <random>
4
Austin Schuh99f7c6a2024-06-25 22:07:44 -07005#include "absl/flags/declare.h"
6#include "absl/flags/flag.h"
7#include "absl/log/check.h"
8#include "absl/log/log.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07009#include "gtest/gtest.h"
10
Milind Upadhyay7c205222022-11-16 18:20:58 -080011#include "aos/events/simulated_event_loop.h"
Milind Upadhyayc5beba12022-12-17 17:41:20 -080012#include "aos/testing/path.h"
Milind Upadhyay7c205222022-11-16 18:20:58 -080013#include "aos/testing/random_seed.h"
Milind Upadhyayc5beba12022-12-17 17:41:20 -080014#include "aos/util/math.h"
Jim Ostrowski6d242d52024-04-03 20:39:03 -070015#include "vision_util_lib.h"
Milind Upadhyay7c205222022-11-16 18:20:58 -080016
Austin Schuh99f7c6a2024-06-25 22:07:44 -070017ABSL_DECLARE_FLAG(int32_t, min_target_id);
18ABSL_DECLARE_FLAG(int32_t, max_target_id);
milind-u8f4e43e2023-04-09 17:11:19 -070019
Milind Upadhyay7c205222022-11-16 18:20:58 -080020namespace frc971::vision {
Jim Ostrowski6d242d52024-04-03 20:39:03 -070021using frc971::vision::PoseUtils;
Milind Upadhyay7c205222022-11-16 18:20:58 -080022
23namespace {
24constexpr double kToleranceMeters = 0.05;
25constexpr double kToleranceRadians = 0.05;
Milind Upadhyayc5beba12022-12-17 17:41:20 -080026// Conversions between euler angles and quaternion result in slightly-off
27// doubles
28constexpr double kOrientationEqTolerance = 1e-10;
29constexpr std::chrono::milliseconds kMaxDt = std::chrono::milliseconds(3);
Milind Upadhyay05652cb2022-12-07 20:51:51 -080030constexpr std::string_view kFieldName = "test";
Milind Upadhyay7c205222022-11-16 18:20:58 -080031} // namespace
32
Milind Upadhyayc5beba12022-12-17 17:41:20 -080033// Angles normalized by aos::math::NormalizeAngle()
34#define EXPECT_NORMALIZED_ANGLES_NEAR(theta1, theta2, tolerance) \
35 { \
36 double delta = std::abs(aos::math::DiffAngle(theta1, theta2)); \
37 /* Have to check delta - 2pi for the case that one angle is very */ \
38 /* close to -pi, and the other is very close to +pi */ \
39 EXPECT_TRUE(delta < tolerance || std::abs(aos::math::DiffAngle( \
40 delta, 2.0 * M_PI)) < tolerance); \
Milind Upadhyay7c205222022-11-16 18:20:58 -080041 }
42
Milind Upadhyayc5beba12022-12-17 17:41:20 -080043#define EXPECT_POSE_NEAR(pose1, pose2) \
44 { \
45 for (size_t i = 0; i < 3; i++) { \
46 EXPECT_NEAR(pose1.p(i), pose2.p(i), kToleranceMeters); \
47 } \
48 auto rpy_1 = PoseUtils::QuaternionToEulerAngles(pose1.q); \
49 auto rpy_2 = PoseUtils::QuaternionToEulerAngles(pose2.q); \
50 for (size_t i = 0; i < 3; i++) { \
51 SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
52 rpy_1(i), i, rpy_2(i))); \
53 EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), kToleranceRadians) \
54 } \
55 }
Milind Upadhyay7c205222022-11-16 18:20:58 -080056
Milind Upadhyayc5beba12022-12-17 17:41:20 -080057#define EXPECT_POSE_EQ(pose1, pose2) \
58 EXPECT_EQ(pose1.p, pose2.p); \
59 EXPECT_EQ(pose1.q, pose2.q);
Milind Upadhyay7c205222022-11-16 18:20:58 -080060
Milind Upadhyayc5beba12022-12-17 17:41:20 -080061#define EXPECT_QUATERNION_NEAR(q1, q2) \
62 EXPECT_NEAR(q1.x(), q2.x(), kOrientationEqTolerance) << q1 << " != " << q2; \
63 EXPECT_NEAR(q1.y(), q2.y(), kOrientationEqTolerance) << q1 << " != " << q2; \
64 EXPECT_NEAR(q1.z(), q2.z(), kOrientationEqTolerance) << q1 << " != " << q2; \
65 EXPECT_NEAR(q1.w(), q2.w(), kOrientationEqTolerance) << q1 << " != " << q2;
66
67// Expects same roll, pitch, and yaw values (not equivalent rotation)
68#define EXPECT_RPY_EQ(rpy_1, rpy_2) \
69 { \
70 for (size_t i = 0; i < 3; i++) { \
71 SCOPED_TRACE(absl::StrFormat("rpy_1(%d) = %f, rpy_2(%d) = %f", i, \
72 rpy_1(i), i, rpy_2(i))); \
73 EXPECT_NORMALIZED_ANGLES_NEAR(rpy_1(i), rpy_2(i), \
74 kOrientationEqTolerance) \
75 } \
76 }
77
78#define EXPECT_EULER_ANGLES_QUATERNION_BACK_AND_FORTH_EQ(roll, pitch, yaw) \
79 { \
80 auto rpy = Eigen::Vector3d(roll, pitch, yaw); \
81 auto converted_rpy = PoseUtils::QuaternionToEulerAngles( \
82 PoseUtils::EulerAnglesToQuaternion(rpy)); \
83 EXPECT_RPY_EQ(converted_rpy, rpy); \
84 }
Milind Upadhyay7c205222022-11-16 18:20:58 -080085
86// Both confidence matrixes should have the same dimensions and be square
87#define EXPECT_CONFIDENCE_GT(confidence1, confidence2) \
88 { \
89 ASSERT_EQ(confidence1.rows(), confidence2.rows()); \
90 ASSERT_EQ(confidence1.rows(), confidence1.cols()); \
91 ASSERT_EQ(confidence2.rows(), confidence2.cols()); \
92 for (size_t i = 0; i < confidence1.rows(); i++) { \
93 EXPECT_GT(confidence1(i, i), confidence2(i, i)); \
94 } \
95 }
96
97namespace {
Milind Upadhyayc5beba12022-12-17 17:41:20 -080098ceres::examples::Pose3d Make2dPose(double x, double y, double yaw_radians) {
99 return ceres::examples::Pose3d{Eigen::Vector3d(x, y, 0.0),
100 PoseUtils::EulerAnglesToQuaternion(
101 Eigen::Vector3d(0.0, 0.0, yaw_radians))};
Milind Upadhyay7c205222022-11-16 18:20:58 -0800102}
103
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800104// Assumes camera and robot origin are the same
105DataAdapter::TimestampedDetection MakeTimestampedDetection(
106 aos::distributed_clock::time_point time, Eigen::Affine3d H_robot_target,
milind-ud62f80a2023-03-04 16:37:09 -0800107 TargetMapper::TargetId id, double distortion_factor = 0.001) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800108 auto target_pose = PoseUtils::Affine3dToPose3d(H_robot_target);
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800109 return DataAdapter::TimestampedDetection{
milind-ud62f80a2023-03-04 16:37:09 -0800110 .time = time,
111 .H_robot_target = H_robot_target,
112 .distance_from_camera = target_pose.p.norm(),
113 .distortion_factor = distortion_factor,
114 .id = id};
115}
116
117DataAdapter::TimestampedDetection MakeTimestampedDetectionForConfidence(
118 aos::distributed_clock::time_point time, TargetMapper::TargetId id,
119 double distance_from_camera, double distortion_factor) {
120 auto target_pose = ceres::examples::Pose3d{
121 .p = Eigen::Vector3d(distance_from_camera, 0.0, 0.0),
122 .q = Eigen::Quaterniond(1.0, 0.0, 0.0, 0.0)};
123 return DataAdapter::TimestampedDetection{
124 .time = time,
125 .H_robot_target = PoseUtils::Pose3dToAffine3d(target_pose),
126 .distance_from_camera = target_pose.p.norm(),
127 .distortion_factor = distortion_factor,
128 .id = id};
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800129}
130
Milind Upadhyay7c205222022-11-16 18:20:58 -0800131bool TargetIsInView(TargetMapper::TargetPose target_detection) {
132 // And check if it is within the fov of the robot /
133 // camera, assuming camera is pointing in the
134 // positive x-direction of the robot
135 double angle_to_target =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800136 atan2(target_detection.pose.p(1), target_detection.pose.p(0));
Milind Upadhyay7c205222022-11-16 18:20:58 -0800137
138 // Simulated camera field of view, in radians
139 constexpr double kCameraFov = M_PI_2;
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800140 if (std::abs(angle_to_target) <= kCameraFov / 2.0) {
141 VLOG(2) << "Found target in view, based on T = "
142 << target_detection.pose.p(0) << ", " << target_detection.pose.p(1)
143 << " with angle " << angle_to_target;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800144 return true;
145 } else {
146 return false;
147 }
148}
149
150aos::distributed_clock::time_point TimeInMs(size_t ms) {
151 return aos::distributed_clock::time_point(std::chrono::milliseconds(ms));
152}
153
154} // namespace
155
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800156TEST(DataAdapterTest, ComputeConfidence) {
157 // Check the confidence matrices. Don't check the actual values
158 // in case the constants change, just check that the confidence of contraints
159 // decreases as time period or distances from camera increase.
milind-ud62f80a2023-03-04 16:37:09 -0800160
161 constexpr size_t kIdStart = 0;
162 constexpr size_t kIdEnd = 1;
163
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800164 {
165 // Vary time period
166 constexpr double kDistanceStart = 0.5;
167 constexpr double kDistanceEnd = 2.0;
milind-ud62f80a2023-03-04 16:37:09 -0800168 constexpr double kDistortionFactor = 0.001;
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800169
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800170 TargetMapper::ConfidenceMatrix last_confidence =
171 TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800172 for (size_t dt = 0; dt < 15; dt++) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800173 TargetMapper::ConfidenceMatrix confidence =
milind-ud62f80a2023-03-04 16:37:09 -0800174 DataAdapter::ComputeConfidence(
175 MakeTimestampedDetectionForConfidence(
176 TimeInMs(0), kIdStart, kDistanceStart, kDistortionFactor),
177 MakeTimestampedDetectionForConfidence(
178 TimeInMs(dt), kIdEnd, kDistanceEnd, kDistortionFactor));
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800179
180 if (dt != 0) {
181 // Confidence only decreases every 5ms (control loop period)
182 if (dt % 5 == 0) {
183 EXPECT_CONFIDENCE_GT(last_confidence, confidence);
184 } else {
185 EXPECT_EQ(last_confidence, confidence);
186 }
187 }
188 last_confidence = confidence;
189 }
190 }
191
192 {
193 // Vary distance at start
194 constexpr int kDt = 3;
195 constexpr double kDistanceEnd = 1.5;
milind-ud62f80a2023-03-04 16:37:09 -0800196 constexpr double kDistortionFactor = 0.001;
197
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800198 TargetMapper::ConfidenceMatrix last_confidence =
199 TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800200 for (double distance_start = 0.0; distance_start < 3.0;
201 distance_start += 0.5) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800202 TargetMapper::ConfidenceMatrix confidence =
milind-ud62f80a2023-03-04 16:37:09 -0800203 DataAdapter::ComputeConfidence(
204 MakeTimestampedDetectionForConfidence(
205 TimeInMs(0), kIdStart, distance_start, kDistortionFactor),
206 MakeTimestampedDetectionForConfidence(
207 TimeInMs(kDt), kIdEnd, kDistanceEnd, kDistortionFactor));
208
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800209 if (distance_start != 0.0) {
210 EXPECT_CONFIDENCE_GT(last_confidence, confidence);
211 }
212 last_confidence = confidence;
213 }
214 }
215
216 {
217 // Vary distance at end
218 constexpr int kDt = 2;
219 constexpr double kDistanceStart = 2.5;
milind-ud62f80a2023-03-04 16:37:09 -0800220 constexpr double kDistortionFactor = 0.001;
221
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800222 TargetMapper::ConfidenceMatrix last_confidence =
223 TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800224 for (double distance_end = 0.0; distance_end < 3.0; distance_end += 0.5) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800225 TargetMapper::ConfidenceMatrix confidence =
milind-ud62f80a2023-03-04 16:37:09 -0800226 DataAdapter::ComputeConfidence(
227 MakeTimestampedDetectionForConfidence(
228 TimeInMs(0), kIdStart, kDistanceStart, kDistortionFactor),
229 MakeTimestampedDetectionForConfidence(
230 TimeInMs(kDt), kIdEnd, distance_end, kDistortionFactor));
231
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800232 if (distance_end != 0.0) {
233 EXPECT_CONFIDENCE_GT(last_confidence, confidence);
234 }
235 last_confidence = confidence;
236 }
237 }
milind-ud62f80a2023-03-04 16:37:09 -0800238
239 {
240 // Vary distortion factor
241 constexpr int kDt = 2;
242 constexpr double kDistanceStart = 2.5;
243 constexpr double kDistanceEnd = 1.5;
244
245 TargetMapper::ConfidenceMatrix last_confidence =
246 TargetMapper::ConfidenceMatrix::Zero();
247 for (double distortion_factor = 0.0; distortion_factor <= 1.0;
248 distortion_factor += 0.01) {
249 TargetMapper::ConfidenceMatrix confidence =
250 DataAdapter::ComputeConfidence(
251 MakeTimestampedDetectionForConfidence(
252 TimeInMs(0), kIdStart, kDistanceStart, distortion_factor),
253 MakeTimestampedDetectionForConfidence(
254 TimeInMs(kDt), kIdEnd, kDistanceEnd, distortion_factor));
255
256 if (distortion_factor != 0.0) {
257 EXPECT_CONFIDENCE_GT(last_confidence, confidence);
258 }
259 last_confidence = confidence;
260 }
261 }
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800262}
263
Milind Upadhyayec493912022-12-18 21:33:15 -0800264TEST(DataAdapterTest, MatchTargetDetections) {
Milind Upadhyayec493912022-12-18 21:33:15 -0800265 std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800266 {MakeTimestampedDetection(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800267 TimeInMs(5), PoseUtils::Pose3dToAffine3d(Make2dPose(5.0, -5.0, 0.0)),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800268 2),
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800269 MakeTimestampedDetection(
270 TimeInMs(6),
271 PoseUtils::Pose3dToAffine3d(Make2dPose(5.0, -4.0, M_PI)), 0),
272 MakeTimestampedDetection(
273 TimeInMs(10),
274 PoseUtils::Pose3dToAffine3d(Make2dPose(3.0, -3.0, M_PI)), 1),
275 MakeTimestampedDetection(
276 TimeInMs(13),
277 PoseUtils::Pose3dToAffine3d(Make2dPose(4.0, -7.0, M_PI_2)), 2),
278 MakeTimestampedDetection(
279 TimeInMs(14),
280 PoseUtils::Pose3dToAffine3d(Make2dPose(4.0, -4.0, M_PI_2)), 2)};
Milind Upadhyayec493912022-12-18 21:33:15 -0800281
Milind Upadhyayec493912022-12-18 21:33:15 -0800282 auto target_constraints =
283 DataAdapter::MatchTargetDetections(timestamped_target_detections, kMaxDt);
284
285 // The constraint between the detection at 6ms and the one at 10 ms is skipped
286 // because dt > kMaxDt.
287 // Also, the constraint between the last two detections is skipped because
288 // they are the same target
289 EXPECT_EQ(target_constraints.size(),
290 timestamped_target_detections.size() - 3);
291
292 // Between 5ms and 6ms detections
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800293 EXPECT_DOUBLE_EQ(target_constraints[0].t_be.p(0), 0.0);
294 EXPECT_DOUBLE_EQ(target_constraints[0].t_be.p(1), 1.0);
295 EXPECT_QUATERNION_NEAR(
296 target_constraints[0].t_be.q,
297 PoseUtils::EulerAnglesToQuaternion(Eigen::Vector3d(0.0, 0.0, M_PI)));
Milind Upadhyayec493912022-12-18 21:33:15 -0800298 EXPECT_EQ(target_constraints[0].id_begin, 2);
299 EXPECT_EQ(target_constraints[0].id_end, 0);
300
301 // Between 10ms and 13ms detections
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800302 EXPECT_DOUBLE_EQ(target_constraints[1].t_be.p(0), -1.0);
303 EXPECT_DOUBLE_EQ(target_constraints[1].t_be.p(1), 4.0);
304 EXPECT_QUATERNION_NEAR(
305 target_constraints[1].t_be.q,
306 PoseUtils::EulerAnglesToQuaternion(Eigen::Vector3d(0.0, 0.0, -M_PI_2)));
Milind Upadhyayec493912022-12-18 21:33:15 -0800307 EXPECT_EQ(target_constraints[1].id_begin, 1);
308 EXPECT_EQ(target_constraints[1].id_end, 2);
309}
310
Milind Upadhyay7c205222022-11-16 18:20:58 -0800311TEST(TargetMapperTest, TwoTargetsOneConstraint) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700312 absl::SetFlag(&FLAGS_min_target_id, 0);
313 absl::SetFlag(&FLAGS_max_target_id, 1);
milind-u8f4e43e2023-04-09 17:11:19 -0700314
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800315 ceres::examples::MapOfPoses target_poses;
316 target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
317 target_poses[1] = Make2dPose(-5.0, 0.0, 0.0);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800318
Milind Upadhyay7c205222022-11-16 18:20:58 -0800319 std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800320 {MakeTimestampedDetection(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800321 TimeInMs(5), PoseUtils::Pose3dToAffine3d(Make2dPose(3.0, 0.0, M_PI)),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800322 0),
323 MakeTimestampedDetection(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800324 TimeInMs(6), PoseUtils::Pose3dToAffine3d(Make2dPose(-7.0, 0.0, 0.0)),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800325 1)};
Milind Upadhyay7c205222022-11-16 18:20:58 -0800326 auto target_constraints =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800327 DataAdapter::MatchTargetDetections(timestamped_target_detections, kMaxDt);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800328
329 frc971::vision::TargetMapper mapper(target_poses, target_constraints);
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800330 mapper.Solve(kFieldName);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800331
332 ASSERT_EQ(mapper.target_poses().size(), 2);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800333 EXPECT_POSE_NEAR(mapper.target_poses()[0], Make2dPose(5.0, 0.0, M_PI));
334 EXPECT_POSE_NEAR(mapper.target_poses()[1], Make2dPose(-5.0, 0.0, 0.0));
Milind Upadhyay7c205222022-11-16 18:20:58 -0800335}
336
337TEST(TargetMapperTest, TwoTargetsTwoConstraints) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700338 absl::SetFlag(&FLAGS_min_target_id, 0);
339 absl::SetFlag(&FLAGS_max_target_id, 1);
milind-u8f4e43e2023-04-09 17:11:19 -0700340
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800341 ceres::examples::MapOfPoses target_poses;
342 target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
343 target_poses[1] = Make2dPose(-5.0, 0.0, -M_PI_2);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800344
Milind Upadhyay7c205222022-11-16 18:20:58 -0800345 std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800346 {MakeTimestampedDetection(
347 TimeInMs(5),
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800348 PoseUtils::Pose3dToAffine3d(Make2dPose(6.0, 0.0, M_PI_2)), 0),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800349 MakeTimestampedDetection(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800350 TimeInMs(7),
351 PoseUtils::Pose3dToAffine3d(Make2dPose(6.0, 10.0, -M_PI)), 1),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800352 MakeTimestampedDetection(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800353 TimeInMs(12),
354 PoseUtils::Pose3dToAffine3d(Make2dPose(1.0, 0.0, M_PI)), 0),
355 MakeTimestampedDetection(
356 TimeInMs(13),
357 PoseUtils::Pose3dToAffine3d(Make2dPose(-9.0, 0.0, -M_PI_2)), 1)};
Milind Upadhyay7c205222022-11-16 18:20:58 -0800358 auto target_constraints =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800359 DataAdapter::MatchTargetDetections(timestamped_target_detections, kMaxDt);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800360
361 frc971::vision::TargetMapper mapper(target_poses, target_constraints);
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800362 mapper.Solve(kFieldName);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800363
364 ASSERT_EQ(mapper.target_poses().size(), 2);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800365 EXPECT_POSE_NEAR(mapper.target_poses()[0], Make2dPose(5.0, 0.0, M_PI));
366 EXPECT_POSE_NEAR(mapper.target_poses()[1], Make2dPose(-5.0, 0.0, -M_PI_2));
Milind Upadhyay7c205222022-11-16 18:20:58 -0800367}
368
369TEST(TargetMapperTest, TwoTargetsOneNoisyConstraint) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700370 absl::SetFlag(&FLAGS_min_target_id, 0);
371 absl::SetFlag(&FLAGS_max_target_id, 1);
milind-u8f4e43e2023-04-09 17:11:19 -0700372
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800373 ceres::examples::MapOfPoses target_poses;
374 target_poses[0] = Make2dPose(5.0, 0.0, M_PI);
375 target_poses[1] = Make2dPose(-5.0, 0.0, 0.0);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800376
Milind Upadhyay7c205222022-11-16 18:20:58 -0800377 std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800378 {MakeTimestampedDetection(
379 TimeInMs(5),
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800380 PoseUtils::Pose3dToAffine3d(Make2dPose(3.01, 0.001, M_PI - 0.001)),
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800381 0),
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800382 MakeTimestampedDetection(
383 TimeInMs(7),
384 PoseUtils::Pose3dToAffine3d(Make2dPose(-7.01, 0.0, 0.0)), 1)};
Milind Upadhyay7c205222022-11-16 18:20:58 -0800385
386 auto target_constraints =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800387 DataAdapter::MatchTargetDetections(timestamped_target_detections, kMaxDt);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800388
389 frc971::vision::TargetMapper mapper(target_poses, target_constraints);
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800390 mapper.Solve(kFieldName);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800391
392 ASSERT_EQ(mapper.target_poses().size(), 2);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800393 EXPECT_POSE_NEAR(mapper.target_poses()[0], Make2dPose(5.0, 0.0, M_PI));
394 EXPECT_POSE_NEAR(mapper.target_poses()[1], Make2dPose(-5.0, 0.0, 0.0));
Milind Upadhyay7c205222022-11-16 18:20:58 -0800395}
396
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800397class ChargedUpTargetMapperTest : public ::testing::Test {
398 public:
399 ChargedUpTargetMapperTest() : generator_(aos::testing::RandomSeed()) {}
400
401 // Generates noisy target detection if target in camera FOV
402 std::optional<DataAdapter::TimestampedDetection> MaybeGenerateNoisyDetection(
403 const ceres::examples::Pose3d &robot_pose,
404 const TargetMapper::TargetPose &target_pose, size_t t,
405 bool force_in_view = false) {
406 TargetMapper::TargetPose target_detection = {
407 .id = target_pose.id,
408 .pose = PoseUtils::ComputeRelativePose(robot_pose, target_pose.pose)};
409 if (force_in_view || TargetIsInView(target_detection)) {
410 // Define random generator with Gaussian
411 // distribution
412 constexpr double kMean = 0.0;
413 constexpr double kStdDev = 1.0;
414 // Can play with this to see how it impacts
415 // randomness
416 constexpr double kNoiseScalePosition = 0.01;
417 constexpr double kNoiseScaleOrientation = 0.0005;
418 std::normal_distribution<double> dist(kMean, kStdDev);
419
420 target_detection.pose.p(0) += dist(generator_) * kNoiseScalePosition;
421 target_detection.pose.p(1) += dist(generator_) * kNoiseScalePosition;
422 target_detection.pose.p(2) += dist(generator_) * kNoiseScalePosition;
423
424 target_detection.pose.q.w() += dist(generator_) * kNoiseScaleOrientation;
425 target_detection.pose.q.x() += dist(generator_) * kNoiseScaleOrientation;
426 target_detection.pose.q.y() += dist(generator_) * kNoiseScaleOrientation;
427 target_detection.pose.q.z() += dist(generator_) * kNoiseScaleOrientation;
428
milind-ud62f80a2023-03-04 16:37:09 -0800429 // Get most distortion factors close to zero, but have a few outliers
430 const std::vector<double> kDistortionFactorIntervals = {0.0, 0.01, 1.0};
431 const std::vector<double> kDistortionFactorWeights = {0.9, 0.1};
432 std::piecewise_constant_distribution<double> distortion_factor_dist(
433 kDistortionFactorIntervals.begin(), kDistortionFactorIntervals.end(),
434 kDistortionFactorWeights.begin());
435 double distortion_factor = distortion_factor_dist(generator_);
436
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800437 auto time_point = TimeInMs(t);
438 return MakeTimestampedDetection(
439 time_point, PoseUtils::Pose3dToAffine3d(target_detection.pose),
milind-ud62f80a2023-03-04 16:37:09 -0800440 target_detection.id, distortion_factor);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800441 }
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800442
443 return std::nullopt;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800444 }
445
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800446 private:
447 std::default_random_engine generator_;
448};
449
450// Drive in a circle around the 2023 field, and add a bit of randomness to 3d
milind-u401de982023-04-14 17:32:03 -0700451// pose detections.
452// TODO(milind): use valgrind to debug why this test passes, but then segfaults
453// when freeing memory. For some reason this segfault occurs only in this test,
454// but when running the test with gdb it doesn't occur...
455TEST_F(ChargedUpTargetMapperTest, DISABLED_FieldCircleMotion) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700456 absl::SetFlag(&FLAGS_min_target_id, 1);
457 absl::SetFlag(&FLAGS_max_target_id, 8);
milind-u401de982023-04-14 17:32:03 -0700458
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800459 // Read target map
460 auto target_map_fbs = aos::JsonFileToFlatbuffer<TargetMap>(
461 aos::testing::ArtifactPath("frc971/vision/target_map.json"));
462
463 std::vector<TargetMapper::TargetPose> actual_target_poses;
464 ceres::examples::MapOfPoses target_poses;
milind-u3f5f83c2023-01-29 15:23:51 -0800465 for (const auto *target_pose_fbs : *target_map_fbs.message().target_poses()) {
466 const TargetMapper::TargetPose target_pose =
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700467 TargetMapper::TargetPoseFromFbs(*target_pose_fbs);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800468 actual_target_poses.emplace_back(target_pose);
469 target_poses[target_pose.id] = target_pose.pose;
470 }
471
472 double kFieldHalfLength = 16.54 / 2.0; // half length of the field
473 double kFieldHalfWidth = 8.02 / 2.0; // half width of the field
474
Milind Upadhyay7c205222022-11-16 18:20:58 -0800475 // Now, create a bunch of robot poses and target
476 // observations
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800477 constexpr size_t kDt = 5;
478 constexpr double kRobotZ = 1.0;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800479
Milind Upadhyay7c205222022-11-16 18:20:58 -0800480 std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections;
481
482 constexpr size_t kTotalSteps = 100;
483 for (size_t step_count = 0; step_count < kTotalSteps; step_count++) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800484 size_t t = kDt * step_count;
485
Milind Upadhyay7c205222022-11-16 18:20:58 -0800486 // Circle clockwise around the center of the field
487 double robot_theta = t;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800488
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800489 constexpr double kRadiusScalar = 0.5;
490 double robot_x = (kRadiusScalar * kFieldHalfLength) * cos(robot_theta);
491 double robot_y = (kRadiusScalar * -kFieldHalfWidth) * sin(robot_theta);
492
493 auto robot_pose =
494 ceres::examples::Pose3d{.p = Eigen::Vector3d(robot_x, robot_y, kRobotZ),
495 .q = PoseUtils::EulerAnglesToQuaternion(
496 Eigen::Vector3d(robot_theta, 0.0, 0.0))};
497
Milind Upadhyay7c205222022-11-16 18:20:58 -0800498 for (TargetMapper::TargetPose target_pose : actual_target_poses) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800499 auto optional_detection =
500 MaybeGenerateNoisyDetection(robot_pose, target_pose, t);
501 if (optional_detection.has_value()) {
502 timestamped_target_detections.emplace_back(*optional_detection);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800503 }
504 }
505 }
506
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800507 // The above circular motion only captures targets 1-4, so add another
508 // detection with the camera looking at targets 5-8, and 4 at the same time to
509 // have a connection to the rest of the targets
Milind Upadhyay7c205222022-11-16 18:20:58 -0800510 {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800511 auto last_robot_pose =
512 ceres::examples::Pose3d{.p = Eigen::Vector3d(0.0, 0.0, kRobotZ),
513 .q = PoseUtils::EulerAnglesToQuaternion(
514 Eigen::Vector3d(0.0, 0.0, M_PI))};
515 for (size_t id = 4; id <= 8; id++) {
516 auto target_pose =
517 TargetMapper::GetTargetPoseById(actual_target_poses, id).value();
518 auto optional_detection = MaybeGenerateNoisyDetection(
519 last_robot_pose, target_pose, kTotalSteps * kDt, true);
520
521 ASSERT_TRUE(optional_detection.has_value())
522 << "Didn't detect target " << target_pose.id;
523 timestamped_target_detections.emplace_back(*optional_detection);
524 }
Milind Upadhyay7c205222022-11-16 18:20:58 -0800525 }
526
527 auto target_constraints =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800528 DataAdapter::MatchTargetDetections(timestamped_target_detections, kMaxDt);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800529 frc971::vision::TargetMapper mapper(target_poses, target_constraints);
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800530 mapper.Solve(kFieldName);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800531
532 for (auto [target_pose_id, mapper_target_pose] : mapper.target_poses()) {
533 TargetMapper::TargetPose actual_target_pose =
534 TargetMapper::GetTargetPoseById(actual_target_poses, target_pose_id)
535 .value();
536 EXPECT_POSE_NEAR(mapper_target_pose, actual_target_pose.pose);
537 }
Milind Upadhyay7c205222022-11-16 18:20:58 -0800538}
539
540} // namespace frc971::vision