blob: 3eba91952c9a6c0335a1c65dca6da5eb6113874d [file] [log] [blame]
Milind Upadhyay7c205222022-11-16 18:20:58 -08001#include "frc971/vision/target_mapper.h"
2
Milind Upadhyayc5beba12022-12-17 17:41:20 -08003#include "absl/strings/str_format.h"
Milind Upadhyay7c205222022-11-16 18:20:58 -08004#include "frc971/control_loops/control_loop.h"
Milind Upadhyayc5beba12022-12-17 17:41:20 -08005#include "frc971/vision/ceres/pose_graph_3d_error_term.h"
Milind Upadhyay7c205222022-11-16 18:20:58 -08006#include "frc971/vision/geometry.h"
7
8DEFINE_uint64(max_num_iterations, 100,
9 "Maximum number of iterations for the ceres solver");
milind-ud62f80a2023-03-04 16:37:09 -080010DEFINE_double(distortion_noise_scalar, 1.0,
11 "Scale the target pose distortion factor by this when computing "
12 "the noise.");
milind-u6ff399f2023-03-24 18:33:38 -070013DEFINE_uint64(
14 frozen_target_id, 1,
15 "Freeze the pose of this target so the map can have one fixed point.");
Milind Upadhyay7c205222022-11-16 18:20:58 -080016
17namespace frc971::vision {
Milind Upadhyayc5beba12022-12-17 17:41:20 -080018Eigen::Affine3d PoseUtils::Pose3dToAffine3d(
19 const ceres::examples::Pose3d &pose3d) {
Milind Upadhyay7c205222022-11-16 18:20:58 -080020 Eigen::Affine3d H_world_pose =
Milind Upadhyayc5beba12022-12-17 17:41:20 -080021 Eigen::Translation3d(pose3d.p(0), pose3d.p(1), pose3d.p(2)) * pose3d.q;
Milind Upadhyay7c205222022-11-16 18:20:58 -080022 return H_world_pose;
23}
24
Milind Upadhyayc5beba12022-12-17 17:41:20 -080025ceres::examples::Pose3d PoseUtils::Affine3dToPose3d(const Eigen::Affine3d &H) {
26 return ceres::examples::Pose3d{.p = H.translation(),
27 .q = Eigen::Quaterniond(H.rotation())};
Milind Upadhyay7c205222022-11-16 18:20:58 -080028}
29
Milind Upadhyayc5beba12022-12-17 17:41:20 -080030ceres::examples::Pose3d PoseUtils::ComputeRelativePose(
31 const ceres::examples::Pose3d &pose_1,
32 const ceres::examples::Pose3d &pose_2) {
33 Eigen::Affine3d H_world_1 = Pose3dToAffine3d(pose_1);
34 Eigen::Affine3d H_world_2 = Pose3dToAffine3d(pose_2);
Milind Upadhyay7c205222022-11-16 18:20:58 -080035
36 // Get the location of 2 in the 1 frame
37 Eigen::Affine3d H_1_2 = H_world_1.inverse() * H_world_2;
Milind Upadhyayc5beba12022-12-17 17:41:20 -080038 return Affine3dToPose3d(H_1_2);
Milind Upadhyay7c205222022-11-16 18:20:58 -080039}
40
Milind Upadhyayc5beba12022-12-17 17:41:20 -080041ceres::examples::Pose3d PoseUtils::ComputeOffsetPose(
42 const ceres::examples::Pose3d &pose_1,
43 const ceres::examples::Pose3d &pose_2_relative) {
44 auto H_world_1 = Pose3dToAffine3d(pose_1);
45 auto H_1_2 = Pose3dToAffine3d(pose_2_relative);
Milind Upadhyay7c205222022-11-16 18:20:58 -080046 auto H_world_2 = H_world_1 * H_1_2;
47
Milind Upadhyayc5beba12022-12-17 17:41:20 -080048 return Affine3dToPose3d(H_world_2);
Milind Upadhyay7c205222022-11-16 18:20:58 -080049}
50
Milind Upadhyayc5beba12022-12-17 17:41:20 -080051Eigen::Quaterniond PoseUtils::EulerAnglesToQuaternion(
52 const Eigen::Vector3d &rpy) {
53 Eigen::AngleAxisd roll_angle(rpy.x(), Eigen::Vector3d::UnitX());
54 Eigen::AngleAxisd pitch_angle(rpy.y(), Eigen::Vector3d::UnitY());
55 Eigen::AngleAxisd yaw_angle(rpy.z(), Eigen::Vector3d::UnitZ());
56
57 return yaw_angle * pitch_angle * roll_angle;
Milind Upadhyay7c205222022-11-16 18:20:58 -080058}
59
Milind Upadhyayc5beba12022-12-17 17:41:20 -080060Eigen::Vector3d PoseUtils::QuaternionToEulerAngles(
61 const Eigen::Quaterniond &q) {
62 return RotationMatrixToEulerAngles(q.toRotationMatrix());
Milind Upadhyay7c205222022-11-16 18:20:58 -080063}
64
Milind Upadhyayc5beba12022-12-17 17:41:20 -080065Eigen::Vector3d PoseUtils::RotationMatrixToEulerAngles(
66 const Eigen::Matrix3d &R) {
67 double roll = aos::math::NormalizeAngle(std::atan2(R(2, 1), R(2, 2)));
68 double pitch = aos::math::NormalizeAngle(-std::asin(R(2, 0)));
69 double yaw = aos::math::NormalizeAngle(std::atan2(R(1, 0), R(0, 0)));
70
71 return Eigen::Vector3d(roll, pitch, yaw);
72}
73
milind-u3f5f83c2023-01-29 15:23:51 -080074flatbuffers::Offset<TargetPoseFbs> PoseUtils::TargetPoseToFbs(
75 const TargetMapper::TargetPose &target_pose,
76 flatbuffers::FlatBufferBuilder *fbb) {
77 const auto position_offset =
78 CreatePosition(*fbb, target_pose.pose.p(0), target_pose.pose.p(1),
79 target_pose.pose.p(2));
80 const auto orientation_offset =
81 CreateQuaternion(*fbb, target_pose.pose.q.w(), target_pose.pose.q.x(),
82 target_pose.pose.q.y(), target_pose.pose.q.z());
83 return CreateTargetPoseFbs(*fbb, target_pose.id, position_offset,
84 orientation_offset);
85}
86
87TargetMapper::TargetPose PoseUtils::TargetPoseFromFbs(
88 const TargetPoseFbs &target_pose_fbs) {
89 return {.id = static_cast<TargetMapper::TargetId>(target_pose_fbs.id()),
90 .pose = ceres::examples::Pose3d{
91 Eigen::Vector3d(target_pose_fbs.position()->x(),
92 target_pose_fbs.position()->y(),
93 target_pose_fbs.position()->z()),
94 Eigen::Quaterniond(target_pose_fbs.orientation()->w(),
95 target_pose_fbs.orientation()->x(),
96 target_pose_fbs.orientation()->y(),
97 target_pose_fbs.orientation()->z())}};
98}
99
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800100ceres::examples::VectorOfConstraints DataAdapter::MatchTargetDetections(
Milind Upadhyayec493912022-12-18 21:33:15 -0800101 const std::vector<DataAdapter::TimestampedDetection>
102 &timestamped_target_detections,
103 aos::distributed_clock::duration max_dt) {
104 CHECK_GE(timestamped_target_detections.size(), 2ul)
105 << "Must have at least 2 detections";
106
107 // Match consecutive detections
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800108 ceres::examples::VectorOfConstraints target_constraints;
milind-ud62f80a2023-03-04 16:37:09 -0800109 for (auto detection = timestamped_target_detections.begin() + 1;
110 detection < timestamped_target_detections.end(); detection++) {
111 auto last_detection = detection - 1;
Milind Upadhyayec493912022-12-18 21:33:15 -0800112
113 // Skip two consecutive detections of the same target, because the solver
114 // doesn't allow this
milind-ud62f80a2023-03-04 16:37:09 -0800115 if (detection->id == last_detection->id) {
Milind Upadhyayec493912022-12-18 21:33:15 -0800116 continue;
117 }
118
119 // Don't take into account constraints too far apart in time, because the
120 // recording device could have moved too much
milind-ud62f80a2023-03-04 16:37:09 -0800121 if ((detection->time - last_detection->time) > max_dt) {
Milind Upadhyayec493912022-12-18 21:33:15 -0800122 continue;
123 }
124
milind-ud62f80a2023-03-04 16:37:09 -0800125 auto confidence = ComputeConfidence(*last_detection, *detection);
Milind Upadhyayec493912022-12-18 21:33:15 -0800126 target_constraints.emplace_back(
milind-ud62f80a2023-03-04 16:37:09 -0800127 ComputeTargetConstraint(*last_detection, *detection, confidence));
Milind Upadhyayec493912022-12-18 21:33:15 -0800128 }
129
130 return target_constraints;
131}
132
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800133TargetMapper::ConfidenceMatrix DataAdapter::ComputeConfidence(
milind-ud62f80a2023-03-04 16:37:09 -0800134 const TimestampedDetection &detection_start,
135 const TimestampedDetection &detection_end) {
Milind Upadhyay7c205222022-11-16 18:20:58 -0800136 constexpr size_t kX = 0;
137 constexpr size_t kY = 1;
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800138 constexpr size_t kZ = 2;
139 constexpr size_t kOrientation1 = 3;
140 constexpr size_t kOrientation2 = 4;
141 constexpr size_t kOrientation3 = 5;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800142
143 // Uncertainty matrix between start and end
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800144 TargetMapper::ConfidenceMatrix P = TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyay7c205222022-11-16 18:20:58 -0800145
146 {
147 // Noise for odometry-based robot position measurements
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800148 TargetMapper::ConfidenceMatrix Q_odometry =
149 TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyay7c205222022-11-16 18:20:58 -0800150 Q_odometry(kX, kX) = std::pow(0.045, 2);
151 Q_odometry(kY, kY) = std::pow(0.045, 2);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800152 Q_odometry(kZ, kZ) = std::pow(0.045, 2);
153 Q_odometry(kOrientation1, kOrientation1) = std::pow(0.01, 2);
154 Q_odometry(kOrientation2, kOrientation2) = std::pow(0.01, 2);
155 Q_odometry(kOrientation3, kOrientation3) = std::pow(0.01, 2);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800156
157 // Add uncertainty for robot position measurements from start to end
milind-ud62f80a2023-03-04 16:37:09 -0800158 int iterations = (detection_end.time - detection_start.time) /
159 frc971::controls::kLoopFrequency;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800160 P += static_cast<double>(iterations) * Q_odometry;
161 }
162
163 {
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800164 // Noise for vision-based target localizations. Multiplying this matrix by
milind-u6ff399f2023-03-24 18:33:38 -0700165 // the distance from camera to target squared results in the uncertainty
166 // in that measurement
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800167 TargetMapper::ConfidenceMatrix Q_vision =
168 TargetMapper::ConfidenceMatrix::Zero();
Milind Upadhyayebf93ee2023-01-05 14:12:58 -0800169 Q_vision(kX, kX) = std::pow(0.045, 2);
170 Q_vision(kY, kY) = std::pow(0.045, 2);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800171 Q_vision(kZ, kZ) = std::pow(0.045, 2);
172 Q_vision(kOrientation1, kOrientation1) = std::pow(0.02, 2);
173 Q_vision(kOrientation2, kOrientation2) = std::pow(0.02, 2);
174 Q_vision(kOrientation3, kOrientation3) = std::pow(0.02, 2);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800175
176 // Add uncertainty for the 2 vision measurements (1 at start and 1 at end)
milind-ufbc5c812023-04-06 21:24:29 -0700177 P += Q_vision * std::pow(detection_start.distance_from_camera *
178 (1.0 + FLAGS_distortion_noise_scalar *
179 detection_start.distortion_factor),
180 2);
181 P += Q_vision * std::pow(detection_end.distance_from_camera *
182 (1.0 + FLAGS_distortion_noise_scalar *
183 detection_end.distortion_factor),
184 2);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800185 }
186
187 return P.inverse();
188}
189
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800190ceres::examples::Constraint3d DataAdapter::ComputeTargetConstraint(
Milind Upadhyay7c205222022-11-16 18:20:58 -0800191 const TimestampedDetection &target_detection_start,
Milind Upadhyay7c205222022-11-16 18:20:58 -0800192 const TimestampedDetection &target_detection_end,
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800193 const TargetMapper::ConfidenceMatrix &confidence) {
Milind Upadhyay7c205222022-11-16 18:20:58 -0800194 // Compute the relative pose (constraint) between the two targets
195 Eigen::Affine3d H_targetstart_targetend =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800196 target_detection_start.H_robot_target.inverse() *
Milind Upadhyay7c205222022-11-16 18:20:58 -0800197 target_detection_end.H_robot_target;
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800198 ceres::examples::Pose3d target_constraint =
199 PoseUtils::Affine3dToPose3d(H_targetstart_targetend);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800200
milind-uf3ab8ba2023-02-04 17:56:16 -0800201 const auto constraint_3d =
202 ceres::examples::Constraint3d{target_detection_start.id,
203 target_detection_end.id,
204 {target_constraint.p, target_constraint.q},
205 confidence};
206
207 VLOG(2) << "Computed constraint: " << constraint_3d;
208 return constraint_3d;
Milind Upadhyayec493912022-12-18 21:33:15 -0800209}
210
Milind Upadhyay7c205222022-11-16 18:20:58 -0800211TargetMapper::TargetMapper(
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800212 std::string_view target_poses_path,
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800213 const ceres::examples::VectorOfConstraints &target_constraints)
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800214 : target_constraints_(target_constraints) {
215 aos::FlatbufferDetachedBuffer<TargetMap> target_map =
216 aos::JsonFileToFlatbuffer<TargetMap>(target_poses_path);
217 for (const auto *target_pose_fbs : *target_map.message().target_poses()) {
milind-u3f5f83c2023-01-29 15:23:51 -0800218 target_poses_[target_pose_fbs->id()] =
219 PoseUtils::TargetPoseFromFbs(*target_pose_fbs).pose;
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800220 }
221}
222
223TargetMapper::TargetMapper(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800224 const ceres::examples::MapOfPoses &target_poses,
225 const ceres::examples::VectorOfConstraints &target_constraints)
Milind Upadhyay7c205222022-11-16 18:20:58 -0800226 : target_poses_(target_poses), target_constraints_(target_constraints) {}
227
228std::optional<TargetMapper::TargetPose> TargetMapper::GetTargetPoseById(
229 std::vector<TargetMapper::TargetPose> target_poses, TargetId target_id) {
230 for (auto target_pose : target_poses) {
231 if (target_pose.id == target_id) {
232 return target_pose;
233 }
234 }
235
236 return std::nullopt;
237}
238
Jim Ostrowski49be8232023-03-23 01:00:14 -0700239std::optional<TargetMapper::TargetPose> TargetMapper::GetTargetPoseById(
milind-u2ab4db12023-03-25 21:59:23 -0700240 TargetId target_id) const {
Jim Ostrowski49be8232023-03-23 01:00:14 -0700241 if (target_poses_.count(target_id) > 0) {
milind-u2ab4db12023-03-25 21:59:23 -0700242 return TargetMapper::TargetPose{target_id, target_poses_.at(target_id)};
Jim Ostrowski49be8232023-03-23 01:00:14 -0700243 }
244
245 return std::nullopt;
246}
247
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800248// Taken from ceres/examples/slam/pose_graph_3d/pose_graph_3d.cc
249// Constructs the nonlinear least squares optimization problem from the pose
250// graph constraints.
Milind Upadhyay7c205222022-11-16 18:20:58 -0800251void TargetMapper::BuildOptimizationProblem(
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800252 const ceres::examples::VectorOfConstraints &constraints,
253 ceres::examples::MapOfPoses *poses, ceres::Problem *problem) {
254 CHECK(poses != nullptr);
255 CHECK(problem != nullptr);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800256 if (constraints.empty()) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800257 LOG(INFO) << "No constraints, no problem to optimize.";
Milind Upadhyay7c205222022-11-16 18:20:58 -0800258 return;
259 }
260
milind-u13ff1a52023-01-22 17:10:49 -0800261 ceres::LossFunction *loss_function = new ceres::HuberLoss(2.0);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800262 ceres::LocalParameterization *quaternion_local_parameterization =
263 new ceres::EigenQuaternionParameterization;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800264
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800265 for (ceres::examples::VectorOfConstraints::const_iterator constraints_iter =
266 constraints.begin();
Milind Upadhyay7c205222022-11-16 18:20:58 -0800267 constraints_iter != constraints.end(); ++constraints_iter) {
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800268 const ceres::examples::Constraint3d &constraint = *constraints_iter;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800269
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800270 ceres::examples::MapOfPoses::iterator pose_begin_iter =
Milind Upadhyay7c205222022-11-16 18:20:58 -0800271 poses->find(constraint.id_begin);
272 CHECK(pose_begin_iter != poses->end())
273 << "Pose with ID: " << constraint.id_begin << " not found.";
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800274 ceres::examples::MapOfPoses::iterator pose_end_iter =
Milind Upadhyay7c205222022-11-16 18:20:58 -0800275 poses->find(constraint.id_end);
276 CHECK(pose_end_iter != poses->end())
277 << "Pose with ID: " << constraint.id_end << " not found.";
278
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800279 const Eigen::Matrix<double, 6, 6> sqrt_information =
Milind Upadhyay7c205222022-11-16 18:20:58 -0800280 constraint.information.llt().matrixL();
281 // Ceres will take ownership of the pointer.
282 ceres::CostFunction *cost_function =
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800283 ceres::examples::PoseGraph3dErrorTerm::Create(constraint.t_be,
284 sqrt_information);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800285
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800286 problem->AddResidualBlock(cost_function, loss_function,
287 pose_begin_iter->second.p.data(),
288 pose_begin_iter->second.q.coeffs().data(),
289 pose_end_iter->second.p.data(),
290 pose_end_iter->second.q.coeffs().data());
291
292 problem->SetParameterization(pose_begin_iter->second.q.coeffs().data(),
293 quaternion_local_parameterization);
294 problem->SetParameterization(pose_end_iter->second.q.coeffs().data(),
295 quaternion_local_parameterization);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800296 }
297
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800298 // The pose graph optimization problem has six DOFs that are not fully
milind-u3f5f83c2023-01-29 15:23:51 -0800299 // constrained. This is typically referred to as gauge freedom. You can
300 // apply a rigid body transformation to all the nodes and the optimization
301 // problem will still have the exact same cost. The Levenberg-Marquardt
302 // algorithm has internal damping which mitigates this issue, but it is
303 // better to properly constrain the gauge freedom. This can be done by
304 // setting one of the poses as constant so the optimizer cannot change it.
milind-u6ff399f2023-03-24 18:33:38 -0700305 CHECK_NE(poses->count(FLAGS_frozen_target_id), 0ul)
306 << "Got no poses for frozen target id " << FLAGS_frozen_target_id;
307 ceres::examples::MapOfPoses::iterator pose_start_iter =
308 poses->find(FLAGS_frozen_target_id);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800309 CHECK(pose_start_iter != poses->end()) << "There are no poses.";
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800310 problem->SetParameterBlockConstant(pose_start_iter->second.p.data());
311 problem->SetParameterBlockConstant(pose_start_iter->second.q.coeffs().data());
Milind Upadhyay7c205222022-11-16 18:20:58 -0800312}
313
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800314// Taken from ceres/examples/slam/pose_graph_3d/pose_graph_3d.cc
Milind Upadhyay7c205222022-11-16 18:20:58 -0800315bool TargetMapper::SolveOptimizationProblem(ceres::Problem *problem) {
316 CHECK_NOTNULL(problem);
317
318 ceres::Solver::Options options;
319 options.max_num_iterations = FLAGS_max_num_iterations;
320 options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
Jim Ostrowski49be8232023-03-23 01:00:14 -0700321 options.minimizer_progress_to_stdout = true;
Milind Upadhyay7c205222022-11-16 18:20:58 -0800322
323 ceres::Solver::Summary summary;
324 ceres::Solve(options, problem, &summary);
325
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800326 LOG(INFO) << summary.FullReport() << '\n';
Milind Upadhyay7c205222022-11-16 18:20:58 -0800327
328 return summary.IsSolutionUsable();
329}
330
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800331void TargetMapper::Solve(std::string_view field_name,
332 std::optional<std::string_view> output_dir) {
Milind Upadhyay7c205222022-11-16 18:20:58 -0800333 ceres::Problem problem;
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800334 BuildOptimizationProblem(target_constraints_, &target_poses_, &problem);
Milind Upadhyay7c205222022-11-16 18:20:58 -0800335
336 CHECK(SolveOptimizationProblem(&problem))
337 << "The solve was not successful, exiting.";
338
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800339 auto map_json = MapToJson(field_name);
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800340 VLOG(1) << "Solved target poses: " << map_json;
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800341
342 if (output_dir.has_value()) {
343 std::string output_path =
344 absl::StrCat(output_dir.value(), "/", field_name, ".json");
345 LOG(INFO) << "Writing map to file: " << output_path;
346 aos::util::WriteStringToFileOrDie(output_path, map_json);
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800347 }
348}
349
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800350std::string TargetMapper::MapToJson(std::string_view field_name) const {
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800351 flatbuffers::FlatBufferBuilder fbb;
352
353 // Convert poses to flatbuffers
354 std::vector<flatbuffers::Offset<TargetPoseFbs>> target_poses_fbs;
355 for (const auto &[id, pose] : target_poses_) {
milind-u3f5f83c2023-01-29 15:23:51 -0800356 target_poses_fbs.emplace_back(
357 PoseUtils::TargetPoseToFbs(TargetPose{.id = id, .pose = pose}, &fbb));
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800358 }
359
Milind Upadhyay05652cb2022-12-07 20:51:51 -0800360 const auto field_name_offset = fbb.CreateString(field_name);
361 flatbuffers::Offset<TargetMap> target_map_offset = CreateTargetMap(
362 fbb, fbb.CreateVector(target_poses_fbs), field_name_offset);
Milind Upadhyaycd677a32022-12-04 13:06:43 -0800363
364 return aos::FlatbufferToJson(
365 flatbuffers::GetMutableTemporaryPointer(fbb, target_map_offset),
366 {.multi_line = true});
Milind Upadhyay7c205222022-11-16 18:20:58 -0800367}
368
milind-ufbc5c812023-04-06 21:24:29 -0700369} // namespace frc971::vision
370
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800371std::ostream &operator<<(std::ostream &os, ceres::examples::Pose3d pose) {
milind-ufbc5c812023-04-06 21:24:29 -0700372 auto rpy = frc971::vision::PoseUtils::QuaternionToEulerAngles(pose.q);
Milind Upadhyayc5beba12022-12-17 17:41:20 -0800373 os << absl::StrFormat(
374 "{x: %.3f, y: %.3f, z: %.3f, roll: %.3f, pitch: "
375 "%.3f, yaw: %.3f}",
376 pose.p(0), pose.p(1), pose.p(2), rpy(0), rpy(1), rpy(2));
377 return os;
378}
379
380std::ostream &operator<<(std::ostream &os,
381 ceres::examples::Constraint3d constraint) {
382 os << absl::StrFormat("{id_begin: %d, id_end: %d, pose: ",
383 constraint.id_begin, constraint.id_end)
384 << constraint.t_be << "}";
385 return os;
386}