CLI reference

Generated from lunar <command> --help when the docs are built.

Command Purpose
build-db Build the local Parquet index database(s).
download Download LROC image files to a local directory.
query Print metadata for LROC NAC images from the Parquet index.
search Find images whose footprint intersects a box, point+radius or AOI polygon (local index).
ode-search Search lunar products (ShadowCam, LROC NAC, Kaguya TC) via the ODE REST API.
pairs Rank NAC stereo pairs (or select SfS images with --sfs) over a box, point+radius or AOI polygon.
reference Reference data for the Artemis III regions: crop windows, PGDA LOLA 5 m DEMs, Bertone SfS DEMs.
process Run the ISIS + GDAL pipeline on LROC NAC images.
kaguya Download Kaguya TC / MI images and import them into ISIS (cube + CSM camera).
shadowcam Download ShadowCam cubes, spiceinit them and write CSM cameras for ASP.
stereo Build a DEM from a NAC stereo pair (bundle_adjust, parallel_stereo, point2dem).
sfs Build a shape-from-shading DEM from NAC images and a prior DEM (ISIS + ASP).
sfs-report Metrics, figures and an HTML report of an SfS work dir (Bertone et al. 2026 validation).
workspace Query a work dir's DuckDB workspace (images, pairs, bundle-adjustment tables and views).
serve Serve the Lunar Toolkit web app with local parquet files.
app Open the Lunar Toolkit web app in a desktop window.

lunar

usage: lunar [-h] [-v]
             {build-db,download,query,search,ode-search,pairs,reference,process,kaguya,shadowcam,stereo,sfs,sfs-report,workspace,serve,app} ...

Lunar imagery toolkit - build LROC index databases, search footprints, query ODE (ShadowCam, LROC, Kaguya TC and MI), download and import images into ISIS, build stereo and shape-from-shading DEMs with ISIS + ASP, and fetch Artemis III reference data.

positional arguments:
  {build-db,download,query,search,ode-search,pairs,reference,process,kaguya,shadowcam,stereo,sfs,sfs-report,workspace,serve,app}
    build-db            Build the local Parquet index database(s).
    download            Download LROC image files to a local directory.
    query               Print metadata for LROC NAC images from the Parquet index.
    search              Find images whose footprint intersects a box, point+radius or AOI polygon
                        (local index).
    ode-search          Search lunar products (ShadowCam, LROC NAC, Kaguya TC) via the ODE REST
                        API.
    pairs               Rank NAC stereo pairs (or select SfS images with --sfs) over a box,
                        point+radius or AOI polygon.
    reference           Reference data for the Artemis III regions: crop windows, PGDA LOLA 5 m
                        DEMs, Bertone SfS DEMs.
    process             Run the ISIS + GDAL pipeline on LROC NAC images.
    kaguya              Download Kaguya TC / MI images and import them into ISIS (cube + CSM
                        camera).
    shadowcam           Download ShadowCam cubes, spiceinit them and write CSM cameras for ASP.
    stereo              Build a DEM from a NAC stereo pair (bundle_adjust, parallel_stereo,
                        point2dem).
    sfs                 Build a shape-from-shading DEM from NAC images and a prior DEM (ISIS +
                        ASP).
    sfs-report          Metrics, figures and an HTML report of an SfS work dir (Bertone et al.
                        2026 validation).
    workspace           Query a work dir's DuckDB workspace (images, pairs, bundle-adjustment
                        tables and views).
    serve               Serve the Lunar Toolkit web app with local parquet files.
    app                 Open the Lunar Toolkit web app in a desktop window.

options:
  -h, --help            show this help message and exit
  -v, --verbose         Enable DEBUG-level logging.

lunar build-db

usage: lunar build-db [-h] [--all] [--force] [--update] [--release VOLUME] [--volume-only] [--wkt]
                      [--memory-limit SIZE] [--threads N] [--data-dir DIR] [--cache-dir DIR]
                      [DATASET ...]

positional arguments:
  DATASET              Datasets to build: edr, cdr, rdr (default: edr if none given).

options:
  -h, --help           show this help message and exit
  --all                Build all three datasets: edr, cdr, rdr.
  --force              Rebuild even if the Parquet already exists.
  --update             Append newer per-volume INDEX.TABs to an existing database.
  --release VOLUME     Build from this PDS volume, e.g. LROLRC_0066C (default: newest on the
                       server).
  --volume-only        Use the volume's own INDEX/INDEX.TAB instead of the cumulative
                       CUMINDEX.TAB.
  --wkt                Also store an FP_WKT footprint polygon column.
  --memory-limit SIZE  DuckDB memory limit; sorting spills to disk beyond it (default: 4GB).
  --threads N          DuckDB worker threads (default: all cores).
  --data-dir DIR       Where to write Parquet files (default: LUNAR_DATA_DIR or the user data
                       dir).
  --cache-dir DIR      Where to cache raw TAB/LBL downloads (default:
                       /home/runner/.cache/lunar_toolkit).

Examples
--------
  lunar build-db              # EDR only (default)
  lunar build-db edr cdr      # EDR and CDR
  lunar build-db --all        # EDR, CDR, and RDR
  lunar build-db edr --force  # rebuild even if exists
  lunar build-db edr --update # append PDS volumes newer than the built release
  lunar build-db edr --release LROLRC_0066C --volume-only   # one volume (~20k rows)

lunar download

usage: lunar download [-h] [--from-file FILE] [--output-dir DIR] [--dataset {edr,cdr}] [--force]
                      [--workers N] [--data-dir DIR]
                      [SOURCE ...]

positional arguments:
  SOURCE               Product IDs (e.g. M1096757719LE) or PDS URLs.

options:
  -h, --help           show this help message and exit
  --from-file FILE     Text file of product IDs or URLs, one per line (#comments ignored).
  --output-dir DIR     Destination directory (default: current working directory).
  --dataset {edr,cdr}  Download each image's raw EDR or calibrated CDR file (default: edr); IDs
                       are looked up in the index matching their LE/RE or LC/RC suffix.
  --force              Download again even if the file exists.
  --workers N          Parallel download workers (default: 8).
  --data-dir DIR       Override Parquet data directory.

Examples
--------
  lunar download M1096757719LE M1096757719RE
  lunar download M1096757719LE --output-dir ./raw
  lunar download --from-file ids.txt --output-dir ./raw
  lunar download https://pds.lroc.im-ldi.com/data/.../M1096757719LE.IMG
  lunar download M1096757719LC --dataset cdr  # CDR image

Files already in --output-dir are skipped (use --force to download them again);
interrupted downloads resume from their .part file.

lunar query

usage: lunar query [-h] [--from-file FILE] [--data-dir DIR] [SOURCE ...]

positional arguments:
  SOURCE            Product IDs, local .IMG paths, or a text file of IDs.

options:
  -h, --help        show this help message and exit
  --from-file FILE  Text file of product IDs or paths, one per line.
  --data-dir DIR    Override Parquet data directory.

Examples
--------
  lunar query M1096757719LE
  lunar query M1096757719LE M1096757719RE
  lunar query --from-file ids.txt
  lunar query /path/to/M1096757719LE.IMG
usage: lunar search [-h] [--dataset {edr,cdr}] (--bbox WEST SOUTH EAST NORTH | --point LAT LON |
                    --aoi FILE_OR_WKT) [--radius-km KM] [--inc MIN MAX] [--emi MIN MAX]
                    [--phase MIN MAX] [--nac-only] [--limit N] [--csv FILE] [--wkt]
                    [--data-dir DIR]

options:
  -h, --help            show this help message and exit
  --dataset {edr,cdr}   Index to search (default: edr).
  --bbox WEST SOUTH EAST NORTH
  --point LAT LON
  --aoi FILE_OR_WKT     Area of interest: GeoJSON / WKT file or WKT polygon string.
  --radius-km KM
  --inc MIN MAX         Incidence angle range.
  --emi MIN MAX         Emission angle range.
  --phase MIN MAX       Phase angle range.
  --nac-only            Only NAC products.
  --limit N
  --csv FILE            Write matching rows to CSV.
  --wkt                 Include an FP_WKT footprint column.
  --data-dir DIR        Override Parquet data directory.

Examples
--------
  lunar search --bbox 0 -90 360 -85 --nac-only --inc 80 95
  lunar search --bbox 355 -5 5 5               # crosses the 0/360 seam
  lunar search --point -89.67 129.8 --radius-km 15 --csv shackleton.csv
  lunar search --aoi aoi.geojson --nac-only    # QuickMap-exported polygon
  lunar search --aoi "POLYGON((350 -5, 10 -5, 10 5, 350 5, 350 -5))"

--bbox is WEST SOUTH EAST NORTH (degrees, positive east; WEST > EAST crosses 0/360).
--aoi takes a GeoJSON / WKT file or a WKT string (lon lat, degrees, positive
east); bbox matches are refined by exact polygon intersection.
usage: lunar ode-search [-h] [--instrument INSTRUMENT] [--product-type PT]
                        [--bbox WEST SOUTH EAST NORTH | --point LAT LON | --aoi FILE_OR_WKT]
                        [--radius-km KM] [--inc MIN MAX] [--emi MIN MAX] [--phase MIN MAX]
                        [--time START END] [--limit N] [--count] [--csv FILE] [--download DIR]
                        [--kind KIND] [--match TEXT] [--workers N]

options:
  -h, --help            show this help message and exit
  --instrument, -i INSTRUMENT
                        Instrument: shadowcam, lroc, kaguya_tc, kaguya_mi (default: shadowcam).
  --product-type, -t PT
                        Product type alias or ODE pt code (default: instrument's default).
  --bbox WEST SOUTH EAST NORTH
                        Bounding box in degrees.
  --point LAT LON       Search around a point (use with --radius-km).
  --aoi FILE_OR_WKT     Area of interest: GeoJSON / WKT file or WKT polygon string.
  --radius-km KM        Radius for --point (default: 1 km).
  --inc MIN MAX         Incidence angle range.
  --emi MIN MAX         Emission angle range.
  --phase MIN MAX       Phase angle range.
  --time START END      Observation time range (UTC, e.g. 2023-01-01 2023-12-31).
  --limit N             Maximum number of products to return (default: 200).
  --count               Only print the number of matches.
  --csv FILE            Write results (common schema) to CSV.
  --download DIR        Download the selected files to DIR.
  --kind KIND           Files to download: product, label, browse, referenced, derived, all.
  --match TEXT          Only download files whose name contains TEXT (e.g. _cog.tif).
  --workers N           Parallel download workers (default: 4).

Examples
--------
  lunar ode-search --instrument shadowcam --bbox 0 -90 360 -89 --inc 80 90
  lunar ode-search --instrument shadowcam --point -89.67 129.8 --radius-km 15
  lunar ode-search --instrument lroc --product-type cdr --bbox 355 -1 5 1 --csv hits.csv
  lunar ode-search --instrument shadowcam --point -89.67 129.8 --radius-km 5 \
      --download ./shadowcam --kind product
  lunar ode-search --instrument shadowcam --aoi aoi.geojson   # QuickMap polygon

Product types - shadowcam: raw, cal, cmos, umos, dtm; lroc: edr, cdr; kaguya_tc: stereo, mono, dtm, ortho, morning, evening; kaguya_mi: vis, nir, vis2c, nir2c, l3c5, map
(raw ODE pt codes such as EDRNAC4 are accepted too).
--bbox is WEST SOUTH EAST NORTH, longitudes positive east; WEST > EAST crosses 0/360.
--aoi takes a GeoJSON / WKT file or a WKT string; ODE is queried with its bounding
box and footprints are then intersected with the polygon.

lunar pairs

usage: lunar pairs [-h] [--dataset {edr,cdr}] (--bbox WEST SOUTH EAST NORTH | --point LAT LON |
                   --aoi FILE_OR_WKT) [--radius-km KM] [--inc MIN MAX] [--emi-max DEG]
                   [--conv MIN MAX] [--max-inc-diff DEG] [--max-az-diff DEG] [--min-overlap FRAC]
                   [--sfs] [--az-bin DEG] [--per-bin N] [--max-images N] [--greedy] [--coverage]
                   [--views N] [--engine {auto,python,duckdb}] [--workspace DIR] [--top N]
                   [--csv FILE] [--list FILE] [--data-dir DIR]

options:
  -h, --help            show this help message and exit
  --dataset {edr,cdr}   Index to search (default: edr).
  --bbox WEST SOUTH EAST NORTH
  --point LAT LON
  --aoi FILE_OR_WKT     Area of interest: GeoJSON / WKT file or WKT polygon string.
  --radius-km KM
  --inc MIN MAX         Incidence angle range.
  --emi-max DEG         Maximum emission angle.
  --conv MIN MAX        Convergence angle limits (default: 10 60).
  --max-inc-diff DEG
  --max-az-diff DEG     Maximum sub-solar azimuth difference within a pair.
  --min-overlap FRAC
  --sfs                 Select illumination-diverse images instead of stereo pairs.
  --az-bin DEG          --sfs: azimuth bin width / minimum separation (default: 30).
  --per-bin N
  --max-images N
  --greedy              --sfs: enforce --az-bin separation between all picks instead of fixed
                        bins.
  --coverage            --sfs: per-location greedy selection over an AOI grid.
  --views N             --sfs --coverage: target distinct Sun azimuths per location (default: 3).
  --engine {auto,python,duckdb}
                        Stereo pair engine (default: auto = duckdb from 300 images).
  --workspace DIR       Write the images and pairs tables to DIR/lunar.duckdb (implies --engine
                        duckdb).
  --top N               Rows to print (default: 30).
  --csv FILE            Write the full result to CSV.
  --list FILE           --sfs: write selected product IDs, one per line.
  --data-dir DIR        Override Parquet data directory.

Examples
--------
  lunar pairs --point -89.67 129.8 --radius-km 3 --top 20
  lunar pairs --bbox 355 -5 5 5 --conv 15 45 --csv pairs.csv
  lunar pairs --point -89.67 129.8 --radius-km 3 --sfs --inc 80 95 --list sfs_images.txt
  lunar pairs --point -89.67 129.8 --radius-km 3 --sfs --coverage --views 3
  lunar pairs --aoi aoi.geojson --sfs --coverage
  lunar pairs --point -89.5 0 --radius-km 30 --engine duckdb --workspace work/pole

--bbox is WEST SOUTH EAST NORTH (degrees, positive east; WEST > EAST crosses 0/360).
--aoi takes a GeoJSON / WKT file or a WKT string (lon lat, degrees, positive east).
The search region is also the AOI that overlaps / coverage are measured in.
--sfs output (and --list) is sorted by sub-solar azimuth, as ASP SfS expects.
--engine duckdb ranks pairs with a DuckDB spatial self-join (same pairs and metrics as
the Python engine); auto uses it from 300 images when the spatial extension loads.
--workspace keeps the images and pairs tables in DIR/lunar.duckdb (see lunar workspace).

lunar reference

usage: lunar reference [-h] {list,show,crop,pgda,sdem} ...

positional arguments:
  {list,show,crop,pgda,sdem}
    list                List the 13 regions.
    show                Show one region.
    crop                Print a square crop window centred on a region.
    pgda                Download a region's PGDA LOLA 5 m site files.
    sdem                List / download / extract a region's Bertone et al. Zenodo zip.

options:
  -h, --help            show this help message and exit

Examples
--------
  lunar reference list
  lunar reference show malapert                 # bbox, ROI projwin, PGDA site, Zenodo zip
  lunar reference crop 11 --crop-km 2.5         # 2.5 km window at the ROI centre
  lunar reference crop malapert --center -85.98 -0.26 --format wkt
  lunar reference pgda malapert --kind surf toterr -o ref
  lunar reference pgda malapert --kind clones --clones 10 -o ref
  lunar reference pgda malapert --kind xyzi --csv --crop-km 2.5 -o ref
  lunar reference sdem malapert --list           # zip members (remote central directory)
  lunar reference sdem malapert --remote --member GLDELEV GLDSIGM -o ref
  lunar reference sdem malapert --download -o ref --member GLDELEV

REGION is an id (0-12), name, slug, unique part of a name or PGDA site
(11, "Malapert Massif", malapert-massif, malapert, Site23).  The ROI is the
polar stereographic square the SfS DEM covers (= the PGDA site extent);
projwin is ULX ULY LRX LRY in metres (+proj=stere +lat_0=-90 +R=1737400).
Existing files are skipped (--force to redo); downloads resume.

lunar process

usage: lunar process [-h] [--from-file FILE] [--output-dir DIR] [--keep] [--force] [--workers N]
                     [--projection PROJ] [--resolution M] [--no-web] [--data-dir DIR]
                     [SOURCE ...]

positional arguments:
  SOURCE             Product IDs (e.g. M1096757719LE), local .IMG paths, or PDS URLs.

options:
  -h, --help         show this help message and exit
  --from-file FILE   Text file of sources, one per line (#comments ignored).
  --output-dir DIR   Output directory (default: current working directory).
  --keep             Keep the downloaded EDR and the intermediate ISIS cubes.
  --force            Redo steps whose output exists.
  --workers N        Parallel workers for batch processing (default: 1).
  --projection PROJ  ISIS map projection for mosrange (default: equirectangular).
  --resolution M     Map resolution in m/pixel.
  --no-web           Use local SPICE kernels in spiceinit instead of the USGS web service.
  --data-dir DIR     Override Parquet data directory.

Examples
--------
  lunar process M1096757719LE
  lunar process M1096757719LE M1096757719RE --workers 2
  lunar process --from-file ids.txt --output-dir ./output
  lunar process /data/M1096757719LE.IMG --keep --resolution 1
  lunar process https://pds.lroc.im-ldi.com/data/.../M1096757719LE.IMG

Everything is written to --output-dir: the GeoTIFF <id>.tif (map-projected I/F,
DEFLATE-compressed) plus intermediates (<id>.IMG, <id>.cal.echo.cub, <id>.map.cub),
which are removed after success unless --keep.  Steps whose output exists are
skipped, so re-running resumes (use --force to redo them).

lunar kaguya

usage: lunar kaguya [-h] [--instrument {tc,mi}] [--from-file FILE] [--output-dir DIR]
                    [--bbox WEST SOUTH EAST NORTH | --point LAT LON] [--radius-km KM]
                    [--product-type PT] [--inc MIN MAX] [--emi MIN MAX] [--phase MIN MAX]
                    [--time START END] [--limit N] [--no-web] [--shape SHAPE] [--csm | --no-csm]
                    [--only-naif-spice | --no-only-naif-spice] [--mapproject] [--projection PROJ]
                    [--resolution M] [--keep] [--force] [--download-only] [--dry-run]
                    [--workers N]
                    [SOURCE ...]

positional arguments:
  SOURCE                TC / MI product IDs (e.g. TC1W2B0_01_02936N034E0938) or local .lbl files.

options:
  -h, --help            show this help message and exit
  --instrument, -i {tc,mi}
                        Terrain Camera or Multiband Imager (default: from SOURCE, else tc).
  --from-file FILE      Text file of sources, one per line (#comments ignored).
  --output-dir, -o DIR  Output directory (default: current working directory).

ODE search (instead of / in addition to SOURCE):
  --bbox WEST SOUTH EAST NORTH
                        Bounding box in degrees.
  --point LAT LON       Search around a point (use with --radius-km).
  --radius-km KM        Radius for --point (default: 1 km).
  --product-type, -t PT
                        TC: stereo (TCWL2B, default) or mono (TCSL2B); MI: vis (VIS2B, default) or
                        nir (NIR2B).
  --inc MIN MAX         Incidence angle range.
  --emi MIN MAX         Emission angle range.
  --phase MIN MAX       Phase angle range.
  --time START END      Observation time range (UTC).
  --limit N             Maximum number of products from the search (default: 10).

ISIS import:
  --no-web              spiceinit with local kernels ($ISISDATA/kaguya) instead of the web
                        service.
  --shape SHAPE         spiceinit shape: system (LOLA DEM in $ISISDATA/base/dems, default) or
                        ellipsoid.
  --csm, --no-csm       Write a CSM camera (.json) with isd_generate (default: TC always, MI with
                        --no-web).
  --only-naif-spice, --no-only-naif-spice
                        TC: pass --only_naif_spice to isd_generate (default: with --no-web; MI
                        always).
  --mapproject          Also cam2map and write <id>.tif.
  --projection PROJ     ISIS projection for --mapproject (default: equirectangular).
  --resolution M        Map resolution in m/pixel for --mapproject.
  --keep                Keep the PDS .img and the map-projected cube.
  --force               Redo steps whose output exists.
  --download-only       Download (and gunzip) only; no ISIS processing.
  --dry-run             Print the ISIS commands without downloading or running anything.
  --workers N           Products processed in parallel (default: 1).

Examples
--------
  lunar kaguya TC1W2B0_01_02936N034E0938 TC2W2B0_01_02936N036E0938 -o ./tc
  lunar kaguya --point 34.0 93.8 --radius-km 2 --limit 4 -o ./tc
  lunar kaguya --bbox 93.5 33.9 94.0 34.1 --product-type mono --inc 40 70 -o ./tc
  lunar kaguya ./tc/TC1W2B0_01_02936N034E0938.lbl --mapproject --resolution 10
  lunar kaguya TC1W2B0_01_02936N034E0938 --download-only
  lunar kaguya TC1W2B0_01_02936N034E0938 --dry-run
  lunar kaguya MVA_2B2_01_01935N340E0939 MNA_2B2_01_01935N339E0939 -o ./mi
  lunar kaguya --instrument mi --point 34.0 93.9 --product-type nir --limit 2 -o ./mi

Per product, <output-dir> gets <id>.lbl, <id>.cub (kaguyatc2isis / kaguyami2isis
+ spiceinit), <id>.json (CSM camera from isd_generate) and, with --mapproject,
<id>.tif.  MI cubes hold all VIS (5) or NIR (4) bands; ALE's MI drivers need
local kernels, so MI CSM cameras default to --no-web runs only (--csm forces).
The instrument is inferred from the product IDs (MI: M[VN]A_2B2_...) unless
--instrument is given.
Steps whose output exists are skipped (use --force to redo them).
--bbox is WEST SOUTH EAST NORTH, longitudes positive east.

lunar shadowcam

usage: lunar shadowcam [-h] [--from-file FILE] [--output-dir DIR] [--product {raw,cal}]
                       [--bbox WEST SOUTH EAST NORTH | --point LAT LON | --aoi FILE_OR_WKT]
                       [--radius-km KM] [--inc MIN MAX] [--emi MIN MAX] [--phase MIN MAX]
                       [--time START END] [--limit N] [--web] [--shape SHAPE] [--no-csm]
                       [--validate] [--mapproject] [--dem DEM] [--resolution M] [--t-srs PROJ]
                       [--projection PROJ] [--keep] [--force] [--download-only] [--dry-run]
                       [--workers N]
                       [SOURCE ...]

positional arguments:
  SOURCE                Observation / product IDs (M013178466S, M013178466SE, M013178466SC) or
                        local .cub files.

options:
  -h, --help            show this help message and exit
  --from-file FILE      Text file of sources, one per line (#comments ignored).
  --output-dir, -o DIR  Output directory (default: current working directory).
  --product, -t {raw,cal}
                        raw (M...SE, ASP's example, default) or cal (M...SC) for IDs and searches.

ODE search (instead of / in addition to SOURCE):
  --bbox WEST SOUTH EAST NORTH
                        Bounding box in degrees.
  --point LAT LON       Search around a point (use with --radius-km).
  --aoi FILE_OR_WKT     Area of interest: GeoJSON / WKT file or WKT polygon string.
  --radius-km KM        Radius for --point (default: 1 km).
  --inc MIN MAX         Incidence angle range.
  --emi MIN MAX         Emission angle range.
  --phase MIN MAX       Phase angle range.
  --time START END      Observation time range (UTC).
  --limit N             Maximum number of products from the search (default: 10).

Processing:
  --web                 spiceinit with the USGS SPICE web service (isd_generate still needs local
                        kernels).
  --shape SHAPE         spiceinit shape: system (LOLA DEM in $ISISDATA/base/dems, default) or
                        ellipsoid.
  --no-csm              Do not write a CSM camera (.json) with isd_generate.
  --validate            Compare ISIS and CSM cameras with cam_test (<id>.cam_test.txt).
  --mapproject          Also write a map-projected <id>.tif.
  --dem DEM             DEM for ASP mapproject (default: ISIS cam2map).
  --resolution M        Map resolution in m/pixel for --mapproject.
  --t-srs PROJ          PROJ string for ASP mapproject with --dem.
  --projection PROJ     ISIS projection for --mapproject without --dem (default: equirectangular).
  --keep                Keep the ISIS map-projected cube.
  --force               Redo processing steps whose output exists.
  --download-only       Download only; no processing.
  --dry-run             Print the ISIS / ASP commands without downloading or running anything.
  --workers N           Products processed in parallel (default: 1).

Examples
--------
  lunar shadowcam M074289249S M074296291S -o ./sc
  lunar shadowcam M013178466SC --validate -o ./sc
  lunar shadowcam --point -89.67 129.8 --radius-km 5 --inc 80 90 --limit 4 -o ./sc
  lunar shadowcam --aoi malapert.geojson --product cal --download-only -o ./sc
  lunar shadowcam ./sc/M013178466SE.cub --mapproject --dem ref.tif --resolution 1.7
  lunar shadowcam M013178466S --dry-run

Follows the ASP 3.7 ShadowCam example, which needs ASP's custom ISIS / ALE
build and the KPLO kernels in $ISISDATA/kplo/kernels (rsync.im-ldi.com::kplo).
Observation IDs (M...S) map to the raw cube M...SE (used in ASP's example) or,
with --product cal, the calibrated M...SC.  Per product, <output-dir> gets
<id>.cub (spiceinit'ed in place), <id>.json (isd_generate), with --validate
<id>.cam_test.txt and with --mapproject <id>.tif (ASP mapproject with --dem,
else ISIS cam2map).  Steps whose output exists are skipped (--force redoes them).
--bbox is WEST SOUTH EAST NORTH, longitudes positive east.

lunar stereo

usage: lunar stereo [-h] [--work-dir DIR] [--res M] [--dem TIF] [--image-res M] [--no-align]
                    [--max-displacement M] [--no-csm] [--local-spice] [--processes N]
                    [--threads N] [--stereo-opt KEY=VALUE] [--force] [--dry-run] [--data-dir DIR]
                    left right

positional arguments:
  left                  Left image: NAC EDR product ID, .IMG or .cub path.
  right                 Right image: NAC EDR product ID, .IMG or .cub path.

options:
  -h, --help            show this help message and exit
  --work-dir DIR
  --res M               DEM m/px (default: 2).
  --dem TIF             Prior DEM to mapproject onto and align to.
  --image-res M         Mapprojected image m/px when --dem is set (default: 1).
  --no-align            Skip pc_align to --dem.
  --max-displacement M
  --no-csm              Use ISIS cameras instead of CSM.
  --local-spice
  --processes N
  --threads N
  --stereo-opt KEY=VALUE
                        Extra / overriding parallel_stereo option (repeatable).
  --force
  --dry-run             Print the commands without running them.
  --data-dir DIR

Examples
--------
  lunar stereo M181058717LE M181073012LE --res 2 --work-dir pair1
  lunar stereo L.IMG R.IMG --dem ldem.tif --res 2      # mapproject first, align to the DEM
  lunar stereo L.cub R.cub --dry-run

Needs ISIS and ASP on PATH (or ASP_ROOT / ISISROOT).

lunar sfs

usage: lunar sfs [-h] [--config YAML] [--dump-config] [--check] [--from-file FILE] [--dem TIF]
                 [--region REGION] [--crop-km KM] [--aoi FILE_OR_WKT | --projwin ULX ULY LRX LRY]
                 [--work-dir DIR] [--res M] [--preset {bertone,asp}] [--ba-res M]
                 [--reduce N|auto|none] [--no-csm] [--local-spice]
                 [--registration {stereo,residuals,none}] [--stereo-pairs N]
                 [--max-displacement M] [--no-refine] [--max-offset-p85 M]
                 [--selection {coverage,kmedoids,image_subset,all}] [--selection-clusters N]
                 [--sfs-cameras {csm,isis}] [--cameras-from {ba,published}] [--max-sfs-images N]
                 [--max-slew DEG] [--max-sfs-slew DEG] [--max-images N] [--max-pixel-scale M]
                 [--ensemble N] [--seed N] [--region-id ID] [--processes N] [--threads N]
                 [--ba-opt KEY=VALUE] [--refine-opt KEY=VALUE] [--sfs-opt KEY=VALUE] [--force]
                 [--dry-run] [--data-dir DIR]
                 [SOURCE ...]

positional arguments:
  SOURCE                NAC EDR product IDs (downloaded), .IMG or calibrated .cub paths (default
                        with --aoi / --region: search the local EDR index).

options:
  -h, --help            show this help message and exit
  --config YAML         Run configuration file (flags override it).
  --dump-config         Print the effective configuration as YAML and exit.
  --check               Compare tool versions, settings and input files with
                        WORK_DIR/provenance.json and exit (status 1 when they differ).
  --from-file FILE      Text file of sources, one per line.
  --dem TIF             Prior DEM (e.g. LOLA LDEM GeoTIFF), or pgda (default with --region).
  --region REGION       Artemis III region (id, name or PGDA site, see lunar reference list): sets
                        the crop, product prefix and image search, and the PGDA LOLA 5 m prior
                        DEM.
  --crop-km KM          Square crop of this side at the region centre.
  --aoi FILE_OR_WKT     Area of interest (GeoJSON / WKT file or WKT, lon/lat): crops a polar
                        stereographic prior DEM and, without SOURCEs, selects the images.
  --projwin ULX ULY LRX LRY
                        Crop window in the prior DEM's projected units.
  --work-dir DIR        Working / output directory (default: sfs_work).
  --res M               SfS DEM m/px (default: 5).
  --preset {bertone,asp}
                        Option defaults (default: bertone).
  --ba-res M            m/px of the images mapprojected for bundle adjustment (default: 1, NAC
                        native).
  --reduce N|auto|none  ISIS reduce factor of the SfS cubes (default: auto = target / native
                        m/px).
  --no-csm              Use ISIS cameras instead of CSM.
  --local-spice         spiceinit with local kernels instead of the SPICE web service.
  --registration {stereo,residuals,none}
                        Register cameras to the prior DEM from stereo DEMs, BA points or not at
                        all (default: stereo).
  --stereo-pairs N      Max stereo pairs (default: 30).
  --max-displacement M  pc_align --max-displacement for registration (default: 400).
  --no-refine           Skip the --heights-from-dem refinement.
  --max-offset-p85 M    Drop images whose 85th-percentile mapprojected match offset exceeds this
                        (default: 1.8).
  --selection {coverage,kmedoids,image_subset,all}
                        SfS image selection (default: coverage = 3 Sun directions 30 deg apart per
                        pixel; kmedoids = Bertone et al. k-medoids seeding + coverage refinement).
  --selection-clusters N
                        Medoids for --selection kmedoids (default: 30).
  --sfs-cameras {csm,isis}
                        Cameras of the reduced SfS cubes: re-mapped CSM (default) or ISIS
                        approximate models.
  --cameras-from {ba,published}
                        ba (default) or published: Bertone et al. adjusted cameras, skipping
                        bundle adjustment.
  --max-sfs-images N    Cap on SfS images.
  --max-slew DEG        Search cut on |slew| (default: none; see --max-sfs-slew).
  --max-sfs-slew DEG    Only images with |slew| below this go into SfS (default: 5; slewed images
                        still help BA / stereo).
  --max-images N        Cap on searched images before download: stereo pairs + illumination
                        coverage (default: none).
  --max-pixel-scale M   Search cut on the image m/px (default: 1.5).
  --ensemble N          SfS ensemble members (default: 0).
  --seed N              Random seed of the ensemble subsets (default: 0).
  --region-id ID        Product name prefix (default: region or work dir name).
  --processes N
  --threads N
  --ba-opt KEY=VALUE    Extra / overriding bundle_adjust option (repeatable; VALUE none drops it).
  --refine-opt KEY=VALUE
                        Extra / overriding refinement bundle_adjust option (repeatable; VALUE none
                        drops it).
  --sfs-opt KEY=VALUE   Extra / overriding parallel_sfs option (repeatable; VALUE none drops it).
  --force               Recompute steps whose outputs exist.
  --dry-run             Print the commands without running them.
  --data-dir DIR        Override Parquet data directory.

Examples
--------
  lunar sfs --config examples/malapert_crop.yaml
  lunar sfs --config run.yaml --res 2 --sfs-opt smoothness_weight=0.05   # flags override the file
  lunar sfs --config run.yaml --dump-config > effective.yaml             # print every setting
  lunar sfs --config run.yaml --check          # compare tools / config / inputs with the work dir
  lunar sfs --region malapert --crop-km 2.5 --work-dir malapert_crop   # PGDA prior DEM, ROI search
  lunar sfs --aoi site.geojson --dem LDEM_87S_5MPP_ADJ.TIF --work-dir malapert
  lunar sfs M1234567890LE M1234567891RE ... --dem LDEM_87S_5MPP_ADJ.TIF \
      --projwin -10000 50000 0 40000 --res 5 --work-dir malapert
  lunar sfs raw/*.IMG --dem ldem.tif --registration residuals --processes 8
  lunar sfs --aoi "POLYGON((...))" --dem ldem.tif --dry-run   # print the commands only

Config files are flat YAML mappings of the same settings (keys as printed by
--dump-config); command-line flags override them.  Each run writes the effective
settings to WORK_DIR/config.yaml and appends its environment, tool versions,
input checksums and per-step commands to WORK_DIR/provenance.json.

Steps (Bertone et al. 2026, PSJ 7:118): [search + slew/resolution cut + download] ->
calibrate + CSM cameras -> crop/resample prior DEM -> sort by Sun azimuth -> mapproject at
--ba-res -> bundle adjust -> drop badly registered images -> register to the prior DEM
(stereo DEMs or BA points, translation only) -> refine with --heights-from-dem -> drop
again -> mapproject at --res -> max-lit QA mosaics (all + 30 deg Sun bins) -> select images ->
reduce cubes to --res -> parallel_sfs -> products.
Re-running resumes: steps whose outputs exist are skipped (--force to redo).
Needs ISIS and ASP on PATH (or ASP_ROOT / ISISROOT).

lunar sfs-report

usage: lunar sfs-report [-h] [-o DIR] [--sfs-dem TIF] [--prior-dem TIF] [--region-id ID] [--res M]
                        [--ldec TIF] [--toterr TIF] [--slperr TIF] [--ldem-slope TIF]
                        [--lola-points CSV] [--lola-xyzi XYZI] [--published-dem TIF]
                        [--region REGION] [--pgda KIND [KIND ...]] [--fetch-published]
                        [--reference-dir DIR] [--sections NAME [NAME ...]] [--preview-size PX]
                        WORK_DIR

positional arguments:
  WORK_DIR              lunar sfs work directory.

options:
  -h, --help            show this help message and exit
  -o, --output-dir DIR  Destination (default: WORK_DIR/report).
  --sfs-dem TIF         SfS DEM (default: products GLDELEV or sfs/).
  --prior-dem TIF       Prior DEM (default: dem/prior-<RES>m.tif).
  --region-id ID        Product name prefix (default: from config.yaml).
  --res M               Product resolution label (default: from config.yaml).
  --ldec TIF            PGDA LOLA count raster (*_ldec.tif).
  --toterr TIF          PGDA elevation uncertainty (*_toterr.tif).
  --slperr TIF          PGDA slope uncertainty (*_slperr.tif).
  --ldem-slope TIF      PGDA LDEM slope (*_slp.tif).
  --lola-points CSV     LOLA points as an ASP CSV (easting, northing, height).
  --lola-xyzi XYZI      PGDA LOLA point cloud (*.xyzi).
  --published-dem TIF   Published Bertone et al. SDEM (GLDELEV).
  --region REGION       Artemis III region: download its PGDA files (see --pgda) for the inputs
                        not given.
  --pgda KIND [KIND ...]
                        PGDA files fetched with --region (default: ldec toterr slp slperr xyzi).
  --fetch-published     With --region: extract the published GLDELEV from the Zenodo zip (HTTP
                        range reads).
  --reference-dir DIR   Download directory for --region files (default: the reference cache).
  --sections NAME [NAME ...]
                        Only these sections (default: all).
  --preview-size PX     Longer side of the map figures (default: 800).

Examples
--------
  lunar sfs-report malapert_crop                       # work dir files only
  lunar sfs-report malapert_crop --region malapert     # + PGDA LDEC, uncertainty maps, LOLA points
  lunar sfs-report malapert_crop --region malapert --fetch-published   # + published SDEM
  lunar sfs-report w --toterr Site23_final_adj_5mpp_toterr.tif --lola-points lola.csv -o w/report2

Sections (skipped when their inputs are missing): bundle-adjustment residuals before /
after and match offsets (Fig. 5); removed images; illumination coverage with 0/1/2/>=3
non-redundant Sun directions (Figs. 3, 6); SfS minus prior DEM, split by LOLA count
(Table 2); SfS minus LOLA points vs slope (Fig. 7); ensemble uncertainty (Fig. 8);
SfS - LDEM residuals over the LDEM uncertainty maps (Fig. 9); SfS minus the published SDEM.
Writes OUTPUT_DIR/report.html (figures embedded), metrics.json, tables/*.csv, figures/*.png.

lunar workspace

usage: lunar workspace [-h] [--sql SQL] [--tables] [--export TABLE PATH] [--csv FILE]
                       [--max-rows N] [--memory-limit SIZE] [--threads N]
                       WORK_DIR

positional arguments:
  WORK_DIR             Work directory or .duckdb file.

options:
  -h, --help           show this help message and exit
  --sql SQL            Run SQL and print the result (repeatable).
  --tables             List tables and views with row counts.
  --export TABLE PATH  Write a table or view to Parquet / CSV.
  --csv FILE           Write the last --sql result to CSV.
  --max-rows N         Rows to print (default: 50).
  --memory-limit SIZE  DuckDB memory_limit (default: 1GB).
  --threads N          DuckDB threads (default: all cores).

Examples
--------
  lunar workspace work/malapert                      # tables and views with row counts
  lunar workspace work/malapert --sql "SELECT * FROM ba_run_summary"
  lunar workspace work/malapert --sql "SELECT run, rule, count(*) FROM image_rejections GROUP BY ALL"
  lunar workspace work/malapert --sql "SELECT * FROM image_rejections_at(1.5, 0.8, 1.0, 1)"
  lunar workspace work/malapert --export ba_image_stats ba_stats.parquet

WORK_DIR is a lunar sfs work directory (its lunar.duckdb) or a .duckdb file; it is
opened read-only.  Tables: images, pairs, ba_runs, ba_residual_stats,
ba_pixel_residuals, ba_match_offsets, ba_points, ba_convergence, ba_camera_offsets,
rejection_params.  Views: ba_image_stats, ba_run_summary, image_rejections.
--export writes Parquet for .parquet paths, else CSV (geometries as WKT).

lunar serve

usage: lunar serve [-h] [--port PORT] [--host HOST] [--data-dir DIR] [--images DIR]
                   [--runs-dir DIR] [--admin-token TOKEN] [--no-browser]

options:
  -h, --help           show this help message and exit
  --port PORT          Port to listen on (default: 8080).
  --host HOST          Address to bind (default: localhost); 0.0.0.0 serves the network (read-only
                       there).
  --data-dir DIR       Directory containing parquet files. Defaults to LUNAR_DATA_DIR or the user
                       data dir.
  --images DIR         Also serve the GeoTIFF / PNG / JPEG files under DIR (with Range) for the
                       Image view.
  --runs-dir DIR       SfS runs listed in the Settings panel (default: LUNAR_RUNS_DIR).
  --admin-token TOKEN  Let remote clients with this token (Authorization: Bearer TOKEN or
                       ?token=TOKEN) start jobs and see local paths; 'auto' generates one.
  --no-browser         Don't open a browser tab automatically.

Examples
--------
  lunar serve
  lunar serve --port 9000
  lunar serve --data-dir /mnt/lroc-data
  lunar serve --images ~/sfs_runs      # browse local GeoTIFF / COG results in the Image view
  lunar serve --host 0.0.0.0           # share on the network; remote clients are read-only
  lunar serve --host 0.0.0.0 --admin-token auto   # ... and print a token for remote admin

Admin (the Settings panel: build indexes, download, see local paths) is allowed from this
machine (loopback) and for requests carrying --admin-token; everyone else can browse,
search and view. Use `lunar app` for a desktop window.

lunar app

usage: lunar app [-h] [--port PORT] [--host HOST] [--data-dir DIR] [--images DIR] [--runs-dir DIR]
                 [--admin-token TOKEN] [--backend {auto,webview,chromium,browser}]

options:
  -h, --help            show this help message and exit
  --port PORT           Port to listen on (default: any free).
  --host HOST           Address to bind (default: 127.0.0.1); 0.0.0.0 serves the network (read-
                        only there).
  --data-dir DIR        Directory containing parquet files. Defaults to LUNAR_DATA_DIR or the user
                        data dir.
  --images DIR          Also serve the GeoTIFF / PNG / JPEG files under DIR (with Range) for the
                        Image view.
  --runs-dir DIR        SfS runs listed in the Settings panel (default: LUNAR_RUNS_DIR).
  --admin-token TOKEN   Let remote clients with this token (Authorization: Bearer TOKEN or
                        ?token=TOKEN) start jobs and see local paths; 'auto' generates one.
  --backend {auto,webview,chromium,browser}
                        Window backend (default: auto, in the order above).

Examples
--------
  lunar app
  lunar app --images ~/lunar_runs
  lunar app --host 0.0.0.0 --port 8080        # window here, read-only page for the network
  lunar app --backend browser                 # a normal browser tab instead

The server runs on this machine (a free port on 127.0.0.1 by default) and stops when
the window closes. Window backends, first available wins (--backend auto):
  webview   pywebview: Qt WebEngine on Linux (pip install "lunar-toolkit[app]",
            or PyGObject + WebKitGTK), Edge WebView2 on Windows, WebKit on macOS
  chromium  Chrome / Chromium / Edge / Brave in --app mode (own profile in the cache dir)
  browser   the default browser (Ctrl-C stops the server)