Skip to content

Added GlobalPositioner Class#2495

Merged
dellaert merged 5 commits intoborglab:developfrom
kathirgounder:feature/global_positioner
Apr 21, 2026
Merged

Added GlobalPositioner Class#2495
dellaert merged 5 commits intoborglab:developfrom
kathirgounder:feature/global_positioner

Conversation

@kathirgounder
Copy link
Copy Markdown
Contributor

@kathirgounder kathirgounder commented Apr 10, 2026

Status

  • Base class refactor: LocationRecoveryGlobalPositioner / TranslationRecovery
  • Tested end-to-end with GTSFM (baseline + GP + mixed configs)
  • Commit and push tests
  • Concise documentation

TLDR

Extracts a LocationRecovery base class from TranslationRecovery. The base class is an unopinionated, directly instantiable solver: direction measurements in, Point3 positions out. Two opinionated subclasses enforce specific graph structures:

LocationRecovery        — any Point3-from-directions problem
├── GlobalPositioner    — bipartite camera+landmark (GLOMAP)
└── TranslationRecovery — homogeneous camera-camera (1DSFM)

TranslationRecovery API is unchanged. No regressions. Ran end to end benchmark tests locally, with gtsam-develop master branch and my local build, same numbers for both.


What's in this PR

File Action
LocationRecovery.h/cpp NEW — base class (~170 lines)
GlobalPositioner.h/cpp MODIFIED — inherits from LocationRecovery
TranslationRecovery.h/cpp MODIFIED — inherits from LocationRecovery, API unchanged
sfm.i MODIFIED — bindings for all three classes

Why a base class?

LocationRecovery can express problems directly as well, I used it to test "MixedGlobalPositioning" (GP with additional camera-camera BATA Factors). In GTSFM, we use all three tiers:

Opinionated GP (bipartite camera+landmark, one-liner):

gp = gtsam.GlobalPositioner(lm_params)
result = gp.run(cameraPointDirections, camera_keys, landmark_keys, anchor_camera_key)

Opinionated TA (unchanged from before this PR):

tr = gtsam.TranslationRecovery(lm_params)
result = tr.run(relative_translations, 1.0)

Unopinionated base (custom mixed-edge experiment):

lr = gtsam.LocationRecovery(lm_params)
graph = lr.buildGraph(all_measurements, True)  # CP + CC edges in one graph
lr.addAnchorPrior(anchor_key, graph)
initial = lr.initializeRandomly(all_keys, total_edges, True)
result = gtsam.LevenbergMarquardtOptimizer(graph, initial, lm_params).optimize()

Downstream GTSFM Benchmark Experiments

Configs compared:

  • GP (CP only)GlobalPositioner with camera–point edges only
  • Mixed (CC+CP)LocationRecovery base class with camera–camera + camera–point edges
  • GP no 2vBAGlobalPositioner without 2-view bundle adjustment
  • BaselineTranslationRecovery (1DSFM, pre-refactor)

door-12

Config                  Cams  Tracks   Reproj     Rot   Trans   AUC@1  AUC@2.5   AUC@5  AUC@10  AUC@20   BA(s)
-------------------------------------------------------------------------------------------------------------------
GP (CP only)              12    7089   0.1210   0.100   0.045   0.845    0.938   0.969   0.985   0.992     3.1
Mixed (CC+CP)             12    7089   0.1210   0.100   0.045   0.845    0.938   0.969   0.985   0.992     3.1
GP no 2vBA                                                          MISSING
Baseline                  12    7106   0.1210   0.098   0.046   0.849    0.940   0.970   0.985   0.993     6.0

skydio-8

Config                  Cams  Tracks   Reproj     Rot   Trans   AUC@1  AUC@2.5   AUC@5  AUC@10  AUC@20   BA(s)
-------------------------------------------------------------------------------------------------------------------
GP (CP only)               8     290   0.2547   0.382   0.432   0.358    0.731   0.865   0.933   0.966     3.8
Mixed (CC+CP)              8     289   0.2589   0.389   0.334   0.323    0.716   0.858   0.929   0.965     1.3
GP no 2vBA                 6      14   0.5321   0.118  41.874   0.000    0.000   0.000   0.000   0.025    24.6
Baseline                   8     301   0.2547   0.309   0.382   0.553    0.821   0.910   0.955   0.978     2.2

gerrard-100

Config                  Cams  Tracks   Reproj     Rot   Trans   AUC@1  AUC@2.5   AUC@5  AUC@10  AUC@20   BA(s)
-------------------------------------------------------------------------------------------------------------------
GP (CP only)             100   13948   0.1951   0.060   0.029   0.906    0.962   0.981   0.991   0.995    14.7
Mixed (CC+CP)            100   13948   0.1952   0.054   0.029   0.913    0.965   0.982   0.991   0.996    15.9
GP no 2vBA               100   13914   0.1997   0.117   0.065   0.843    0.937   0.968   0.984   0.992    20.2
Baseline                 100   14259   0.1996   0.091   0.040   0.826    0.929   0.964   0.982   0.991    15.0

south-128

Config                  Cams  Tracks   Reproj     Rot   Trans   AUC@1  AUC@2.5   AUC@5  AUC@10  AUC@20   BA(s)
-------------------------------------------------------------------------------------------------------------------
GP (CP only)             128   20929   0.1958   0.051   0.049   0.890    0.956   0.978   0.989   0.995    18.7
Mixed (CC+CP)            128   20902   0.1953   0.219   0.143   0.727    0.888   0.944   0.972   0.986    19.9
GP no 2vBA               128   20732   0.1997   0.070   0.053   0.874    0.949   0.975   0.987   0.994    18.7
Baseline                 128   21364   0.1981   0.061   0.029   0.898    0.959   0.980   0.990   0.995    27.6

palace-281

Config                  Cams  Tracks   Reproj     Rot   Trans   AUC@1  AUC@2.5   AUC@5  AUC@10  AUC@20   BA(s)
-------------------------------------------------------------------------------------------------------------------
GP (CP only)             281   12209   0.3439   0.153   0.153   0.595    0.822   0.909   0.954   0.977    34.5
Mixed (CC+CP)              9       3   0.6073   0.346  45.646   0.000    0.000   0.000   0.000   0.000  1133.4
GP no 2vBA                 9       3   0.0000   0.274  13.651   0.000    0.000   0.000   0.000   0.000   704.7
Baseline                 281   12261   0.6068   0.238   0.132   0.431    0.742   0.866   0.931   0.965    15.8

Tests

All pass locally (testLocationRecovery, testGlobalPositioner, testTranslationRecovery):

  • testLocationRecovery — base class solves end-to-end with both BATA and chordal costs; verifies graph construction and initialization for each factor type
  • testGlobalPositioner — recovers camera + landmark positions from synthetic bipartite scene; validates anchor-must-be-camera constraint
  • testTranslationRecovery — existing tests pass unchanged (backward compatible)

References

  • Pan et al., "Global Structure-from-Motion Revisited," ECCV 2024.
  • Zhuang et al., "Baseline Desensitizing in Translation Averaging," CVPR 2018.

@dellaert
Copy link
Copy Markdown
Member

We agreed on creating a base class, which should make it more clear what the difference is between the two. Also edit the PR comments if your understanding is now different after our discussion.

@dellaert
Copy link
Copy Markdown
Member

For the record, not in love with the name “global positioner”

@kathirgounder
Copy link
Copy Markdown
Contributor Author

kathirgounder commented Apr 10, 2026

@dellaert Hi Frank, I agree with not being in love with the GlobalPositioner name, it implies a very detailed and novel estimator architecture and doesn't provide any intuition on the structure of the actual optimization being performed. That being said I think the name is basically the standard for this new pipeline stage in all the sfm papers I've been reading that came out since Glomap.

I think with this base class refactor I am working on we will be able to satisfy everyone with the base class representing an Un-opinionated absolute position optimization that doesn't enforce any specific graph structure (I haven't come up with a good name yet) and with the opinionated (and standard definition following) TranslationAveraging and GlobalPositioner subclasses. GTSFM users should also be able to instantiate the Base Class and impose any custom graph structure they think for benchmarks and research questions (eg. do camera-to-camera constraints help on dataset like Skydio-8)

I am about to board a flight to Oregon to visit my girlfriend and family for a quick break, I will be able to tackle everything in full force starting Thursday ! I'm super energized about this Frank, thank you for your support

@dellaert dellaert marked this pull request as ready for review April 19, 2026 14:45
Copy link
Copy Markdown
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR body still has “Commit and push tests” unchecked; please attach the exact test targets and pass/fail output (or CI links) before review is finalized.

Comment thread gtsam/sfm/GlobalPositioner.h Outdated
Comment thread gtsam/sfm/GlobalPositioner.cpp Outdated
Comment thread gtsam/sfm/GlobalPositioner.cpp Outdated
@dellaert
Copy link
Copy Markdown
Member

dellaert commented Apr 19, 2026

Also launching a quick copilot review. Evaluate its comments with grain of salt - but sometimes quite useful.

Update: copilot not available :-/

@kathirgounder
Copy link
Copy Markdown
Contributor Author

All pass locally (testLocationRecovery, testGlobalPositioner, testTranslationRecovery):

  • testLocationRecovery — base class solves end-to-end with both BATA and chordal costs; verifies graph construction and initialization for each factor type
  • testGlobalPositioner — recovers camera + landmark positions from synthetic bipartite scene; validates anchor-must-be-camera constraint
  • testTranslationRecovery — existing tests pass unchanged (backward compatible)

@dellaert Pushed the tests, I think I need maintainer approval to run the CI

@kathirgounder
Copy link
Copy Markdown
Contributor Author

@dellaert Hey frank sorry need reapproval, had to update the python bindings in the pr

@dellaert
Copy link
Copy Markdown
Member

Ping on slack next time

Copy link
Copy Markdown
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@dellaert dellaert merged commit e1bbb08 into borglab:develop Apr 21, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants