Skip to content

Commit a5d5a8c

Browse files
committed
factor out key from state object
1 parent 7d9496f commit a5d5a8c

File tree

9 files changed

+102
-90
lines changed

9 files changed

+102
-90
lines changed

gtsam_unstable/navigation/EqVIOFilter.cpp

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
#include <gtsam_unstable/navigation/EqVIOFilter.h>
1919

20+
#include <algorithm>
2021
#include <cassert>
22+
#include <map>
23+
#include <numeric>
24+
#include <set>
2125
#include <stdexcept>
2226
#include <tuple>
2327

@@ -41,6 +45,7 @@ EqVIOFilter::EqVIOFilter(const EqVIOFilterParams& params)
4145
xi0.sensor.velocity.setZero();
4246
xi0.sensor.cameraOffset = Pose3::Identity();
4347
resetReferenceAndGroup(xi0, defaultCovariance(0), makeVioGroupIdentity());
48+
landmarkKeys_.clear();
4449
}
4550

4651
/**
@@ -53,6 +58,8 @@ EqVIOFilter::EqVIOFilter(const State& xi_ref, const Matrix& Sigma,
5358
: Base(State(), defaultCovariance(0), makeVioGroupIdentity()),
5459
params_(params) {
5560
resetReferenceAndGroup(xi_ref, Sigma, makeVioGroupIdentity(xi_ref.n()));
61+
landmarkKeys_.resize(xi_ref.n());
62+
std::iota(landmarkKeys_.begin(), landmarkKeys_.end(), 0);
5663
initialized_ = true;
5764
}
5865

@@ -192,9 +199,9 @@ void EqVIOFilter::innovationUpdate(
192199
if (measurement.empty()) return;
193200

194201
const VisionMeasurement estimatedMeasurement =
195-
measureSystemState(state(), camera);
196-
const Matrix Ct = EqFoutputMatrixC(referenceState(), groupEstimate(),
197-
measurement, camera, true);
202+
measureSystemState(state(), landmarkKeys_, camera);
203+
const Matrix Ct = EqFoutputMatrixC(referenceState(), landmarkKeys_,
204+
groupEstimate(), measurement, camera, true);
198205
const Matrix Rused = (outputGainMatrix.rows() == Ct.rows() &&
199206
outputGainMatrix.cols() == Ct.rows())
200207
? outputGainMatrix
@@ -222,13 +229,15 @@ void EqVIOFilter::addNewLandmarks(
222229
if (!camera) throw std::invalid_argument("EqVIOFilter::addNewLandmarks: camera is null");
223230

224231
std::vector<Landmark> newLandmarks;
225-
const std::vector<Key> existingIds = referenceState().ids();
232+
std::vector<Key> newKeys;
233+
const std::vector<Key> existingIds = landmarkKeys_;
226234
for (const auto& [id, coord] : measurement) {
227235
if (std::find(existingIds.begin(), existingIds.end(), id) != existingIds.end()) {
228236
continue;
229237
}
230238
const Vector3 bearing = undistortPoint(*camera, coord);
231-
newLandmarks.push_back(Landmark{bearing, id});
239+
newLandmarks.push_back(Landmark{bearing});
240+
newKeys.push_back(id);
232241
}
233242

234243
if (newLandmarks.empty()) return;
@@ -244,6 +253,7 @@ void EqVIOFilter::addNewLandmarks(
244253
State xi_ref = referenceState();
245254
xi_ref.cameraLandmarks.insert(xi_ref.cameraLandmarks.end(), newLandmarks.begin(),
246255
newLandmarks.end());
256+
landmarkKeys_.insert(landmarkKeys_.end(), newKeys.begin(), newKeys.end());
247257

248258
const auto& [A, Beta, B, Q] = decompose(groupEstimate());
249259
std::vector<SOT3> q;
@@ -270,7 +280,7 @@ void EqVIOFilter::addNewLandmarks(
270280

271281
/// Remove any landmarks that are absent from the current measurement id list.
272282
void EqVIOFilter::removeOldLandmarks(const std::vector<Key>& measurementIds) {
273-
const std::vector<Key> existingIds = referenceState().ids();
283+
const std::vector<Key> existingIds = landmarkKeys_;
274284
std::vector<int> lostIndices(existingIds.size());
275285
std::iota(lostIndices.begin(), lostIndices.end(), 0);
276286
if (lostIndices.empty()) return;
@@ -295,6 +305,7 @@ void EqVIOFilter::removeOldLandmarks(const std::vector<Key>& measurementIds) {
295305
void EqVIOFilter::removeLandmarkByIndex(int idx) {
296306
State xi_ref = referenceState();
297307
xi_ref.cameraLandmarks.erase(xi_ref.cameraLandmarks.begin() + idx);
308+
landmarkKeys_.erase(landmarkKeys_.begin() + idx);
298309

299310
const auto& [A, Beta, B, Q] = decompose(groupEstimate());
300311

@@ -316,22 +327,16 @@ void EqVIOFilter::removeLandmarkByIndex(int idx) {
316327

317328
/// Remove landmark with matching key id.
318329
void EqVIOFilter::removeLandmarkById(Key id) {
319-
const auto it = std::find_if(
320-
referenceState().cameraLandmarks.begin(), referenceState().cameraLandmarks.end(),
321-
[&id](const Landmark& lm) { return lm.id == id; });
322-
assert(it != referenceState().cameraLandmarks.end());
323-
removeLandmarkByIndex(
324-
static_cast<int>(std::distance(referenceState().cameraLandmarks.begin(), it)));
330+
const auto it = std::find(landmarkKeys_.begin(), landmarkKeys_.end(), id);
331+
assert(it != landmarkKeys_.end());
332+
removeLandmarkByIndex(static_cast<int>(std::distance(landmarkKeys_.begin(), it)));
325333
}
326334

327335
/// Return 3x3 landmark-state covariance block for the specified id.
328336
Matrix3 EqVIOFilter::getLandmarkCovById(Key id) const {
329-
const auto it = std::find_if(
330-
referenceState().cameraLandmarks.begin(), referenceState().cameraLandmarks.end(),
331-
[&id](const Landmark& lm) { return lm.id == id; });
332-
assert(it != referenceState().cameraLandmarks.end());
333-
const int i =
334-
static_cast<int>(std::distance(referenceState().cameraLandmarks.begin(), it));
337+
const auto it = std::find(landmarkKeys_.begin(), landmarkKeys_.end(), id);
338+
assert(it != landmarkKeys_.end());
339+
const int i = static_cast<int>(std::distance(landmarkKeys_.begin(), it));
335340
return errorCovariance().block<3, 3>(SensorState::CompDim + 3 * i,
336341
SensorState::CompDim + 3 * i);
337342
}
@@ -340,17 +345,15 @@ Matrix3 EqVIOFilter::getLandmarkCovById(Key id) const {
340345
Matrix2 EqVIOFilter::outputCovarianceById(
341346
Key id, const std::shared_ptr<const CameraModel>& camera) const {
342347
const Matrix3 lmCov = getLandmarkCovById(id);
343-
const auto it = std::find_if(
344-
referenceState().cameraLandmarks.begin(), referenceState().cameraLandmarks.end(),
345-
[&id](const Landmark& lm) { return lm.id == id; });
346-
assert(it != referenceState().cameraLandmarks.end());
348+
const auto it = std::find(landmarkKeys_.begin(), landmarkKeys_.end(), id);
349+
assert(it != landmarkKeys_.end());
347350

348-
const size_t i =
349-
static_cast<size_t>(std::distance(referenceState().cameraLandmarks.begin(), it));
351+
const size_t i = static_cast<size_t>(std::distance(landmarkKeys_.begin(), it));
350352
const LandmarkGroup& Q = std::get<3>(decompose(groupEstimate()));
351353
const SOT3& Q_i = Q[i];
354+
const Point3& q0 = referenceState().cameraLandmarks[i].p;
352355

353-
const Matrix23 C0i = EqFoutputMatrixCi(it->p, Q_i, camera);
356+
const Matrix23 C0i = EqFoutputMatrixCi(q0, Q_i, camera);
354357
return C0i * lmCov * C0i.transpose();
355358
}
356359

@@ -361,7 +364,7 @@ void EqVIOFilter::removeInvalidLandmarks() {
361364
for (size_t i = 0; i < Q.size(); ++i) {
362365
const double a = SOT3Scale(Q[i]);
363366
if (!std::isfinite(a) || a <= 1e-8 || a > 1e8) {
364-
invalidIds.insert(referenceState().cameraLandmarks[i].id);
367+
invalidIds.insert(landmarkKeys_[i]);
365368
}
366369
}
367370
for (const Key id : invalidIds) {
@@ -382,7 +385,7 @@ void EqVIOFilter::removeOutliers(
382385
(1.0 - params_.featureRetention) * measurement.size());
383386
if (!camera) return;
384387

385-
const VisionMeasurement yHat = measureSystemState(state(), camera);
388+
const VisionMeasurement yHat = measureSystemState(state(), landmarkKeys_, camera);
386389

387390
std::vector<Key> proposedOutliers;
388391
std::map<Key, double> absoluteOutliers;

gtsam_unstable/navigation/EqVIOFilter.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@ struct EqVIOFilterParams {
6464
/**
6565
* @brief Standalone EqVIO filter built on top of `EquivariantFilter`.
6666
*
67-
* Prediction is split into covariance propagation (single averaged IMU segment)
68-
* and state propagation (piecewise IMU holds), preserving the original EqVIO
69-
* replay semantics while still using the base equivariant filter machinery.
67+
* Prediction uses the base equivariant `predict(...)` path per IMU hold,
68+
* while dynamic landmark add/remove bookkeeping stays in this runtime filter.
7069
*/
7170
class GTSAM_UNSTABLE_EXPORT EqVIOFilter
7271
: public EquivariantFilter<State, Symmetry> {
@@ -76,6 +75,8 @@ class GTSAM_UNSTABLE_EXPORT EqVIOFilter
7675
private:
7776
EqVIOFilterParams params_;
7877
bool initialized_ = false;
78+
// Runtime key ordering aligned with `cameraLandmarks` and covariance blocks.
79+
std::vector<Key> landmarkKeys_;
7980

8081
public:
8182
EqVIOFilter();

gtsam_unstable/navigation/EqVIOState.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ namespace eqvio {
2222

2323
void Landmark::print(const std::string& s) const {
2424
if (!s.empty()) std::cout << s << " ";
25-
std::cout << "Landmark{id=" << id << ", p=" << p.transpose() << "}"
25+
std::cout << "Landmark{p=" << p.transpose() << "}"
2626
<< std::endl;
2727
}
2828

2929
bool Landmark::equals(const Landmark& other, double tol) const {
30-
return id == other.id && equal_with_abs_tol(p, other.p, tol);
30+
return equal_with_abs_tol(p, other.p, tol);
3131
}
3232

3333
/// Gravity direction in body frame derived from current body pose.
@@ -59,13 +59,6 @@ int State::dim() const {
5959
return SensorState::CompDim + Landmark::CompDim * static_cast<int>(n());
6060
}
6161

62-
std::vector<Key> State::ids() const {
63-
std::vector<Key> out;
64-
out.reserve(cameraLandmarks.size());
65-
for (const Landmark& lm : cameraLandmarks) out.push_back(lm.id);
66-
return out;
67-
}
68-
6962
State State::retract(const TangentVector& v, ChartJacobian H1,
7063
ChartJacobian H2) const {
7164
const int d = dim();

gtsam_unstable/navigation/EqVIOState.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,13 @@ namespace eqvio {
3434
/**
3535
* @brief One visual landmark state block.
3636
*
37-
* Stores a 3D landmark position plus its key id. The Lie/chart dimension
38-
* contribution is 3.
37+
* Stores a 3D landmark position. The Lie/chart dimension contribution is 3.
3938
*/
4039
struct GTSAM_UNSTABLE_EXPORT Landmark {
4140
static constexpr int CompDim = 3;
4241

4342
/// Landmark position in world coordinates.
4443
Point3 p = Z_3x1;
45-
/// Stable landmark identifier used for ordering/alignment.
46-
Key id = 0;
4744

4845
void print(const std::string& s = "") const;
4946
bool equals(const Landmark& other, double tol = 1e-9) const;
@@ -93,7 +90,6 @@ class GTSAM_UNSTABLE_EXPORT State {
9390
/// Number of landmarks.
9491
size_t n() const;
9592
int dim() const;
96-
std::vector<Key> ids() const;
9793

9894
/// Retract in the state chart.
9995
State retract(const TangentVector& v, ChartJacobian H1 = {},

gtsam_unstable/navigation/EqVIOSymmetry.cpp

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
#include <gtsam_unstable/navigation/EqVIOSymmetry.h>
2020

21+
#include <algorithm>
2122
#include <functional>
23+
#include <numeric>
2224
#include <stdexcept>
2325

2426
namespace gtsam {
@@ -330,20 +332,25 @@ Matrix23 EqFoutputMatrixCi(
330332

331333
/// Assemble full stacked output matrix for currently observed landmarks.
332334
Matrix EqFoutputMatrixC(
333-
const State& xi0, const VioGroup& X, const VisionMeasurement& y,
335+
const State& xi0, const std::vector<Key>& landmarkIds, const VioGroup& X,
336+
const VisionMeasurement& y,
334337
const std::shared_ptr<const CameraModel>& camera, bool useEquivariance) {
335338
if (!camera) {
336339
throw std::invalid_argument("EqFoutputMatrixC: null camera");
337340
}
338341
const int M = static_cast<int>(xi0.n());
342+
if (landmarkIds.size() != xi0.n()) {
343+
throw std::invalid_argument(
344+
"EqFoutputMatrixC: landmark id count mismatch");
345+
}
339346
const std::vector<Key> yIds = measurementIds(y);
340347
const int N = static_cast<int>(yIds.size());
341348
const LandmarkGroup& Q = std::get<3>(decompose(X));
342349

343350
Matrix C = Matrix::Zero(2 * N, SensorState::CompDim + Landmark::CompDim * M);
344351

345352
for (int i = 0; i < M; ++i) {
346-
const Key idNum = xi0.cameraLandmarks[static_cast<size_t>(i)].id;
353+
const Key idNum = landmarkIds[static_cast<size_t>(i)];
347354
const auto itY = std::find(yIds.begin(), yIds.end(), idNum);
348355
if (itY == yIds.end()) continue;
349356

@@ -407,7 +414,6 @@ State stateGroupAction(const VioGroup& X, const State& state) {
407414
for (size_t i = 0; i < Q.size(); ++i) {
408415
out.cameraLandmarks[i].p =
409416
SOT3ApplyInverse(Q[i], state.cameraLandmarks[i].p);
410-
out.cameraLandmarks[i].id = state.cameraLandmarks[i].id;
411417
}
412418

413419
return out;
@@ -441,13 +447,11 @@ VioGroup liftVelocityDiscrete(const State& state, const IMUInput& velocity,
441447
std::vector<SOT3> q;
442448
q.resize(state.n());
443449
for (size_t i = 0; i < state.n(); ++i) {
444-
const Landmark& lm0 = state.cameraLandmarks[i];
445-
Landmark lm1;
446-
lm1.id = lm0.id;
447-
lm1.p = c1Tc0.transformFrom(lm0.p);
450+
const Point3 p0 = state.cameraLandmarks[i].p;
451+
const Point3 p1 = c1Tc0.transformFrom(p0);
448452

449-
const Rot3 R = RotationFromTwoVectors(lm1.p, lm0.p);
450-
const double a = lm0.p.norm() / lm1.p.norm();
453+
const Rot3 R = RotationFromTwoVectors(p1, p0);
454+
const double a = p0.norm() / p1.norm();
451455
q[i] = MakeSOT3(SO3(R.matrix()), a);
452456
}
453457

@@ -459,22 +463,34 @@ VioGroup liftVelocityDiscrete(const State& state, const IMUInput& velocity,
459463

460464
/**
461465
* @brief Predict ideal normalized image measurements from current state.
462-
* @throws std::invalid_argument if `camera` is null.
466+
* @throws std::invalid_argument if `camera` is null or id count mismatches landmarks.
463467
*/
464468
VisionMeasurement measureSystemState(
465-
const State& state, const std::shared_ptr<const CameraModel>& camera) {
469+
const State& state, const std::vector<Key>& landmarkIds,
470+
const std::shared_ptr<const CameraModel>& camera) {
466471
if (!camera) {
467472
throw std::invalid_argument("measureSystemState: camera model is null");
468473
}
474+
if (landmarkIds.size() != state.n()) {
475+
throw std::invalid_argument(
476+
"measureSystemState: landmark id count mismatch");
477+
}
469478

470-
// Project each landmark through the camera model
471479
VisionMeasurement out;
472-
for (const Landmark& lm : state.cameraLandmarks) {
473-
out[lm.id] = camera->project2(lm.p);
480+
for (size_t i = 0; i < state.n(); ++i) {
481+
out[landmarkIds[i]] = camera->project2(state.cameraLandmarks[i].p);
474482
}
475483
return out;
476484
}
477485

486+
/// Predict ideal measurements with sequential keys `0..n-1`.
487+
VisionMeasurement measureSystemState(
488+
const State& state, const std::shared_ptr<const CameraModel>& camera) {
489+
std::vector<Key> ids(state.n());
490+
std::iota(ids.begin(), ids.end(), 0);
491+
return measureSystemState(state, ids, camera);
492+
}
493+
478494
/// Evaluate symmetry action and optionally compute Jacobians w.r.t. state and group.
479495
State Symmetry::operator()(
480496
const State& xi, const VioGroup& X,

gtsam_unstable/navigation/EqVIOSymmetry.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ GTSAM_UNSTABLE_EXPORT VioGroup liftVelocityDiscrete(const State& state,
5555

5656
/**
5757
* @brief Generate ideal image measurements by projecting all landmarks.
58+
*
59+
* `landmarkIds[i]` is the key assigned to `state.cameraLandmarks[i]`.
60+
*
61+
* @throws std::invalid_argument if `camera` is null or id count mismatches landmarks.
62+
*/
63+
GTSAM_UNSTABLE_EXPORT VisionMeasurement measureSystemState(
64+
const State& state, const std::vector<Key>& landmarkIds,
65+
const std::shared_ptr<const CameraModel>& camera);
66+
67+
/**
68+
* @brief Generate ideal image measurements with sequential keys `0..n-1`.
5869
* @throws std::invalid_argument if `camera` is null.
5970
*/
6071
GTSAM_UNSTABLE_EXPORT VisionMeasurement measureSystemState(
@@ -86,7 +97,8 @@ GTSAM_UNSTABLE_EXPORT Matrix23 EqFoutputMatrixCi(
8697
const std::shared_ptr<const CameraModel>& camera);
8798
/// Stacked output matrix for all currently observed landmarks.
8899
GTSAM_UNSTABLE_EXPORT Matrix EqFoutputMatrixC(
89-
const State& xi0, const VioGroup& X, const VisionMeasurement& y,
100+
const State& xi0, const std::vector<Key>& landmarkIds, const VioGroup& X,
101+
const VisionMeasurement& y,
90102
const std::shared_ptr<const CameraModel>& camera,
91103
bool useEquivariance = true);
92104

0 commit comments

Comments
 (0)