My Project 1.0
Holistic Fusion: A Generic Framework for Multimodal Sensor Fusion
Loading...
Searching...
No Matches
AnymalEstimator.cpp
Go to the documentation of this file.
1/*
2Copyright 2024 by Julian Nubert, Robotic Systems Lab, ETH Zurich.
3All rights reserved.
4This file is released under the "BSD-3-Clause License".
5Please see the LICENSE file that has been included as part of this package.
6 */
7
8// Implementation
10
11// Project
13
14// Workspace
22
23namespace anymal_se {
24
25// Constexpr from header
26constexpr std::array<const char*, 4> AnymalEstimator::legNames_;
27
28AnymalEstimator::AnymalEstimator(const std::shared_ptr<ros::NodeHandle>& privateNodePtr) : holistic_fusion::HolisticFusionRos(privateNodePtr) {
29 REGULAR_COUT << GREEN_START << " AnymalEstimatorGraph-Constructor called." << COLOR_END << std::endl;
30
31 // Configurations ----------------------------
32 // Static transforms
33 staticTransformsPtr_ = std::make_shared<AnymalStaticTransforms>(privateNodePtr);
34
35 // Set up
37}
38
40 REGULAR_COUT << GREEN_START << " AnymalEstimator-Setup called." << COLOR_END << std::endl;
41
42 // Read parameters ----------------------------
44
45 // Super class
46 HolisticFusionRos::setup(staticTransformsPtr_);
47
48 // Wait for static transforms ----------------------------
49 staticTransformsPtr_->findTransformations();
50
51 // Publishers ----------------------------
53
54 // Subscribers ----------------------------
56
57 // Messages ----------------------------
59
60 // Services ----------------------------
62}
63
65 // Paths
66 pubMeasMapLioPath_ = privateNode.advertise<nav_msgs::Path>("/holistic_fusion/measLiDAR_path_map_lidar", ROS_QUEUE_SIZE);
67 pubMeasWorldGnssPath_ = privateNode.advertise<nav_msgs::Path>("/holistic_fusion/measGnss_path_world_gnss", ROS_QUEUE_SIZE);
68
69 // Markers
70 pubFootContactMarkers_ = privateNode.advertise<visualization_msgs::MarkerArray>("/holistic_fusion/foot_contact_markers", ROS_QUEUE_SIZE);
71}
72
74 // GNSS
76 subGnssUnary_ = privateNode.subscribe<sensor_msgs::NavSatFix>("/gnss_topic", ROS_QUEUE_SIZE, &AnymalEstimator::gnssUnaryCallback_, this,
77 ros::TransportHints().tcpNoDelay());
78 REGULAR_COUT << " Initialized Gnss subscriber with topic: " << subGnssUnary_.getTopic() << std::endl;
79 }
80
81 // LiDAR Odometry
82 // Unary
83 if (useLioUnaryFlag_) {
84 subLioUnary_ = privateNode.subscribe<nav_msgs::Odometry>("/lidar_odometry_topic", ROS_QUEUE_SIZE, &AnymalEstimator::lidarUnaryCallback_,
85 this, ros::TransportHints().tcpNoDelay());
86
87 REGULAR_COUT << COLOR_END << " Initialized LiDAR Unary Factor Odometry subscriber with topic: " << subLioUnary_.getTopic() << std::endl;
88 }
89 // Between
91 subLioBetween_ = privateNode.subscribe<nav_msgs::Odometry>(
92 "/lidar_odometry_topic", ROS_QUEUE_SIZE, &AnymalEstimator::lidarBetweenCallback_, this, ros::TransportHints().tcpNoDelay());
93 REGULAR_COUT << COLOR_END << " Initialized LiDAR Odometry subscriber with topic: " << subLioBetween_.getTopic() << std::endl;
94 }
95
96 // Legged Odometry
97 // Between
99 subLeggedBetween_ = privateNode.subscribe<geometry_msgs::PoseWithCovarianceStamped>(
100 "/legged_odometry_pose_topic", ROS_QUEUE_SIZE, &AnymalEstimator::leggedBetweenCallback_, this, ros::TransportHints().tcpNoDelay());
101 REGULAR_COUT << COLOR_END << " Initialized Legged Odometry subscriber with topic: " << subLeggedBetween_.getTopic() << std::endl;
102 }
103 // Unary
106 privateNode.subscribe<nav_msgs::Odometry>("/legged_odometry_odom_topic", ROS_QUEUE_SIZE,
107 &AnymalEstimator::leggedVelocityUnaryCallback_, this, ros::TransportHints().tcpNoDelay());
109 << " Initialized Legged Velocity Unary Factor Odometry subscriber with topic: " << subLeggedVelocityUnary_.getTopic()
110 << std::endl;
111 }
112 // Kinematics
114 subLeggedKinematics_ = privateNode.subscribe<holistic_fusion_anymal_msgs::AnymalState>(
115 "/anymal_state_topic", ROS_QUEUE_SIZE, &AnymalEstimator::leggedKinematicsCallback_, this, ros::TransportHints().tcpNoDelay());
116 REGULAR_COUT << COLOR_END << " Initialized Legged Kinematics subscriber with topic: " << subLeggedKinematics_.getTopic() << std::endl;
117 }
118}
119
121 // Path
122 measLio_mapLidarPathPtr_ = nav_msgs::PathPtr(new nav_msgs::Path);
123 measGnss_worldGnssPathPtr_ = nav_msgs::PathPtr(new nav_msgs::Path);
124}
125
127 // Nothing
128 // TODO: add soft reset of the graph for on-the-go re-init.
129}
130
131// Offline Optimization Service
132bool AnymalEstimator::srvOfflineSmootherOptimizeCallback(holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Request& req,
133 holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Response& res) {
134 // Call super class
136
137 // if GNSS is used at all, also log the reference frame
138 if (gnssCallbackCounter_ > 0) {
139 // Get the reference coordinates of the GNSS handler
140 const double referenceLatitude = gnssHandlerPtr_->getGnssReferenceLatitude();
141 const double referenceLongitude = gnssHandlerPtr_->getGnssReferenceLongitude();
142 const double referenceAltitude = gnssHandlerPtr_->getGnssReferenceAltitude();
143 const double referenceHeading = gnssHandlerPtr_->getGnssReferenceHeading();
144 REGULAR_COUT << " GNSS reference (latitude, longitude, altitude, heading): " << referenceLatitude << ", " << referenceLongitude << ", "
145 << referenceAltitude << ", " << referenceHeading << std::endl;
146
147 // File streams
148 std::map<std::string, std::ofstream> fileStreams;
149
150 // Write T_totalStation_totalStationOld_ to file ----------------------------
151 // Get time string by finding latest directory in optimizationResultLoggingPath
153 if (timeString.empty()) {
154 REGULAR_COUT << " Could not find latest directory in " << optimizationResultLoggingPath << std::endl;
155 return false;
156 } else {
157 REGULAR_COUT << " Found latest directory in " << optimizationResultLoggingPath << ": " << timeString << std::endl;
158 }
159 // Create file stream
160 std::string transformIdentifier = "gnss_reference_lat_lon_alt";
161 holistic_fusion::createLatLonAltCsvFileStream(fileStreams, optimizationResultLoggingPath, transformIdentifier, timeString);
162 // Add to file
163 holistic_fusion::writeLatLonAltToCsvFile(fileStreams, Eigen::Vector3d(referenceLatitude, referenceLongitude, referenceAltitude),
164 transformIdentifier, ros::Time::now().toSec());
165 REGULAR_COUT << " Wrote GNSS reference (latitude, longitude, altitude) coordinates to file." << std::endl;
166
167 // If optional GNSS ENU coordinates are logged, write them to pose file
169 transformIdentifier = "gnss_enu_trajectory";
170 holistic_fusion::createPose3CsvFileStream(fileStreams, optimizationResultLoggingPath, transformIdentifier, timeString, false);
171 // Iterate over all optional GNSS ENU coordinates
172 int index = 0;
173 for (const auto& optionalGnssEnuCoordinates : optionalGnssEnuCoordinatesTrajectory_) {
174 // Create Isometry3d with identity rotation
175 Eigen::Isometry3d optionalGnssEnuCoordinatesIsometry = Eigen::Isometry3d::Identity();
176 optionalGnssEnuCoordinatesIsometry.translation() = optionalGnssEnuCoordinates;
177 // Add to file
178 holistic_fusion::writePose3ToCsvFile(fileStreams, optionalGnssEnuCoordinatesIsometry, transformIdentifier,
180 index++;
181 }
182 }
183 }
184
185 // Return
186 return success;
187}
188
189// Priority: 1
190void AnymalEstimator::gnssUnaryCallback_(const sensor_msgs::NavSatFix::ConstPtr& gnssMsgPtr) {
191 // Counter
193
194 // Convert to Eigen
195 Eigen::Vector3d gnssCoord = Eigen::Vector3d(gnssMsgPtr->latitude, gnssMsgPtr->longitude, gnssMsgPtr->altitude);
196 Eigen::Vector3d estStdDevXYZ(sqrt(gnssMsgPtr->position_covariance[0]), sqrt(gnssMsgPtr->position_covariance[4]),
197 sqrt(gnssMsgPtr->position_covariance[8]));
198
199 // Initialize GNSS Handler
200 if (gnssCallbackCounter_ < NUM_GNSS_CALLBACKS_UNTIL_START) { // Accumulate measurements
201 // Wait until measurements got accumulated
202 accumulatedGnssCoordinates_ += gnssCoord;
203 if ((gnssCallbackCounter_ % 10) == 0) {
204 REGULAR_COUT << " NOT ENOUGH GNSS MESSAGES ARRIVED!" << std::endl;
205 }
206 return;
207 } else if (gnssCallbackCounter_ == NUM_GNSS_CALLBACKS_UNTIL_START) { // Initialize GNSS Handler
209 REGULAR_COUT << " GNSS Handler initialized." << std::endl;
210 return;
211 }
212
213 // Convert to Cartesian Coordinates
214 Eigen::Vector3d W_t_W_Gnss;
215 // gnssHandlerPtr_->convertNavSatToPosition(gnssCoord, W_t_W_Gnss);
216 gnssHandlerPtr_->convertNavSatToPositionLV03(gnssCoord, W_t_W_Gnss);
217 std::string fixedFrame = staticTransformsPtr_->getWorldFrame(); // Alias
218 // fixedFrame = "east_north_up";
219
220 // // For Debugging: Add Gaussian Noise with 0.1m std deviation
221 // // Random number generator
222 // std::default_random_engine generator(std::chrono::system_clock::now().time_since_epoch().count());
223 // // Add noise
224 // W_t_W_Gnss +=
225 // Eigen::Vector3d(std::normal_distribution<double>(0.0, 0.1)(generator), std::normal_distribution<double>(0.0, 0.1)(generator),
226 // std::normal_distribution<double>(0.0, 0.1)(generator));
227 // // Add to assumed standard deviation (\sigma_{GNSS+noise} = \sqrt{\sigma_{GNSS}^2 + \sigma_{noise}^2})
228 // for (int i = 0; i < 3; ++i) {
229 // estStdDevXYZ(i) = sqrt(estStdDevXYZ(i) * estStdDevXYZ(i) + 0.1 * 0.1);
230 // }
231
232 // Initial world yaw initialization options
233 // Case 1: Initialization
235 // a: Default
236 double initYaw_W_Base{0.0}; // Default is 0 yaw
237 // b: From file
238 if (gnssHandlerPtr_->getUseYawInitialGuessFromFile()) {
239 initYaw_W_Base = gnssHandlerPtr_->getGlobalYawDegFromFile() / 180.0 * M_PI;
240 }
241 // c: From alignment
242 else if (gnssHandlerPtr_->getUseYawInitialGuessFromAlignment()) {
243 // Adding the GNSS measurement
244 trajectoryAlignmentHandler_->addR3Position(W_t_W_Gnss, gnssMsgPtr->header.stamp.toSec());
245 // In radians
246 Eigen::Isometry3d T_W_Base = Eigen::Isometry3d::Identity();
247 if (!(trajectoryAlignmentHandler_->alignTrajectories(initYaw_W_Base, T_W_Base))) {
248 if (gnssCallbackCounter_ % 10 == 0) {
249 REGULAR_COUT << YELLOW_START << "Trajectory alignment not ready. Waiting for more motion." << COLOR_END << std::endl;
250 }
251 return;
252 }
253 REGULAR_COUT << GREEN_START << "Trajectory Alignment Successful. Obtained Yaw Value of T_W_Base (deg): " << COLOR_END
254 << 180.0 * initYaw_W_Base / M_PI << std::endl;
255 }
256
257 // Actual Initialization
258 if (this->initYawAndPositionInWorld(initYaw_W_Base, W_t_W_Gnss,
260 dynamic_cast<AnymalStaticTransforms*>(staticTransformsPtr_.get())->getGnssFrame())) {
261 REGULAR_COUT << GREEN_START << " GNSS initialization of yaw and position successful." << std::endl;
262
263 } else {
264 REGULAR_COUT << RED_START << " GNSS initialization of yaw and position failed." << std::endl;
265 }
266 } else { // Case 2: Already initialized --> Unary factor
267 const std::string& gnssFrameName = dynamic_cast<AnymalStaticTransforms*>(staticTransformsPtr_.get())->getGnssFrame(); // Alias
268 // Measurement
270 "GnssPosition", int(gnssRate_), gnssFrameName, gnssFrameName + sensorFrameCorrectedNameId, holistic_fusion::RobustNorm::None(),
271 gnssMsgPtr->header.stamp.toSec(), gnssPositionOutlierThreshold_, W_t_W_Gnss, estStdDevXYZ, fixedFrame,
272 staticTransformsPtr_->getWorldFrame());
273 this->addUnaryPosition3AbsoluteMeasurement(meas_W_t_W_Gnss);
274 }
275
276 // Add _gmsf to the frame
277 if (fixedFrame != staticTransformsPtr_->getWorldFrame()) {
278 fixedFrame += referenceFrameAlignedNameId;
279 }
280
282 addToPathMsg(measGnss_worldGnssPathPtr_, fixedFrame, gnssMsgPtr->header.stamp, W_t_W_Gnss, graphConfigPtr_->imuBufferLength_ * 4);
285
286 // Potentially log the GNSS ENU coordinates
288 optionalGnssEnuCoordinatesTimeStamps_.push_back(gnssMsgPtr->header.stamp.toSec());
289 optionalGnssEnuCoordinatesTrajectory_.push_back(W_t_W_Gnss);
290 }
291}
292
293// Priority: 2
294void AnymalEstimator::lidarUnaryCallback_(const nav_msgs::Odometry::ConstPtr& odomLidarPtr) {
295 // Counter
297
298 // Prepare Data
299 Eigen::Isometry3d lio_T_M_Lk = Eigen::Isometry3d::Identity();
300 holistic_fusion::odomMsgToEigen(*odomLidarPtr, lio_T_M_Lk.matrix());
301 double lidarUnaryTimeK = odomLidarPtr->header.stamp.toSec();
302
303 if (useGnssUnaryFlag_ && gnssHandlerPtr_->getUseYawInitialGuessFromAlignment()) {
304 trajectoryAlignmentHandler_->addSe3Position(lio_T_M_Lk.translation(), lidarUnaryTimeK);
305 }
306
307 // Measurement
308 const std::string& lioOdomFrameName = dynamic_cast<AnymalStaticTransforms*>(staticTransformsPtr_.get())->getLioOdometryFrame(); // alias
310 "Lidar_unary_6D", int(lioOdometryRate_), lioOdomFrameName, lioOdomFrameName + sensorFrameCorrectedNameId,
311 holistic_fusion::RobustNorm::None(), lidarUnaryTimeK, 1.0, lio_T_M_Lk, lioPoseUnaryNoise_, odomLidarPtr->header.frame_id,
313
315 return;
316 } else if (!areYawAndPositionInited()) { // Initializing if no GNSS
317 if (!useGnssUnaryFlag_) {
318 REGULAR_COUT << GREEN_START << " LiDAR odometry callback is setting global yaw, as it was not set so far." << COLOR_END << std::endl;
319 this->initYawAndPosition(unary6DMeasurement);
320 }
321 } else { // Already initialized --> unary factor
322 this->addUnaryPose3AbsoluteMeasurement(unary6DMeasurement);
323 }
324
325 // Visualization ----------------------------
326 // Add to path message
327 addToPathMsg(measLio_mapLidarPathPtr_, odomLidarPtr->header.frame_id + referenceFrameAlignedNameId, odomLidarPtr->header.stamp,
328 lio_T_M_Lk.translation(), graphConfigPtr_->imuBufferLength_ * 4);
329
330 // Publish Path
332}
333
334// Priority: 3
335void AnymalEstimator::lidarBetweenCallback_(const nav_msgs::Odometry::ConstPtr& odomLidarPtr) {
336 if (!areRollAndPitchInited()) {
337 return;
338 }
339
340 // Counter
342
343 // Convert
344 Eigen::Isometry3d lio_T_M_Lk = Eigen::Isometry3d::Identity();
345 holistic_fusion::odomMsgToEigen(*odomLidarPtr, lio_T_M_Lk.matrix());
346 // Get the time
347 double lidarBetweenTimeK = odomLidarPtr->header.stamp.toSec();
348
349 // At start
351 lio_T_M_Lkm1_ = lio_T_M_Lk;
352 lidarBetweenTimeKm1_ = lidarBetweenTimeK;
353 }
354
355 // Add to trajectory aligner if needed.
356 if (useGnssUnaryFlag_ && gnssHandlerPtr_->getUseYawInitialGuessFromAlignment()) {
357 trajectoryAlignmentHandler_->addSe3Position(lio_T_M_Lk.translation(), lidarBetweenTimeK);
358 }
359
360 // Frame Name
361 const std::string& lioOdomFrameName = dynamic_cast<AnymalStaticTransforms*>(staticTransformsPtr_.get())->getLioOdometryFrame(); // alias
362
363 // State Machine
365 return;
366 } else if (!areYawAndPositionInited()) { // Initializing
368 // Measurement
370 "Lidar_unary_6D", int(lioOdometryRate_), lioOdomFrameName, lioOdomFrameName + sensorFrameCorrectedNameId,
371 holistic_fusion::RobustNorm::None(), lidarBetweenTimeK, 1.0, lio_T_M_Lk, lioPoseUnaryNoise_);
372 // Add to graph
373 REGULAR_COUT << GREEN_START << " LiDAR odometry callback is setting global yaw, as it was not set so far." << COLOR_END << std::endl;
374 this->initYawAndPosition(unary6DMeasurement);
375 }
376 } else { // Already initialized --> Between factor
377 // Compute Delta
378 const Eigen::Isometry3d T_Lkm1_Lk = lio_T_M_Lkm1_.inverse() * lio_T_M_Lk;
379 // Create measurement
381 "Lidar_between_6D", int(lioOdometryRate_), lioOdomFrameName, lioOdomFrameName + sensorFrameCorrectedNameId,
383 // Add to graph
384 this->addBinaryPose3Measurement(delta6DMeasurement);
385 }
386 // Provide for next iteration
387 lio_T_M_Lkm1_ = lio_T_M_Lk;
388 lidarBetweenTimeKm1_ = lidarBetweenTimeK;
389
390 // Visualization ----------------------------
391 // Add to path message
392 addToPathMsg(measLio_mapLidarPathPtr_, staticTransformsPtr_->getWorldFrame(), odomLidarPtr->header.stamp, lio_T_M_Lk.translation(),
393 graphConfigPtr_->imuBufferLength_ * 4);
394
395 // Publish Path
397}
398
399// Priority: 4
400void AnymalEstimator::leggedBetweenCallback_(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr& leggedOdometryPoseKPtr) {
401 if (!areRollAndPitchInited()) {
402 return;
403 }
404
405 // Counter
407
408 // Eigen Type
409 Eigen::Isometry3d T_O_Bl_k = Eigen::Isometry3d::Identity();
410 holistic_fusion::geometryPoseToEigen(*leggedOdometryPoseKPtr, T_O_Bl_k.matrix());
411 double legOdometryTimeK = leggedOdometryPoseKPtr->header.stamp.toSec();
412
413 // At start
415 T_O_Bl_km1_ = T_O_Bl_k;
416 legOdometryTimeKm1_ = legOdometryTimeK;
417 return;
418 }
419
420 // Frame Name
421 const std::string& leggedOdometryFrameName =
423
424 // State Machine
427 // Measurement
429 "Leg_odometry_6D", int(leggedOdometryBetweenRate_), leggedOdometryFrameName, leggedOdometryFrameName + sensorFrameCorrectedNameId,
430 holistic_fusion::RobustNorm::None(), legOdometryTimeK, 1.0, T_O_Bl_k, legPoseBetweenNoise_);
431 // Add to graph
432 REGULAR_COUT << GREEN_START << " Legged odometry between callback is setting global yaw, as it was not set so far." << COLOR_END
433 << std::endl;
434 this->initYawAndPosition(unary6DMeasurement);
435 }
436 } else {
437 // Only add every 40th measurement
438 int measurementRate = static_cast<int>(leggedOdometryBetweenRate_) / leggedOdometryPoseDownsampleFactor_;
439 // Check
441 // Compute Delta
442 const Eigen::Isometry3d T_Bkm1_Bk = T_O_Bl_km1_.inverse() * T_O_Bl_k;
443 // Create measurement
445 "Leg_odometry_6D", measurementRate, leggedOdometryFrameName, leggedOdometryFrameName + sensorFrameCorrectedNameId,
447 // Add to graph
448 this->addBinaryPose3Measurement(delta6DMeasurement);
449
450 // Prepare for next iteration
451 T_O_Bl_km1_ = T_O_Bl_k;
452 legOdometryTimeKm1_ = legOdometryTimeK;
453 }
454 }
455}
456
457// Priority: 5
458void AnymalEstimator::leggedVelocityUnaryCallback_(const nav_msgs::Odometry ::ConstPtr& leggedOdometryKPtr) {
459 if (!areRollAndPitchInited()) {
460 return;
461 }
462
463 // Counter
465
468 // Measurement
470 "Leg_odometry_6D", int(leggedOdometryVelocityRate_),
473 holistic_fusion::RobustNorm::None(), leggedOdometryKPtr->header.stamp.toSec(), 1.0, Eigen::Isometry3d::Identity(),
475 // Add to graph
476 REGULAR_COUT << GREEN_START << " Legged odometry velocity callback is setting global yaw, as it was not set so far." << COLOR_END
477 << std::endl;
478 this->initYawAndPosition(unary6DMeasurement);
479 }
480 } else {
481 // Only add every nth measurement
482 int measurementRate = static_cast<int>(leggedOdometryVelocityRate_) / leggedOdometryVelocityDownsampleFactor_;
483
484 // Check
486 // Eigen Type
487 Eigen::Vector3d legVelocity = Eigen::Vector3d(leggedOdometryKPtr->twist.twist.linear.x, leggedOdometryKPtr->twist.twist.linear.y,
488 leggedOdometryKPtr->twist.twist.linear.z);
489
490 // Alias
491 const std::string& leggedOdometryFrameName =
493
494 // Create the unary measurement
496 "LegVelocityUnary", measurementRate, leggedOdometryFrameName, leggedOdometryFrameName + sensorFrameCorrectedNameId,
497 holistic_fusion::RobustNorm::None(), leggedOdometryKPtr->header.stamp.toSec(), 1.0, legVelocity, legVelocityUnaryNoise_);
498
499 // Add to graph
500 this->addUnaryVelocity3LocalMeasurement(legVelocityUnaryMeasurement);
501 }
502 }
503}
504
505void AnymalEstimator::leggedKinematicsCallback_(const holistic_fusion_anymal_msgs::AnymalState::ConstPtr& anymalStatePtr) {
506 if (!areRollAndPitchInited()) {
507 return;
508 }
509
510 // Counter
512
515 // Measurement
517 "Leg_odometry_6D", int(leggedKinematicsRate_),
520 holistic_fusion::RobustNorm::None(), anymalStatePtr->header.stamp.toSec(), 1.0, Eigen::Isometry3d::Identity(),
521 Eigen::Matrix<double, 6, 1>::Identity());
522 // Add to graph
523 REGULAR_COUT << GREEN_START << " Legged kinematics callback is setting global yaw, as it was not set so far." << COLOR_END
524 << std::endl;
525 this->initYawAndPosition(unary6DMeasurement);
526 }
527 }
528 // Normal Operation
529 else {
530 // Only add every nth measurement
531 int measurementRate = static_cast<int>(leggedKinematicsRate_) / leggedKinematicsDownsampleFactor_;
532
533 // Check whether it is time to add a measurement
535 // Alias
536 const std::string& leggedOdometryFrameName =
538
539 // Pose of the base in odom frame
540 Eigen::Isometry3d T_O_B = Eigen::Isometry3d::Identity();
541 holistic_fusion::geometryPoseToEigen(anymalStatePtr->pose.pose, T_O_B.matrix());
542
543 // Create the unary measurement for each foot (loop unrolling with constexpr arrays) ----------------------------
544 visualization_msgs::MarkerArray footContactMarkers = visualization_msgs::MarkerArray();
545 // Loop
546 for (int legIndex = 0; legIndex < legNames_.size(); ++legIndex) {
547 // Get contact state
548 bool footInContact = anymalStatePtr->contacts[legIndex].state;
549 // Foot name as specified in the message
550 const std::string legName = anymalStatePtr->contacts[legIndex].name;
551
552 // Check whether leg is in contact ----------------------------
553 if (footInContact) {
554 // Increase debounce counter
555 ++legInContactForNSteps_[legIndex];
556
557 // Check whether leg was at least N times in contact to count it as in contact --> debouncing
559 // If it was not in contact before
561 // Increase contact counter
562 ++legTotalContactsCounter_[legIndex];
563 // Print
564 if (graphConfigPtr_->verboseLevel_ > 1) {
565 REGULAR_COUT << " Leg " << legName << " came back into contact for the " << legTotalContactsCounter_[legIndex] << ". time."
566 << std::endl;
567 }
568 }
569
570 // Add measurement ----------------------------
571 // Position of foot in legged odometry frame
572 Eigen::Vector3d O_t_O_foot =
573 Eigen::Vector3d(anymalStatePtr->contacts[legIndex].position.x, anymalStatePtr->contacts[legIndex].position.y,
574 anymalStatePtr->contacts[legIndex].position.z);
575 Eigen::Vector3d B_t_B_foot = T_O_B.inverse() * O_t_O_foot;
576
577 // Create the unary measurement with contact counter
578 std::string legIdentifier = legName;
580 legIdentifier, measurementRate, leggedOdometryFrameName, leggedOdometryFrameName + sensorFrameCorrectedNameId,
581 holistic_fusion::RobustNorm::None(), anymalStatePtr->header.stamp.toSec(), 1.0, B_t_B_foot, // holistic_fusion::RobustNorm::Huber(1)
583
584 // Add to graph
585 this->addUnaryPosition3LandmarkMeasurement(footContactPositionMeasurement, legTotalContactsCounter_[legIndex]);
586
587 // Visualize foot contact in RViz
588 visualization_msgs::Marker footContactMarker;
589 createContactMarker(leggedOdometryFrameName, anymalStatePtr->header.stamp, B_t_B_foot, "foot_contact", legIndex,
590 Eigen::Vector3d(0.0, 1.0, 0.0), footContactMarker);
591 footContactMarkers.markers.push_back(footContactMarker);
592 }
593 }
594 // Leg not in contact ----------------------------
595 else if (legInContactForNSteps_[legIndex] > 0) {
596 // Set to 0
597 legInContactForNSteps_[legIndex] = 0;
598 // Print
599 if (graphConfigPtr_->verboseLevel_ > 1) {
600 REGULAR_COUT << " Leg " << legName << " lost contact. " << std::endl;
601 }
602 // Remove marker if it lost contact
603 visualization_msgs::Marker deleteMarker;
604 deleteMarker.header.frame_id = leggedOdometryFrameName;
605 deleteMarker.header.stamp = anymalStatePtr->header.stamp;
606 deleteMarker.ns = "foot_contact";
607 deleteMarker.id = legIndex;
608 deleteMarker.action = visualization_msgs::Marker::DELETE;
609 footContactMarkers.markers.push_back(deleteMarker);
610 }
611 }
612 // Publish foot contact markers
613 if (!footContactMarkers.markers.empty() && pubFootContactMarkers_.getNumSubscribers() > 0) {
614 pubFootContactMarkers_.publish(footContactMarkers);
615 }
616 }
617 }
618}
619
620} // namespace anymal_se
#define NUM_GNSS_CALLBACKS_UNTIL_START
#define REGULAR_COUT
#define COLOR_END
#define GREEN_START
#define RED_START
#define ROS_QUEUE_SIZE
#define YELLOW_START
Eigen::Isometry3d lio_T_M_Lkm1_
Eigen::Matrix< double, 6, 1 > initialSe3AlignmentNoise_
Eigen::Matrix< double, 6, 1 > legPoseBetweenNoise_
nav_msgs::PathPtr measGnss_worldGnssPathPtr_
nav_msgs::PathPtr measLio_mapLidarPathPtr_
void leggedBetweenCallback_(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &leggedOdometryPoseKPtr)
void initializePublishers(ros::NodeHandle &privateNode) override
ros::Subscriber subLeggedBetween_
void initializeSubscribers(ros::NodeHandle &privateNode) override
Eigen::Matrix< double, 3, 1 > legVelocityUnaryNoise_
void gnssUnaryCallback_(const sensor_msgs::NavSatFix::ConstPtr &gnssPtr)
void leggedVelocityUnaryCallback_(const nav_msgs::Odometry::ConstPtr &leggedOdometryKPtr)
std::shared_ptr< holistic_fusion::GnssHandler > gnssHandlerPtr_
static constexpr long legInContactDebounceThreshold_
bool srvOfflineSmootherOptimizeCallback(holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Request &req, holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Response &res) override
AnymalEstimator(const std::shared_ptr< ros::NodeHandle > &privateNodePtr)
void leggedKinematicsCallback_(const holistic_fusion_anymal_msgs::AnymalState::ConstPtr &anymalStatePtr)
Eigen::Isometry3d T_O_Bl_km1_
void lidarBetweenCallback_(const nav_msgs::Odometry::ConstPtr &lidar_odom_ptr)
static constexpr std::array< const char *, 4 > legNames_
Eigen::Matrix< double, 3, 1 > legKinematicsFootPositionUnaryNoise_
void initializeMessages(ros::NodeHandle &privateNode) override
ros::Subscriber subLeggedKinematics_
std::shared_ptr< holistic_fusion::TrajectoryAlignmentHandler > trajectoryAlignmentHandler_
std::vector< Eigen::Vector3d > optionalGnssEnuCoordinatesTrajectory_
std::vector< double > optionalGnssEnuCoordinatesTimeStamps_
std::array< long, legNames_.size()> legInContactForNSteps_
Eigen::Matrix< double, 6, 1 > lioSe3AlignmentRandomWalk_
Eigen::Vector3d accumulatedGnssCoordinates_
void readParams(const ros::NodeHandle &privateNode) override
ros::Publisher pubMeasWorldGnssPath_
ros::Subscriber subLeggedVelocityUnary_
Eigen::Matrix< double, 6, 1 > lioPoseUnaryNoise_
void initializeServices_(ros::NodeHandle &privateNode)
void lidarUnaryCallback_(const nav_msgs::Odometry::ConstPtr &lidar_odom_ptr)
ros::Publisher pubFootContactMarkers_
std::array< long, legNames_.size()> legTotalContactsCounter_
static constexpr bool logOptionalGnssEnuCoordinatesFlag_
void addBinaryPose3Measurement(const BinaryMeasurementXD< Eigen::Isometry3d, 6 > &F_T_F_S) override
Binary Measurements.
void addUnaryPosition3AbsoluteMeasurement(UnaryMeasurementXDAbsolute< Eigen::Vector3d, 3 > &R_t_R_S) override
void addUnaryPose3AbsoluteMeasurement(const UnaryMeasurementXDAbsolute< Eigen::Isometry3d, 6 > &R_T_R_S, const bool addToOnlineSmootherFlag=true) override
Unary Measurements / Absolute Measurements: In reference frame --> systematic drift.
void addUnaryVelocity3LocalMeasurement(UnaryMeasurementXD< Eigen::Vector3d, 3 > &S_v_F_S) override
void addUnaryPosition3LandmarkMeasurement(UnaryMeasurementXDLandmark< Eigen::Vector3d, 3 > &S_t_S_L, const int landmarkCreationCounter) override
Landmark Measurements: No systematic drift.
virtual bool srvOfflineSmootherOptimizeCallback(holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Request &req, holistic_fusion_ros_msgs::OfflineOptimizationTrigger::Response &res)
static void addToPathMsg(const nav_msgs::PathPtr &pathPtr, const std::string &frameName, const ros::Time &stamp, const Eigen::Vector3d &t, int maxBufferLength)
static void createContactMarker(const std::string &referenceFrameName, const ros::Time &stamp, const Eigen::Vector3d &position, const std::string &nameSpace, const int id, const Eigen::Vector3d &colorRgb, visualization_msgs::Marker &marker)
HolisticFusionRos(const std::shared_ptr< ros::NodeHandle > &privateNodePtr)
std::shared_ptr< GraphConfig > graphConfigPtr_
bool initYawAndPositionInWorld(const double yaw_W_frame1, const Eigen::Vector3d &W_t_W_frame2, const std::string &frame1, const std::string &frame2)
std::shared_ptr< StaticTransforms > staticTransformsPtr_
bool initYawAndPosition(const UnaryMeasurementXD< Eigen::Isometry3d, 6 > &unary6DMeasurement)
const std::string & getBaseLinkFrame()
Class to represent x-dimensional absolute unary measurement in the graph.
Class to represent x-dimensional landmark unary measurement in the graph.
Class to represent x-dimensional unary measurement in the graph.
void createPose3CsvFileStream(std::map< std::string, std::ofstream > &fileStreams, const std::string &savePath, const std::string &transformIdentifier, const std::string &timeString, const bool saveCovarianceFlag)
void createLatLonAltCsvFileStream(std::map< std::string, std::ofstream > &fileStreams, const std::string &savePath, const std::string &transformIdentifier, const std::string &timeString)
void odomMsgToEigen(const nav_msgs::Odometry &odomLidar, Eigen::Matrix4d &T)
void writeLatLonAltToCsvFile(std::map< std::string, std::ofstream > &fileStreams, const Eigen::Vector3d &latLonAlt, const std::string &transformIdentifier, const double timeStamp)
void geometryPoseToEigen(const geometry_msgs::Pose &pose, Eigen::Matrix4d &T)
std::string getLatestSubdirectory(const std::string &directoryPath)
void writePose3ToCsvFile(std::map< std::string, std::ofstream > &fileStreams, const Eigen::Isometry3d pose, const std::string &transformIdentifier, const double timeStamp, const bool saveCovarianceFlag, const std::optional< Eigen::Matrix< double, 6, 6 > > &optionalPoseCovarianceInWorldRos=std::nullopt)
static RobustNorm None()
Definition Measurement.h:33