Repository navigation
[sfm] Fix bugs in the expanding pipeline and its local bundle adjustment - #2224
Merged
Merged
Conversation
servantftransperfect
force-pushed
the
fix/sfmBugs
branch
from
October 26, 2026 13:42
83f64cd to
4b75de1
Compare
Weighted observations created a ScaledLoss per residual while the problem was configured with DO_NOT_TAKE_OWNERSHIP, so these objects were never freed (leak at each adjust). Each ScaledLoss also took ownership of the HuberLoss shared through CeresOptions, which would have caused a double free if they had been released. CeresOptions now stores the Huber parameter instead of a loss object. Losses are created per problem through getLossFunction() and owned by the ceres problem (shared pointers are deleted once by ceres). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landmark state was only ever set to IGNORED and never reset, so once a landmark was ignored by the local strategy it stayed ignored for the rest of the expansion (resetParameterStates is no longer called once LBA is active). Always assign the computed state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ig pose is removed Regression from "Non removable poses concept": rig views were set as independent but kept the removed shared pose id. When one of them was localized again, all its siblings appeared reconstructed with the same (wrong) pose. Restore the previous behavior using the view id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… are removed The result of eraseObservationsWithMissingPoses was overwritten by eraseLandmarksWithMissingReferenceView, so removing observations never triggered a new pass. Poses could be left with fewer points than minPointsPerPose. Also remove redundant flags: observations are only removed as a consequence of removed poses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Distances were looked up by pose id but stored by view id. For rig views sharing a pose, the pose was never found, got an infinite distance and was ignored by the local bundle adjustment (with all its landmarks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The option was parsed but never used, so intrinsics were always refined. Lock all intrinsics when it is set. Locked intrinsics are constant in the bundle adjustment and in the local strategy: their state is CONSTANT, so the connexity graph does not link all the views sharing them, which would otherwise make the local bundle refine the whole scene. The lock flag is saved in the output sfmData, as the option requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_useLBA was hard-coded to true, so with useLocalBA disabled and enough poses, neither the local strategy nor resetParameterStates was applied and the parameter states were left as is. The LBA policy build result was also ignored. Use the local strategy only when a policy is set and fall back to a full bundle if it cannot be built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…update Cocardinalities were only computed when a view was first added, so the counts between existing views were never updated when landmarks were added, re-triangulated or removed by the outlier filters. They were also computed from tracks, counting landmarks where the view observation had been rejected. Keep the observers of each landmark from the previous update and only update the pairs of landmarks which changed. The counts are now always equal to the current landmarks observations, for a cost linear in the number of observations. Also skip edges for views which are not in the graph. Add a unit test reusing a graph across builds while landmarks are added, modified and removed, and comparing it with a new graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A re-evaluated track whose new landmark was rejected lost its previous landmark, but a track whose triangulation itself failed kept it. Remove the previous landmark in both cases. The depth prior pass may still provide a landmark for these tracks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExpansionChunk::process returned false both when no view could be resected and when triangulation or bundle adjustment failed after the scene was modified. ExpansionIteration skipped both cases, so a failed bundle left unrefined poses and landmarks in the scene and the expansion could finish successfully without any refinement. Return an EChunkResult to distinguish success, skipped chunk and error. ExpansionIteration now stops on errors, like the other callers already did. sfmExpanding still exits with a failure, but saves the partial scene first, so that the work done so far is not lost. It can be resumed, as an existing scene is bundled again on input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nored Mesh-based landmarks are parameterized in their reference view frame and the bundle adjustment requires the reference pose block. When the reference view observation had been removed, the landmark could stay active while its reference pose was ignored by the local strategy, and the bundle adjustment threw std::out_of_range. Take the reference pose into account when computing the landmark state. Add a unit test with a landmark whose reference pose is ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ready in use Using the view id as the new pose id of a detached rig view could collide with another surviving pose, making the view appear reconstructed with an unrelated pose. Use the view id only when it is free, otherwise the next unused id. Add a regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
servantftransperfect
force-pushed
the
fix/sfmBugs
branch
from
October 28, 2026 07:26
4b75de1 to
4cff084
Compare
fabiencastan
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Bug fixes in the expanding SfM pipeline (
sfmExpanding) and the Ceres bundle adjustment. Each commit is one self-contained fix and can be reviewed on its own.Bundle adjustment
ScaledLoss.CeresOptionsstores the Huber threshold instead of a loss object.Local bundle adjustment strategy
std::out_of_range.Scene cleanup
eraseUnstablePosesAndObservationsloops again when observations are removed — the result of the observation filter was overwritten by the landmark filter.Expansion
ExpansionChunk::processnow returnsEChunkResult(success / skipped / error). Before, a failed bundle left an unrefined scene and the expansion could still succeed.sfmExpandingstill exits with a failure, but first saves the partial scene to the output, so the work can be resumed.lockAllIntrinsicsis honored — the option was parsed but never used. All intrinsics are now locked, so they stay constant in the bundle and do not link every view in the connexity graph.Behavior changes for users
lockAllIntrinsics, the lock flag is now saved in the output sfmData.sfmExpandingfails instead of finishing with an unrefined scene. The partial scene is still written to the output file.Tests
New
sfm_lbaPolicyConnexitytest:ConnexityGraph_incrementalUpdate: reuses one graph across builds while landmarks are added, modified and removed, and compares it with a new graph.LbaPolicyConnexity_ignoredReferencePose: a landmark whose reference pose is ignored must be ignored as well.Both tests fail when their fix is reverted.
🤖 Generated with Claude Code