Lunar Reconnaissance Orbiter (LRO) Narrow Angle Camera (NAC)#

Stereo example based on the ASP “Lightning Fast” lunar tutorial, but processing the full 5000×5000 px crop in the LRONAC_example.tar bundle instead of the small 900×973 sub-window the readme uses.

Why the larger crop:

  • A bigger DEM (~5 km × 5 km on the lunar surface vs ~1 km × 1 km) brings out spacecraft jitter in the disparity / hillshade panels.

  • The query area pulls thousands of LOLA points (vs ~19 in the tutorial crop), enough for a meaningful pc_align against LOLA.

  • This mirrors the WorldView notebooks in this repo, which also process full-frame stereo rather than tiny sub-crops.

We use ASP’s CSM camera models (the .json files in the bundle) so no SPICE / ISIS spice setup is needed.

Data download#

wget https://github.com/NeoGeographyToolkit/StereoPipelineSolvedExamples/releases/download/LRONAC/LRONAC_example.tar
tar xf LRONAC_example.tar

Stereo + DEM (diverges from the ASP docs)#

The ASP tutorial uses --left-image-crop-win 2259 1196 900 973 to keep the demo tiny. We drop both crop-win flags so stereo runs on the full extent of the cubes that the tar already contains. Threading kept conservative (--processes 2 --threads 4, total 8 cores) to match this repo’s WorldView examples and avoid memory pressure on a laptop:

parallel_stereo \
  M181058717LE_crop.cub M181073012LE_crop.cub \
  M181058717LE.json M181073012LE.json \
  --alignment-method local_epipolar \
  --stereo-algorithm asp_mgm --subpixel-mode 9 \
  --processes 2 --threads 4 \
  out_stereo/output

point2dem --auto-proj-center --errorimage --orthoimage \
  out_stereo/output-PC.tif out_stereo/output-L.tif

LOLA altimetry data#

Request via request_planetary_altimetry:

request_planetary_altimetry \
  --dem out_stereo/output-DEM.tif \
  --email user@example.com \
  --channels ttttt   # all five LOLA detectors (default tffff = channel 1)

When the email arrives, download and unzip. For LOLA either CSV format works — the loader auto-detects:

  • *_topo_simple_csv.csv (results=u): Pt_Longitude / Pt_Latitude / Topography (meters)

  • *_pts_csv.csv (results=p): Pt_Longitude / Pt_Latitude / Pt_Radius (kilometers, auto-converted to meters)

The Moon is essentially spherical (~1.4 km equatorial-vs-polar variation), so LOLA topography ≈ height above the IAU 1737.4 km lunar sphere to ~1 m — both formats give the same dh against an ASP lunar DEM. (Mars is different — see the MOC NA notebook.)

directory = "~/Desktop/asp-plot-examples/lunar_lro_nac/"
stereo_directory = "out_stereo/"
lola_csv = f"{directory}/lola/LolaRDR_-10N-10N_15E15E_20260428T183313426_pts_csv.csv"

Full report (with LOLA + automatic pc_align)#

--pc_align True (default) runs pc_align against LOLA after the unaligned dh plots, applies the translation, and appends a pre/post comparison.

!asp_plot \
  --directory $directory \
  --stereo_directory $stereo_directory \
  --subset_km 0.5 \
  --add_basemap False \
  --plot_geometry False \
  --altimetry_csv $lola_csv \
  --report_filename ../../reports/LRO_NAC-asp-plot-report.pdf

Individual plots#

Modular API examples.

Processing Parameters#

%load_ext autoreload
%autoreload 2

from asp_plot.processing_parameters import ProcessingParameters

processing_parameters = ProcessingParameters(
    processing_directory=directory,
    stereo_directory=stereo_directory,
)
processing_parameters_dict = processing_parameters.from_log_files()

print(f"Processed on: {processing_parameters_dict['processing_timestamp']}\n")
print(f"Reference DEM: {processing_parameters_dict['reference_dem']}\n")
print(f"Bundle adjustment ({processing_parameters_dict['bundle_adjust_run_time']}):\n")
print(processing_parameters_dict["bundle_adjust"])
print(f"\nStereo ({processing_parameters_dict['stereo_run_time']}):\n")
print(processing_parameters_dict["stereo"])
print(f"\nPoint2dem ({processing_parameters_dict['point2dem_run_time']}):\n")
print(processing_parameters_dict["point2dem"])

Scene plots#

from asp_plot.scenes import ScenePlotter

ScenePlotter(directory, stereo_directory, title="Scenes").plot_scenes()

Stereo plots#

from asp_plot.stereo import StereoPlotter

plotter = StereoPlotter(directory, stereo_directory)
plotter.title = "Hillshade with details"
plotter.plot_detailed_hillshade(subset_km=0.5)
plotter.title = "Stereo Match Points"
plotter.plot_match_points()
plotter.title = "Disparity (pixels)"
plotter.plot_disparity(unit="pixels", quiver=True)
plotter.title = "Stereo DEM Results"
plotter.plot_dem_results()

LOLA altimetry comparison + pc_align#

from asp_plot.altimetry import Altimetry

dem_fn = f"{directory}/{stereo_directory}/output-DEM.tif"
alt = Altimetry(directory=directory, dem_fn=dem_fn)
alt.load_planetary_csv(lola_csv)
alt.planetary_to_dem_dh()
alt.planetary_points[["lon", "lat", "height", "dem_height", "altimetry_minus_dem"]].head(10)
alt.mapview_plot_planetary_to_dem()
alt.histogram_planetary_to_dem()
result = alt.align_and_evaluate_planetary()
print(f"Status:           {result.status}")
print(f"Improvement:      {result.improvement_pct:.1f}%" if result.improvement_pct else "Improvement:      n/a")
print(f"Aligned DEM:      {result.aligned_dem_fn}")
print(f"Message:          {result.message}")
result.alignment_report_df
alt.mapview_plot_planetary_to_dem(plot_aligned=True)
alt.histogram_planetary_to_dem(plot_aligned=True)