Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Custom Factor Trajectory Localization

This notebook shows how to use gtsam.CustomFactor inside a small Pose2 trajectory problem.

Each custom factor:

  • touches one pose in the trajectory

  • stores that pose’s landmark measurements

  • stacks all landmark residuals into one vector

  • fills the analytical Jacobian using Pose2.transformTo

We then combine those custom factors with standard odometry BetweenFactorPose2 factors so the graph is a connected trajectory.

Open In Colab

1. Landmarks and Noise Models

We use the same known landmarks for every pose. To make the plot more interesting, the landmark measurements are fairly noisy, while odometry stays reasonably accurate.

Landmarks:
  L0: [5. 0.]
  L1: [2. 3.]
  L2: [6. 4.]
  L3: [8. 1.]

2. Custom Residual Object

The residual object is itself the callback passed into CustomFactor. This keeps the example compact while still using analytical Jacobians.

The important trick in Python is to allocate Jacobian arrays in Fortran order before passing them into Pose2.transformTo.

3. Create a Noisy Trajectory Problem

Landmark measurements at pose 0:
  [ 3.23061308 -3.01806742]
  [1.18232223 0.43716832]
  [5.25405516 0.18516031]
  [ 6.40687112 -2.68892412]

4. Inspect One Custom Factor

At the ground truth, the residual is not zero anymore because we injected noise into the landmark measurements. Still, we can linearize the factor and inspect the Jacobian generated by the callback.

Nonlinear error: 1.9737191833686931
Jacobian shape: (8, 3)
array([[ -4. , 0. , -12.37101522], [ 0. , -4. , -12.92122217], [ -4. , 0. , 2.63926513], [ 0. , -4. , -5.00342678], [ -4. , 0. , 1.73228778], [ 0. , -4. , -21.47089144], [ -4. , 0. , -12.09591174], [ 0. , -4. , -25.56734087]])

5. Solve the Trajectory

Each pose gets one landmark CustomFactor, and consecutive poses are connected with odometry BetweenFactorPose2 factors.

True pose 0:      (1, 2, 0.3)

Estimated pose 0: (1.14111, 2.30025, 0.237817)


True pose 1:      (2.5, 1, -0.2)

Estimated pose 1: (2.53991, 1.15384, -0.231066)


True pose 2:      (3, 2.8, 0.4)

Estimated pose 2: (3.21574, 2.96852, 0.396358)


True pose 3:      (4.4, 1.6, -0.1)

Estimated pose 3: (4.59661, 1.6944, -0.112688)


6. Plot Ground Truth and Optimized Trajectories

The landmark measurements are noisy enough that the optimized trajectory does not sit exactly on top of ground truth, which makes the comparison easier to see.

<Figure size 700x600 with 1 Axes>

Takeaways

  • CustomFactor works well when you already know the residual you want to write.

  • A small callable object is often the cleanest way to package measurements and constants.

  • In Python, Pose2.transformTo can now provide analytical Jacobians directly when you pass Fortran-order output arrays.

  • Custom factors combine naturally with standard GTSAM factors such as BetweenFactorPose2 in the same trajectory graph.