asp_plot.bundle_adjust

Contents

asp_plot.bundle_adjust#

Attributes#

Classes#

PlotBundleAdjustCameras

Visualize before/after camera position and orientation changes.

PlotBundleAdjustFiles

Plot bundle adjustment results from GeoDataFrames.

ReadBundleAdjustCameras

Read before/after camera geometry from an ASP bundle_adjust folder.

ReadBundleAdjustFiles

Read and process bundle adjustment output files from ASP.

Module Contents#

class asp_plot.bundle_adjust.PlotBundleAdjustCameras(gdf, **kwargs)#

Bases: asp_plot.utils.Plotter

Visualize before/after camera position and orientation changes.

Consumes the GeoDataFrame from ReadBundleAdjustCameras.get_camera_optimization_gdf() and renders per-camera bar rows (issues #95 and #43):

  1. Horizontal and vertical camera-center change (meters).

  2. Roll / pitch / yaw orientation change (degrees), with the value printed on every bar. A single satellite cartoon beside the row is a legend for the body axes and the sense of each rotation (X = along-track, Y = across-track, Z = nadir); it is deliberately not scaled – the numbers carry the magnitude.

  3. Only when ASP wrote triangulation_offsets.txt (>= 3.6): the per-image median and mean change of the triangulated points (meters), i.e. what the camera change did on the ground. A solver can trade tens of meters of camera translation against millidegrees of rotation with almost no effect on the ground, so this row is the context for the first.

When a run changed nothing (e.g. an identity --initial-transform used to recover the unadjusted cameras), the panels are still drawn with a “no camera change” note overlaid, rather than left as empty axes.

Parameters:
plot_center_offset_bars(ax=None, save_dir=None, fig_fn=None, index_labels=False, legend_outside=False)#

Per-camera bars of horizontal and vertical camera-center change.

Uses ASP’s camera_offsets.txt values when available (see offsets_from_asp); otherwise falls back to the horizontal/vertical components of the .adjust translation.

Parameters:
  • ax (matplotlib.axes.Axes or None, optional) – Axes to draw on. A new figure is created if None.

  • save_dir (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • fig_fn (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • index_labels (bool, optional) – Label cameras by number only (for stacked rows that share the camera order); default False prints #n  camera_id.

  • legend_outside (bool, optional) – Place the legend to the right of the axes instead of inside.

plot_orientation_bars(ax=None, save_dir=None, fig_fn=None, index_labels=False)#

Per-camera bars of the roll / pitch / yaw orientation change (degrees).

The .adjust rotation is shown as signed intrinsic XYZ Euler angles (roll about the along-track X axis, pitch about across-track Y, yaw about nadir Z), colored to match _draw_satellite(), with the value printed on every bar so sub-millidegree changes stay readable.

Parameters:
  • ax (matplotlib.axes.Axes or None, optional) – Axes to draw on. A new figure is created if None.

  • save_dir (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • fig_fn (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • index_labels (bool, optional) – Label cameras by number only; default False prints #n  camera_id.

plot_triangulation_offset_bars(ax=None, save_dir=None, fig_fn=None, index_labels=False, legend_outside=False)#

Per-camera bars of the triangulated-point change (meters).

From ASP’s triangulation_offsets.txt (>= 3.6): for each input image, the median and mean distance between its initial and final triangulated points, with the point count printed above each pair.

Parameters:
  • ax (matplotlib.axes.Axes or None, optional) – Axes to draw on. A new figure is created if None.

  • save_dir (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • fig_fn (str or None, optional) – If both are given (and a new figure was created), save the figure.

  • index_labels (bool, optional) – Label cameras by number only; default False prints #n  camera_id.

  • legend_outside (bool, optional) – Place the legend to the right of the axes instead of inside.

Raises:

ValueError – If the GeoDataFrame carries no triangulation_offsets.txt values.

summary_plot(save_dir=None, fig_fn=None)#

Combined summary: center-displacement bars, orientation bars, and – when triangulation_offsets.txt was written – triangulated-point change bars.

The rows share the camera order; only the bottom row prints the camera ids. The orientation legend cartoon sits beside its row.

Parameters:
  • save_dir (str or None, optional) – If both are given, save the figure.

  • fig_fn (str or None, optional) – If both are given, save the figure.

gdf#
property has_triangulation_offsets#

True when triangulation_offsets.txt values are present.

property is_identity#

True when every camera-center offset and rotation angle is zero.

class asp_plot.bundle_adjust.PlotBundleAdjustFiles(geodataframes, **kwargs)#

Bases: asp_plot.utils.Plotter

Plot bundle adjustment results from GeoDataFrames.

This class extends the base Plotter class to provide specialized plotting functionality for bundle adjustment results. It can create visualizations of residuals, geodiff results, and other bundle adjustment outputs.

geodataframes#

List of GeoDataFrames containing bundle adjustment data to plot

Type:

list of geopandas.GeoDataFrame

title#

Plot title, inherited from Plotter class

Type:

str

Examples

>>> ba_reader = ReadBundleAdjustFiles('/path/to/asp', 'ba')
>>> initial_gdf, final_gdf = ba_reader.get_initial_final_residuals_gdfs()
>>> ba_plotter = PlotBundleAdjustFiles([initial_gdf, final_gdf], title="Bundle Adjustment Residuals")
>>> ba_plotter.plot_n_gdfs(column_name="mean_residual_meters", cbar_label="Mean residual (m)")
gdf_percentile_stats(gdf, column_name='mean_residual')#

Calculate percentile statistics for a GeoDataFrame column.

Computes the 25th, 50th, 84th, and 95th percentiles for a specified column in a GeoDataFrame.

Parameters:
  • gdf (geopandas.GeoDataFrame) – GeoDataFrame to analyze

  • column_name (str, optional) – Column to calculate statistics for, default is “mean_residual”

Returns:

List of statistics at the 25th, 50th, 84th, and 95th percentiles

Return type:

list

Notes

These percentiles are commonly used in remote sensing and photogrammetry to assess error distributions. The 50th percentile is the median, while the 84th percentile is approximately equivalent to one standard deviation in a normal distribution.

plot_n_gdfs(column_name='mean_residual', cbar_label='Mean residual (px)', clip_final=True, clim=None, common_clim=True, symm_clim=False, cmap='inferno', map_crs='EPSG:4326', save_dir=None, fig_fn=None, **ctx_kwargs)#

Plot multiple GeoDataFrames in a grid layout.

Creates a figure with multiple subplots, one for each GeoDataFrame in the geodataframes list. Each subplot shows the spatial distribution of the specified column values.

Parameters:
  • column_name (str, optional) – Column to visualize, default is “mean_residual”

  • cbar_label (str, optional) – Label for the colorbar, default is “Mean residual (px)”

  • clip_final (bool, optional) – Whether to clip the final plot to prevent autoscaling, default is True

  • clim (tuple or None, optional) – Color limits as (min, max), default is None (auto)

  • common_clim (bool, optional) – Whether to use the same color limits for all plots, default is True

  • symm_clim (bool, optional) – Whether to use symmetric color limits, default is False

  • cmap (str, optional) – Matplotlib colormap name, default is “inferno”

  • map_crs (str, optional) – Coordinate reference system for plotting, default is “EPSG:4326”

  • save_dir (str or None, optional) – Directory to save the figure, default is None (don’t save)

  • fig_fn (str or None, optional) – Filename for the saved figure, default is None

  • **ctx_kwargs (dict, optional) – Additional keyword arguments for contextily basemap

Returns:

Displays the plot and optionally saves it

Return type:

None

Notes

Each subplot includes a text box showing statistics for the displayed data, including the number of points and percentile values. The layout automatically adjusts based on the number of GeoDataFrames to plot.

geodataframes#
class asp_plot.bundle_adjust.ReadBundleAdjustCameras(directory, bundle_adjust_directory, stem=None)#

Read before/after camera geometry from an ASP bundle_adjust folder.

Unlike asp_plot.csm_camera.csm_camera_summary_plot, which requires the user to supply the original (pre-adjustment) camera files, this reader works directly on a bundle_adjust output directory. It combines three self-contained products that ASP always writes there:

  • *.adjust – the rigid adjustment (ECEF translation + rotation quaternion) applied to each camera. Per ASP’s convention a world point projects the same in the original camera as R * (P - C) + C + T in the adjusted camera, so the translation T is the bulk camera-center shift (exact at the camera center for pixel (0, 0)).

  • *.adjusted_state.json – the optimized CSM camera state, used to locate each camera center in space.

  • *camera_offsets.txt (optional) – ASP’s authoritative per-camera horizontal and vertical camera-center change, in the local North-East-Down frame. When present it is used for the reported magnitudes (it also folds in the rotation lever-arm that the bulk translation T does not).

Parameters:
  • directory (str) – Root directory of ASP processing.

  • bundle_adjust_directory (str) – Subdirectory containing bundle adjustment outputs.

Examples

>>> reader = ReadBundleAdjustCameras('/path/to/asp', 'ba')
>>> gdf = reader.get_camera_optimization_gdf(map_crs=32616)
get_camera_offsets_df()#

Read *camera_offsets.txt into a DataFrame if it exists.

Returns:

DataFrame with columns image, horizontal_offset_m, and vertical_offset_m (one row per input image, in ASP’s order). Returns None if the file is not present (it is only written by recent ASP versions).

Return type:

pandas.DataFrame or None

get_camera_optimization_gdf(map_crs=None, original_cameras_directory=None)#

Build a per-camera GeoDataFrame of before/after camera changes.

Discovery is driven by the .adjust files, which ASP writes for every camera. Each camera’s absolute center comes from its .adjusted_state.json (WorldView/CSM and jitter runs) or, when that is absent (DigitalGlobe runs), from the original camera .xml ephemeris.

Parameters:
  • map_crs (int or None, optional) – EPSG code (e.g. a UTM zone) for the output geometry. If None, the geometry is returned in geographic coordinates (EPSG:4326). The east/north/up offsets do not depend on it.

  • original_cameras_directory (str or None, optional) – Directory holding the original .xml cameras, used only for DigitalGlobe runs that lack .adjusted_state.json. If None, the BA directory and its parent are searched automatically.

Returns:

One row per camera, with geometry at the (projected) camera center and columns:

  • camera_id : camera filename stem (display label).

  • t_east, t_north, t_up : ECEF translation T decomposed into the local ENU frame (meters).

  • t_horizontal : horizontal magnitude of T (meters).

  • adj_roll, adj_pitch, adj_yaw : the .adjust rotation as intrinsic XYZ Euler angles (degrees).

  • horizontal_offset_m, vertical_offset_m : ASP’s reported camera-center change from camera_offsets.txt when available, otherwise filled from t_horizontal / t_up.

  • offsets_from_asp : True if the offsets came from camera_offsets.txt, False if derived from T.

  • tri_mean_m, tri_median_m, tri_count : per-image mean and median change of the triangulated points (meters) and the point count, from triangulation_offsets.txt (ASP >= 3.6); NaN when that report is absent.

Return type:

geopandas.GeoDataFrame

Raises:

ValueError – If no .adjust files are found in the directory.

get_triangulation_offsets_df()#

Read *triangulation_offsets.txt into a DataFrame if it exists.

ASP >= 3.6 writes this report (“Changes in triangulated points” in the bundle_adjust docs): for each input image, the mean, median, and count of the distances (ECEF, meters) between the initial triangulated points (after any initial adjustment or alignment transform, before any DEM constraint) and the final ones after optimization. It is the ground effect of the camera change, which the camera-center offsets alone do not show.

Returns:

Columns image, tri_mean_m, tri_median_m, tri_count (one row per input image, in ASP’s order), or None when the file is absent (older ASP versions).

Return type:

pandas.DataFrame or None

static read_adjust_file(adjust_path)#

Parse an ASP .adjust file.

Parameters:

adjust_path (str) – Path to a .adjust file.

Returns:

(translation, rotation) where translation is a length-3 numpy array of the ECEF camera-center shift in meters and rotation is a scipy.spatial.transform.Rotation.

Return type:

tuple

Notes

The first line holds the translation x y z (meters); the second holds the rotation quaternion in ASP’s w x y z order, which is reordered to the x y z w order that SciPy expects.

bundle_adjust_directory#
directory#
full_directory#
stem = None#
class asp_plot.bundle_adjust.ReadBundleAdjustFiles(directory, bundle_adjust_directory, stem=None)#

Read and process bundle adjustment output files from ASP.

This class provides functionality to read and process the outputs from ASP’s bundle_adjust tool, including residual point maps, map-projected residuals, and triangulation uncertainty. It can also generate geodiff files to compare bundle adjustment results with reference DEMs.

directory#

Root directory of ASP processing

Type:

str

bundle_adjust_directory#

Subdirectory containing bundle adjustment outputs

Type:

str

full_directory#

Full path to bundle adjustment directory

Type:

str

Examples

>>> ba_reader = ReadBundleAdjustFiles('/path/to/asp', 'ba')
>>> initial_gdf, final_gdf = ba_reader.get_initial_final_residuals_gdfs()
>>> geodiff_initial, geodiff_final = ba_reader.get_initial_final_geodiff_gdfs()
>>> mapproj_gdf = ba_reader.get_mapproj_residuals_gdf()
generate_geodiff(path)#

Generate geodiff file comparing residuals to a reference DEM.

Uses ASP’s geodiff tool to calculate height differences between bundle adjustment residual points and a reference DEM.

Parameters:

path (str) – Path to the bundle adjustment residual CSV file

Notes

The geodiff output file will be saved with the same name as the input file, but with “-diff.csv” appended. This method requires the geodiff tool from ASP and a reference DEM that was used in the bundle adjustment process.

get_csv_paths(geodiff_files=False)#

Get paths to bundle adjustment residual CSV files.

Finds the paths to the initial and final residual point map CSV files from bundle adjustment. Can optionally find or generate geodiff files comparing these residuals to a reference DEM.

Parameters:

geodiff_files (bool, optional) – Whether to return paths to geodiff files instead of regular residual CSV files, default is False

Returns:

Paths to initial and final residual CSV files or geodiff files

Return type:

tuple of str

Raises:

ValueError – If required bundle adjustment CSV files cannot be found

Notes

If geodiff_files is True and the files don’t exist, this method will attempt to generate them using the geodiff ASP tool.

get_geodiff_gdf(csv_path)#

Read geodiff output into a GeoDataFrame.

Reads a geodiff output CSV file and converts it to a GeoDataFrame with appropriate geometry.

Parameters:

csv_path (str) – Path to the geodiff CSV file

Returns:

GeoDataFrame containing geodiff data with point geometry

Return type:

geopandas.GeoDataFrame

Notes

The returned GeoDataFrame has a ‘filename’ attribute set to the base name of the input file. The main data column is ‘height_diff_meters’, which contains the height differences between residual points and the reference DEM.

get_initial_final_geodiff_gdfs()#

Get both initial and final geodiff results as GeoDataFrames.

A convenience method that combines get_csv_paths() and get_geodiff_gdf() to obtain both initial and final geodiff results.

Returns:

GeoDataFrames containing initial and final geodiff data

Return type:

tuple of geopandas.GeoDataFrame

Notes

This is a convenience method that calls get_csv_paths(geodiff_files=True) and get_geodiff_gdf() for both initial and final geodiff files. If the geodiff files don’t exist, they will be generated using generate_geodiff().

get_initial_final_residuals_gdfs(residuals_in_meters=True)#

Get both initial and final residuals as GeoDataFrames.

A convenience method that combines get_csv_paths() and get_residuals_gdf() to obtain both initial and final bundle adjustment residuals.

Parameters:

residuals_in_meters (bool, optional) – Whether to convert residuals from pixels to meters using the mean GSD from the stereopair metadata, default is True

Returns:

GeoDataFrames containing initial and final residual data

Return type:

tuple of geopandas.GeoDataFrame

Notes

This is a convenience method that calls get_csv_paths() and get_residuals_gdf() for both initial and final residual files.

get_mapproj_residuals_gdf()#

Get map-projected residuals as a GeoDataFrame.

Reads the map-projected match offsets file produced by bundle_adjust and converts it to a GeoDataFrame.

Returns:

GeoDataFrame containing map-projected residual data

Return type:

geopandas.GeoDataFrame

Raises:

ValueError – If the map-projected match offsets file cannot be found

Notes

The main data column is ‘mapproj_ip_dist_meters’, which contains the distance between matched interest points in the map-projected images after bundle adjustment.

get_propagated_triangulation_uncert_df()#

Get propagated triangulation uncertainty as a DataFrame.

Reads the triangulation uncertainty file produced by bundle_adjust, which contains statistics on the expected horizontal and vertical error in the DEM.

Returns:

DataFrame containing triangulation uncertainty data

Return type:

pandas.DataFrame

Raises:

ValueError – If the triangulation uncertainty file cannot be found

Notes

The triangulation uncertainty file contains statistics on how the bundle adjustment residuals propagate to uncertainty in triangulation. This is an estimate of the final DEM error.

get_residuals_gdf(csv_path, residuals_in_meters=True)#

Read bundle adjustment residuals into a GeoDataFrame.

Reads a bundle adjustment residuals CSV file and converts it to a GeoDataFrame with appropriate geometry. Can optionally convert residuals from pixels to meters.

Parameters:
  • csv_path (str) – Path to the residuals CSV file

  • residuals_in_meters (bool, optional) – Whether to convert residuals from pixels to meters using the mean GSD from the stereopair metadata, default is True

Returns:

GeoDataFrame containing residual data with point geometry

Return type:

geopandas.GeoDataFrame

Notes

The returned GeoDataFrame has a ‘filename’ attribute set to the base name of the input file. If residuals_in_meters is True, a new column ‘mean_residual_meters’ will be added with pixel residuals converted to meters.

bundle_adjust_directory#
directory#
full_directory#
stem_glob#
asp_plot.bundle_adjust.logger#