| #include "y2023/localizer/localizer.h" |
| |
| #include "absl/flags/declare.h" |
| #include "absl/flags/flag.h" |
| #include "absl/flags/reflection.h" |
| #include "gtest/gtest.h" |
| |
| #include "aos/events/logging/log_writer.h" |
| #include "aos/events/simulated_event_loop.h" |
| #include "frc971/control_loops/drivetrain/drivetrain_test_lib.h" |
| #include "frc971/control_loops/drivetrain/localizer_generated.h" |
| #include "frc971/control_loops/pose.h" |
| #include "frc971/vision/target_map_generated.h" |
| #include "frc971/vision/target_map_utils.h" |
| #include "y2023/constants/simulated_constants_sender.h" |
| #include "y2023/control_loops/drivetrain/drivetrain_base.h" |
| #include "y2023/localizer/status_generated.h" |
| |
| ABSL_FLAG(std::string, output_folder, "", |
| "If set, logs all channels to the provided logfile."); |
| ABSL_DECLARE_FLAG(double, max_distance_to_target); |
| |
| namespace y2023::localizer::testing { |
| |
| using frc971::control_loops::drivetrain::Output; |
| |
| class LocalizerTest : public ::testing::Test { |
| protected: |
| static constexpr uint64_t kTargetId = 1; |
| LocalizerTest() |
| : configuration_(aos::configuration::ReadConfig("y2023/aos_config.json")), |
| event_loop_factory_(&configuration_.message()), |
| roborio_node_([this]() { |
| // Get the constants sent before anything else happens. |
| // It has nothing to do with the roborio node. |
| SendSimulationConstants(&event_loop_factory_, 7971, |
| "y2023/constants/test_constants.json"); |
| return aos::configuration::GetNode(&configuration_.message(), |
| "roborio"); |
| }()), |
| imu_node_( |
| aos::configuration::GetNode(&configuration_.message(), "imu")), |
| camera_node_( |
| aos::configuration::GetNode(&configuration_.message(), "pi1")), |
| dt_config_(frc971::control_loops::drivetrain::testing:: |
| GetTestDrivetrainConfig()), |
| localizer_event_loop_( |
| event_loop_factory_.MakeEventLoop("localizer", imu_node_)), |
| localizer_(localizer_event_loop_.get(), dt_config_), |
| drivetrain_plant_event_loop_(event_loop_factory_.MakeEventLoop( |
| "drivetrain_plant", roborio_node_)), |
| drivetrain_plant_imu_event_loop_( |
| event_loop_factory_.MakeEventLoop("drivetrain_plant", imu_node_)), |
| drivetrain_plant_(drivetrain_plant_event_loop_.get(), |
| drivetrain_plant_imu_event_loop_.get(), dt_config_, |
| std::chrono::microseconds(500)), |
| roborio_test_event_loop_( |
| event_loop_factory_.MakeEventLoop("test", roborio_node_)), |
| imu_test_event_loop_( |
| event_loop_factory_.MakeEventLoop("test", imu_node_)), |
| camera_test_event_loop_( |
| event_loop_factory_.MakeEventLoop("test", camera_node_)), |
| logger_test_event_loop_( |
| event_loop_factory_.GetNodeEventLoopFactory("logger") |
| ->MakeEventLoop("test")), |
| constants_fetcher_(imu_test_event_loop_.get()), |
| output_sender_( |
| roborio_test_event_loop_->MakeSender<Output>("/drivetrain")), |
| target_sender_( |
| camera_test_event_loop_->MakeSender<frc971::vision::TargetMap>( |
| "/camera")), |
| control_sender_(roborio_test_event_loop_->MakeSender< |
| frc971::control_loops::drivetrain::LocalizerControl>( |
| "/drivetrain")), |
| output_fetcher_( |
| roborio_test_event_loop_ |
| ->MakeFetcher<frc971::controls::LocalizerOutput>("/localizer")), |
| status_fetcher_( |
| imu_test_event_loop_->MakeFetcher<Status>("/localizer")) { |
| absl::SetFlag(&FLAGS_max_distance_to_target, 100.0); |
| { |
| aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() { |
| { |
| auto builder = output_sender_.MakeBuilder(); |
| auto output_builder = builder.MakeBuilder<Output>(); |
| output_builder.add_left_voltage(output_voltages_(0)); |
| output_builder.add_right_voltage(output_voltages_(1)); |
| builder.CheckOk(builder.Send(output_builder.Finish())); |
| } |
| }); |
| roborio_test_event_loop_->OnRun([timer, this]() { |
| timer->Schedule(roborio_test_event_loop_->monotonic_now(), |
| std::chrono::milliseconds(5)); |
| }); |
| } |
| { |
| // Sanity check that the test calibration files look like what we |
| // expect. |
| CHECK_EQ("pi1", constants_fetcher_.constants() |
| .cameras() |
| ->Get(0) |
| ->calibration() |
| ->node_name() |
| ->string_view()); |
| const Eigen::Matrix<double, 4, 4> H_robot_camera = |
| frc971::control_loops::drivetrain::FlatbufferToTransformationMatrix( |
| *constants_fetcher_.constants() |
| .cameras() |
| ->Get(0) |
| ->calibration() |
| ->fixed_extrinsics()); |
| |
| CHECK_EQ(kTargetId, constants_fetcher_.constants() |
| .target_map() |
| ->target_poses() |
| ->Get(0) |
| ->id()); |
| const Eigen::Matrix<double, 4, 4> H_field_target = PoseToTransform( |
| constants_fetcher_.constants().target_map()->target_poses()->Get(0)); |
| // For reference, the camera should pointed straight forwards on the |
| // robot, offset by 1 meter. |
| aos::TimerHandler *timer = camera_test_event_loop_->AddTimer( |
| [this, H_robot_camera, H_field_target]() { |
| if (!send_targets_) { |
| return; |
| } |
| const frc971::control_loops::Pose robot_pose( |
| {drivetrain_plant_.GetPosition().x(), |
| drivetrain_plant_.GetPosition().y(), 0.0}, |
| drivetrain_plant_.state()(2, 0) + implied_yaw_error_); |
| |
| const Eigen::Matrix<double, 4, 4> H_field_camera = |
| robot_pose.AsTransformationMatrix() * H_robot_camera; |
| const Eigen::Matrix<double, 4, 4> H_camera_target = |
| H_field_camera.inverse() * H_field_target; |
| |
| const Eigen::Quaterniond quat(H_camera_target.block<3, 3>(0, 0)); |
| const Eigen::Vector3d translation( |
| H_camera_target.block<3, 1>(0, 3)); |
| |
| auto builder = target_sender_.MakeBuilder(); |
| frc971::vision::Quaternion::Builder quat_builder(*builder.fbb()); |
| quat_builder.add_w(quat.w()); |
| quat_builder.add_x(quat.x()); |
| quat_builder.add_y(quat.y()); |
| quat_builder.add_z(quat.z()); |
| auto quat_offset = quat_builder.Finish(); |
| frc971::vision::Position::Builder position_builder(*builder.fbb()); |
| position_builder.add_x(translation.x()); |
| position_builder.add_y(translation.y()); |
| position_builder.add_z(translation.z()); |
| auto position_offset = position_builder.Finish(); |
| |
| frc971::vision::TargetPoseFbs::Builder target_builder( |
| *builder.fbb()); |
| target_builder.add_id(send_target_id_); |
| target_builder.add_position(position_offset); |
| target_builder.add_orientation(quat_offset); |
| target_builder.add_pose_error(pose_error_); |
| target_builder.add_pose_error_ratio(pose_error_ratio_); |
| auto target_offset = target_builder.Finish(); |
| |
| auto targets_offset = builder.fbb()->CreateVector({target_offset}); |
| frc971::vision::TargetMap::Builder map_builder(*builder.fbb()); |
| map_builder.add_target_poses(targets_offset); |
| map_builder.add_monotonic_timestamp_ns( |
| std::chrono::duration_cast<std::chrono::nanoseconds>( |
| camera_test_event_loop_->monotonic_now().time_since_epoch()) |
| .count()); |
| |
| builder.CheckOk(builder.Send(map_builder.Finish())); |
| }); |
| camera_test_event_loop_->OnRun([timer, this]() { |
| timer->Schedule(camera_test_event_loop_->monotonic_now(), |
| std::chrono::milliseconds(50)); |
| }); |
| } |
| |
| localizer_control_send_timer_ = |
| roborio_test_event_loop_->AddTimer([this]() { |
| auto builder = control_sender_.MakeBuilder(); |
| auto control_builder = builder.MakeBuilder< |
| frc971::control_loops::drivetrain::LocalizerControl>(); |
| control_builder.add_x(localizer_control_x_); |
| control_builder.add_y(localizer_control_y_); |
| control_builder.add_theta(localizer_control_theta_); |
| control_builder.add_theta_uncertainty(0.01); |
| control_builder.add_keep_current_theta(false); |
| builder.CheckOk(builder.Send(control_builder.Finish())); |
| }); |
| |
| // Get things zeroed. |
| event_loop_factory_.RunFor(std::chrono::seconds(10)); |
| CHECK(status_fetcher_.Fetch()); |
| CHECK(status_fetcher_->imu()->zeroed()); |
| |
| if (!absl::GetFlag(FLAGS_output_folder).empty()) { |
| logger_event_loop_ = |
| event_loop_factory_.MakeEventLoop("logger", imu_node_); |
| logger_ = std::make_unique<aos::logger::Logger>(logger_event_loop_.get()); |
| logger_->StartLoggingOnRun(absl::GetFlag(FLAGS_output_folder)); |
| } |
| } |
| |
| void SendLocalizerControl(double x, double y, double theta) { |
| localizer_control_x_ = x; |
| localizer_control_y_ = y; |
| localizer_control_theta_ = theta; |
| localizer_control_send_timer_->Schedule( |
| roborio_test_event_loop_->monotonic_now()); |
| } |
| ::testing::AssertionResult IsNear(double expected, double actual, |
| double epsilon) { |
| if (std::abs(expected - actual) < epsilon) { |
| return ::testing::AssertionSuccess(); |
| } else { |
| return ::testing::AssertionFailure() |
| << "Expected " << expected << " but got " << actual |
| << " with a max difference of " << epsilon |
| << " and an actual difference of " << std::abs(expected - actual); |
| } |
| } |
| ::testing::AssertionResult VerifyEstimatorAccurate(double eps) { |
| const Eigen::Matrix<double, 5, 1> true_state = drivetrain_plant_.state(); |
| ::testing::AssertionResult result(true); |
| status_fetcher_.Fetch(); |
| if (!(result = IsNear(status_fetcher_->state()->x(), true_state(0), eps))) { |
| return result; |
| } |
| if (!(result = IsNear(status_fetcher_->state()->y(), true_state(1), eps))) { |
| return result; |
| } |
| if (!(result = |
| IsNear(status_fetcher_->state()->theta(), true_state(2), eps))) { |
| return result; |
| } |
| return result; |
| } |
| |
| aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_; |
| aos::SimulatedEventLoopFactory event_loop_factory_; |
| const aos::Node *const roborio_node_; |
| const aos::Node *const imu_node_; |
| const aos::Node *const camera_node_; |
| const frc971::control_loops::drivetrain::DrivetrainConfig<double> dt_config_; |
| std::unique_ptr<aos::EventLoop> localizer_event_loop_; |
| Localizer localizer_; |
| |
| std::unique_ptr<aos::EventLoop> drivetrain_plant_event_loop_; |
| std::unique_ptr<aos::EventLoop> drivetrain_plant_imu_event_loop_; |
| frc971::control_loops::drivetrain::testing::DrivetrainSimulation |
| drivetrain_plant_; |
| |
| std::unique_ptr<aos::EventLoop> roborio_test_event_loop_; |
| std::unique_ptr<aos::EventLoop> imu_test_event_loop_; |
| std::unique_ptr<aos::EventLoop> camera_test_event_loop_; |
| std::unique_ptr<aos::EventLoop> logger_test_event_loop_; |
| |
| frc971::constants::ConstantsFetcher<Constants> constants_fetcher_; |
| |
| aos::Sender<Output> output_sender_; |
| aos::Sender<frc971::vision::TargetMap> target_sender_; |
| aos::Sender<frc971::control_loops::drivetrain::LocalizerControl> |
| control_sender_; |
| aos::Fetcher<frc971::controls::LocalizerOutput> output_fetcher_; |
| aos::Fetcher<Status> status_fetcher_; |
| |
| Eigen::Vector2d output_voltages_ = Eigen::Vector2d::Zero(); |
| |
| aos::TimerHandler *localizer_control_send_timer_; |
| |
| bool send_targets_ = false; |
| |
| double localizer_control_x_ = 0.0; |
| double localizer_control_y_ = 0.0; |
| double localizer_control_theta_ = 0.0; |
| |
| std::unique_ptr<aos::EventLoop> logger_event_loop_; |
| std::unique_ptr<aos::logger::Logger> logger_; |
| |
| uint64_t send_target_id_ = kTargetId; |
| double pose_error_ = 1e-7; |
| double pose_error_ratio_ = 0.1; |
| double implied_yaw_error_ = 0.0; |
| |
| absl::FlagSaver flag_saver_; |
| }; |
| |
| // Test a simple scenario with no errors where the robot should just drive |
| // straight forwards. |
| TEST_F(LocalizerTest, Nominal) { |
| output_voltages_ << 1.0, 1.0; |
| event_loop_factory_.RunFor(std::chrono::seconds(2)); |
| CHECK(output_fetcher_.Fetch()); |
| CHECK(status_fetcher_.Fetch()); |
| // The two can be different because they may've been sent at different |
| // times. |
| EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2); |
| EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6); |
| EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(), |
| 1e-6); |
| // Confirm that we did indeed drive forwards (and straight), as expected. |
| EXPECT_LT(0.1, output_fetcher_->x()); |
| EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10); |
| EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1); |
| |
| // And check that we actually think that we are near where the simulator |
| // says we are. |
| EXPECT_TRUE(VerifyEstimatorAccurate(1e-2)); |
| } |
| |
| // Confirm that when the robot drives backwards that we localize correctly. |
| TEST_F(LocalizerTest, NominalReverse) { |
| output_voltages_ << -1.0, -1.0; |
| event_loop_factory_.RunFor(std::chrono::seconds(2)); |
| CHECK(output_fetcher_.Fetch()); |
| CHECK(status_fetcher_.Fetch()); |
| // The two can be different because they may've been sent at different |
| // times. |
| EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2); |
| EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6); |
| EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(), |
| 1e-6); |
| // Confirm that we did indeed drive backwards (and straight), as expected. |
| EXPECT_GT(-0.1, output_fetcher_->x()); |
| EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10); |
| EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1); |
| |
| // And check that we actually think that we are near where the simulator |
| // says we are. |
| EXPECT_TRUE(VerifyEstimatorAccurate(1e-2)); |
| } |
| |
| // Confirm that when the robot turns counter-clockwise that we localize |
| // correctly. |
| TEST_F(LocalizerTest, NominalSpinInPlace) { |
| output_voltages_ << -1.0, 1.0; |
| // Go 1 ms over 2 sec to make sure we actually see relatively recent messages |
| // on each channel. |
| event_loop_factory_.RunFor(std::chrono::milliseconds(2001)); |
| CHECK(output_fetcher_.Fetch()); |
| CHECK(status_fetcher_.Fetch()); |
| // The two can be different because they may've been sent at different |
| // times. |
| EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-6); |
| EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6); |
| EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(), |
| 1e-2); |
| // Confirm that we did indeed turn counter-clockwise. |
| EXPECT_NEAR(0.0, output_fetcher_->x(), 1e-10); |
| EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10); |
| EXPECT_LT(0.1, output_fetcher_->theta()); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1); |
| EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1); |
| |
| // And check that we actually think that we are near where the simulator |
| // says we are. |
| EXPECT_TRUE(VerifyEstimatorAccurate(1e-2)); |
| } |
| |
| // Confirm that when the robot drives in a curve that we localize |
| // successfully. |
| TEST_F(LocalizerTest, NominalCurve) { |
| output_voltages_ << 2.0, 3.0; |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(output_fetcher_.Fetch()); |
| CHECK(status_fetcher_.Fetch()); |
| // The two can be different because they may've been sent at different |
| // times. |
| EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2); |
| EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-2); |
| EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(), |
| 1e-2); |
| // Confirm that we did indeed drive in a rough, counter-clockwise, curve. |
| EXPECT_LT(0.1, output_fetcher_->x()); |
| EXPECT_LT(0.1, output_fetcher_->y()); |
| EXPECT_LT(0.1, output_fetcher_->theta()); |
| |
| // And check that we actually think that we are near where the simulator |
| // says we are. |
| EXPECT_TRUE(VerifyEstimatorAccurate(1e-2)); |
| } |
| |
| // Tests that, in the presence of a non-zero voltage error, that we correct |
| // for it. |
| TEST_F(LocalizerTest, VoltageErrorDisabled) { |
| output_voltages_ << 0.0, 0.0; |
| drivetrain_plant_.set_left_voltage_offset(2.0); |
| drivetrain_plant_.set_right_voltage_offset(2.0); |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(2)); |
| CHECK(output_fetcher_.Fetch()); |
| CHECK(status_fetcher_.Fetch()); |
| // We should've just ended up driving straight forwards. |
| EXPECT_LT(0.1, output_fetcher_->x()); |
| EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10); |
| EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10); |
| EXPECT_NEAR(2.0, status_fetcher_->state()->left_voltage_error(), 1.0); |
| EXPECT_NEAR(2.0, status_fetcher_->state()->right_voltage_error(), 1.0); |
| |
| // And check that we actually think that we are near where the simulator |
| // says we are. |
| EXPECT_TRUE(VerifyEstimatorAccurate(0.05)); |
| } |
| |
| // Tests that image corrections in the nominal case (no errors) causes no |
| // issues. |
| TEST_F(LocalizerTest, NominalImageCorrections) { |
| output_voltages_ << 3.0, 2.0; |
| send_targets_ = true; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| EXPECT_TRUE(VerifyEstimatorAccurate(1e-2)); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(), |
| status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| // Tests that image corrections when there is an error at the start results |
| // in us actually getting corrected over time. |
| TEST_F(LocalizerTest, ImageCorrections) { |
| output_voltages_ << 0.0, 0.0; |
| drivetrain_plant_.mutable_state()->x() = 2.0; |
| drivetrain_plant_.mutable_state()->y() = 2.0; |
| SendLocalizerControl(5.0, 3.0, 0.0); |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(output_fetcher_.Fetch()); |
| ASSERT_NEAR(5.0, output_fetcher_->x(), 1e-5); |
| ASSERT_NEAR(3.0, output_fetcher_->y(), 1e-5); |
| ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-5); |
| |
| send_targets_ = true; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| EXPECT_TRUE(VerifyEstimatorAccurate(0.1)); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(), |
| status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| // Tests that we correctly reject an invalid target. |
| TEST_F(LocalizerTest, InvalidTargetId) { |
| output_voltages_ << 0.0, 0.0; |
| send_targets_ = true; |
| send_target_id_ = 100; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| ASSERT_EQ(status_fetcher_->statistics() |
| ->Get(0) |
| ->rejection_reasons() |
| ->Get(static_cast<size_t>(RejectionReason::NO_SUCH_TARGET)) |
| ->count(), |
| status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| // Tests that we correctly reject a detection with a high pose error. |
| TEST_F(LocalizerTest, HighPoseError) { |
| output_voltages_ << 0.0, 0.0; |
| send_targets_ = true; |
| // Send the minimum pose error to be rejected |
| constexpr double kEps = 1e-9; |
| pose_error_ = 1e-6 + kEps; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| ASSERT_EQ(status_fetcher_->statistics() |
| ->Get(0) |
| ->rejection_reasons() |
| ->Get(static_cast<size_t>(RejectionReason::HIGH_POSE_ERROR)) |
| ->count(), |
| status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| // Tests that we correctly reject a detection with a high implied yaw error. |
| TEST_F(LocalizerTest, HighImpliedYawError) { |
| output_voltages_ << 0.0, 0.0; |
| send_targets_ = true; |
| implied_yaw_error_ = 31.0 * M_PI / 180.0; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| ASSERT_EQ( |
| status_fetcher_->statistics() |
| ->Get(0) |
| ->rejection_reasons() |
| ->Get(static_cast<size_t>(RejectionReason::HIGH_IMPLIED_YAW_ERROR)) |
| ->count(), |
| status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| // Tests that we correctly reject a detection with a high pose error ratio. |
| TEST_F(LocalizerTest, HighPoseErrorRatio) { |
| output_voltages_ << 0.0, 0.0; |
| send_targets_ = true; |
| // Send the minimum pose error to be rejected |
| constexpr double kEps = 1e-9; |
| pose_error_ratio_ = 0.4 + kEps; |
| |
| event_loop_factory_.RunFor(std::chrono::seconds(4)); |
| CHECK(status_fetcher_.Fetch()); |
| ASSERT_TRUE(status_fetcher_->has_statistics()); |
| ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size()); |
| ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted()); |
| ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates()); |
| ASSERT_EQ( |
| status_fetcher_->statistics() |
| ->Get(0) |
| ->rejection_reasons() |
| ->Get(static_cast<size_t>(RejectionReason::HIGH_POSE_ERROR_RATIO)) |
| ->count(), |
| status_fetcher_->statistics()->Get(0)->total_candidates()); |
| } |
| |
| } // namespace y2023::localizer::testing |