blob: a2fc20095db95aabb42d7eda41cfe9ae1f9bae01 [file] [log] [blame]
James Kuszmaul313e9ce2024-02-11 17:47:33 -08001#include "y2024/localizer/localizer.h"
2
3#include "gtest/gtest.h"
4
5#include "aos/events/logging/log_writer.h"
6#include "aos/events/simulated_event_loop.h"
7#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
8#include "frc971/control_loops/drivetrain/localizer_generated.h"
James Kuszmaulf6aa0382024-03-01 19:46:05 -08009#include "frc971/control_loops/drivetrain/rio_localizer_inputs_static.h"
James Kuszmaul313e9ce2024-02-11 17:47:33 -080010#include "frc971/control_loops/pose.h"
11#include "frc971/vision/target_map_generated.h"
12#include "frc971/vision/target_map_utils.h"
13#include "y2024/constants/simulated_constants_sender.h"
14#include "y2024/control_loops/drivetrain/drivetrain_base.h"
15#include "y2024/localizer/status_generated.h"
16
17DEFINE_string(output_folder, "",
18 "If set, logs all channels to the provided logfile.");
19DECLARE_double(max_distance_to_target);
20
21namespace y2024::localizer::testing {
22
23using frc971::control_loops::drivetrain::Output;
24
25class LocalizerTest : public ::testing::Test {
26 protected:
27 static constexpr uint64_t kTargetId = 1;
28 LocalizerTest()
29 : configuration_(aos::configuration::ReadConfig("y2024/aos_config.json")),
30 event_loop_factory_(&configuration_.message()),
31 roborio_node_([this]() {
32 // Get the constants sent before anything else happens.
33 // It has nothing to do with the roborio node.
34 SendSimulationConstants(&event_loop_factory_, 7971,
35 "y2024/constants/test_constants.json");
36 return aos::configuration::GetNode(&configuration_.message(),
37 "roborio");
38 }()),
39 imu_node_(
40 aos::configuration::GetNode(&configuration_.message(), "imu")),
41 camera_node_(
42 aos::configuration::GetNode(&configuration_.message(), "orin1")),
43 roborio_test_event_loop_(
44 event_loop_factory_.MakeEventLoop("test", roborio_node_)),
45 dt_config_(y2024::control_loops::drivetrain::GetDrivetrainConfig(
46 roborio_test_event_loop_.get())),
47 localizer_event_loop_(
48 event_loop_factory_.MakeEventLoop("localizer", imu_node_)),
49 localizer_(localizer_event_loop_.get()),
50 drivetrain_plant_event_loop_(event_loop_factory_.MakeEventLoop(
51 "drivetrain_plant", roborio_node_)),
52 drivetrain_plant_imu_event_loop_(
53 event_loop_factory_.MakeEventLoop("drivetrain_plant", imu_node_)),
54 drivetrain_plant_(drivetrain_plant_event_loop_.get(),
55 drivetrain_plant_imu_event_loop_.get(), dt_config_,
56 std::chrono::microseconds(1000)),
57 imu_test_event_loop_(
58 event_loop_factory_.MakeEventLoop("test", imu_node_)),
59 camera_test_event_loop_(
60 event_loop_factory_.MakeEventLoop("test", camera_node_)),
61 constants_fetcher_(imu_test_event_loop_.get()),
62 output_sender_(
63 roborio_test_event_loop_->MakeSender<Output>("/drivetrain")),
James Kuszmaulf6aa0382024-03-01 19:46:05 -080064 combined_sender_(
65 roborio_test_event_loop_->MakeSender<
66 frc971::control_loops::drivetrain::RioLocalizerInputsStatic>(
67 "/drivetrain")),
James Kuszmaul313e9ce2024-02-11 17:47:33 -080068 target_sender_(
69 camera_test_event_loop_->MakeSender<frc971::vision::TargetMap>(
70 "/camera0")),
71 control_sender_(roborio_test_event_loop_->MakeSender<
72 frc971::control_loops::drivetrain::LocalizerControl>(
73 "/drivetrain")),
74 output_fetcher_(
75 roborio_test_event_loop_
76 ->MakeFetcher<frc971::controls::LocalizerOutput>("/localizer")),
77 status_fetcher_(
78 imu_test_event_loop_->MakeFetcher<Status>("/localizer")) {
79 FLAGS_max_distance_to_target = 100.0;
80 {
81 aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() {
82 {
83 auto builder = output_sender_.MakeBuilder();
84 auto output_builder = builder.MakeBuilder<Output>();
85 output_builder.add_left_voltage(output_voltages_(0));
86 output_builder.add_right_voltage(output_voltages_(1));
87 builder.CheckOk(builder.Send(output_builder.Finish()));
88 }
James Kuszmaulf6aa0382024-03-01 19:46:05 -080089 {
90 auto builder = combined_sender_.MakeStaticBuilder();
91 builder->set_left_voltage(output_voltages_(0));
92 builder->set_right_voltage(output_voltages_(1));
93 builder->set_left_encoder(drivetrain_plant_.GetLeftPosition());
94 builder->set_right_encoder(drivetrain_plant_.GetRightPosition());
95 builder.CheckOk(builder.Send());
96 }
James Kuszmaul313e9ce2024-02-11 17:47:33 -080097 });
98 roborio_test_event_loop_->OnRun([timer, this]() {
99 timer->Schedule(roborio_test_event_loop_->monotonic_now(),
100 std::chrono::milliseconds(5));
101 });
102 }
103 {
104 // Sanity check that the test calibration files look like what we
105 // expect.
106 CHECK_EQ("orin1", constants_fetcher_.constants()
107 .cameras()
108 ->Get(0)
109 ->calibration()
110 ->node_name()
111 ->string_view());
112 CHECK_EQ(0, constants_fetcher_.constants()
113 .cameras()
114 ->Get(0)
115 ->calibration()
116 ->camera_number());
117 const Eigen::Matrix<double, 4, 4> H_robot_camera =
118 frc971::control_loops::drivetrain::FlatbufferToTransformationMatrix(
119 *constants_fetcher_.constants()
120 .cameras()
121 ->Get(0)
122 ->calibration()
123 ->fixed_extrinsics());
124
125 CHECK(constants_fetcher_.constants().common()->has_target_map());
126 CHECK(constants_fetcher_.constants()
127 .common()
128 ->target_map()
129 ->has_target_poses());
130 CHECK_LE(1u, constants_fetcher_.constants()
131 .common()
132 ->target_map()
133 ->target_poses()
134 ->size());
135 CHECK_EQ(kTargetId, constants_fetcher_.constants()
136 .common()
137 ->target_map()
138 ->target_poses()
139 ->Get(0)
140 ->id());
141 const Eigen::Matrix<double, 4, 4> H_field_target =
142 PoseToTransform(constants_fetcher_.constants()
143 .common()
144 ->target_map()
145 ->target_poses()
146 ->Get(0));
147 // For reference, the camera should pointed straight forwards on the
148 // robot, offset by 1 meter.
149 aos::TimerHandler *timer = camera_test_event_loop_->AddTimer(
150 [this, H_robot_camera, H_field_target]() {
151 if (!send_targets_) {
152 return;
153 }
154 const frc971::control_loops::Pose robot_pose(
155 {drivetrain_plant_.GetPosition().x(),
156 drivetrain_plant_.GetPosition().y(), 0.0},
157 drivetrain_plant_.state()(2, 0) + implied_yaw_error_);
158
159 const Eigen::Matrix<double, 4, 4> H_field_camera =
160 robot_pose.AsTransformationMatrix() * H_robot_camera;
161 const Eigen::Matrix<double, 4, 4> H_camera_target =
162 H_field_camera.inverse() * H_field_target;
163
164 Eigen::Quaterniond quat(H_camera_target.block<3, 3>(0, 0));
165 quat.normalize();
166 const Eigen::Vector3d translation(
167 H_camera_target.block<3, 1>(0, 3));
168
169 auto builder = target_sender_.MakeBuilder();
170 frc971::vision::Quaternion::Builder quat_builder(*builder.fbb());
171 quat_builder.add_w(quat.w());
172 quat_builder.add_x(quat.x());
173 quat_builder.add_y(quat.y());
174 quat_builder.add_z(quat.z());
175 auto quat_offset = quat_builder.Finish();
176 frc971::vision::Position::Builder position_builder(*builder.fbb());
177 position_builder.add_x(translation.x());
178 position_builder.add_y(translation.y());
179 position_builder.add_z(translation.z());
180 auto position_offset = position_builder.Finish();
181
182 frc971::vision::TargetPoseFbs::Builder target_builder(
183 *builder.fbb());
184 target_builder.add_id(send_target_id_);
185 target_builder.add_position(position_offset);
186 target_builder.add_orientation(quat_offset);
187 target_builder.add_pose_error(pose_error_);
188 target_builder.add_pose_error_ratio(pose_error_ratio_);
189 auto target_offset = target_builder.Finish();
190
191 auto targets_offset = builder.fbb()->CreateVector({target_offset});
192 frc971::vision::TargetMap::Builder map_builder(*builder.fbb());
193 map_builder.add_target_poses(targets_offset);
194 map_builder.add_monotonic_timestamp_ns(
195 std::chrono::duration_cast<std::chrono::nanoseconds>(
196 camera_test_event_loop_->monotonic_now().time_since_epoch())
197 .count());
198
199 builder.CheckOk(builder.Send(map_builder.Finish()));
200 });
201 camera_test_event_loop_->OnRun([timer, this]() {
202 timer->Schedule(camera_test_event_loop_->monotonic_now(),
203 std::chrono::milliseconds(50));
204 });
205 }
206
207 localizer_control_send_timer_ =
208 roborio_test_event_loop_->AddTimer([this]() {
209 auto builder = control_sender_.MakeBuilder();
210 auto control_builder = builder.MakeBuilder<
211 frc971::control_loops::drivetrain::LocalizerControl>();
212 control_builder.add_x(localizer_control_x_);
213 control_builder.add_y(localizer_control_y_);
214 control_builder.add_theta(localizer_control_theta_);
215 control_builder.add_theta_uncertainty(0.01);
216 control_builder.add_keep_current_theta(false);
217 builder.CheckOk(builder.Send(control_builder.Finish()));
218 });
219
220 // Get things zeroed.
221 event_loop_factory_.RunFor(std::chrono::seconds(10));
222 CHECK(status_fetcher_.Fetch());
223 CHECK(status_fetcher_->imu()->zeroed());
224
225 if (!FLAGS_output_folder.empty()) {
226 logger_event_loop_ =
227 event_loop_factory_.MakeEventLoop("logger", imu_node_);
228 logger_ = std::make_unique<aos::logger::Logger>(logger_event_loop_.get());
229 logger_->StartLoggingOnRun(FLAGS_output_folder);
230 }
231 }
232
233 void SendLocalizerControl(double x, double y, double theta) {
234 localizer_control_x_ = x;
235 localizer_control_y_ = y;
236 localizer_control_theta_ = theta;
237 localizer_control_send_timer_->Schedule(
238 roborio_test_event_loop_->monotonic_now());
239 }
240
241 ::testing::AssertionResult IsNear(double expected, double actual,
242 double epsilon) {
243 if (std::abs(expected - actual) < epsilon) {
244 return ::testing::AssertionSuccess();
245 } else {
246 return ::testing::AssertionFailure()
247 << "Expected " << expected << " but got " << actual
248 << " with a max difference of " << epsilon
249 << " and an actual difference of " << std::abs(expected - actual);
250 }
251 }
252 ::testing::AssertionResult VerifyEstimatorAccurate(double eps) {
253 const Eigen::Matrix<double, 5, 1> true_state = drivetrain_plant_.state();
254 ::testing::AssertionResult result(true);
255 status_fetcher_.Fetch();
256 if (!(result = IsNear(status_fetcher_->state()->x(), true_state(0), eps))) {
257 return result;
258 }
259 if (!(result = IsNear(status_fetcher_->state()->y(), true_state(1), eps))) {
260 return result;
261 }
262 if (!(result =
263 IsNear(status_fetcher_->state()->theta(), true_state(2), eps))) {
264 return result;
265 }
266 return result;
267 }
268
269 aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
270 aos::SimulatedEventLoopFactory event_loop_factory_;
271 const aos::Node *const roborio_node_;
272 const aos::Node *const imu_node_;
273 const aos::Node *const camera_node_;
274 std::unique_ptr<aos::EventLoop> roborio_test_event_loop_;
275 const frc971::control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
276 std::unique_ptr<aos::EventLoop> localizer_event_loop_;
277 Localizer localizer_;
278
279 std::unique_ptr<aos::EventLoop> drivetrain_plant_event_loop_;
280 std::unique_ptr<aos::EventLoop> drivetrain_plant_imu_event_loop_;
281 frc971::control_loops::drivetrain::testing::DrivetrainSimulation
282 drivetrain_plant_;
283
284 std::unique_ptr<aos::EventLoop> imu_test_event_loop_;
285 std::unique_ptr<aos::EventLoop> camera_test_event_loop_;
286
287 frc971::constants::ConstantsFetcher<Constants> constants_fetcher_;
288
289 aos::Sender<Output> output_sender_;
James Kuszmaulf6aa0382024-03-01 19:46:05 -0800290 aos::Sender<frc971::control_loops::drivetrain::RioLocalizerInputsStatic>
291 combined_sender_;
James Kuszmaul313e9ce2024-02-11 17:47:33 -0800292 aos::Sender<frc971::vision::TargetMap> target_sender_;
293 aos::Sender<frc971::control_loops::drivetrain::LocalizerControl>
294 control_sender_;
295 aos::Fetcher<frc971::controls::LocalizerOutput> output_fetcher_;
296 aos::Fetcher<Status> status_fetcher_;
297
298 Eigen::Vector2d output_voltages_ = Eigen::Vector2d::Zero();
299
300 aos::TimerHandler *localizer_control_send_timer_;
301
302 bool send_targets_ = false;
303
304 double localizer_control_x_ = 0.0;
305 double localizer_control_y_ = 0.0;
306 double localizer_control_theta_ = 0.0;
307
308 std::unique_ptr<aos::EventLoop> logger_event_loop_;
309 std::unique_ptr<aos::logger::Logger> logger_;
310
311 uint64_t send_target_id_ = kTargetId;
312 double pose_error_ = 1e-7;
313 double pose_error_ratio_ = 0.1;
314 double implied_yaw_error_ = 0.0;
315
316 gflags::FlagSaver flag_saver_;
317};
318
319// Test a simple scenario with no errors where the robot should just drive
320// straight forwards.
321TEST_F(LocalizerTest, Nominal) {
322 output_voltages_ << 1.0, 1.0;
323 event_loop_factory_.RunFor(std::chrono::seconds(2));
324 CHECK(output_fetcher_.Fetch());
325 CHECK(status_fetcher_.Fetch());
326 // The two can be different because they may've been sent at different
327 // times.
328 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2);
329 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
330 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
331 1e-6);
332 // Confirm that we did indeed drive forwards (and straight), as expected.
333 EXPECT_LT(0.1, output_fetcher_->x());
334 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
335 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
336 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
337 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
338
339 // And check that we actually think that we are near where the simulator
340 // says we are.
341 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
342}
343
344// Confirm that when the robot drives backwards that we localize correctly.
345TEST_F(LocalizerTest, NominalReverse) {
346 output_voltages_ << -1.0, -1.0;
347 event_loop_factory_.RunFor(std::chrono::seconds(2));
348 CHECK(output_fetcher_.Fetch());
349 CHECK(status_fetcher_.Fetch());
350 // The two can be different because they may've been sent at different
351 // times.
352 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2);
353 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
354 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
355 1e-6);
356 // Confirm that we did indeed drive backwards (and straight), as expected.
357 EXPECT_GT(-0.1, output_fetcher_->x());
358 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
359 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
360 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
361 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
362
363 // And check that we actually think that we are near where the simulator
364 // says we are.
365 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
366}
367
368// Confirm that when the robot turns counter-clockwise that we localize
369// correctly.
370TEST_F(LocalizerTest, NominalSpinInPlace) {
371 output_voltages_ << -1.0, 1.0;
372 // Go 1 ms over 2 sec to make sure we actually see relatively recent messages
373 // on each channel.
374 event_loop_factory_.RunFor(std::chrono::milliseconds(2001));
375 CHECK(output_fetcher_.Fetch());
376 CHECK(status_fetcher_.Fetch());
377 // The two can be different because they may've been sent at different
378 // times.
379 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-6);
380 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
381 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
382 1e-2);
383 // Confirm that we did indeed turn counter-clockwise.
384 EXPECT_NEAR(0.0, output_fetcher_->x(), 1e-10);
385 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
386 EXPECT_LT(0.1, output_fetcher_->theta());
387 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
388 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
389
390 // And check that we actually think that we are near where the simulator
391 // says we are.
392 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
393}
394
395// Confirm that when the robot drives in a curve that we localize
396// successfully.
397TEST_F(LocalizerTest, NominalCurve) {
398 output_voltages_ << 2.0, 3.0;
399 event_loop_factory_.RunFor(std::chrono::seconds(4));
400 CHECK(output_fetcher_.Fetch());
401 CHECK(status_fetcher_.Fetch());
402 // The two can be different because they may've been sent at different
403 // times.
404 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 2e-2);
405 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 2e-2);
406 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
407 2e-2);
408 // Confirm that we did indeed drive in a rough, counter-clockwise, curve.
409 EXPECT_LT(0.1, output_fetcher_->x());
410 EXPECT_LT(0.1, output_fetcher_->y());
411 EXPECT_LT(0.1, output_fetcher_->theta());
412
413 // And check that we actually think that we are near where the simulator
414 // says we are.
415 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
416}
417
418// Tests that, in the presence of a non-zero voltage error, that we correct
419// for it.
420TEST_F(LocalizerTest, VoltageErrorDisabled) {
421 output_voltages_ << 0.0, 0.0;
422 drivetrain_plant_.set_left_voltage_offset(2.0);
423 drivetrain_plant_.set_right_voltage_offset(2.0);
424
425 event_loop_factory_.RunFor(std::chrono::seconds(2));
426 CHECK(output_fetcher_.Fetch());
427 CHECK(status_fetcher_.Fetch());
428 // We should've just ended up driving straight forwards.
429 EXPECT_LT(0.1, output_fetcher_->x());
430 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
431 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
432 EXPECT_NEAR(2.0, status_fetcher_->state()->left_voltage_error(), 1.0);
433 EXPECT_NEAR(2.0, status_fetcher_->state()->right_voltage_error(), 1.0);
434
435 // And check that we actually think that we are near where the simulator
436 // says we are.
437 EXPECT_TRUE(VerifyEstimatorAccurate(0.05));
438}
439
440// Tests that image corrections in the nominal case (no errors) causes no
441// issues.
442TEST_F(LocalizerTest, NominalImageCorrections) {
443 output_voltages_ << 3.0, 2.0;
444 send_targets_ = true;
445
446 event_loop_factory_.RunFor(std::chrono::seconds(4));
447 CHECK(status_fetcher_.Fetch());
448 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
449 ASSERT_TRUE(status_fetcher_->has_statistics());
450 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
451 ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(),
452 status_fetcher_->statistics()->Get(0)->total_accepted());
453 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
454}
455
456// Tests that image corrections when there is an error at the start results
457// in us actually getting corrected over time.
458TEST_F(LocalizerTest, ImageCorrections) {
459 output_voltages_ << 0.0, 0.0;
460 // Put ourselves somewhat near the target so that we don't ignore its
461 // corrections too much.
462 drivetrain_plant_.mutable_state()->x() = 3.0;
463 drivetrain_plant_.mutable_state()->y() = -2.0;
464 SendLocalizerControl(5.0, 0.0, 0.0);
465 event_loop_factory_.RunFor(std::chrono::seconds(4));
466 ASSERT_TRUE(output_fetcher_.Fetch());
467 EXPECT_NEAR(5.0, output_fetcher_->x(), 1e-5);
468 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-5);
469 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-5);
470
471 send_targets_ = true;
472
473 event_loop_factory_.RunFor(std::chrono::seconds(10));
474 CHECK(status_fetcher_.Fetch());
475 EXPECT_TRUE(VerifyEstimatorAccurate(0.1));
476 ASSERT_TRUE(status_fetcher_->has_statistics());
477 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
478 ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(),
479 status_fetcher_->statistics()->Get(0)->total_accepted());
480 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
481}
482
483// Tests that we correctly reject an invalid target.
484TEST_F(LocalizerTest, InvalidTargetId) {
485 output_voltages_ << 0.0, 0.0;
486 send_targets_ = true;
487 send_target_id_ = 100;
488
489 event_loop_factory_.RunFor(std::chrono::seconds(4));
490 CHECK(status_fetcher_.Fetch());
491 ASSERT_TRUE(status_fetcher_->has_statistics());
492 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
493 ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted());
494 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
495 ASSERT_EQ(status_fetcher_->statistics()
496 ->Get(0)
497 ->rejection_reasons()
498 ->Get(static_cast<size_t>(RejectionReason::NO_SUCH_TARGET))
499 ->count(),
500 status_fetcher_->statistics()->Get(0)->total_candidates());
501}
502
503// Tests that we correctly reject a detection with a high pose error.
504TEST_F(LocalizerTest, HighPoseError) {
505 output_voltages_ << 0.0, 0.0;
506 send_targets_ = true;
507 // Send the minimum pose error to be rejected
508 constexpr double kEps = 1e-9;
509 pose_error_ = 1e-6 + kEps;
510
511 event_loop_factory_.RunFor(std::chrono::seconds(4));
512 CHECK(status_fetcher_.Fetch());
513 ASSERT_TRUE(status_fetcher_->has_statistics());
514 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
515 ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted());
516 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
517 ASSERT_EQ(status_fetcher_->statistics()
518 ->Get(0)
519 ->rejection_reasons()
520 ->Get(static_cast<size_t>(RejectionReason::HIGH_POSE_ERROR))
521 ->count(),
522 status_fetcher_->statistics()->Get(0)->total_candidates());
523}
524
525// Tests that we correctly reject a detection with a high implied yaw error.
526TEST_F(LocalizerTest, HighImpliedYawError) {
527 output_voltages_ << 0.0, 0.0;
528 send_targets_ = true;
529 implied_yaw_error_ = 31.0 * M_PI / 180.0;
530
531 event_loop_factory_.RunFor(std::chrono::seconds(4));
532 CHECK(status_fetcher_.Fetch());
533 ASSERT_TRUE(status_fetcher_->has_statistics());
534 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
535 ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted());
536 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
537 ASSERT_EQ(
538 status_fetcher_->statistics()
539 ->Get(0)
540 ->rejection_reasons()
541 ->Get(static_cast<size_t>(RejectionReason::HIGH_IMPLIED_YAW_ERROR))
542 ->count(),
543 status_fetcher_->statistics()->Get(0)->total_candidates());
544}
545
546// Tests that we correctly reject a detection with a high pose error ratio.
547TEST_F(LocalizerTest, HighPoseErrorRatio) {
548 output_voltages_ << 0.0, 0.0;
549 send_targets_ = true;
550 // Send the minimum pose error to be rejected
551 constexpr double kEps = 1e-9;
552 pose_error_ratio_ = 0.4 + kEps;
553
554 event_loop_factory_.RunFor(std::chrono::seconds(4));
555 CHECK(status_fetcher_.Fetch());
556 ASSERT_TRUE(status_fetcher_->has_statistics());
557 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
558 ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted());
559 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
560 ASSERT_EQ(
561 status_fetcher_->statistics()
562 ->Get(0)
563 ->rejection_reasons()
564 ->Get(static_cast<size_t>(RejectionReason::HIGH_POSE_ERROR_RATIO))
565 ->count(),
566 status_fetcher_->statistics()->Get(0)->total_candidates());
567}
568
569} // namespace y2024::localizer::testing