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