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