WorldView Jitter Plot Example for Salar de Uyuni with asp_plot#

This notebook demonstrates CSM camera analysis to compare original and optimized camera models, using an example WorldView-3 stereo pair over Salar de Uyuni, Bolivia — a large, flat salt pan that makes the jitter signal easy to see.

CSM Camera Model Analysis#

Compare original and optimized CSM (Community Sensor Model) camera models from bundle_adjust or jitter_solve.

Visualizes, for each camera:

  • Position differences (X, Y, Z) along the satellite orbit

  • Orientation angle differences (roll, pitch, yaw)

  • The magnitude of both, mapped along the camera footprint

Both cameras are expressed in a single satellite body frame, estimated from the original ephemeris. That matters: jitter_solve resamples the trajectory to a finer spacing and nudges the positions, so a frame estimated separately from each camera’s own ephemeris (as ASP’s orbit_plot.py does) would swing by ~1 degree from position noise alone and swamp the orientation change actually being measured (see #53).

import contextily as ctx
from asp_plot.csm_camera import csm_camera_summary_plot
map_crs = "32619" # UTM 19S (for Uyuni)
title = "Jitter correction (Uyuni), Less Constrained"

ctx_kwargs = {
    "crs": f"EPSG:{map_crs}",
    "source": ctx.providers.Esri.WorldImagery,
    "attribution_size": 0,
    "alpha": 0.5,
}

# First set of cameras (scene 1)
original_camera = "../../tests/test_data/jitter/uyuni/csm-104001001427B900.r100.adjusted_state.json"
optimized_camera = "../../tests/test_data/jitter/uyuni/jitter_solved_run-csm-104001001427B900.r100.adjusted_state.json"
cam1_list = [original_camera, optimized_camera]

# Second set of cameras (scene 2)
original_camera = "../../tests/test_data/jitter/uyuni/csm-1040010014761800.r100.adjusted_state.json"
optimized_camera = "../../tests/test_data/jitter/uyuni/jitter_solved_run-csm-1040010014761800.r100.adjusted_state.json"
cam2_list = [original_camera, optimized_camera]

CSM Camera Comparison#

The default trim=True restricts the plot to the camera samples that actually image the ground. jitter_solve pads the trajectory beyond the first and last image line, and those padded samples are unconstrained by any interest point match, so they are free to wander by kilometers — see the last cell of this notebook for what they look like.

For this pair, the run moved the camera positions by roughly ±2 m, with the oscillation characteristic of WorldView jitter, and left the camera orientations essentially untouched (differences of 1e-6 to 1e-3 degrees, i.e. numerical noise). Reading the two rows per camera together is what tells you which of the two the solver actually used.

csm_camera_summary_plot(
    cam1_list,
    cam2_list,
    map_crs=map_crs,
    title=title,
    figsize=(20, 15),
    add_basemap=True,
    **ctx_kwargs
)
../../_images/f15b4177541955b800705bd6d5b14453745c6dedacb9ce0ef575bed5c9b8682d.png

Emphasizing Patterns#

shared_scales puts every position (and every angle) panel on one y-axis, so the components are directly comparable. log_scale_positions and log_scale_angles switch to a symmetric log axis, and upper_magnitude_percentile clips the map colorbars. These are most useful when a run has a large dynamic range or a few extreme samples.

csm_camera_summary_plot(
    cam1_list,
    cam2_list,
    map_crs=map_crs,
    title=title,
    shared_scales=True,
    log_scale_positions=True,
    log_scale_angles=True,
    upper_magnitude_percentile=95,
    figsize=(20, 15),
    add_basemap=True,
    **ctx_kwargs
)
../../_images/1b98ca498e8f80e50bb0883695216a35cb7c3176be54e8b229776845447fa77e.png

Single Camera Comparison#

You can also analyze a single satellite by passing only cam1_list.

csm_camera_summary_plot(
    cam1_list,
    map_crs=map_crs,
    title=title,
    figsize=(20, 15),
    add_basemap=True,
    **ctx_kwargs
)
../../_images/8d7de046f3f13c6c2703b9146b7cd589b40837b96ec7afc85ad83105fd116dc4.png

Why trim Matters#

Passing trim=False plots every ephemeris sample in the camera file, including the ones jitter_solve padded beyond the first and last image line. Those samples are not constrained by any interest point match, so they carry position “changes” of tens of kilometers that have no physical meaning and that dominate every axis. The x-axis label changes from Linescan Image Line Number to Position Sample to make it clear you are no longer looking at the imaged part of the orbit.

csm_camera_summary_plot(
    cam1_list,
    map_crs=map_crs,
    title=f"{title} (untrimmed)",
    trim=False,
    figsize=(20, 15),
    add_basemap=True,
    **ctx_kwargs
)
../../_images/ee012e20a548e22a52576bcf5db81ff716d005e96124bae52ec83a2a88fa05b0.png