My Project 1.0
Holistic Fusion: A Generic Framework for Multimodal Sensor Fusion
Loading...
Searching...
No Matches
GraphManager.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#define MIN_ITERATIONS_BEFORE_REMOVING_STATIC_TRANSFORM 200
9
10// C++
11#include <algorithm>
12#include <chrono>
13#include <cmath>
14#include <filesystem>
15#include <iomanip>
16#include <string>
17#include <utility>
18
19// IO
20#include <gtsam/slam/dataset.h>
21
22// Factors
23#include <gtsam/navigation/CombinedImuFactor.h>
24#include <gtsam/nonlinear/ExpressionFactorGraph.h>
25
26// Workspace
30// ISAM2
33// LM
36
37#define REGULAR_COUT std::cout << YELLOW_START << "GMSF-GraphManager" << COLOR_END
38
39namespace holistic_fusion {
40
41namespace {
42
43// Returns Earth rate in rad/s in a gravity-aligned, z-up frame. The full vector requires ENU alignment.
44gtsam::Vector3 earthRotationRateInWorld(double latitudeDeg, bool worldFrameNorthAligned) {
45 if (!std::isfinite(latitudeDeg) || std::fabs(latitudeDeg) > 90.0) {
46 throw std::runtime_error("GraphManager: noise_params.latitudeDeg must be finite and within [-90, 90], got " +
47 std::to_string(latitudeDeg));
48 }
49 constexpr double earthRotationRate = 7.2921159e-05; // [rad/s]
50 const double latitudeRad = latitudeDeg * M_PI / 180.0;
51 gtsam::Vector3 W_omega_IW(0.0, 0.0, earthRotationRate * std::sin(latitudeRad)); // vertical component, valid for any yaw
52 if (worldFrameNorthAligned) {
53 W_omega_IW.y() = earthRotationRate * std::cos(latitudeRad); // horizontal component, only if y points north (ENU)
54 }
55 return W_omega_IW;
56}
57
58} // namespace
59
60// Public --------------------------------------------------------------------
61GraphManager::GraphManager(std::shared_ptr<GraphConfig> graphConfigPtr, std::string imuFrame, std::string worldFrame)
62 : graphConfigPtr_(std::move(graphConfigPtr)), imuFrame_(std::move(imuFrame)), worldFrame_(std::move(worldFrame)) {
63 // Buffer
64 rtFactorGraphBufferPtr_ = std::make_shared<gtsam::NonlinearFactorGraph>();
65 batchFactorGraphBufferPtr_ = std::make_shared<gtsam::NonlinearFactorGraph>();
66 rtGraphValuesBufferPtr_ = std::make_shared<gtsam::Values>();
67 batchGraphValuesBufferPtr_ = std::make_shared<gtsam::Values>();
68 rtGraphKeysTimestampsMapBufferPtr_ = std::make_shared<std::map<gtsam::Key, double>>();
69 batchGraphKeysTimestampsMapBufferPtr_ = std::make_shared<std::map<gtsam::Key, double>>();
70
71 // Keys
72 timeToKeyBufferPtr_ = std::make_shared<TimeGraphKeyBuffer>(graphConfigPtr_->imuBufferLength_, graphConfigPtr_->verboseLevel_);
73
74 // Optimizers
75 // A. Real-time Optimizer
76 if (graphConfigPtr_->realTimeSmootherUseIsamFlag_) {
77 rtOptimizerPtr_ = std::make_shared<OptimizerIsam2FixedLag>(graphConfigPtr_);
78 } else {
79 rtOptimizerPtr_ = std::make_shared<OptimizerLMFixedLag>(graphConfigPtr_);
80 }
81 // B. Batch Optimizer
82 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
83 if (graphConfigPtr_->slowBatchSmootherUseIsamFlag_) {
84 batchOptimizerPtr_ = std::make_shared<OptimizerIsam2Batch>(graphConfigPtr_);
85 } else {
86 batchOptimizerPtr_ = std::make_shared<OptimizerLMBatch>(graphConfigPtr_);
87 }
88 }
89}
90
91// Initialization Interface ---------------------------------------------------
92bool GraphManager::initImuIntegrators(const double gravityValue) {
93 // Gravity direction definition
94 imuParamsPtr_ = gtsam::PreintegratedCombinedMeasurements::Params::MakeSharedU(gravityValue); // ROS convention
95
96 // Set noise and bias parameters
98 imuParamsPtr_->setAccelerometerCovariance(
99 gtsam::Matrix33::Identity() * std::pow(graphConfigPtr_->accNoiseDensity_, 2));
100 imuParamsPtr_->setIntegrationCovariance(gtsam::Matrix33::Identity(3, 3) *
101 std::pow(graphConfigPtr_->integrationNoiseDensity_, 2)); // error committed in integrating
102 // position from velocities
104 imuParamsPtr_->setGyroscopeCovariance(
105 gtsam::Matrix33::Identity() * std::pow(graphConfigPtr_->gyroNoiseDensity_, 2));
109 if (graphConfigPtr_->earthRotationCompensationFlag_) {
110 const gtsam::Vector3 W_omega_IW =
111 earthRotationRateInWorld(graphConfigPtr_->latitudeDeg_, graphConfigPtr_->worldFrameNorthAlignedFlag_);
112 imuParamsPtr_->setOmegaCoriolis(W_omega_IW);
113 REGULAR_COUT << " Earth rotation compensation enabled, latitude " << graphConfigPtr_->latitudeDeg_
114 << " deg, world frame north-aligned: " << (graphConfigPtr_->worldFrameNorthAlignedFlag_ ? "yes" : "no")
115 << ", W_omega_IW [rad/s]: " << W_omega_IW.transpose() << std::endl;
116 }
118 imuParamsPtr_->setBiasAccCovariance(gtsam::Matrix33::Identity(3, 3) * std::pow(graphConfigPtr_->accBiasRandomWalkNoiseDensity_, 2));
119 imuParamsPtr_->setBiasOmegaCovariance(gtsam::Matrix33::Identity(3, 3) * std::pow(graphConfigPtr_->gyroBiasRandomWalkNoiseDensity_, 2));
120
121 // Use previously defined prior for gyro
122 imuBiasPriorPtr_ = std::make_shared<gtsam::imuBias::ConstantBias>(graphConfigPtr_->accBiasPrior_, graphConfigPtr_->gyroBiasPrior_);
123
124 // Init Pre-integrators
125 imuStepPreintegratorPtr_ = std::make_shared<gtsam::PreintegratedCombinedMeasurements>(imuParamsPtr_, *imuBiasPriorPtr_);
126 imuBufferPreintegratorPtr_ = std::make_shared<gtsam::PreintegratedCombinedMeasurements>(imuParamsPtr_, *imuBiasPriorPtr_);
127 imuParamsPtr_->print("HolisticFusion-IMU PreIntegration Parameters:");
128 return true;
129}
130
131bool GraphManager::initPoseVelocityBiasGraph(const double timeStamp, const gtsam::Pose3& T_W_I0, const gtsam::Pose3& T_O_I0,
132 const gtsam::Vector3& imuAngularVelocity) {
133 // Create Prior factor ----------------------------------------------------
135 auto priorPoseNoise = gtsam::noiseModel::Diagonal::Sigmas(
136 (gtsam::Vector(6) << graphConfigPtr_->initialOrientationStdDev_, graphConfigPtr_->initialOrientationStdDev_,
137 graphConfigPtr_->initialOrientationStdDev_, graphConfigPtr_->initialPositionStdDev_,
138 graphConfigPtr_->initialPositionStdDev_, graphConfigPtr_->initialPositionStdDev_)
139 .finished()); // rad,rad,rad,m, m, m
140 auto priorVelocityNoise = gtsam::noiseModel::Isotropic::Sigma(3, graphConfigPtr_->initialVelocityStdDev_); // m/s
141 auto priorBiasNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << graphConfigPtr_->initialAccBiasStdDev_, // m/s^2
142 graphConfigPtr_->initialAccBiasStdDev_, // m/s^2
143 graphConfigPtr_->initialAccBiasStdDev_, // m/s^2
144 graphConfigPtr_->initialGyroBiasStdDev_, // rad/s
145 graphConfigPtr_->initialGyroBiasStdDev_, // rad/s
146 graphConfigPtr_->initialGyroBiasStdDev_) // rad/s
147 .finished()); // acc, acc, acc, gyro, gyro, gyro
148
149 // Pre-allocate
150 gtsam::NonlinearFactorGraph newRtGraphFactors, newBatchGraphFactors;
151 gtsam::Values newRtGraphValues, newBatchGraphValues;
152 std::shared_ptr<std::map<gtsam::Key, double>> priorKeyTimestampMapPtr = std::make_shared<std::map<gtsam::Key, double>>();
153
154 // Looking up from IMU buffer --> acquire mutex (otherwise values for key might not be set)
155 {
156 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
157
158 // Initial estimate
159 gtsam::Values valuesEstimate;
160 REGULAR_COUT << " Initial Pose of imu in world frame: " << T_W_I0 << std::endl;
161 valuesEstimate.insert(gtsam::symbol_shorthand::X(propagatedStateKey_), T_W_I0);
162 REGULAR_COUT << " Initial velocity assumed to be: " << gtsam::Vector3(0, 0, 0) << std::endl;
163 valuesEstimate.insert(gtsam::symbol_shorthand::V(propagatedStateKey_), gtsam::Vector3(0, 0, 0));
164 REGULAR_COUT << " Initial bias set to: " << *imuBiasPriorPtr_ << std::endl;
165 valuesEstimate.insert(gtsam::symbol_shorthand::B(propagatedStateKey_), *imuBiasPriorPtr_);
167 rtGraphValuesBufferPtr_->insert(valuesEstimate);
168 batchGraphValuesBufferPtr_->insert(valuesEstimate); // TODO: Check whether velocity can be removed from batch
169 writeValueKeysToKeyTimeStampMap_(valuesEstimate, timeStamp, priorKeyTimestampMapPtr);
170
171 // Initialize graph -------------------------------------------------
172 rtFactorGraphBufferPtr_->resize(0);
174 rtFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::Pose3>>(
175 gtsam::symbol_shorthand::X(propagatedStateKey_), T_W_I0,
176 priorPoseNoise); // POSE - PriorFactor format is (key,value,matrix) value
177 batchFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::Pose3>>(
178 gtsam::symbol_shorthand::X(propagatedStateKey_), T_W_I0,
179 priorPoseNoise); // POSE - PriorFactor format is (key,value,matrix) value
180 // is same type as type of PriorFactor
181 rtFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::Vector3>>(gtsam::symbol_shorthand::V(propagatedStateKey_),
182 gtsam::Vector3(0, 0, 0),
183 priorVelocityNoise); // VELOCITY
184 batchFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::Vector3>>(gtsam::symbol_shorthand::V(propagatedStateKey_),
185 gtsam::Vector3(0, 0, 0),
186 priorVelocityNoise); // VELOCITY
187 rtFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::imuBias::ConstantBias>>(
188 gtsam::symbol_shorthand::B(propagatedStateKey_), *imuBiasPriorPtr_,
189 priorBiasNoise); // BIAS
190 batchFactorGraphBufferPtr_->emplace_shared<gtsam::PriorFactor<gtsam::imuBias::ConstantBias>>(
191 gtsam::symbol_shorthand::B(propagatedStateKey_), *imuBiasPriorPtr_,
192 priorBiasNoise); // BIAS
193
194 // Add to Time Key Buffer
195 timeToKeyBufferPtr_->addToBuffer(timeStamp, propagatedStateKey_);
196
197 // Copy over
198 // Rt
199 newRtGraphFactors = *rtFactorGraphBufferPtr_;
200 newRtGraphValues = *rtGraphValuesBufferPtr_;
201 rtFactorGraphBufferPtr_->resize(0);
203 // Batch
204 newBatchGraphFactors = *batchFactorGraphBufferPtr_;
205 newBatchGraphValues = *batchGraphValuesBufferPtr_;
208 }
209
211 // Rt Smoother
212 addFactorsToRtSmootherAndOptimize(newRtGraphFactors, newRtGraphValues, *priorKeyTimestampMapPtr, graphConfigPtr_, 0);
213 // Batch Smoother
214 addFactorsToBatchSmootherAndOptimize(newBatchGraphFactors, newBatchGraphValues, *priorKeyTimestampMapPtr, graphConfigPtr_);
215
216 // Update Current State ---------------------------------------------------
217 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
218 latestGraphStateKeyTime_ = timeStamp;
219 latestGraphStateKeyAngularVelocity_ = imuAngularVelocity;
220 optimizedGraphState_.updateNavStateAndBias(propagatedStateKey_, timeStamp, gtsam::NavState(T_W_I0, gtsam::Vector3(0, 0, 0)),
221 imuBiasPriorPtr_->correctGyroscope(imuAngularVelocity), *imuBiasPriorPtr_);
222 O_imuPropagatedState_ = gtsam::NavState(T_O_I0, gtsam::Vector3(0, 0, 0));
223 W_imuPropagatedState_ = gtsam::NavState(T_W_I0, gtsam::Vector3(0, 0, 0));
224 T_W_O_ = (T_W_I0.inverse() * T_O_I0).matrix();
225 return true;
226}
227
228// IMU at the core --------------------------------------------------------------
230 std::shared_ptr<SafeNavStateWithCovarianceAndBias>& newOptimizedNavStatePtr,
231 const std::shared_ptr<ImuBuffer>& imuBufferPtr, const double imuTimeK, bool createNewStateFlag) {
232 // Logging of latency
233 if (graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
234 latencyStartTime_ = std::chrono::high_resolution_clock::now();
235 }
236
237 // Looking up from IMU buffer --> acquire mutex (otherwise values for key might not be set)
238 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
239
240 // Part 1 (ALWAYS): Propagate state and imu measurement to pre-integrator -----------------------
241 // 1.1 Get last two measurements from buffer to determine dt
242 TimeToImuMap imuMeas;
243 imuBufferPtr->getLastTwoMeasurements(imuMeas);
244 propagatedImuSampleTime_ = imuTimeK;
245
246 // 1.2 Update IMU Pre-integrator
247 updateImuIntegrators_(imuMeas);
248
249 // 1.3 Predict propagated state via forward integration
250 // Previous pose
251 gtsam::Pose3 T_W_I_beforeInt = W_imuPropagatedState_.pose();
252 if (graphConfigPtr_->usingBiasForPreIntegrationFlag_) {
254 imuBufferPtr->integrateNavStateFromTimestamp(imuMeas.begin()->first, imuMeas.rbegin()->first, W_imuPropagatedState_,
255 optimizedGraphState_.imuBias(), graphConfigPtr_->W_gravityVector_);
256 } else {
258 imuBufferPtr->integrateNavStateFromTimestamp(imuMeas.begin()->first, imuMeas.rbegin()->first, W_imuPropagatedState_,
259 gtsam::imuBias::ConstantBias(), graphConfigPtr_->W_gravityVector_);
260 }
261
262 // 1.4 Propagate to state in odom
263 // Delta pose
264 const gtsam::Pose3 T_W_I_afterInt = W_imuPropagatedState_.pose();
265 const gtsam::Pose3 T_Ikm1_Ik = T_W_I_beforeInt.between(T_W_I_afterInt);
266 // Pose of IMU in Odom
267 gtsam::Pose3 T_O_I = O_imuPropagatedState_.pose() * T_Ikm1_Ik;
268 // Get Gravity Alignment right also in Odom frame
269 const Eigen::Matrix3d& R_W_I = T_W_I_afterInt.rotation().matrix();
270 Eigen::Matrix3d R_O_I = T_O_I.rotation().matrix();
271 R_O_I.row(2) = R_W_I.row(2); // Copy gravity direction
272 R_O_I.row(1) = R_O_I.row(2).cross(R_O_I.row(0)).normalized();
273 R_O_I.row(0) = R_O_I.row(1).cross(R_O_I.row(2)).normalized();
274 T_O_I = gtsam::Pose3(gtsam::Rot3(R_O_I), T_O_I.translation());
275 // Velocity
276 gtsam::Vector3 O_v_O_I = T_O_I.rotation() * W_imuPropagatedState_.bodyVelocity();
277 // Update the NavState
278 O_imuPropagatedState_ = gtsam::NavState(T_O_I.rotation(), T_O_I.translation(), O_v_O_I);
279
280 // 1.5 Fill in optimized state container
281 if (optimizedGraphState_.isOptimized()) {
282 newOptimizedNavStatePtr = std::make_shared<SafeNavStateWithCovarianceAndBias>(optimizedGraphState_);
283 } else {
284 newOptimizedNavStatePtr = nullptr;
285 }
286
287 // 1.6 Transformation between World and Odom
288 T_W_O_ = Eigen::Isometry3d((W_imuPropagatedState_.pose() * O_imuPropagatedState_.pose().inverse()).matrix());
289
290 // 1.7 Return pre-integrated state
291 gtsam::NavState& T_O_Ik_nav = O_imuPropagatedState_; // Alias
292 // Assign poses and velocities
293 returnPreIntegratedNavState.update(T_W_O_, Eigen::Isometry3d(T_O_Ik_nav.pose().matrix()), T_O_Ik_nav.bodyVelocity(),
294 optimizedGraphState_.imuBias().correctGyroscope(imuMeas.rbegin()->second.angularVelocity), imuTimeK,
295 false);
296
297 // Check whether new state can be created (in case fixed lag smoother has a short window)
298 if (createNewStateFlag && imuTimeK - lastOptimizedStateTime_ > graphConfigPtr_->realTimeSmootherLag_ - WORST_CASE_OPTIMIZATION_TIME &&
300 createNewStateFlag = false;
302 << " The current measurement would fall outside of the real-time smoother lag, hence skipping the creation of a new"
303 << "IMU measurement. Not creating new state." << COLOR_END << std::endl;
304 }
305
306 // Part 2 (OPTIONAL): Create new state and add IMU factor to graph ------------------------------
307 if (createNewStateFlag) {
308 // Get new key
309 const gtsam::Key oldKey = propagatedStateKey_;
310 const gtsam::Key newKey = newPropagatedStateKey_();
311 latestGraphStateKeyTime_ = imuTimeK;
312 latestGraphStateKeyAngularVelocity_ = imuMeas.rbegin()->second.angularVelocity;
313
314 // Add to time key buffer
315 timeToKeyBufferPtr_->addToBuffer(imuTimeK, newKey);
316
317 // Add IMU Factor to graph
318 gtsam::CombinedImuFactor imuFactor(gtsam::symbol_shorthand::X(oldKey), gtsam::symbol_shorthand::V(oldKey),
319 gtsam::symbol_shorthand::X(newKey), gtsam::symbol_shorthand::V(newKey),
320 gtsam::symbol_shorthand::B(oldKey), gtsam::symbol_shorthand::B(newKey), *imuStepPreintegratorPtr_);
322
323 // Add IMU values
324 gtsam::Values valuesEstimate;
325 valuesEstimate.insert(gtsam::symbol_shorthand::X(newKey), W_imuPropagatedState_.pose());
326 valuesEstimate.insert(gtsam::symbol_shorthand::V(newKey), W_imuPropagatedState_.velocity());
327 valuesEstimate.insert(gtsam::symbol_shorthand::B(newKey), optimizedGraphState_.imuBias());
328 rtGraphValuesBufferPtr_->insert(valuesEstimate);
329 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
330 batchGraphValuesBufferPtr_->insert(valuesEstimate);
331 }
332
333 // Add timestamps
335 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
337 }
338
339 // After adding this factor we can again empty the step integrator
340 // Reset IMU Step Pre-integration
341 if (graphConfigPtr_->usingBiasForPreIntegrationFlag_) {
342 imuStepPreintegratorPtr_->resetIntegrationAndSetBias(optimizedGraphState_.imuBias());
343 } else {
344 imuStepPreintegratorPtr_->resetIntegrationAndSetBias(gtsam::imuBias::ConstantBias());
345 }
346 } // End of create new state
347
348 // Potentially log real-time state to container
349 if (graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
352 }
353
354 // Logging of latency
355 if (graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
356 latencyEndTime_ = std::chrono::high_resolution_clock::now();
357 // Duration in seconds
358 std::chrono::duration<double> latencyDuration = latencyEndTime_ - latencyStartTime_;
359 latencyContainer_[imuTimeK] = latencyDuration.count();
360 }
361}
362
363// Set T_W_F
364bool GraphManager::setInitialWorldFrameToFixedFrameTransform(const Eigen::Isometry3d& T_W_F, const std::string& fixedFrame) {
365 gtsamDynamicExpressionKeys_.get<gtsam::Pose3>()
366 .setInitialGuessForFramePair(worldFrame_, fixedFrame, gtsam::Pose3(T_W_F.matrix()));
367 std::cout << " Set world to fixed frame transform T_" << worldFrame_ << "_" << fixedFrame << " to: " << T_W_F.matrix()
368 << std::endl;
369 return true;
370}
371
372// Unary factors ----------------------------------------------------------------
373// Key Lookup
374bool GraphManager::hasReferenceFrameKeyframe(const std::string& worldFrame, const std::string& fixedFrame) {
375 auto& referenceFrameKeys = gtsamDynamicExpressionKeys_.get<gtsam::Pose3>();
376 std::lock_guard<std::mutex> modifyGraphKeysLock(referenceFrameKeys.mutex());
377 return referenceFrameKeys.isFramePairInDictionary(worldFrame, fixedFrame);
378}
379
380bool GraphManager::getUnaryFactorGeneralKey(gtsam::Key& returnedKey, double& returnedGraphTime, const UnaryMeasurement& unaryMeasurement) {
381 // Find the closest key in existing graph
382 // Case 1: Can't add immediately
383 if (!timeToKeyBufferPtr_->getClosestKeyAndTimestamp(returnedGraphTime, returnedKey, unaryMeasurement.measurementName(),
384 graphConfigPtr_->maxSearchDeviation_, unaryMeasurement.timeK())) {
385 // Measurement coming from the future
386 if (propagatedImuSampleTime_ - unaryMeasurement.timeK() < 0.0) { // Factor is coming from the future, hence add it to the buffer
387 // Not too far in the future --> add to buffer and add later
388 if (unaryMeasurement.timeK() - propagatedImuSampleTime_ < 4 * graphConfigPtr_->maxSearchDeviation_) {
389 // TODO: Add to buffer and return --> still add it until we are there
390 return true;
391 }
392 // Too far in the future --> do not add it
393 else {
394 std::cout << 1000 * (propagatedImuSampleTime_ - unaryMeasurement.timeK()) << std::endl;
395 std::cout << 1000 * (returnedGraphTime - unaryMeasurement.timeK()) << std::endl;
396 REGULAR_COUT << RED_START << " Factor coming from the future, AND time deviation of " << typeid(unaryMeasurement).name()
397 << " at key " << returnedKey << " is " << 1000 * std::abs(returnedGraphTime - unaryMeasurement.timeK())
398 << " ms, being larger than admissible deviation of " << 4 * 1000 * graphConfigPtr_->maxSearchDeviation_
399 << " ms. Not adding to graph." << COLOR_END << std::endl;
400 return false;
401 }
402 }
403 // Measurement coming from the past, but could not find suitable key
404 else { // Otherwise do not add it
405 REGULAR_COUT << RED_START << " Time deviation of " << typeid(unaryMeasurement).name() << " at key " << returnedKey << " is "
406 << 1000 * std::abs(returnedGraphTime - unaryMeasurement.timeK()) << " ms, being larger than admissible deviation of "
407 << 1000 * graphConfigPtr_->maxSearchDeviation_ << " ms. Not adding to graph." << COLOR_END << std::endl;
408 return false;
409 }
410 }
411 // Case 2: Can add immediately
412 else {
413 return true;
414 }
415}
416
417// Robust Aware Between factors ------------------------------------------------------------------------------------------------------
418gtsam::Key GraphManager::addPoseBetweenFactor(const gtsam::Pose3& deltaPose, const Eigen::Matrix<double, 6, 1>& poseBetweenNoiseDensity,
419 const double timeKm1, const double timeK, const double rate,
420 const RobustNormEnum& robustNormEnum, const double robustNormConstant) {
421 // Find corresponding keys in graph
422 const double maxLidarTimestampDistance = (1.0 / rate) + (2.0 * graphConfigPtr_->maxSearchDeviation_);
423 gtsam::Key closestKeyKm1, closestKeyK;
424 double keyTimeStampDistance{0.0};
425
426 if (!findGraphKeys_(closestKeyKm1, closestKeyK, keyTimeStampDistance, maxLidarTimestampDistance, timeKm1, timeK, "pose between")) {
427 REGULAR_COUT << RED_START << " Current propagated key: " << propagatedStateKey_ << " , PoseBetween factor not added between keys "
428 << closestKeyKm1 << " and " << closestKeyK << COLOR_END << std::endl;
429 return closestKeyK;
430 }
431
432 // Scale delta pose according to timeStampDistance
433 const double scale = keyTimeStampDistance / (timeK - timeKm1);
434 if (graphConfigPtr_->verboseLevel_ > 3) {
435 REGULAR_COUT << " Scaling factor in tangent space for pose between delta pose: " << scale << std::endl;
436 }
437 gtsam::Pose3 scaledDeltaPose = gtsam::Pose3::Expmap(scale * gtsam::Pose3::Logmap(deltaPose));
438
439 // Create noise model
440 assert(poseBetweenNoiseDensity.size() == 6);
441 auto noise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(poseBetweenNoiseDensity))); // rad,rad,rad,m,m,m
442 gtsam::noiseModel::Robust::shared_ptr robustErrorFunction;
443 // Pick Robust Error Function
444 switch (robustNormEnum) {
446 robustErrorFunction = gtsam::noiseModel::Robust::Create(gtsam::noiseModel::mEstimator::Huber::Create(robustNormConstant), noise);
447 break;
449 robustErrorFunction = gtsam::noiseModel::Robust::Create(gtsam::noiseModel::mEstimator::Cauchy::Create(robustNormConstant), noise);
450 break;
452 robustErrorFunction = gtsam::noiseModel::Robust::Create(gtsam::noiseModel::mEstimator::Tukey::Create(robustNormConstant), noise);
453 break;
455 robustErrorFunction =
456 gtsam::noiseModel::Robust::Create(gtsam::noiseModel::mEstimator::GemanMcClure::Create(robustNormConstant), noise);
457 break;
458 }
459
460 // Create pose between factor and add it
461 gtsam::BetweenFactor<gtsam::Pose3> poseBetweenFactor;
462 if (robustNormEnum == RobustNormEnum::None) {
463 poseBetweenFactor = gtsam::BetweenFactor<gtsam::Pose3>(gtsam::symbol_shorthand::X(closestKeyKm1),
464 gtsam::symbol_shorthand::X(closestKeyK), scaledDeltaPose, noise);
465 } else {
466 poseBetweenFactor = gtsam::BetweenFactor<gtsam::Pose3>(gtsam::symbol_shorthand::X(closestKeyKm1),
467 gtsam::symbol_shorthand::X(closestKeyK), scaledDeltaPose, robustErrorFunction);
468 }
469
470 // Write to graph
472
473 // Print summary
474 if (graphConfigPtr_->verboseLevel_ > 3) {
475 REGULAR_COUT << " Current propagated key: " << propagatedStateKey_ << ", " << YELLOW_START << " PoseBetween factor added between key "
476 << closestKeyKm1 << " and key " << closestKeyK << COLOR_END << std::endl;
477 }
478
479 return closestKeyK;
480}
481
482gtsam::NavState GraphManager::calculateNavStateAtGeneralKey(bool& computeSuccessfulFlag,
483 const std::shared_ptr<holistic_fusion::OptimizerBase> optimizerPtr,
484 const gtsam::Key& generalKey, const char* callingFunctionName) {
485 // Nav State (pose and velocity)
486 gtsam::Pose3 resultPose;
487 gtsam::Vector3 resultVelocity;
488 try {
489 resultPose = optimizerPtr->calculateEstimatedPose3(gtsam::symbol_shorthand::X(generalKey)); // auto result = mainGraphPtr_->estimate();
490 resultVelocity = optimizerPtr->calculateEstimatedVelocity3(gtsam::symbol_shorthand::V(generalKey));
491 computeSuccessfulFlag = true;
492 } catch (const std::out_of_range& outOfRangeExeception) {
493 REGULAR_COUT << "Out of Range exeception while optimizing graph: " << outOfRangeExeception.what() << '\n';
495 << " This happens if the measurement delay is larger than the graph-smootherLag, i.e. the optimized graph instances are "
496 "not connected. Increase the lag in this case."
497 << COLOR_END << std::endl;
498 REGULAR_COUT << RED_START << " CalculateNavStateAtKey called by " << callingFunctionName << COLOR_END << std::endl;
499 computeSuccessfulFlag = false;
500 }
501 return gtsam::NavState(resultPose, resultVelocity);
502}
503
504Eigen::Matrix<double, 6, 6> GraphManager::calculatePoseCovarianceAtKeyInWorldFrame(std::shared_ptr<holistic_fusion::OptimizerBase> graphPtr,
505 const gtsam::Key& graphKey,
506 const char* callingFunctionName,
507 std::optional<const gtsam::NavState> optionalNavState) {
508 // Pose Covariance in Tangent Space
509 std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
510 gtsam::Matrix66 resultPoseCovarianceBodyFrame = graphPtr->calculateMarginalCovarianceMatrixAtKey(graphKey);
511 std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
512 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
513 if (!optionalNavState.has_value() && duration > 400) {
514 std::cout << RED_START << "Time to calculate marginal covariance: " << duration << " microseconds, which is too much." << std::endl;
515 }
516 // Transform covariance from I_S_I in body frame, to W_S_W in world frame
517 // Get NavState
518 gtsam::NavState navState;
519 if (optionalNavState.has_value()) {
520 navState = optionalNavState.value();
521 } else {
522 gtsam::Pose3 resultPose = graphPtr->calculateEstimatedPose3(graphKey); // auto result = mainGraphPtr_->estimate();
523 gtsam::Point3 resultVelocity = gtsam::Point3::Zero(); // Assume Zero Velocity, but adjoint below is still fine to map to world
524 navState = gtsam::NavState(resultPose, resultVelocity);
525 }
526 // Compute adjoint matrix
527 gtsam::Matrix66 adjointMatrix = navState.pose().AdjointMap();
528 // Transform covariance
529 gtsam::Matrix66 resultPoseCovarianceWorldFrame = adjointMatrix * resultPoseCovarianceBodyFrame * adjointMatrix.transpose();
530 // Return
531 return resultPoseCovarianceWorldFrame;
532}
533
535 // At compile time get the symbols for the 6D and 3D states
536 constexpr int numDynamic6DStates = countNDStates<6>();
537 constexpr int numDynamic3DStates = countNDStates<3>();
538 constexpr std::array<char, numDynamic6DStates> dim6StateSymbols = getSymbolArrayForNDStates<6, numDynamic6DStates>();
539 constexpr std::array<char, numDynamic3DStates> dim3StateSymbols = getSymbolArrayForNDStates<3, numDynamic3DStates>();
540
541 // Method variables
542 gtsam::NonlinearFactorGraph newRtGraphFactors, newBatchGraphFactors;
543 gtsam::Values newRtGraphValues, newBatchGraphValues;
544 std::map<gtsam::Key, double> newRtGraphKeysTimestampsMap, newBatchGraphKeysTimestampsMap;
545 gtsam::Key currentPropagatedKey;
546 gtsam::Vector3 currentGraphStateKeyAngularVelocity;
547 double currentGraphStateKeyTime;
548
549 // Mutex Block 1 -----------------
550 {
551 // Lock
552 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
553 // Get current key and time
554 currentPropagatedKey = propagatedStateKey_;
555 currentGraphStateKeyTime = latestGraphStateKeyTime_;
556 currentGraphStateKeyAngularVelocity = latestGraphStateKeyAngularVelocity_;
557 // Get copy of factors and values and empty buffers
558 newRtGraphFactors = *rtFactorGraphBufferPtr_;
559 newRtGraphValues = *rtGraphValuesBufferPtr_;
560 newRtGraphKeysTimestampsMap = *rtGraphKeysTimestampsMapBufferPtr_;
561 rtFactorGraphBufferPtr_->resize(0);
564 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
565 newBatchGraphFactors = *batchFactorGraphBufferPtr_;
566 newBatchGraphValues = *batchGraphValuesBufferPtr_;
567 newBatchGraphKeysTimestampsMap = *batchGraphKeysTimestampsMapBufferPtr_;
571 }
572
573 // Catch-up starts at the graph key, including its unfinished IMU interval.
575 } // end of locking
576
577 // Log Update Duration
578 if (graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
579 updateDurationStartTime_ = std::chrono::high_resolution_clock::now();
580 }
581
582 // Graph Update (time consuming) -------------------------------------------
583 bool successfulOptimizationFlag = true;
584 // Rt Smoother
585 if (newRtGraphFactors.size() > 0) {
586 successfulOptimizationFlag = addFactorsToRtSmootherAndOptimize(newRtGraphFactors, newRtGraphValues, newRtGraphKeysTimestampsMap,
587 graphConfigPtr_, graphConfigPtr_->additionalOptimizationIterations_);
588 } else if (graphConfigPtr_->verboseLevel_ > 3) {
589 REGULAR_COUT << " No factors present in rt smoother, not optimizing." << std::endl;
590 }
591 // Batch Smoother
592 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_ && newBatchGraphFactors.size() > 0) {
593 successfulOptimizationFlag =
594 addFactorsToBatchSmootherAndOptimize(newBatchGraphFactors, newBatchGraphValues, newBatchGraphKeysTimestampsMap, graphConfigPtr_);
595 } else if (graphConfigPtr_->verboseLevel_ > 3) {
596 REGULAR_COUT << " No factors present in batch smoother, not optimizing." << std::endl;
597 }
598
599 // Log Update Duration
600 if (graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
601 updateDurationEndTime_ = std::chrono::high_resolution_clock::now();
602 // Duration in seconds
603 std::chrono::duration<double> updateDuration = updateDurationEndTime_ - updateDurationStartTime_;
604 updateDurationContainer_[currentGraphStateKeyTime] = updateDuration.count();
605 }
606
607 // Return if optimization failed
608 if (!successfulOptimizationFlag) {
609 REGULAR_COUT << RED_START << " Graph optimization failed. " << COLOR_END << std::endl;
610 return;
611 }
612
613 // Compute entire results ----------------------------------------------
614 // A. NavState ------------------------------
615 gtsam::NavState resultNavState =
616 calculateNavStateAtGeneralKey(successfulOptimizationFlag, rtOptimizerPtr_, currentPropagatedKey, __func__);
617 rtOptimizerPtr_->addLatestPoseBelief(resultNavState.pose());
618 // B. Bias ------------------------------
619 gtsam::imuBias::ConstantBias resultBias = rtOptimizerPtr_->calculateEstimatedBias(gtsam::symbol_shorthand::B(currentPropagatedKey));
620 rtOptimizerPtr_->addLatestImuBiasBelief(resultBias);
621 // gtsam::Matrix resultAccBiasCov =
622 // rtOptimizerPtr_->calculateMarginalCovarianceMatrixAtKey(gtsam::symbol_shorthand::B(currentPropagatedKey)); C. Compute & Transform
623 // Covariances ------------------------------ Pose Covariance in World Frame
624 gtsam::Matrix66 resultPoseCovarianceWorldFrame =
625 calculatePoseCovarianceAtKeyInWorldFrame(rtOptimizerPtr_, gtsam::symbol_shorthand::X(currentPropagatedKey), __func__, resultNavState);
626 // Velocity Covariance
627 gtsam::Matrix33 resultVelocityCovariance =
628 rtOptimizerPtr_->calculateMarginalCovarianceMatrixAtKey(gtsam::symbol_shorthand::V(currentPropagatedKey));
629
630 // D. Reference Frame Transformations ------------------------------
631 // Containers
632 TransformsDictionary<Eigen::Isometry3d> resultReferenceFrameTransformations(Eigen::Isometry3d::Identity());
633 TransformsDictionary<Eigen::Matrix<double, 6, 6>> resultReferenceFrameTransformationsCovariance(gtsam::Z_6x6);
634 TransformsDictionary<Eigen::Isometry3d> resultLandmarkTransformations(Eigen::Isometry3d::Identity());
635 TransformsDictionary<Eigen::Matrix<double, 6, 6>> resultLandmarkTransformationsCovariance(gtsam::Z_6x6);
636
637 // Actual computation of fixed-frame transformations
638 if (graphConfigPtr_->optimizeReferenceFramePosesWrtWorldFlag_) {
639 // Mutex because we are changing the dynamically allocated graphKeys
640 std::lock_guard<std::mutex> modifyGraphKeysLock(gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().mutex());
641
642 // Iterate through all dynamically allocated variables (holistic, calibration, landmarks) --------------------------------
643 for (auto& framePairKeyMapIterator : gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().getTransformsMap()) {
644 // Frame Pair
645 const std::pair<std::string, std::string>& framePair = framePairKeyMapIterator.first; // alias
646
647 // Get Variable Key
648 const gtsam::Key& gtsamKey = framePairKeyMapIterator.second.key(); // alias
649
650 // Check whether it is landmark state
651 bool isLandmarkState = false;
652 if (framePairKeyMapIterator.second.getVariableTypeEnum() == DynamicVariableTypeEnum::Landmark) {
653 isLandmarkState = true;
654 }
655
656 // State Category Character
657 const char stateCategory = gtsam::Symbol(gtsamKey).chr();
658
659 // Try to compute results and uncertainties (if variable is still active)
660 if (framePairKeyMapIterator.second.isVariableActive()) {
661 // Case 1: All worked ------------------------------------------
662 try { // Obtain estimate and covariance from the extrinsic transformations
663 gtsam::Pose3 T_frame1_frame2;
664 gtsam::Matrix66 T_frame1_frame2_covariance = gtsam::Z_6x6;
665
666 // 6D Transformations
667 if (isCharInCharArray<numDynamic6DStates>(stateCategory, dim6StateSymbols)) {
668 T_frame1_frame2 = rtOptimizerPtr_->calculateEstimatedPose3(gtsamKey);
669 T_frame1_frame2_covariance = rtOptimizerPtr_->calculateMarginalCovarianceMatrixAtKey(gtsamKey);
670 // Add current belief back to the map
671 framePairKeyMapIterator.second.updateLatestEstimate(T_frame1_frame2, T_frame1_frame2_covariance);
672 // Update the initial guess for the offline graph
673 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
674 gtsam::Values valuesEstimate;
675 valuesEstimate.insert(gtsamKey, T_frame1_frame2);
676 std::ignore = batchOptimizerPtr_->updateExistingValues(valuesEstimate);
677 }
678 }
679 // 3D Position3 Vectors
680 else if (isCharInCharArray<numDynamic3DStates>(stateCategory, dim3StateSymbols)) {
681 gtsam::Point3 point3Estimate = rtOptimizerPtr_->calculateEstimatedPoint3(gtsamKey);
682 T_frame1_frame2 = gtsam::Pose3(gtsam::Rot3::Identity(), point3Estimate);
683 T_frame1_frame2_covariance.block<3, 3>(3, 3) = rtOptimizerPtr_->calculateMarginalCovarianceMatrixAtKey(gtsamKey);
684 // Add current belief back to the map
685 framePairKeyMapIterator.second.updateLatestEstimate(T_frame1_frame2, T_frame1_frame2_covariance);
686 // Update the initial guess for the offline graph
687 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
688 gtsam::Values valuesEstimate;
689 valuesEstimate.insert(gtsamKey, point3Estimate);
690 std::ignore = batchOptimizerPtr_->updateExistingValues(valuesEstimate);
691 }
692 }
693 // Only these two types are allowed for dynamic states for now
694 else {
695 throw std::runtime_error("Key is neither a pose nor a displacement key.");
696 }
697
698 // Transform quantities to world frame (instead of keyframe)
699 // For correction first have to transform T_W_F to T_F_W, apply correction, and then transform back to T_W_F
700 gtsam::Pose3 T_frame2_frame1_corrected = T_frame1_frame2.inverse();
701 T_frame2_frame1_corrected =
702 gtsam::Pose3(T_frame2_frame1_corrected.rotation(),
703 T_frame2_frame1_corrected.translation() + framePairKeyMapIterator.second.getReferenceFrameKeyframePosition());
704 gtsam::Pose3 T_frame1_frame2_corrected = T_frame2_frame1_corrected.inverse();
705 // Write to container
706 // 1: For Landmark States
707 if (isLandmarkState) {
708 // Write to landmark container
709 resultLandmarkTransformations.set_T_frame1_frame2(framePairKeyMapIterator.first.first, framePairKeyMapIterator.first.second,
710 Eigen::Isometry3d(T_frame1_frame2_corrected.matrix()));
711 resultLandmarkTransformationsCovariance.set_T_frame1_frame2(framePairKeyMapIterator.first.first,
712 framePairKeyMapIterator.first.second, T_frame1_frame2_covariance);
713 }
714 // 2: For Reference Frame States
715 else {
716 // Write to reference frame container
717 resultReferenceFrameTransformations.set_T_frame1_frame2(framePairKeyMapIterator.first.first,
718 framePairKeyMapIterator.first.second,
719 Eigen::Isometry3d(T_frame1_frame2_corrected.matrix()));
720 resultReferenceFrameTransformationsCovariance.set_T_frame1_frame2(
721 framePairKeyMapIterator.first.first, framePairKeyMapIterator.first.second, T_frame1_frame2_covariance);
722 // Potentially fill in existing pairs for logging
723 if (graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
725 realTimeReferenceFrameNamePairs_.insert(framePair);
726 REGULAR_COUT << " Added frame pair: " << framePair.first << " to " << framePair.second << " to real-time logging."
727 << std::endl;
728 }
729 }
730 }
731
732 // Mark that this key has at least been optimized once
733 if (framePairKeyMapIterator.second.getNumberStepsOptimized() == 0) {
735 REGULAR_COUT << " Fixed-frame Transformation between " << framePairKeyMapIterator.first.first << " and "
736 << framePairKeyMapIterator.first.second << " optimized for the first time." << COLOR_END << std::endl;
737 }
739 REGULAR_COUT << GREEN_START << " Result, RPY (deg): " << T_frame1_frame2.rotation().rpy().transpose() * (180.0 / M_PI)
740 << ", t (x, y, z): " << T_frame1_frame2.translation().transpose() << COLOR_END << std::endl;
741 }
742 }
743 // Increase Counter
744 framePairKeyMapIterator.second.incrementNumberStepsOptimized();
745
746 // Check health status of transformation --> Delete if diverged too much --> only necessary for global fixed frames
747 if (framePairKeyMapIterator.first.second == worldFrame_ &&
748 framePairKeyMapIterator.second.getNumberStepsOptimized() > MIN_ITERATIONS_BEFORE_REMOVING_STATIC_TRANSFORM) {
749 const gtsam::Pose3& T_frame1_frame2_initial =
750 framePairKeyMapIterator.second.getApproximateTransformationBeforeOptimization(); // alias
751 const double errorTangentSpace = gtsam::Pose3::Logmap(T_frame1_frame2_initial.between(T_frame1_frame2)).norm();
752
753 // Check error in tangent space
754 if (errorTangentSpace > graphConfigPtr_->referenceFramePosesResetThreshold_) {
755 REGULAR_COUT << RED_START << "Error in tangent space: " << errorTangentSpace << std::endl;
756 REGULAR_COUT << YELLOW_START << "GMsf-GraphManager" << RED_START << " Fixed Frame Transformation between "
757 << framePairKeyMapIterator.first.first << " and " << framePairKeyMapIterator.first.second
758 << " diverged too much. Removing from optimization and adding again freshly at next possibility." << COLOR_END
759 << std::endl;
760 // Remove state from state dictionary
761 gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().removeOrDeactivateTransform(framePairKeyMapIterator.first.first,
762 framePairKeyMapIterator.first.second);
763 break;
764 }
765 }
766 } // end: try statement
767 // Case 2: Result computation failed -----------------------------------------
768 catch (const std::out_of_range& exception) {
769 // Only remove/deactivate measurements that are still active and have been optimized before
770 if (framePairKeyMapIterator.second.getNumberStepsOptimized() > 0) {
771 REGULAR_COUT << RED_START << " OutOfRange-Exception while querying the active transformation and/or covariance at key "
772 << gtsam::Symbol(gtsamKey) << ", for frame pair " << framePairKeyMapIterator.first.first << ","
773 << framePairKeyMapIterator.first.second << std::endl
774 << " This happens if the requested variable is outside of the smoother window (e.g. because corresponding "
775 "measurement stopped). Hence, we keep this estimate unchanged and remove/deactivate it from the state "
776 "dictionary."
777 << COLOR_END << std::endl;
778 // Remove state from state dictionary
779 gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().removeOrDeactivateTransform(framePairKeyMapIterator.first.first,
780 framePairKeyMapIterator.first.second);
781 return;
782 }
783 // If active but added newly but never optimized
784 else {
785 // If too old but was never optimized --> remove or deactivate
786 double variableAge = framePairKeyMapIterator.second.computeVariableAge(currentGraphStateKeyTime);
787 if (variableAge > graphConfigPtr_->realTimeSmootherLag_) {
788 REGULAR_COUT << YELLOW_START << "GMsf-GraphManager" << RED_START << " Fixed Frame Transformation between "
789 << framePairKeyMapIterator.first.first << " and " << framePairKeyMapIterator.first.second << " is too old ("
790 << variableAge
791 << ")s and was never optimized. Removing from optimization and adding again freshly at next "
792 "possibility."
793 << COLOR_END << std::endl;
794 // Remove state from state dictionary
795 gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().removeOrDeactivateTransform(framePairKeyMapIterator.first.first,
796 framePairKeyMapIterator.first.second);
797 }
798 // Otherwise keep for now and potentially print out
799 else if (graphConfigPtr_->verboseLevel_ >= 1) {
801 << YELLOW_START << " Tried to query the transformation and/or covariance for frame pair "
802 << framePairKeyMapIterator.first.first << " to " << framePairKeyMapIterator.first.second
803 << ", at key: " << gtsam::Symbol(gtsamKey)
804 << ". Not yet available, as it was not yet optimized. Waiting for next optimization iteration until publishing it. "
805 "Current state key: "
806 << currentPropagatedKey << COLOR_END << std::endl;
807 }
808 }
809 } // catch statement
810 } // end: if active statement
811 } // for loop over all transforms
812 }
813
814 // Mutex block 2 ------------------
815 {
816 // Lock
817 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
818
819 // 1. Optimized Graph State Status
820 optimizedGraphState_.setIsOptimized();
821 // Update Optimized Graph State
822 optimizedGraphState_.updateNavStateAndBias(currentPropagatedKey, currentGraphStateKeyTime, resultNavState,
823 resultBias.correctGyroscope(currentGraphStateKeyAngularVelocity), resultBias);
824 optimizedGraphState_.updateReferenceFrameTransforms(resultReferenceFrameTransformations);
825 optimizedGraphState_.updateReferenceFrameTransformsCovariance(resultReferenceFrameTransformationsCovariance);
826 optimizedGraphState_.updateLandmarkTransforms(resultLandmarkTransformations);
827 optimizedGraphState_.updateLandmarkTransformsCovariance(resultLandmarkTransformationsCovariance);
828 optimizedGraphState_.updateCovariances(resultPoseCovarianceWorldFrame, resultVelocityCovariance);
829
830 // 2. State in World: Predict from solution to obtain refined propagated state
831 if (graphConfigPtr_->usingBiasForPreIntegrationFlag_) {
832 W_imuPropagatedState_ = imuBufferPreintegratorPtr_->predict(resultNavState, optimizedGraphState_.imuBias());
833 } else {
834 W_imuPropagatedState_ = imuBufferPreintegratorPtr_->predict(resultNavState, gtsam::imuBias::ConstantBias());
835 }
836
837 // 3. State in Odom
838 // Correct rotation only for roll and pitch, keep integrated yaw
839 // gtsam::Rot3 R_O_I_rp_corrected =
840 // gtsam::Rot3::Ypr(O_imuPropagatedState_.pose().rotation().yaw(), W_imuPropagatedState_.pose().rotation().pitch(),
841 // W_imuPropagatedState_.pose().rotation().roll());
842 // TODO: Apply gravity correction
843 // gtsam::Rot3 R_O_I_rp_corrected = O_imuPropagatedState_.pose().rotation();
844 // // Rotate corrected velocity to odom frame
845 // gtsam::Vector3 O_v_O_I = R_O_I_rp_corrected * W_imuPropagatedState_.bodyVelocity();
846 // Update the NavState
847 // O_imuPropagatedState_ = gtsam::NavState(R_O_I_rp_corrected, O_imuPropagatedState_.pose().translation(), O_v_O_I);
848
849 // // 4. Transformation between World and Odom
850 // T_W_O_ = Eigen::Isometry3d((W_imuPropagatedState_.pose() * O_imuPropagatedState_.pose().inverse()).matrix());
851
852 // Update the time of the last optimized state
853 lastOptimizedStateTime_ = currentGraphStateKeyTime;
854 } // end of locking
855
856 // Potentially log real-time state to container
857 if (graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
858 realTimeReferenceFrameContainer_.emplace(currentGraphStateKeyTime, resultReferenceFrameTransformations);
859 }
860}
861
862bool GraphManager::optimizeSlowBatchSmoother(int maxIterations, const std::string& savePath, const bool saveCovarianceFlag) {
863 if (!runSlowBatchSmootherOptimization_(maxIterations)) {
864 return false;
865 }
866 saveOptimizedValuesToFile(batchOptimizerPtr_->getAllOptimizedStates(), batchOptimizerPtr_->getFullKeyTimestampMap(), savePath,
867 saveCovarianceFlag);
868 return true;
869}
870
871std::optional<BatchOptimizationResult> GraphManager::optimizeSlowBatchSmoother(int maxIterations, const bool computePoseCovarianceFlag) {
872 if (!runSlowBatchSmootherOptimization_(maxIterations)) {
873 return std::nullopt;
874 }
875 return extractBatchOptimizationResult_(computePoseCovarianceFlag);
876}
877
878// Logging of the real-time navigation states
879bool GraphManager::logRealTimeNavStates(const std::string& savePath, const std::string& timeString) {
880 // Note enabled
881 if (!graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
882 REGULAR_COUT << RED_START << " Logging of real-time states is disabled. " << COLOR_END << std::endl;
883 return false;
884 }
885 // Enabled
886 else {
887 // Main container: map
888 std::map<std::string, std::ofstream> fileStreams;
889
890 // Create directory if it does not exist
891 if (!std::filesystem::exists(savePath + timeString)) {
892 std::filesystem::create_directories(savePath + timeString);
893 }
894
895 // Create File Streams
896 // CSV
897 FileLogger::createPose3CsvFileStream(fileStreams, savePath, "rt_world_x_state_6D_pose", timeString, false);
898 FileLogger::createPose3CsvFileStream(fileStreams, savePath, "rt_odom_x_state_6D_pose", timeString, false);
899 // TUM
900 FileLogger::createPose3TumFileStream(fileStreams, savePath, "rt_world_x_state_6D_pose_tum", timeString);
901 FileLogger::createPose3TumFileStream(fileStreams, savePath, "rt_odom_x_state_6D_pose_tum", timeString);
902
903 // Iterate through all states and write them to file
904 // A. World Pose
905 for (const auto& statePair : realTimeWorldPoseContainer_) {
906 // Write to files
907 // CSV
908 FileLogger::writePose3ToCsvFile(fileStreams, statePair.second, "rt_world_x_state_6D_pose", statePair.first, false);
909 // TUM
910 FileLogger::writePose3ToTumFile(fileStreams, statePair.second, "rt_world_x_state_6D_pose_tum", statePair.first);
911 }
912 // B. Odom Pose
913 for (const auto& statePair : realTimeOdomPoseContainer_) {
914 // Write to files
915 // CSV
916 FileLogger::writePose3ToCsvFile(fileStreams, statePair.second, "rt_odom_x_state_6D_pose", statePair.first, false);
917 // TUM
918 FileLogger::writePose3ToTumFile(fileStreams, statePair.second, "rt_odom_x_state_6D_pose_tum", statePair.first);
919 }
920 return true;
921 }
922}
923
924// Logging of real-time reference frame states
925bool GraphManager::logRealTimeReferenceFrameStates(const std::string& savePath, const std::string& timeString) {
926 // Note enabled
927 if (!graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
928 REGULAR_COUT << RED_START << " Logging of real-time states is disabled. " << COLOR_END << std::endl;
929 return false;
930 }
931 // Enabled
932 else {
933 // Main container: map
934 std::map<std::string, std::ofstream> fileStreams;
935
936 // Create directory if it does not exist
937 if (!std::filesystem::exists(savePath + timeString)) {
938 std::filesystem::create_directories(savePath + timeString);
939 }
940
941 // State Category String
942 std::string stateCategoryString = "rt_R_6D_transform";
943
944 // Go through all reference frame states that have been logged
945 for (const std::pair<std::string, std::string>& framePair : realTimeReferenceFrameNamePairs_) {
946 // Frame Information
947 std::string frameInformation = "_" + framePair.first + "_to_" + framePair.second;
948 // Replace / with underscore
949 std::replace(frameInformation.begin(), frameInformation.end(), '/', '_');
950 // Create File Streams
951 // CSV
952 FileLogger::createPose3CsvFileStream(fileStreams, savePath, stateCategoryString + frameInformation, timeString, false);
953 // TUM
954 FileLogger::createPose3TumFileStream(fileStreams, savePath, stateCategoryString + frameInformation + "_tum", timeString);
955 }
956
957 // Iterate through all timestamps and corresponding transform maps
958 for (const auto& mapPair : realTimeReferenceFrameContainer_) {
959 const double& timeStamp = mapPair.first; // alias
960 const TransformsDictionary<Eigen::Isometry3d>& referenceFrameTransforms = mapPair.second;
961 // Iterate through all transforms for this time stamp
962 for (const auto& framePairTransform : referenceFrameTransforms.getTransformsMap()) {
963 // Frame Pair
964 const std::pair<std::string, std::string>& framePair = framePairTransform.first; // alias
965 // Transform
966 const Eigen::Isometry3d& transform = framePairTransform.second; // alias
967 // Frame Information
968 std::string frameInformation = "_" + framePair.first + "_to_" + framePair.second;
969 // Replace / with underscore
970 std::replace(frameInformation.begin(), frameInformation.end(), '/', '_');
971 // Write to files
972 // CSV
973 FileLogger::writePose3ToCsvFile(fileStreams, gtsam::Pose3(transform.matrix()), stateCategoryString + frameInformation, timeStamp,
974 false);
975 // TUM
976 FileLogger::writePose3ToTumFile(fileStreams, gtsam::Pose3(transform.matrix()), stateCategoryString + frameInformation + "_tum",
977 timeStamp);
978 }
979 }
980 return true;
981 }
982}
983
984// Log Latency and Update Duration to Memory
985bool GraphManager::logLatencyAndUpdateDuration(const std::string& savePath, const std::string& timeString) {
986 // Note enabled
987 if (!graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
988 REGULAR_COUT << RED_START << " Logging of latency and update duration is disabled. " << COLOR_END << std::endl;
989 return false;
990 }
991 // Enabled
992 else {
993 // Main container: map
994 std::map<std::string, std::ofstream> fileStreams;
995
996 // Create directory if it does not exist
997 if (!std::filesystem::exists(savePath + timeString)) {
998 std::filesystem::create_directories(savePath + timeString);
999 }
1000
1001 // Create File Streams
1002 // CSV
1003 FileLogger::createDoubleCsvFileStream(fileStreams, savePath, "latency", timeString);
1004 FileLogger::createDoubleCsvFileStream(fileStreams, savePath, "update_duration", timeString);
1005
1006 // Iterate through all timestamps and corresponding latency
1007 for (const auto& latencyPair : latencyContainer_) {
1008 const double& timeStamp = latencyPair.first; // alias
1009 const double& latency = latencyPair.second; // alias
1010 // Write to files
1011 // CSV
1012 FileLogger::writeDoubleToCsvFile(fileStreams, latency, "latency", timeStamp);
1013 }
1014
1015 // Iterate through all timestamps and corresponding update duration
1016 for (const auto& updateDurationPair : updateDurationContainer_) {
1017 const double& timeStamp = updateDurationPair.first; // alias
1018 const double& updateDuration = updateDurationPair.second; // alias
1019 // Write to files
1020 // CSV
1021 FileLogger::writeDoubleToCsvFile(fileStreams, updateDuration, "update_duration", timeStamp);
1022 }
1023
1024 return true;
1025 }
1026}
1027
1028// Save optimized values to file
1029void GraphManager::saveOptimizedValuesToFile(const gtsam::Values& optimizedValues, const std::map<gtsam::Key, double>& keyTimestampMap,
1030 const std::string& savePath, const bool saveCovarianceFlag) {
1031 // At compile time get the symbols for the 6D and 3D states
1032 constexpr int num6DStates = countNDStates<6>();
1033 constexpr int num3DStates = countNDStates<3>();
1034 constexpr std::array<char, num6DStates> dim6StateSymbols = getSymbolArrayForNDStates<6, num6DStates>();
1035 constexpr std::array<char, num3DStates> dim3StateSymbols = getSymbolArrayForNDStates<3, num3DStates>();
1036
1037 // Map to hold file streams, keyed by category
1038 std::map<std::string, std::ofstream> fileStreams;
1039
1040 // Get current time as string for file name
1041 std::time_t now_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
1042 std::tm now_tm = *std::localtime(&now_time_t);
1043 // String of time without line breaks: year_month_day_hour_min_sec
1044 std::ostringstream oss;
1045 oss << std::put_time(&now_tm, "%Y_%m_%d_%H_%M_%S");
1046 // Convert stream to string
1047 std::string timeString = oss.str();
1048 // Remove any line breaks
1049 timeString.erase(std::remove(timeString.begin(), timeString.end(), '\n'), timeString.end());
1050 // Replace spaces with underscores
1051 std::replace(timeString.begin(), timeString.end(), ' ', '_');
1052
1053 // Create directory if it does not exist
1054 if (!std::filesystem::exists(savePath + timeString)) {
1055 std::filesystem::create_directories(savePath + timeString);
1056 }
1057
1058 // Save real-time states
1059 if (graphConfigPtr_->logRealTimeStateToMemoryFlag_) {
1060 // Real-time states
1061 std::ignore = logRealTimeNavStates(savePath, timeString);
1062 // Real-time Reference Frame States
1063 std::ignore = logRealTimeReferenceFrameStates(savePath, timeString);
1064 }
1065
1066 // Save Latency and Update Duration
1067 if (graphConfigPtr_->logLatencyAndUpdateDurationToMemoryFlag_) {
1068 std::ignore = logLatencyAndUpdateDuration(savePath, timeString);
1069 }
1070
1071 // Save optimized states
1072 // A. 6D SE(3) states -----------------------------------------------------------
1073 for (const auto& keyPosePair : optimizedValues.extract<gtsam::Pose3>()) {
1074 // Read out information
1075 const gtsam::Key& graphKey = keyPosePair.first;
1076 gtsam::Pose3 pose = keyPosePair.second;
1077 const gtsam::Symbol stateSymbol(graphKey);
1078 const char stateCategory = stateSymbol.chr();
1079 const double timeStamp = keyTimestampMap.at(graphKey);
1080
1081 // Additional strings
1082 std::string stateCategoryString = "";
1083 std::string frameInformation = "";
1084
1085 // State Category Capital
1086 const std::string stateCategoryCapital(1, static_cast<char>(std::toupper(stateCategory)));
1087
1088 // A.A Creation of the identifier for the file -----------------------------------
1089 // Case 1: Navigation State Pose
1090 if (stateCategory == 'x') {
1091 stateCategoryString = stateCategoryCapital + "_state_6D_pose";
1092 }
1093 // Case 2: Frame Transform
1094 else if (isCharInCharArray<num6DStates>(stateCategory, dim6StateSymbols)) {
1095 // State Category
1096 stateCategoryString = stateCategoryCapital + "_6D_transform";
1097 // Get Frame Pair
1098 std::pair<std::string, std::string> framePair;
1099 if (gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().getFramePairFromGtsamKey(framePair, graphKey)) {
1100 frameInformation = "_" + framePair.first + "_to_" + framePair.second;
1101 } else {
1102 REGULAR_COUT << RED_START << " Could not find frame pair for key: " << stateSymbol << COLOR_END << std::endl;
1103 throw std::runtime_error("Could not find frame pair for key.");
1104 }
1105 }
1106 // Otherwise: Undefined --> throw error
1107 else {
1108 throw std::logic_error(stateCategoryString + " is an unknown 6D state category. Check the admissible GTSAM symbols in the config.");
1109 }
1110 // Put together the identifier
1111 std::string transformIdentifier = stateCategoryString + frameInformation;
1112 // Replace / with underscore
1113 std::replace(transformIdentifier.begin(), transformIdentifier.end(), '/', '_');
1114
1115 // Check for the keyframe position
1116 Eigen::Vector3d keyframePosition = Eigen::Vector3d::Zero();
1117 gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().getKeyframePositionFromGtsamKey(keyframePosition, graphKey);
1118
1119 // A.B Write to file -----------------------------------------------------------
1120 // Check if we already have a file stream for this category --> if not, create one
1121 // CSV file for Pose3
1122 fileLogger_.createPose3CsvFileStream(fileStreams, savePath, transformIdentifier, timeString, saveCovarianceFlag);
1123 // TUM file for Pose3
1124 fileLogger_.createPose3TumFileStream(fileStreams, savePath, transformIdentifier + "_tum", timeString);
1125
1126 // Compute Covariance of Pose
1127 Eigen::Matrix<double, 6, 6> poseCovarianceInWorldRos = Eigen::Matrix<double, 6, 6>::Zero();
1128 if (saveCovarianceFlag) {
1129 gtsam::Matrix66 poseCovarianceInWorldGtsam = calculatePoseCovarianceAtKeyInWorldFrame(batchOptimizerPtr_, graphKey, __func__);
1130 // Convert to ROS Format
1131 poseCovarianceInWorldRos = convertCovarianceGtsamConventionToRosConvention(poseCovarianceInWorldGtsam);
1132 }
1133
1134 // If keyframe position is not zero, move the frame location to capture the random walk
1135 if (keyframePosition.norm() > 1e-6) {
1136 const gtsam::Pose3& T_W_M = pose; // alias
1137 gtsam::Pose3 T_M_W_corrected = T_W_M.inverse();
1138 T_M_W_corrected = gtsam::Pose3(T_M_W_corrected.rotation(),
1139 T_M_W_corrected.translation() + keyframePosition); // Remove the effect of the keyframe position
1140 gtsam::Pose3 T_W_M_corrected = T_M_W_corrected.inverse();
1141 pose = T_W_M_corrected;
1142 REGULAR_COUT << " Corrected frame position for " << transformIdentifier << " (" << gtsam::Symbol(graphKey) << ") by "
1143 << keyframePosition.transpose() << std::endl;
1144 }
1145
1146 // Write the values to the appropriate file
1147 // CSV file for Pose3
1148 fileLogger_.writePose3ToCsvFile(fileStreams, pose, transformIdentifier, timeStamp, saveCovarianceFlag, poseCovarianceInWorldRos);
1149 // TUM file for Pose3
1150 fileLogger_.writePose3ToTumFile(fileStreams, pose, transformIdentifier + "_tum", timeStamp);
1151 } // end of for loop over all pose states
1152
1153 // B. 3D R(3) states (e.g. velocity, calibration displacement, landmarks) -----------------------------------------------------------
1154 for (const auto& keyVectorPair : optimizedValues.extract<gtsam::Point3>()) {
1155 // Read out information
1156 const gtsam::Key& key = keyVectorPair.first;
1157 const gtsam::Vector& vector = keyVectorPair.second;
1158 const gtsam::Symbol symbol(key);
1159 const char stateCategory = symbol.chr();
1160 const double timeStamp = keyTimestampMap.at(key);
1161
1162 // Additional strings
1163 std::string stateCategoryString = "";
1164 std::string frameInformation = "";
1165
1166 // State Category Non-Capital (as it is a vector instead of a matrix)
1167 const std::string stateCategoryNonCapital(1, stateCategory);
1168
1169 // B.A Creation of the identifier for the file -----------------------------------
1170 // Case 1: Navigation State Velocity
1171 if (stateCategory == 'v') {
1172 stateCategoryString = stateCategoryNonCapital + "_state_3D_velocity";
1173 }
1174 // Case 2: Point3 (e.g. calibration displacement, landmarks), TODO: no landmarks are saved for now
1175 else if (isCharInCharArray<num3DStates>(stateCategory, dim3StateSymbols)) {
1176 // State Category
1177 stateCategoryString = stateCategoryNonCapital + "_3D_vector";
1178 // Skip landmarks for now
1179 if (stateCategory == 'l') {
1180 continue;
1181 }
1182
1183 // Get Frame Pair
1184 std::pair<std::string, std::string> framePair;
1185 if (gtsamDynamicExpressionKeys_.get<gtsam::Pose3>().getFramePairFromGtsamKey(framePair, key)) {
1186 frameInformation = "_" + framePair.first + "_to_" + framePair.second;
1187 } else {
1188 REGULAR_COUT << RED_START << " Could not find frame pair for key: " << symbol.chr() << COLOR_END << std::endl;
1189 }
1190 }
1191 // Otherwise: Undefined --> throw error
1192 else {
1193 throw std::logic_error(stateCategoryString + " is an unknown 3D state category. Check the admissible GTSAM symbols in the config.");
1194 }
1195 // Put together the identifier
1196 std::string transformIdentifier = stateCategoryString + frameInformation;
1197
1198 // B.B Write to file -----------------------------------------------------------
1199 // Check if we already have a file stream for this category --> if not, create one
1200 fileLogger_.createPoint3CsvFileStream(fileStreams, savePath, transformIdentifier, timeString);
1201
1202 // Write the values to the appropriate file
1203 fileLogger_.writePoint3ToCsvFile(fileStreams, vector, transformIdentifier, timeStamp);
1204 } // end of for loop over all vector states
1205
1206 // C. Bias states (accelerometer and gyroscope) -----------------------------------------------------------
1207 for (const auto& keyBiasPair : optimizedValues.extract<gtsam::imuBias::ConstantBias>()) {
1208 // Read out information
1209 const gtsam::Key& key = keyBiasPair.first;
1210 const gtsam::imuBias::ConstantBias& bias = keyBiasPair.second;
1211 const gtsam::Symbol symbol(key);
1212 const char stateCategory = symbol.chr();
1213 const double timeStamp = keyTimestampMap.at(key);
1214
1215 // Assert that the symbol is a bias
1216 assert(stateCategory == 'b');
1217
1218 // Capitalized state category
1219 const std::string stateCategoryCapital(1, static_cast<char>(std::toupper(stateCategory)));
1220 ;
1221
1222 // C.A Creation of the identifier for the file -----------------------------------
1223 std::string stateCategoryIdentifier = stateCategoryCapital + "_imu_bias";
1224
1225 // C.B Write to file -----------------------------------------------------------
1226 // Check if we already have a file stream for this category --> if not, create one
1227 fileLogger_.createImuBiasCsvFileStream(fileStreams, savePath, stateCategoryIdentifier, timeString);
1228
1229 // Write the values to the appropriate file
1230 fileLogger_.writeImuBiasToCsvFile(fileStreams, bias, stateCategoryIdentifier, timeStamp);
1231 }
1232
1233 // Close all file streams
1234 for (auto& pair : fileStreams) {
1235 pair.second.close();
1236 }
1237
1238 // Print
1239 REGULAR_COUT << GREEN_START << " Saved all optimized states to files." << COLOR_END << std::endl;
1240}
1241
1242// Save optimized Graph to Common Open source G2o format
1243void GraphManager::saveOptimizedGraphToG2o(const OptimizerBase& optimizedGraph, const gtsam::Values& optimizedValues,
1244 const std::string& saveFileName) {
1245 // Safe optimized states
1246 gtsam::writeG2o(optimizedGraph.getNonlinearFactorGraph(), optimizedValues, saveFileName);
1247}
1248
1249// Calculate State at Key
1250gtsam::NavState GraphManager::calculateStateAtGeneralKey(bool& computeSuccessfulFlag, const gtsam::Key& generalKey) {
1251 return calculateNavStateAtGeneralKey(computeSuccessfulFlag, rtOptimizerPtr_, generalKey, __func__);
1252}
1253
1254// Private --------------------------------------------------------------------
1255
1257 if (!graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
1258 return false;
1259 }
1260 std::chrono::time_point<std::chrono::high_resolution_clock> startOptimizationTime = std::chrono::high_resolution_clock::now();
1261 batchOptimizerPtr_->optimize(maxIterations);
1262 std::chrono::time_point<std::chrono::high_resolution_clock> endOptimizationTime = std::chrono::high_resolution_clock::now();
1263 double optimizationDuration =
1264 std::chrono::duration_cast<std::chrono::milliseconds>(endOptimizationTime - startOptimizationTime).count();
1265 std::cout << "Optimization took " << optimizationDuration / 1000 << " seconds." << std::endl;
1266 std::cout << "Number of optimization variables: " << batchOptimizerPtr_->getAllOptimizedStates().size() << std::endl;
1267 return true;
1268}
1269
1271 const gtsam::Values& optimizedStateValues = batchOptimizerPtr_->getAllOptimizedStates();
1272 const std::map<gtsam::Key, double>& keyTimestampMap = batchOptimizerPtr_->getFullKeyTimestampMap();
1273
1275 // X, V and B of one navigation state always share the same key index.
1276 for (const auto& [poseKey, T_W_Ik] : optimizedStateValues.extract<gtsam::Pose3>()) {
1277 const gtsam::Symbol poseSymbol(poseKey);
1278 if (poseSymbol.chr() != 'x') {
1279 continue;
1280 }
1281 const gtsam::Key index = poseSymbol.index();
1282 const gtsam::imuBias::ConstantBias& bias =
1283 optimizedStateValues.at<gtsam::imuBias::ConstantBias>(gtsam::symbol_shorthand::B(index));
1284
1285 BatchOptimizedNavState navState;
1286 navState.timeK = keyTimestampMap.at(poseKey);
1287 navState.T_W_Ik = Eigen::Isometry3d(T_W_Ik.matrix());
1288 navState.W_v_W_I = optimizedStateValues.at<gtsam::Vector3>(gtsam::symbol_shorthand::V(index));
1289 navState.accelerometerBias = bias.accelerometer();
1290 navState.gyroscopeBias = bias.gyroscope();
1291 if (computePoseCovarianceFlag) {
1292 navState.poseCovarianceInWorld =
1294 }
1295 result.navStates.push_back(std::move(navState));
1296 }
1297
1298 std::sort(result.navStates.begin(), result.navStates.end(),
1299 [](const BatchOptimizedNavState& a, const BatchOptimizedNavState& b) { return a.timeK < b.timeK; });
1300 return result;
1301}
1302
1303// Update of the two IMU pre-integrators
1305 if (imuMeas.size() < 2) {
1306 REGULAR_COUT << " Received less than 2 IMU messages --- No Preintegration done." << std::endl;
1307 return;
1308 } else if (imuMeas.size() > 2) {
1309 REGULAR_COUT << RED_START << "Currently only supporting two IMU messages for pre-integration." << COLOR_END << std::endl;
1310 throw std::runtime_error("Terminating.");
1311 }
1312
1313 // Start integrating with imu_meas.begin()+1 meas to calculate dt,
1314 // imu_meas.begin() meas was integrated before
1315 auto currItr = imuMeas.begin();
1316 auto prevItr = currItr++;
1317
1318 // Calculate dt and integrate IMU measurements for both pre-integrators
1319 for (; currItr != imuMeas.end(); ++currItr, ++prevItr) {
1320 double dt = currItr->first - prevItr->first;
1321 imuStepPreintegratorPtr_->integrateMeasurement(currItr->second.acceleration, // acc
1322 currItr->second.angularVelocity, // gyro
1323 dt); // delta t
1324 imuBufferPreintegratorPtr_->integrateMeasurement(currItr->second.acceleration, // acc
1325 currItr->second.angularVelocity, // gyro
1326 dt); // delta t
1327 }
1328}
1329
1330// Returns true if the factors/values were added without any problems
1331// Otherwise returns false (e.g. if exception occurs while adding
1332bool GraphManager::addFactorsToRtSmootherAndOptimize(const gtsam::NonlinearFactorGraph& newRtGraphFactors,
1333 const gtsam::Values& newRtGraphValues,
1334 const std::map<gtsam::Key, double>& newRtGraphKeysTimestampsMap,
1335 const std::shared_ptr<GraphConfig>& graphConfigPtr, const int additionalIterations) {
1336 // Timing
1337 std::chrono::time_point<std::chrono::high_resolution_clock> startLoopTime = std::chrono::high_resolution_clock::now();
1338 std::chrono::time_point<std::chrono::high_resolution_clock> endLoopTime;
1339
1340 // Lock for optimization (as shared rtOptimizer is optimized)
1341 const std::lock_guard<std::mutex> optimization(optimizationRunningMutex_);
1342
1343 // Perform update of the real-time smoother, including optimization
1344 bool successFlag = rtOptimizerPtr_->update(newRtGraphFactors, newRtGraphValues, newRtGraphKeysTimestampsMap);
1345 // Additional iterations
1346 for (size_t itr = 0; itr < additionalIterations; ++itr) {
1347 successFlag = successFlag && rtOptimizerPtr_->update();
1348 }
1349
1350 // Logging
1351 if (graphConfigPtr->verboseLevel_ >= VerbosityLevels::kOptimizationEffort2) {
1352 endLoopTime = std::chrono::high_resolution_clock::now();
1353 REGULAR_COUT << GREEN_START << " Whole optimization loop took "
1354 << std::chrono::duration_cast<std::chrono::milliseconds>(endLoopTime - startLoopTime).count() << " milliseconds."
1355 << COLOR_END << std::endl;
1356 }
1357
1358 return successFlag;
1359}
1360
1361// Batch Smoother
1362bool GraphManager::addFactorsToBatchSmootherAndOptimize(const gtsam::NonlinearFactorGraph& newBatchGraphFactors,
1363 const gtsam::Values& newBatchGraphValues,
1364 const std::map<gtsam::Key, double>& newBatchGraphKeysTimestampsMap,
1365 const std::shared_ptr<GraphConfig>& graphConfigPtr) {
1366 // Lock for optimization (as shared rtOptimizer is optimized)
1367 const std::lock_guard<std::mutex> optimization(optimizationRunningMutex_);
1368
1369 // Add Factors and States to Batch Optimization (if desired) without running optimization
1370 bool successFlag = false;
1371 if (graphConfigPtr->useAdditionalSlowBatchSmootherFlag_) {
1372 successFlag = batchOptimizerPtr_->update(newBatchGraphFactors, newBatchGraphValues, newBatchGraphKeysTimestampsMap);
1373 }
1374
1375 // Return
1376 return successFlag;
1377}
1378
1379// Find closest keys in graph
1380bool GraphManager::findGraphKeys_(gtsam::Key& closestKeyKm1, gtsam::Key& closestKeyK, double& keyTimeStampDistance,
1381 const double maxTimestampDistance, const double timeKm1, const double timeK, const std::string& name) {
1382 // Find closest lidar keys in existing graph
1383 double closestGraphTimeKm1, closestGraphTimeK;
1384 {
1385 // Looking up from IMU buffer --> acquire mutex (otherwise values for key
1386 // might not be set)
1387 const std::lock_guard<std::mutex> operateOnGraphDataLock(operateOnGraphDataMutex_);
1388 bool success =
1389 timeToKeyBufferPtr_->getClosestKeyAndTimestamp(closestGraphTimeKm1, closestKeyKm1, name + " km1", maxTimestampDistance, timeKm1);
1390 success =
1391 success && timeToKeyBufferPtr_->getClosestKeyAndTimestamp(closestGraphTimeK, closestKeyK, name + " k", maxTimestampDistance, timeK);
1392 if (!success) {
1393 REGULAR_COUT << RED_START << " Could not find closest keys for " << name << COLOR_END << std::endl;
1394 return false;
1395 }
1396 }
1397
1398 // Check
1399 if (closestGraphTimeKm1 > closestGraphTimeK) {
1400 REGULAR_COUT << RED_START << " Time at time step k-1 must be smaller than time at time step k." << COLOR_END << std::endl;
1401 return false;
1402 }
1403
1404 keyTimeStampDistance = std::abs(closestGraphTimeK - closestGraphTimeKm1);
1405 if (keyTimeStampDistance > maxTimestampDistance) {
1406 REGULAR_COUT << " Distance of " << name << " timestamps is too big. Found timestamp difference is "
1407 << closestGraphTimeK - closestGraphTimeKm1 << " which is larger than the maximum admissible distance of "
1408 << maxTimestampDistance << ". Still adding constraints to graph." << COLOR_END << std::endl;
1409 }
1410 return true;
1411}
1412
1413void GraphManager::writeValueKeysToKeyTimeStampMap_(const gtsam::Values& values, const double measurementTime,
1414 std::shared_ptr<std::map<gtsam::Key, double>> keyTimestampMapPtr) {
1415 for (const auto& value : values) {
1416 writeKeyToKeyTimeStampMap_(value.key, measurementTime, keyTimestampMapPtr);
1417 }
1418}
1419
1420} // namespace holistic_fusion
#define REGULAR_COUT
#define MIN_ITERATIONS_BEFORE_REMOVING_STATIC_TRANSFORM
#define COLOR_END
#define GREEN_START
#define RED_START
#define YELLOW_START
static void writePose3ToCsvFile(std::map< std::string, std::ofstream > &fileStreams, const gtsam::Pose3 &pose, const std::string &transformIdentifier, const double timeStamp, const bool saveCovarianceFlag, const std::optional< Eigen::Matrix< double, 6, 6 > > &optionalPoseCovarianceInWorldRos=std::nullopt)
static void writeDoubleToCsvFile(std::map< std::string, std::ofstream > &fileStreams, const double value, const std::string &transformIdentifier, const double timeStamp)
static void writePose3ToTumFile(std::map< std::string, std::ofstream > &fileStreams, const gtsam::Pose3 &pose, const std::string &transformIdentifier, const double timeStamp)
static void createPose3CsvFileStream(std::map< std::string, std::ofstream > &fileStreams, const std::string &savePath, const std::string &transformIdentifier, const std::string &timeString, const bool saveCovarianceFlag)
static void createDoubleCsvFileStream(std::map< std::string, std::ofstream > &fileStreams, const std::string &savePath, const std::string &transformIdentifier, const std::string &timeString)
static void createPose3TumFileStream(std::map< std::string, std::ofstream > &fileStreams, const std::string &savePath, const std::string &transformIdentifier, const std::string &timeString)
const uint64_t newPropagatedStateKey_()
Generate new key.
std::shared_ptr< TimeGraphKeyBuffer > timeToKeyBufferPtr_
gtsam::NavState O_imuPropagatedState_
std::shared_ptr< gtsam::PreintegratedCombinedMeasurements > imuStepPreintegratorPtr_
Step Preintegrator.
void saveOptimizedValuesToFile(const gtsam::Values &optimizedValues, const std::map< gtsam::Key, double > &keyTimestampMap, const std::string &savePath, const bool saveCovarianceFlag)
std::set< std::pair< std::string, std::string > > realTimeReferenceFrameNamePairs_
std::map< double, gtsam::Pose3 > realTimeOdomPoseContainer_
bool logLatencyAndUpdateDuration(const std::string &savePath, const std::string &timeString)
void addImuFactorAndGetState(SafeIntegratedNavState &returnPreIntegratedNavState, std::shared_ptr< SafeNavStateWithCovarianceAndBias > &newOptimizedNavStatePtr, const std::shared_ptr< ImuBuffer > &imuBufferPtr, double imuTimeK, bool createNewStateFlag)
bool setInitialWorldFrameToFixedFrameTransform(const Eigen::Isometry3d &T_W_F, const std::string &fixedFrame)
bool addFactorsToBatchSmootherAndOptimize(const gtsam::NonlinearFactorGraph &newBatchGraphFactors, const gtsam::Values &newBatchGraphValues, const std::map< gtsam::Key, double > &newBatchGraphKeysTimestampsMap, const std::shared_ptr< GraphConfig > &graphConfigPtr)
bool runSlowBatchSmootherOptimization_(int maxIterations)
bool getUnaryFactorGeneralKey(gtsam::Key &returnedKey, double &returnedGraphTime, const UnaryMeasurement &unaryMeasurement)
bool hasReferenceFrameKeyframe(const std::string &worldFrame, const std::string &fixedFrame)
std::map< double, double > updateDurationContainer_
gtsam::NavState calculateStateAtGeneralKey(bool &computeSuccessfulFlag, const gtsam::Key &generalKey)
bool logRealTimeNavStates(const std::string &savePath, const std::string &timeString)
std::shared_ptr< holistic_fusion::GraphConfig > graphConfigPtr_
Config.
std::map< double, double > latencyContainer_
std::shared_ptr< gtsam::NonlinearFactorGraph > batchFactorGraphBufferPtr_
bool initImuIntegrators(double gravityValue)
gtsam::Vector3 latestGraphStateKeyAngularVelocity_
static gtsam::NavState calculateNavStateAtGeneralKey(bool &computeSuccessfulFlag, std::shared_ptr< holistic_fusion::OptimizerBase > graphPtr, const gtsam::Key &generalKey, const char *callingFunctionName)
std::shared_ptr< OptimizerBase > batchOptimizerPtr_
void writeValueKeysToKeyTimeStampMap_(const gtsam::Values &values, double measurementTime, std::shared_ptr< std::map< gtsam::Key, double > > keyTimestampMapPtr)
std::map< double, TransformsDictionary< Eigen::Isometry3d > > realTimeReferenceFrameContainer_
bool logRealTimeReferenceFrameStates(const std::string &savePath, const std::string &timeString)
void updateImuIntegrators_(const TimeToImuMap &imuMeas)
Update IMU integrators.
BatchOptimizationResult extractBatchOptimizationResult_(const bool computePoseCovarianceFlag)
std::mutex operateOnGraphDataMutex_
Mutex.
std::chrono::time_point< std::chrono::high_resolution_clock > latencyEndTime_
GraphManager(std::shared_ptr< GraphConfig > graphConfigPtr, std::string imuFrame, std::string worldFrame)
std::chrono::time_point< std::chrono::high_resolution_clock > updateDurationEndTime_
bool initPoseVelocityBiasGraph(double timeStamp, const gtsam::Pose3 &T_W_I0, const gtsam::Pose3 &T_O_I0, const gtsam::Vector3 &imuAngularVelocity)
DynamicDictionaryContainer gtsamDynamicExpressionKeys_
std::shared_ptr< OptimizerBase > rtOptimizerPtr_
bool addFactorToRtAndBatchGraph_(const gtsam::NoiseModelFactor *noiseModelFactorPtr, const bool addToOnlineSmootherFlag=true)
std::chrono::time_point< std::chrono::high_resolution_clock > latencyStartTime_
bool findGraphKeys_(gtsam::Key &closestKeyKm1, gtsam::Key &closestKeyK, double &keyTimeStampDistance, double maxTimestampDistance, double timeKm1, double timeK, const std::string &name)
Find graph keys for timestamps.
std::map< double, gtsam::Pose3 > realTimeWorldPoseContainer_
holistic_fusion::GraphState optimizedGraphState_
std::shared_ptr< gtsam::NonlinearFactorGraph > rtFactorGraphBufferPtr_
Data buffer.
std::shared_ptr< std::map< gtsam::Key, double > > rtGraphKeysTimestampsMapBufferPtr_
std::shared_ptr< gtsam::Values > batchGraphValuesBufferPtr_
bool addFactorsToRtSmootherAndOptimize(const gtsam::NonlinearFactorGraph &newRtGraphFactors, const gtsam::Values &newRtGraphValues, const std::map< gtsam::Key, double > &newRtGraphKeysTimestampsMap, const std::shared_ptr< GraphConfig > &graphConfigPtr, const int additionalIterations)
gtsam::NavState W_imuPropagatedState_
Propagated state (at IMU frequency).
static Eigen::Matrix< double, 6, 6 > calculatePoseCovarianceAtKeyInWorldFrame(std::shared_ptr< holistic_fusion::OptimizerBase > graphPtr, const gtsam::Key &graphKey, const char *callingFunctionName, std::optional< const gtsam::NavState > optionalNavState=std::nullopt)
gtsam::Key addPoseBetweenFactor(const gtsam::Pose3 &deltaPose, const Eigen::Matrix< double, 6, 1 > &poseBetweenNoiseDensity, double lidarTimeKm1, double lidarTimeK, double rate, const RobustNormEnum &robustNormEnum, const double robustNormConstant)
std::shared_ptr< gtsam::imuBias::ConstantBias > imuBiasPriorPtr_
std::shared_ptr< std::map< gtsam::Key, double > > batchGraphKeysTimestampsMapBufferPtr_
static void saveOptimizedGraphToG2o(const OptimizerBase &optimizedGraph, const gtsam::Values &optimizedValues, const std::string &saveFileName)
bool addFactorSafelyToRtAndBatchGraph_(const gtsam::NoiseModelFactor *noiseModelFactorPtr, double measurementTimestamp)
std::chrono::time_point< std::chrono::high_resolution_clock > updateDurationStartTime_
std::shared_ptr< gtsam::Values > rtGraphValuesBufferPtr_
std::shared_ptr< gtsam::PreintegratedCombinedMeasurements > imuBufferPreintegratorPtr_
Buffer Preintegrator.
void writeKeyToKeyTimeStampMap_(const gtsam::Key &key, double measurementTime, std::shared_ptr< std::map< gtsam::Key, double > > keyTimestampMapPtr)
Associate timestamp to each 'value key', e.g. for graph key 0, value keys (x0,v0,b0) need to be assoc...
std::shared_ptr< gtsam::PreintegratedCombinedMeasurements::Params > imuParamsPtr_
bool optimizeSlowBatchSmoother(int maxIterations, const std::string &savePath, const bool saveCovarianceFlag)
const std::string & measurementName() const
Names.
Definition Measurement.h:82
virtual const gtsam::NonlinearFactorGraph & getNonlinearFactorGraph() const =0
void update(const Eigen::Isometry3d &T_W_O, const Eigen::Isometry3d &T_O_Ik_gravityAligned, const Eigen::Vector3d &I_v_W_I, const Eigen::Vector3d &I_w_W_I, double timeK, bool odomNotJump)
Definition NavState.cpp:72
void set_T_frame1_frame2(const std::string &frame1, const std::string &frame2, const TRANSFORM_TYPE T_frame1_frame2)
std::map< std::pair< std::string, std::string >, TRANSFORM_TYPE > & getTransformsMap()
Class to represent a unary measurement in the graph.
std::map< double, holistic_fusion::ImuMeasurement, std::less< double >, Eigen::aligned_allocator< std::pair< const double, holistic_fusion::ImuMeasurement > > > TimeToImuMap
Definition ImuBuffer.hpp:35
@ kFunctioningAndStateMachine1
Definition GraphConfig.h:17
Eigen::Matrix< double, 6, 6 > convertCovarianceGtsamConventionToRosConvention(const Eigen::Matrix< double, 6, 6 > &covGtsam)
Convert a 6x6 covariance matrix from ROS convention to GTSAM convention.
Definition conversions.h:18
constexpr std::array< char, numStates > getSymbolArrayForNDStates()
bool isCharInCharArray(const char c, std::array< char, ARRAY_SIZE > charArray)
constexpr int countNDStates()
std::vector< BatchOptimizedNavState > navStates