asp-plot 2.2.0__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {asp_plot-2.2.0 → asp_plot-3.0.0}/AGENTS.md +1 -1
  2. {asp_plot-2.2.0 → asp_plot-3.0.0}/ARCHITECTURE.md +24 -25
  3. {asp_plot-2.2.0 → asp_plot-3.0.0}/CHANGELOG.md +16 -0
  4. {asp_plot-2.2.0 → asp_plot-3.0.0}/PKG-INFO +2 -2
  5. {asp_plot-2.2.0 → asp_plot-3.0.0}/README.md +1 -1
  6. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/bundle_adjust.py +14 -5
  7. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/asp_report.py +43 -48
  8. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/csm_camera_plot.py +31 -31
  9. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/gallery.py +9 -8
  10. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/request_planetary_altimetry.py +4 -4
  11. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/stereo_geom.py +9 -9
  12. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/processing_parameters.py +15 -2
  13. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/report_pipeline.py +37 -34
  14. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/utils.py +36 -0
  15. {asp_plot-2.2.0 → asp_plot-3.0.0}/pyproject.toml +1 -1
  16. {asp_plot-2.2.0 → asp_plot-3.0.0}/.flake8 +0 -0
  17. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/ISSUE_TEMPLATE/problem-report.yml +0 -0
  19. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/workflows/claude-code-review.yml +0 -0
  20. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/workflows/claude.yml +0 -0
  21. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/workflows/release.yml +0 -0
  22. {asp_plot-2.2.0 → asp_plot-3.0.0}/.github/workflows/run-tests.yml +0 -0
  23. {asp_plot-2.2.0 → asp_plot-3.0.0}/.gitignore +0 -0
  24. {asp_plot-2.2.0 → asp_plot-3.0.0}/.pre-commit-config.yaml +0 -0
  25. {asp_plot-2.2.0 → asp_plot-3.0.0}/.readthedocs.yaml +0 -0
  26. {asp_plot-2.2.0 → asp_plot-3.0.0}/CLAUDE.md +0 -0
  27. {asp_plot-2.2.0 → asp_plot-3.0.0}/LICENSE +0 -0
  28. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/__init__.py +0 -0
  29. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/alignment.py +0 -0
  30. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/altimetry.py +0 -0
  31. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/altimetry_plots.py +0 -0
  32. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/altimetry_source.py +0 -0
  33. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/asp_log.py +0 -0
  34. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/bodies.py +0 -0
  35. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/cli/__init__.py +0 -0
  36. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/csm_analysis.py +0 -0
  37. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/csm_camera.py +0 -0
  38. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/csm_io.py +0 -0
  39. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/gallery.py +0 -0
  40. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/icesat2_source.py +0 -0
  41. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/mapproject.py +0 -0
  42. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/planetary_source.py +0 -0
  43. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/report.py +0 -0
  44. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/report_captions.py +0 -0
  45. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/scenes.py +0 -0
  46. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/selections.py +0 -0
  47. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/__init__.py +0 -0
  48. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/aster.py +0 -0
  49. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/base.py +0 -0
  50. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/dimap.py +0 -0
  51. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/dimap_v1.py +0 -0
  52. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/rpc.py +0 -0
  53. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/sensors/worldview.py +0 -0
  54. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/stereo.py +0 -0
  55. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/stereo_geometry.py +0 -0
  56. {asp_plot-2.2.0 → asp_plot-3.0.0}/asp_plot/stereopair_metadata_parser.py +0 -0
  57. {asp_plot-2.2.0 → asp_plot-3.0.0}/conda-forge-recipe/meta.yaml +0 -0
  58. {asp_plot-2.2.0 → asp_plot-3.0.0}/environment.yml +0 -0
@@ -54,7 +54,7 @@ sphinx-autobuild docs docs/_build/html --open-browser # or sphinx-build for a
54
54
  ## External Data Sources
55
55
 
56
56
  - **ICESat-2 ATL06-SR** (Earth): requested through the SlideRule API; results cached as parquet next to the report.
57
- - **LOLA (Moon) / MOLA (Mars)**: async queries to the ODE GDS REST API via the `request_planetary_altimetry` CLI — the user gets a download link by email, then passes the CSV to `asp_report --altimetry_csv`. Coordinates are east-positive 0–360 longitude, planetocentric latitude.
57
+ - **LOLA (Moon) / MOLA (Mars)**: async queries to the ODE GDS REST API via the `request_planetary_altimetry` CLI — the user gets a download link by email, then passes the CSV to `asp_report --altimetry-csv`. Coordinates are east-positive 0–360 longitude, planetocentric latitude.
58
58
 
59
59
  ## Testing
60
60
 
@@ -42,7 +42,7 @@ The package is organized by functionality, with each module focused on a specifi
42
42
  - `pixel_window_to_bbox()` / `bbox_to_pixel_offset()`: convert a detailed-hillshade clip between a DEM-CRS map bbox and a top-left pixel offset. `bbox_to_pixel_offset` uses `rowcol(..., op=round)` (round-to-nearest, **not** floor) so a replayed clip doesn't drift by a pixel from floating-point error
43
43
  - `reproject_bbox(bbox, src_crs, dst_crs)`: reprojects a clip bbox between CRSs (via `rasterio.warp.transform_bounds`) so clip reuse works across stereo variants in different projections (e.g. MOC non-mapproj Stereographic vs mapproj Sinusoidal). `plot_detailed_hillshade(clip_windows_crs=...)` passes the manifest's `dem_crs`; reprojection is a no-op when CRSs match
44
44
  - Deliberately imports nothing from `report.py` / `fpdf`, so it is safe to use from notebooks
45
- - The `asp_report` CLI always writes `<report_stem>_figure_selections.yml` next to the report; `--reuse_selections PATH` replays a prior run's choices
45
+ - The `asp_report` CLI always writes `<report_stem>_figure_selections.yml` next to the report; `--reuse-selections PATH` replays a prior run's choices
46
46
 
47
47
  **`report.py`** - PDF report generation using fpdf2
48
48
  - `ReportSection`: Dataclass representing a report figure (title, image path, caption)
@@ -53,13 +53,13 @@ The package is organized by functionality, with each module focused on a specifi
53
53
  - `_add_processing_parameters_page()`: helper that renders the runtime summary table plus the bundle_adjust / stereo / point2dem / report commands on page 2 (moved from the trailing page as of v1.13.0). Also renders the reconstructed `mapproject` command(s) (from the optional `mapproject` list key; see `mapproject.py`) with a "reconstructed from output metadata" note.
54
54
  - `_fmt_sig()`: formats a number compactly — 2 decimals for |x| < 10, 1 decimal for 10 ≤ |x| < 100, 0 decimals above, "n/a" for non-finite. Used for alignment stats.
55
55
  - Title page displays: processing date, ASP version (from logs), asp_plot version (from package metadata)
56
- - Page order: title + DEM summary → Processing Parameters → diagnostic figures → (if `--pc_align` ran) alignment report page + aligned-DEM figures.
56
+ - Page order: title + DEM summary → Processing Parameters → diagnostic figures → (if the `pc_align` step ran) alignment report page + aligned-DEM figures.
57
57
  - `report.py` is fed declaratively by `report_pipeline.py`; it was **not** rewritten in #128.
58
58
 
59
59
  **`report_pipeline.py`** - Declarative report pipeline behind the `asp_report` CLI (issue #128)
60
60
  - `ReportConfig`: dataclass packing the ~18 CLI options into one Click-free object (field names/defaults mirror the options one-for-one, guarded by a test)
61
61
  - `run_report(config)`: importable/callable from notebooks and tests with no Click context; returns the written PDF path
62
- - A declarative section registry (`REPORT_SECTIONS`) of `ReportSpec`s replaces the old inline plot-and-append wall: each spec pairs an `enabled(ctx)` predicate with a `build(ctx)` function returning the sections to append. `--plot_geometry` / `--plot_altimetry` / `--pc_align` gating are predicates; figure numbering comes from a per-run counter on the shared `ReportContext`, so section order and numbering are data, not source-line position. The alignment "Page B/C/D" follow-ups are one spec emitting several sections
62
+ - A declarative section registry (`REPORT_SECTIONS`) of `ReportSpec`s replaces the old inline plot-and-append wall: each spec pairs an `enabled(ctx)` predicate with a `build(ctx)` function returning the sections to append. `--no-geometry` / `--no-altimetry` / `--no-pc-align` gating are predicates; figure numbering comes from a per-run counter on the shared `ReportContext`, so section order and numbering are data, not source-line position. The alignment "Page B/C/D" follow-ups are one spec emitting several sections
63
63
  - Section builders: `_build_input_scenes`, `_build_stereo_geometry`, `_build_match_points`, `_build_bundle_adjust`, `_build_disparity`, `_build_dem_results`, `_build_detailed_hillshade`, `_build_altimetry` (→ `_build_altimetry_earth` / `_build_altimetry_planetary`)
64
64
  - `_numbered_sections()`: shared helper turning a plotter's saved-filename list into one `ReportSection` per figure (first gets the caption, continuations are titled "... (continued)") — used by the scenes/geometry/match/disparity builders, whose plotters save one figure per pair on multi-view runs (issue #160)
65
65
 
@@ -218,28 +218,27 @@ All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
218
218
  **`asp_report.py`** - Main CLI tool (`asp_report` command)
219
219
  - Generates comprehensive PDF reports of ASP processing
220
220
  - A **thin Click wrapper** (issue #128): parses the ~18 options into a `ReportConfig` and calls `report_pipeline.run_report(config)`. All orchestration lives in `report_pipeline.py` (see the module structure section); the CLI file itself is now just option definitions + the `ReportConfig` build
221
- - Accepts directories for stereo and bundle_adjust outputs
221
+ - Accepts a directory for stereo outputs and a directory or ASP-style prefix for bundle_adjust outputs; trailing slashes are tolerated (issue #60)
222
222
  - Options for reference DEMs, ICESat-2 comparisons, basemaps
223
223
  - Report section order: Input Scenes → Stereo Geometry → Match Points → Bundle Adjust panels (Log/Linear Residuals, Map-Projected Residuals, Geodiff) → Disparity → DEM Results → Detailed Hillshade → Altimetry panels. Disparity follows Bundle Adjust (not Hillshade) and DEM Results precedes Hillshade, so the title page → inputs → per-step diagnostics → final DEM products narrative reads top-down.
224
224
  - Title-page `ReportMetadata` is populated here; `get_acquisition_dates()` is called with the main directory plus stereo/BA subdirs as `extra_dirs` so the Acquisition Date(s) row appears on both raw-input layouts (WV XMLs at top level) and layouts where XMLs live one level deep.
225
225
  - `--directory`: Root ASP processing directory (default: `./`)
226
- - `--stereo_directory`: Stereo output subdirectory (default: `stereo`)
227
- - `--bundle_adjust_directory`: Optional BA directory
228
- - `--dem_filename`: Custom DEM filename (default: auto-detect `*-DEM.tif`)
229
- - `--dem_gsd`: Custom DEM ground sample distance
230
- - `--map_crs`: Projection as `EPSG:XXXX` (default: auto-detect from DEM, fallback `EPSG:4326`)
231
- - `--reference_dem`: Reference DEM path (auto-detected from logs if not supplied)
232
- - `--add_basemap`: Add Esri WorldImagery basemaps (default: True, requires internet)
233
- - `--plot_altimetry`: Plot altimetry comparisons (default: True). Auto-detects planetary body from DEM CRS: Earth → ICESat-2 (requires internet), Moon → LOLA, Mars → MOLA. For planetary DEMs, requires `--altimetry_csv`. Replaces the deprecated `--plot_icesat` flag.
234
- - `--plot_icesat`: Deprecated alias for `--plot_altimetry`. Prints deprecation warning if used.
235
- - `--altimetry_csv`: Path to a LOLA/MOLA CSV from the ODE GDS API. **Mars: must be the `*_pts_csv.csv` (not `*_topo_csv.csv`) the loader requires the `PLANET_RAD` column to avoid the oblate-areoid offset.** Moon accepts either the `*_topo_simple_csv.csv` (results=u) or the `*_pts_csv.csv` (results=p). Obtained via the `request_planetary_altimetry` CLI tool.
236
- - `--pc_align`: If True (default) and `--plot_altimetry` is True, runs `pc_align` against the reference altimetry (ICESat-2 for Earth, MOLA for Mars, LOLA for Moon) after the existing altimetry plots and appends an alignment report. **Earth success path**: adds four pages (alignment report page, pre/post landcover histogram, aligned profile, aligned best/worst segments). **Planetary success path**: adds three pages (alignment report page, pre/post mapview, pre/post histogram). `insufficient_points` and `no_improvement` outcomes emit a single alignment report page on either branch. Disabled automatically when `--plot_altimetry` / `--plot_icesat` is False.
237
- - `--plot_geometry`: Plot stereo geometry (default: True; disable for planetary missions)
238
- - `--subset_km`: Hillshade subset size in km (default: 1.0)
239
- - `--atl06sr_time_range`: Time range for ICESat-2 ATL06-SR requests. `"all"` (default) for full mission, `"auto"` for scene metadata ±1 year, `"START,END"` for a custom range, or a single date (buffered by ±1 year).
240
- - `--reuse_selections`: Path to a `*_figure_selections.yml` from a prior run. Replays that run's ICESat-2 points (parquet), profile track, best/worst segments, and detailed-hillshade clips so re-processing runs (e.g. mapproj vs non-mapproj) are directly comparable (issue #121). Every run always writes `<report_stem>_figure_selections.yml` next to the report (the `regenerate_reports.sh` paired variants use this to reuse each other). Generated sidecars are gitignored (they hardcode absolute local paths); a sanitized example is in `docs/cli/asp_report.md`.
241
- - `--report_filename`: PDF report filename or path. A bare filename saves in the stereo directory; a path (e.g. `reports/report.pdf`) is used as-is. Default is auto-generated as `asp_report_<report_title>_<UTC timestamp>.pdf`
242
- - `--report_title`: Custom report title (default: directory name)
226
+ - `--stereo-directory`: Stereo output subdirectory (default: `stereo`)
227
+ - `--bundle-adjust-prefix`: Optional BA output location — either a directory (`ba`) or an ASP-style output prefix (`ba/run`, as passed to stereo/mapproject); a prefix narrows file globs to that run's outputs
228
+ - `--dem-filename`: Custom DEM filename (default: auto-detect `*-DEM.tif`)
229
+ - `--dem-gsd`: Custom DEM ground sample distance
230
+ - `--map-crs`: Projection as `EPSG:XXXX` (default: auto-detect from DEM, fallback `EPSG:4326`)
231
+ - `--reference-dem`: Reference DEM path (auto-detected from logs if not supplied)
232
+ - `--no-basemap`: Skip the Esri WorldImagery basemaps added by default (which require internet)
233
+ - `--no-altimetry`: Skip the altimetry comparisons plotted by default. Auto-detects planetary body from DEM CRS: Earth → ICESat-2 (requires internet), Moon → LOLA, Mars → MOLA. For planetary DEMs, requires `--altimetry-csv`.
234
+ - `--altimetry-csv`: Path to a LOLA/MOLA CSV from the ODE GDS API. **Mars: must be the `*_pts_csv.csv` (not `*_topo_csv.csv`) — the loader requires the `PLANET_RAD` column to avoid the oblate-areoid offset.** Moon accepts either the `*_topo_simple_csv.csv` (results=u) or the `*_pts_csv.csv` (results=p). Obtained via the `request_planetary_altimetry` CLI tool.
235
+ - `--no-pc-align`: Skips the `pc_align` step; by default (when altimetry is on) the report runs `pc_align` against the reference altimetry (ICESat-2 for Earth, MOLA for Mars, LOLA for Moon) after the existing altimetry plots and appends an alignment report. **Earth success path**: adds four pages (alignment report page, pre/post landcover histogram, aligned profile, aligned best/worst segments). **Planetary success path**: adds three pages (alignment report page, pre/post mapview, pre/post histogram). `insufficient_points` and `no_improvement` outcomes emit a single alignment report page on either branch. Skipped automatically when `--no-altimetry` is set.
236
+ - `--no-geometry`: Skip the stereo geometry plots drawn by default (used for planetary missions)
237
+ - `--subset-km`: Hillshade subset size in km (default: 1.0)
238
+ - `--atl06sr-time-range`: Time range for ICESat-2 ATL06-SR requests. `"all"` (default) for full mission, `"auto"` for scene metadata ±1 year, `"START,END"` for a custom range, or a single date (buffered by ±1 year).
239
+ - `--reuse-selections`: Path to a `*_figure_selections.yml` from a prior run. Replays that run's ICESat-2 points (parquet), profile track, best/worst segments, and detailed-hillshade clips so re-processing runs (e.g. mapproj vs non-mapproj) are directly comparable (issue #121). Every run always writes `<report_stem>_figure_selections.yml` next to the report (the `regenerate_reports.sh` paired variants use this to reuse each other). Generated sidecars are gitignored (they hardcode absolute local paths); a sanitized example is in `docs/cli/asp_report.md`.
240
+ - `--report-filename`: PDF report filename or path. A bare filename saves in the stereo directory; a path (e.g. `reports/report.pdf`) is used as-is. Default is auto-generated as `asp_report_<report_title>_<UTC timestamp>.pdf`
241
+ - `--report-title`: Custom report title (default: directory name)
243
242
 
244
243
  **`csm_camera_plot.py`** - CSM camera comparison tool (`csm_camera_plot` command)
245
244
  - Wrapper for `csm_camera.py` functions
@@ -253,7 +252,7 @@ All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
253
252
  - `--email`: Email for notification when query finishes (required)
254
253
  - `--channels`: LOLA detector channels (Moon only, default `tffff` = channel 1 only)
255
254
  - Saves request metadata as `altimetry_request_info.yml` alongside the DEM
256
- - Workflow: submit query → receive email → download/unzip → pass `*_pts_csv.csv` (Mars) or `*_topo_simple_csv.csv`/`*_pts_csv.csv` (Moon) to `asp_report --altimetry_csv`
255
+ - Workflow: submit query → receive email → download/unzip → pass `*_pts_csv.csv` (Mars) or `*_topo_simple_csv.csv`/`*_pts_csv.csv` (Moon) to `asp_report --altimetry-csv`
257
256
 
258
257
  **`stereo_geom.py`** - Stereo geometry visualization tool (`stereo_geom` command)
259
258
  - Wrapper for `StereoGeometryPlotter`
@@ -265,7 +264,7 @@ All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
265
264
  **`gallery.py`** - DEM gallery tool (`gallery` command)
266
265
  - Wrapper for `GalleryPlotter`; lays out many DEMs as a grid sharing one color scale
267
266
  - `--directory` + `--pattern` (supports recursive `**` for subdirectories) or an explicit list of `FILES` (files take precedence)
268
- - `--hillshade/--no-hillshade`, `--cmap`, `--downsample`, `--max_filesize_mb`, `--title`, `--output_directory/--output_filename`
267
+ - `--no-hillshade`, `--cmap`, `--downsample`, `--max-filesize-mb`, `--title`, `--output-directory/--output-filename`
269
268
  - Saves `<dirname>_gallery.png` into the input directory by default
270
269
 
271
270
  ## Documentation Website
@@ -307,7 +306,7 @@ docs/
307
306
  - **`html_extra_path`** is used to serve `notebooks/figures/` at the correct relative path for notebook `<img src>` references.
308
307
  - **`docs/` is excluded from sdist** in `pyproject.toml` so docs never ship in the PyPI/conda package.
309
308
  - **Changelog** uses `{include} ../CHANGELOG.md` so there's one source of truth.
310
- - **Selective notebook exclusion**: `.readthedocs.yaml`'s `pre_build` copies every `notebooks/**/*.ipynb` into `docs/examples/notebooks/`, but specific notebooks can be dropped from the build by listing them in `exclude_patterns` in `docs/conf.py` (e.g., `worldview_utqiagvik_stereo.ipynb`). Inter-notebook links in the WorldView examples use fully-qualified `https://asp-plot.readthedocs.io/en/latest/...` URLs so they resolve both on RTD and in raw notebook previews. Report-link convention: each notebook's "Full Report Generation" section ends with a `#### See the resulting [report](https://asp-plot.readthedocs.io/en/latest/_static/reports/<filename>.pdf).` line that links directly to the PDF served from `_static/reports/`. The `<filename>` must match what the cell's `!asp_report --report_filename` writes (so the URL on RTD actually resolves).
309
+ - **Selective notebook exclusion**: `.readthedocs.yaml`'s `pre_build` copies every `notebooks/**/*.ipynb` into `docs/examples/notebooks/`, but specific notebooks can be dropped from the build by listing them in `exclude_patterns` in `docs/conf.py` (e.g., `worldview_utqiagvik_stereo.ipynb`). Inter-notebook links in the WorldView examples use fully-qualified `https://asp-plot.readthedocs.io/en/latest/...` URLs so they resolve both on RTD and in raw notebook previews. Report-link convention: each notebook's "Full Report Generation" section ends with a `#### See the resulting [report](https://asp-plot.readthedocs.io/en/latest/_static/reports/<filename>.pdf).` line that links directly to the PDF served from `_static/reports/`. The `<filename>` must match what the cell's `!asp_report --report-filename` writes (so the URL on RTD actually resolves).
311
310
 
312
311
  ### Dependencies
313
312
 
@@ -337,7 +336,7 @@ Docs dependencies are in `pyproject.toml` under `[project.optional-dependencies]
337
336
 
338
337
  **ODE GDS REST API**: Base URL `https://oderest.rsl.wustl.edu/livegds`. Queries are submitted via `gds_query_async()` in async mode. The `request_planetary_altimetry` CLI submits the query and the user downloads results via email link. Coordinates use east-positive 0-360 longitude and planetocentric latitude.
339
338
 
340
- **Basemaps**: Uses `contextily` to fetch Esri WorldImagery tiles (requires internet). Can be disabled with `--add_basemap False`. Automatically skipped for planetary (non-Earth) DEMs.
339
+ **Basemaps**: Uses `contextily` to fetch Esri WorldImagery tiles (requires internet). Can be disabled with `--no-basemap`. Automatically skipped for planetary (non-Earth) DEMs.
341
340
 
342
341
  ## Example Notebooks
343
342
 
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - 2026-08-24
9
+
10
+ A breaking standardization of the command-line interfaces ([#60](https://github.com/uw-cryo/asp_plot/issues/60)), done deliberately as a clean break — no aliases, no deprecation period — while the user base is small. Every multi-word option across the five CLIs (`asp_report`, `stereo_geom`, `csm_camera_plot`, `gallery`, `request_planetary_altimetry`) moves from underscores to the hyphenated style ASP itself uses (`--stereo_directory` → `--stereo-directory`), booleans become single switches for the non-default behavior (`--add_basemap False` → `--no-basemap`), and `--bundle_adjust_directory` becomes `--bundle-adjust-prefix`, matching both the name and the semantics of ASP's own option. The Python API is unchanged.
11
+
12
+ A major version because every existing `asp_report`/`stereo_geom`/`csm_camera_plot`/`gallery` invocation with a multi-word or boolean option needs editing. **Upgrade:** re-spell the flags per the entries below — the `--help` of each command lists the new names, and the committed example reports and notebooks show them in use. No new dependencies and no entry-point changes.
13
+
14
+ ### Changed
15
+ - **All CLI options are hyphenated** (issue [#60](https://github.com/uw-cryo/asp_plot/issues/60)). One-for-one renames: `--stereo-directory`, `--dem-filename`, `--dem-gsd`, `--map-crs`, `--reference-dem`, `--altimetry-csv`, `--subset-km`, `--atl06sr-time-range`, `--reuse-selections`, `--report-filename`, `--report-title` (asp_report); `--output-directory`, `--output-filename` (stereo_geom, gallery); `--max-filesize-mb` (gallery); `--original-cameras`, `--optimized-cameras`, `--map-crs`, `--upper-magnitude-percentile` (csm_camera_plot). Single-word options are untouched. The command recorded on the report's final page is emitted with the new spellings, so it stays re-runnable.
16
+ - **Boolean options are now single switches for the non-default behavior**, in the Unix idiom: where the default is on, only the negative switch exists — `--no-basemap`, `--no-altimetry`, `--no-pc-align`, `--no-geometry` (asp_report), `--no-basemap` (stereo_geom), `--no-trim` (csm_camera_plot), `--no-hillshade` (gallery, replacing the `--hillshade/--no-hillshade` pair) — and where the default is off, only the positive one — `--shared-scales`, `--log-scale-positions`, `--log-scale-angles`, `--add-basemap` (csm_camera_plot). Where you passed `--add_basemap False`, pass `--no-basemap`; the verb-y `plot`/`add` prefixes are dropped from the switch names.
17
+ - **`--bundle_adjust_directory` is now `--bundle-adjust-prefix`**, and accepts either the containing directory (`ba`, the previous behavior) or the same ASP-style output prefix passed to `stereo`/`mapproject` (`ba/run`). A prefix narrows the residuals/log file search to that run's outputs, so several bundle-adjust runs can share a directory. Internally, `ReportConfig.bundle_adjust_directory` is renamed to `bundle_adjust_prefix`.
18
+ - **`csm_camera_plot` options are harmonized with the other CLIs and ASP's `orbit_plot.py`**: `--save_dir` → `--output-directory`, `--fig_fn` → `--output-filename`, `--figsize` → `--figure-size`. The `csm_camera_summary_plot()` Python keyword arguments are unchanged.
19
+ - **Trailing slashes on directory options are stripped centrally** (`--directory`, `--stereo-directory`, `--bundle-adjust-prefix`), closing the last thread of issue [#60](https://github.com/uw-cryo/asp_plot/issues/60); the dg_mosaic `r100` concern from the same issue was resolved earlier by the sensor readers, which treat `*.r100.xml`/`*.r50.xml` as regenerable intermediates.
20
+
21
+ ### Removed
22
+ - **The deprecated `--plot_icesat` alias** (deprecated in favor of `--plot_altimetry` in 1.10.0) is gone; altimetry is on by default and `--no-altimetry` disables it.
23
+
8
24
  ## [2.2.0] - 2026-08-18
9
25
 
10
26
  A correctness release for the CSM camera comparison, and a new diagnostic layer under the match points.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: asp_plot
3
- Version: 2.2.0
3
+ Version: 3.0.0
4
4
  Summary: Package for plotting outputs Ames Stereo Pipeline processing
5
5
  Project-URL: Homepage, https://github.com/uw-cryo/asp_plot
6
6
  Project-URL: Documentation, https://asp-plot.readthedocs.io
@@ -74,7 +74,7 @@ See the [installation guide](https://asp-plot.readthedocs.io/en/latest/installat
74
74
  Generate a PDF report from an ASP processing directory:
75
75
 
76
76
  ```
77
- asp_report --directory ./ --stereo_directory stereo
77
+ asp_report --directory ./ --stereo-directory stereo
78
78
  ```
79
79
 
80
80
  See the [CLI documentation](https://asp-plot.readthedocs.io/en/latest/cli/index.html) for all options and additional tools (`stereo_geom`, `csm_camera_plot`).
@@ -30,7 +30,7 @@ See the [installation guide](https://asp-plot.readthedocs.io/en/latest/installat
30
30
  Generate a PDF report from an ASP processing directory:
31
31
 
32
32
  ```
33
- asp_report --directory ./ --stereo_directory stereo
33
+ asp_report --directory ./ --stereo-directory stereo
34
34
  ```
35
35
 
36
36
  See the [CLI documentation](https://asp-plot.readthedocs.io/en/latest/cli/index.html) for all options and additional tools (`stereo_geom`, `csm_camera_plot`).
@@ -41,7 +41,7 @@ class ReadBundleAdjustFiles:
41
41
  >>> mapproj_gdf = ba_reader.get_mapproj_residuals_gdf()
42
42
  """
43
43
 
44
- def __init__(self, directory, bundle_adjust_directory):
44
+ def __init__(self, directory, bundle_adjust_directory, stem=None):
45
45
  """
46
46
  Initialize the ReadBundleAdjustFiles object.
47
47
 
@@ -51,10 +51,15 @@ class ReadBundleAdjustFiles:
51
51
  Root directory of ASP processing
52
52
  bundle_adjust_directory : str
53
53
  Subdirectory containing bundle adjustment outputs
54
+ stem : str, optional
55
+ Run stem of an ASP output prefix (the ``run`` in ``ba/run``).
56
+ When given, file globs are narrowed to that run's outputs;
57
+ otherwise any run in the directory matches.
54
58
  """
55
59
  self.directory = os.path.expanduser(directory)
56
60
  self.bundle_adjust_directory = bundle_adjust_directory
57
61
  self.full_directory = os.path.join(self.directory, bundle_adjust_directory)
62
+ self.stem_glob = stem if stem else "*"
58
63
 
59
64
  def get_csv_paths(self, geodiff_files=False):
60
65
  """
@@ -86,8 +91,8 @@ class ReadBundleAdjustFiles:
86
91
  will attempt to generate them using the geodiff ASP tool.
87
92
  """
88
93
  filenames = [
89
- "*-initial_residuals_pointmap.csv",
90
- "*-final_residuals_pointmap.csv",
94
+ f"{self.stem_glob}-initial_residuals_pointmap.csv",
95
+ f"{self.stem_glob}-final_residuals_pointmap.csv",
91
96
  ]
92
97
 
93
98
  if geodiff_files:
@@ -371,7 +376,9 @@ class ReadBundleAdjustFiles:
371
376
  the distance between matched interest points in the map-projected
372
377
  images after bundle adjustment.
373
378
  """
374
- path = glob_file(self.full_directory, "*-mapproj_match_offsets.txt")
379
+ path = glob_file(
380
+ self.full_directory, f"{self.stem_glob}-mapproj_match_offsets.txt"
381
+ )
375
382
  if path is None:
376
383
  raise ValueError("\n\nMapProj Residuals TXT file not found.\n\n")
377
384
 
@@ -411,7 +418,9 @@ class ReadBundleAdjustFiles:
411
418
  the bundle adjustment residuals propagate to uncertainty in
412
419
  triangulation. This is an estimate of the final DEM error.
413
420
  """
414
- path = glob_file(self.full_directory, "*-triangulation_uncertainty.txt")
421
+ path = glob_file(
422
+ self.full_directory, f"{self.stem_glob}-triangulation_uncertainty.txt"
423
+ )
415
424
  if path is None:
416
425
  raise ValueError("\n\nTriangulation Uncertainty TXT file not found.\n\n")
417
426
 
@@ -16,7 +16,10 @@ def _reconstruct_command():
16
16
  for param in click_ctx.command.params:
17
17
  val = click_ctx.params.get(param.name)
18
18
  if val is not None and val != param.default:
19
- cmd_parts.append(f"--{param.name} {shlex.quote(str(val))}")
19
+ if param.is_bool_flag:
20
+ cmd_parts.append(param.opts[0])
21
+ else:
22
+ cmd_parts.append(f"{param.opts[0]} {shlex.quote(str(val))}")
20
23
  return " ".join(cmd_parts)
21
24
 
22
25
 
@@ -28,123 +31,116 @@ def _reconstruct_command():
28
31
  help="Required directory of ASP processing with scenes and sub-directories for stereo and optionally bundle adjustment. Default: current directory.",
29
32
  )
30
33
  @click.option(
31
- "--bundle_adjust_directory",
34
+ "--bundle-adjust-prefix",
32
35
  prompt=False,
33
36
  default=None,
34
- help="Optional directory of bundle adjustment files. If expected *residuals_pointmap.csv files are not found in the supplied directory, no bundle adjustment plots will be generated. Default: None.",
37
+ help="Optional bundle adjustment output location, relative to --directory. Accepts either a directory (e.g. 'ba') or an ASP-style output prefix as passed to --bundle-adjust-prefix in stereo/mapproject (e.g. 'ba/run'). If expected *residuals_pointmap.csv files are not found there, no bundle adjustment plots will be generated. Default: None.",
35
38
  )
36
39
  @click.option(
37
- "--stereo_directory",
40
+ "--stereo-directory",
38
41
  prompt=True,
39
42
  default="stereo",
40
43
  help="Required directory of stereo files. Default: stereo.",
41
44
  )
42
45
  @click.option(
43
- "--dem_filename",
46
+ "--dem-filename",
44
47
  prompt=False,
45
48
  default=None,
46
49
  help="Optional DEM filename in the stereo directory. Default: None, which will search for the *-DEM.tif file in the stereo directory. Specify it as the basename with extension, e.g. my-custom-dem-name.tif.",
47
50
  )
48
51
  @click.option(
49
- "--dem_gsd",
52
+ "--dem-gsd",
50
53
  prompt=False,
51
54
  default=None,
52
55
  help="Optional ground sample distance of the DEM. Default: None, which will search for the *-DEM.tif file in the stereo directory. If there is a GSD in the name of the file, specify it here as a float or integer, e.g. 1, 1.5, etc.",
53
56
  )
54
57
  @click.option(
55
- "--map_crs",
58
+ "--map-crs",
56
59
  prompt=False,
57
60
  default=None,
58
61
  help="Projection for altimetry and bundle adjustment plots. As EPSG:XXXX. Default: None, which will use the projection of the ASP DEM, and fall back on EPSG:4326 if not found.",
59
62
  )
60
63
  @click.option(
61
- "--reference_dem",
64
+ "--reference-dem",
62
65
  prompt=False,
63
66
  default=None,
64
67
  help="Optional reference DEM used in ASP processing. No default. If not supplied, the logs will be examined to find it. If not found, no difference plots will be generated.",
65
68
  )
66
69
  @click.option(
67
- "--add_basemap",
68
- prompt=False,
69
- default=True,
70
- help="If True, add a basemaps to the figures, which requires internet connection. Default: True.",
71
- )
72
- @click.option(
73
- "--plot_altimetry",
74
- prompt=False,
75
- default=True,
76
- help="If True, plot altimetry comparisons (ICESat-2 for Earth, LOLA for Moon, MOLA for Mars). For planetary DEMs, requires --altimetry_csv. Default: True.",
70
+ "--no-basemap",
71
+ is_flag=True,
72
+ default=False,
73
+ help="Skip the figure basemaps (basemaps are added by default, which requires an internet connection).",
77
74
  )
78
75
  @click.option(
79
- "--plot_icesat",
80
- prompt=False,
81
- default=None,
82
- help="Deprecated: use --plot_altimetry instead. Kept for backward compatibility.",
76
+ "--no-altimetry",
77
+ is_flag=True,
78
+ default=False,
79
+ help="Skip the altimetry comparisons (plotted by default: ICESat-2 for Earth, LOLA for Moon, MOLA for Mars; planetary DEMs require --altimetry-csv).",
83
80
  )
84
81
  @click.option(
85
- "--altimetry_csv",
82
+ "--altimetry-csv",
86
83
  prompt=False,
87
84
  default=None,
88
85
  type=click.Path(exists=True),
89
86
  help="Path to a LOLA/MOLA *_topo_csv.csv file from the ODE GDS API. Required for planetary altimetry plots. Obtain via: request_planetary_altimetry --dem <dem> --email <email>, then download and unzip the result.",
90
87
  )
91
88
  @click.option(
92
- "--pc_align",
93
- prompt=False,
94
- default=True,
95
- help="If True and --plot_altimetry is True, run pc_align against the reference altimetry (ICESat-2 for Earth, MOLA for Mars, LOLA for Moon) and append the alignment-report pages. Disabled automatically when --plot_altimetry / --plot_icesat is False. Default: True.",
89
+ "--no-pc-align",
90
+ is_flag=True,
91
+ default=False,
92
+ help="Skip the pc_align step (run by default against the reference altimetry -- ICESat-2 for Earth, MOLA for Mars, LOLA for Moon -- appending the alignment-report pages; skipped automatically with --no-altimetry).",
96
93
  )
97
94
  @click.option(
98
- "--plot_geometry",
99
- prompt=False,
100
- default=True,
101
- help="If True, plot the stereo geometry. Default: True.",
95
+ "--no-geometry",
96
+ is_flag=True,
97
+ default=False,
98
+ help="Skip the stereo geometry plots (plotted by default).",
102
99
  )
103
100
  @click.option(
104
- "--subset_km",
101
+ "--subset-km",
105
102
  prompt=False,
106
103
  default=1.0,
107
104
  help="Size in km of the subset to plot for the detailed hillshade. Default: 1 km.",
108
105
  )
109
106
  @click.option(
110
- "--atl06sr_time_range",
107
+ "--atl06sr-time-range",
111
108
  prompt=False,
112
109
  default="all",
113
110
  help='Time range for ICESat-2 ATL06-SR data requests. "all" for all available data (mission start to present), or "START,END" for a custom range (e.g. "2020-01-01,2024-12-31"), or "auto" for scene metadata +/- 1 year. Default: all.',
114
111
  )
115
112
  @click.option(
116
- "--reuse_selections",
113
+ "--reuse-selections",
117
114
  prompt=False,
118
115
  default=None,
119
116
  type=click.Path(exists=True),
120
117
  help="Path to a *_figure_selections.yml written by a previous run. When supplied, replays that run's ICESat-2 points (parquet), profile track, best/worst segments, and detailed-hillshade clip boxes so figures are directly comparable across re-processing runs. Default: None.",
121
118
  )
122
119
  @click.option(
123
- "--report_filename",
120
+ "--report-filename",
124
121
  prompt=False,
125
122
  default=None,
126
123
  help="PDF report filename or path. A bare filename (e.g. 'report.pdf') is saved in the stereo directory. A path (e.g. 'reports/report.pdf' or '/tmp/report.pdf') is used as-is. Default: auto-generated from directory name.",
127
124
  )
128
125
  @click.option(
129
- "--report_title",
126
+ "--report-title",
130
127
  prompt=False,
131
128
  default=None,
132
129
  help="Title for the report. Default: Directory name of ASP processing.",
133
130
  )
134
131
  def main(
135
132
  directory,
136
- bundle_adjust_directory,
133
+ bundle_adjust_prefix,
137
134
  stereo_directory,
138
135
  dem_filename,
139
136
  dem_gsd,
140
137
  map_crs,
141
138
  reference_dem,
142
- add_basemap,
143
- plot_altimetry,
144
- plot_icesat,
139
+ no_basemap,
140
+ no_altimetry,
145
141
  altimetry_csv,
146
- pc_align,
147
- plot_geometry,
142
+ no_pc_align,
143
+ no_geometry,
148
144
  subset_km,
149
145
  atl06sr_time_range,
150
146
  reuse_selections,
@@ -160,18 +156,17 @@ def main(
160
156
  """
161
157
  config = ReportConfig(
162
158
  directory=directory,
163
- bundle_adjust_directory=bundle_adjust_directory,
159
+ bundle_adjust_prefix=bundle_adjust_prefix,
164
160
  stereo_directory=stereo_directory,
165
161
  dem_filename=dem_filename,
166
162
  dem_gsd=dem_gsd,
167
163
  map_crs=map_crs,
168
164
  reference_dem=reference_dem,
169
- add_basemap=add_basemap,
170
- plot_altimetry=plot_altimetry,
171
- plot_icesat=plot_icesat,
165
+ add_basemap=not no_basemap,
166
+ plot_altimetry=not no_altimetry,
172
167
  altimetry_csv=altimetry_csv,
173
- pc_align=pc_align,
174
- plot_geometry=plot_geometry,
168
+ pc_align=not no_pc_align,
169
+ plot_geometry=not no_geometry,
175
170
  subset_km=subset_km,
176
171
  atl06sr_time_range=atl06sr_time_range,
177
172
  reuse_selections=reuse_selections,
@@ -6,19 +6,19 @@ from asp_plot.csm_camera import csm_camera_summary_plot
6
6
 
7
7
  @click.command()
8
8
  @click.option(
9
- "--original_cameras",
9
+ "--original-cameras",
10
10
  prompt=True,
11
11
  default="",
12
12
  help="Original camera files, supplied as comma separated list 'path/to/original_camera_1,path/to/original_camera_2'. No default. Must be supplied.",
13
13
  )
14
14
  @click.option(
15
- "--optimized_cameras",
15
+ "--optimized-cameras",
16
16
  prompt=True,
17
17
  default="",
18
18
  help="Optimized camera files, supplied as comma separated list 'path/to/optimized_camera_1,path/to/optimized_camera_2'. No default. Must be supplied.",
19
19
  )
20
20
  @click.option(
21
- "--map_crs",
21
+ "--map-crs",
22
22
  prompt=False,
23
23
  default=None,
24
24
  help="UTM EPSG code for map projection. As EPSG:XXXX. If not supplied, the map will be plotted in original camera coordinates of EPSG:4978 (ECEF).",
@@ -30,73 +30,73 @@ from asp_plot.csm_camera import csm_camera_summary_plot
30
30
  help="Optional short title to append to figure output. Default: None.",
31
31
  )
32
32
  @click.option(
33
- "--trim",
34
- prompt=False,
35
- default=True,
36
- help="Trim the beginning and end of the positions plotted to the first and last camera image lines. Default: True.",
33
+ "--no-trim",
34
+ is_flag=True,
35
+ default=False,
36
+ help="Do not trim the plotted positions to the first and last camera image lines (trimmed by default).",
37
37
  )
38
38
  @click.option(
39
- "--shared_scales",
40
- prompt=False,
39
+ "--shared-scales",
40
+ is_flag=True,
41
41
  default=False,
42
- help="If True, the position and angle difference scales are shared between each camera. Default: False.",
42
+ help="Share the position and angle difference scales between the cameras.",
43
43
  )
44
44
  @click.option(
45
- "--log_scale_positions",
46
- prompt=False,
45
+ "--log-scale-positions",
46
+ is_flag=True,
47
47
  default=False,
48
- help="If True, the position difference scales are log scaled. Default: False.",
48
+ help="Log-scale the position difference plots.",
49
49
  )
50
50
  @click.option(
51
- "--log_scale_angles",
52
- prompt=False,
51
+ "--log-scale-angles",
52
+ is_flag=True,
53
53
  default=False,
54
- help="If True, the angle difference scales are log scaled. Default: False.",
54
+ help="Log-scale the angle difference plots.",
55
55
  )
56
56
  @click.option(
57
- "--upper_magnitude_percentile",
57
+ "--upper-magnitude-percentile",
58
58
  prompt=False,
59
59
  default=95,
60
60
  help="Percentile to use for the upper limit of the mapview colorbars. Default: 95.",
61
61
  )
62
62
  @click.option(
63
- "--figsize",
63
+ "--figure-size",
64
64
  prompt=False,
65
65
  default="20,15",
66
66
  help="Figure size as width,height. Default: 20,15.",
67
67
  callback=lambda ctx, param, value: tuple(map(int, value.split(","))),
68
68
  )
69
69
  @click.option(
70
- "--save_dir",
70
+ "--output-directory",
71
71
  prompt=False,
72
72
  default=None,
73
73
  help="Directory to save the figure. Default: None, which does not save the figure.",
74
74
  )
75
75
  @click.option(
76
- "--fig_fn",
76
+ "--output-filename",
77
77
  prompt=False,
78
78
  default="csm_camera_summary_plot.png",
79
79
  help="Figure filename. Default: csm_camera_summary_plot.png.",
80
80
  )
81
81
  @click.option(
82
- "--add_basemap",
83
- prompt=False,
82
+ "--add-basemap",
83
+ is_flag=True,
84
84
  default=False,
85
- help="If True, add a contextily basemap to the figure, which requires internet connection. Default: False.",
85
+ help="Add a contextily basemap to the figure, which requires an internet connection.",
86
86
  )
87
87
  def main(
88
88
  original_cameras,
89
89
  optimized_cameras,
90
90
  map_crs,
91
91
  title,
92
- trim,
92
+ no_trim,
93
93
  shared_scales,
94
94
  log_scale_positions,
95
95
  log_scale_angles,
96
96
  upper_magnitude_percentile,
97
- figsize,
98
- save_dir,
99
- fig_fn,
97
+ figure_size,
98
+ output_directory,
99
+ output_filename,
100
100
  add_basemap,
101
101
  ):
102
102
  """
@@ -127,14 +127,14 @@ def main(
127
127
  cam2_list,
128
128
  map_crs.split(":")[-1] if map_crs else None,
129
129
  title=title,
130
- trim=trim,
130
+ trim=not no_trim,
131
131
  shared_scales=shared_scales,
132
132
  log_scale_positions=log_scale_positions,
133
133
  log_scale_angles=log_scale_angles,
134
134
  upper_magnitude_percentile=upper_magnitude_percentile,
135
- figsize=figsize,
136
- save_dir=save_dir,
137
- fig_fn=fig_fn,
135
+ figsize=figure_size,
136
+ save_dir=output_directory,
137
+ fig_fn=output_filename,
138
138
  add_basemap=add_basemap,
139
139
  **ctx_kwargs,
140
140
  )
@@ -20,9 +20,10 @@ from asp_plot.gallery import GalleryPlotter
20
20
  )
21
21
  @click.argument("files", nargs=-1, type=click.Path(exists=True))
22
22
  @click.option(
23
- "--hillshade/--no-hillshade",
24
- default=True,
25
- help="Draw a gray hillshade underlay beneath each DEM. Default: True.",
23
+ "--no-hillshade",
24
+ is_flag=True,
25
+ default=False,
26
+ help="Skip the gray hillshade underlay drawn beneath each DEM by default.",
26
27
  )
27
28
  @click.option(
28
29
  "--cmap",
@@ -37,7 +38,7 @@ from asp_plot.gallery import GalleryPlotter
37
38
  help="Downsample factor for reads, or 'auto' to size thumbnails automatically. Default: auto.",
38
39
  )
39
40
  @click.option(
40
- "--max_filesize_mb",
41
+ "--max-filesize-mb",
41
42
  prompt=False,
42
43
  default=10.0,
43
44
  type=float,
@@ -50,13 +51,13 @@ from asp_plot.gallery import GalleryPlotter
50
51
  help="Figure suptitle. Default: none.",
51
52
  )
52
53
  @click.option(
53
- "--output_directory",
54
+ "--output-directory",
54
55
  prompt=False,
55
56
  default=None,
56
57
  help="Directory to save the output plot. Default: Input directory.",
57
58
  )
58
59
  @click.option(
59
- "--output_filename",
60
+ "--output-filename",
60
61
  prompt=False,
61
62
  default=None,
62
63
  help="Filename for the output plot. Default: Directory name with _gallery.png suffix.",
@@ -65,7 +66,7 @@ def main(
65
66
  directory,
66
67
  pattern,
67
68
  files,
68
- hillshade,
69
+ no_hillshade,
69
70
  cmap,
70
71
  downsample,
71
72
  max_filesize_mb,
@@ -110,7 +111,7 @@ def main(
110
111
  os.makedirs(output_directory, exist_ok=True)
111
112
 
112
113
  plotter.plot_gallery(
113
- hillshade=hillshade,
114
+ hillshade=not no_hillshade,
114
115
  cmap=cmap,
115
116
  max_filesize_mb=max_filesize_mb,
116
117
  save_dir=output_directory,
@@ -36,13 +36,13 @@ def main(dem, email, channels):
36
36
  1. Run this command with your DEM and email
37
37
  2. Wait for the email notification (may take minutes to hours)
38
38
  3. Download and unzip the result
39
- 4. Pass the *_topo_csv.csv to asp_plot via --altimetry_csv
39
+ 4. Pass the *_topo_csv.csv to asp_plot via --altimetry-csv
40
40
 
41
41
  \b
42
42
  Example:
43
43
  request_planetary_altimetry --dem stereo/output-DEM.tif --email user@example.com
44
44
  # ... wait for email, download and unzip ...
45
- asp_report --directory ./ --altimetry_csv /path/to/*_topo_csv.csv
45
+ asp_report --directory ./ --altimetry-csv /path/to/*_topo_csv.csv
46
46
  """
47
47
  from asp_plot.altimetry import GDS_BASE_URL, gds_query_async
48
48
 
@@ -53,7 +53,7 @@ def main(dem, email, channels):
53
53
  click.echo(
54
54
  "\nThis DEM is an Earth DEM. ICESat-2 altimetry is fetched "
55
55
  "automatically by asp_plot — no separate request needed.\n"
56
- "Run: asp_report --directory <dir> --plot_altimetry True\n"
56
+ "Run: asp_report --directory <dir> (altimetry plots are on by default)\n"
57
57
  )
58
58
  sys.exit(0)
59
59
 
@@ -124,7 +124,7 @@ def main(dem, email, channels):
124
124
  click.echo(
125
125
  "\nYou will receive an email when the data is ready. "
126
126
  "Download and unzip the result, then pass the *_topo_csv.csv to asp_plot:\n"
127
- " asp_report --directory <dir> --altimetry_csv <*_topo_csv.csv>\n"
127
+ " asp_report --directory <dir> --altimetry-csv <*_topo_csv.csv>\n"
128
128
  )
129
129
 
130
130
 
@@ -17,19 +17,19 @@ from asp_plot.stereo_geometry import StereoGeometryPlotter
17
17
  ),
18
18
  )
19
19
  @click.option(
20
- "--add_basemap",
21
- prompt=False,
22
- default=True,
23
- help="If True, add a basemap to the figures, which requires internet connection. Default: True.",
20
+ "--no-basemap",
21
+ is_flag=True,
22
+ default=False,
23
+ help="Skip the figure basemaps (basemaps are added by default, which requires an internet connection).",
24
24
  )
25
25
  @click.option(
26
- "--output_directory",
26
+ "--output-directory",
27
27
  prompt=False,
28
28
  default=None,
29
29
  help="Directory to save the output plot. Default: Input directory.",
30
30
  )
31
31
  @click.option(
32
- "--output_filename",
32
+ "--output-filename",
33
33
  prompt=False,
34
34
  default=None,
35
35
  help=(
@@ -41,7 +41,7 @@ from asp_plot.stereo_geometry import StereoGeometryPlotter
41
41
  def main(
42
42
  inputs,
43
43
  directory,
44
- add_basemap,
44
+ no_basemap,
45
45
  output_directory,
46
46
  output_filename,
47
47
  ):
@@ -72,14 +72,14 @@ def main(
72
72
  # Positional INPUTS take precedence; otherwise fall back to --directory.
73
73
  if inputs:
74
74
  inputs = [os.path.expanduser(i) for i in inputs]
75
- plotter = StereoGeometryPlotter(inputs=inputs, add_basemap=add_basemap)
75
+ plotter = StereoGeometryPlotter(inputs=inputs, add_basemap=not no_basemap)
76
76
  source_desc = " ".join(inputs)
77
77
  # Base directory for default output paths (resolved by the parser).
78
78
  base_directory = plotter.directory
79
79
  else:
80
80
  base_directory = os.path.expanduser(directory or "./")
81
81
  plotter = StereoGeometryPlotter(
82
- directory=base_directory, add_basemap=add_basemap
82
+ directory=base_directory, add_basemap=not no_basemap
83
83
  )
84
84
  source_desc = base_directory
85
85
 
@@ -54,7 +54,11 @@ class ProcessingParameters:
54
54
  """
55
55
 
56
56
  def __init__(
57
- self, processing_directory, bundle_adjust_directory=None, stereo_directory=None
57
+ self,
58
+ processing_directory,
59
+ bundle_adjust_directory=None,
60
+ stereo_directory=None,
61
+ bundle_adjust_stem=None,
58
62
  ):
59
63
  """
60
64
  Initialize the ProcessingParameters object.
@@ -67,6 +71,10 @@ class ProcessingParameters:
67
71
  Subdirectory containing bundle adjustment outputs, default is None
68
72
  stereo_directory : str, optional
69
73
  Subdirectory containing stereo outputs, default is None
74
+ bundle_adjust_stem : str, optional
75
+ Run stem of an ASP output prefix (the ``run`` in ``ba/run``).
76
+ When given, the bundle adjustment log glob is narrowed to that
77
+ run; otherwise any run in the directory matches.
70
78
 
71
79
  Notes
72
80
  -----
@@ -92,8 +100,13 @@ class ProcessingParameters:
92
100
  # Locate log files. A missing directory (None) means that stage was not
93
101
  # requested -- not an error -- so we guard on it rather than catching a
94
102
  # blanket exception that would also hide real I/O problems.
103
+ ba_log_pattern = (
104
+ f"{bundle_adjust_stem}-log-bundle_adjust*.txt"
105
+ if bundle_adjust_stem
106
+ else "*log-bundle_adjust*.txt"
107
+ )
95
108
  self.bundle_adjust_log = (
96
- glob_file(self.full_ba_directory, "*log-bundle_adjust*.txt")
109
+ glob_file(self.full_ba_directory, ba_log_pattern)
97
110
  if self.full_ba_directory
98
111
  else None
99
112
  )
@@ -41,7 +41,12 @@ from asp_plot.selections import (
41
41
  )
42
42
  from asp_plot.stereo import StereoPlotter
43
43
  from asp_plot.stereo_geometry import StereoGeometryPlotter, camera_files_from_stereo_run
44
- from asp_plot.utils import Raster, detect_planetary_body, get_acquisition_dates
44
+ from asp_plot.utils import (
45
+ Raster,
46
+ detect_planetary_body,
47
+ get_acquisition_dates,
48
+ resolve_directory_or_prefix,
49
+ )
45
50
 
46
51
 
47
52
  @dataclass
@@ -53,7 +58,7 @@ class ReportConfig:
53
58
  """
54
59
 
55
60
  directory: str = "./"
56
- bundle_adjust_directory: Optional[str] = None
61
+ bundle_adjust_prefix: Optional[str] = None
57
62
  stereo_directory: str = "stereo"
58
63
  dem_filename: Optional[str] = None
59
64
  dem_gsd: Optional[float] = None
@@ -61,7 +66,6 @@ class ReportConfig:
61
66
  reference_dem: Optional[str] = None
62
67
  add_basemap: bool = True
63
68
  plot_altimetry: bool = True
64
- plot_icesat: Optional[object] = None
65
69
  altimetry_csv: Optional[str] = None
66
70
  pc_align: bool = True
67
71
  plot_geometry: bool = True
@@ -74,6 +78,15 @@ class ReportConfig:
74
78
  # CLI builds this from the Click context; non-CLI callers may leave it None.
75
79
  report_command: Optional[str] = None
76
80
 
81
+ def __post_init__(self):
82
+ # Tolerate trailing slashes on directory-like inputs (issue #60) so
83
+ # path joins and basename-derived titles behave the same either way.
84
+ for attr in ("directory", "stereo_directory", "bundle_adjust_prefix"):
85
+ value = getattr(self, attr)
86
+ if value:
87
+ stripped = value.rstrip("/\\")
88
+ setattr(self, attr, stripped or value)
89
+
77
90
 
78
91
  @dataclass
79
92
  class ReportContext:
@@ -93,6 +106,9 @@ class ReportContext:
93
106
  stereo_plotter: StereoPlotter
94
107
  asp_dem: Optional[str]
95
108
  plot_altimetry: bool
109
+ # --bundle-adjust-prefix resolved into a subdirectory and optional run stem
110
+ ba_directory: Optional[str] = None
111
+ ba_stem: Optional[str] = None
96
112
  report_metadata: Optional[ReportMetadata] = None
97
113
  reuse_clip_windows: Optional[list] = None
98
114
  reuse_clip_windows_crs: Optional[str] = None
@@ -204,7 +220,9 @@ def _build_bundle_adjust(ctx: ReportContext) -> List[object]:
204
220
  cfg = ctx.config
205
221
  sections: List[object] = []
206
222
  try:
207
- ba_files = ReadBundleAdjustFiles(cfg.directory, cfg.bundle_adjust_directory)
223
+ ba_files = ReadBundleAdjustFiles(
224
+ cfg.directory, ctx.ba_directory, stem=ctx.ba_stem
225
+ )
208
226
  resid_initial_gdf, resid_final_gdf = ba_files.get_initial_final_residuals_gdfs()
209
227
 
210
228
  plotter = PlotBundleAdjustFiles(
@@ -316,7 +334,7 @@ def _build_bundle_adjust(ctx: ReportContext) -> List[object]:
316
334
 
317
335
  except ValueError:
318
336
  print(
319
- f"\n\nNo bundle adjustment files found in directory {os.path.join(cfg.directory, cfg.bundle_adjust_directory):}. If you want bundle adjustment plots, make sure you run the tool and supply the correct directory to asp_report.\n\n"
337
+ f"\n\nNo bundle adjustment files found in directory {os.path.join(cfg.directory, ctx.ba_directory):}. If you want bundle adjustment plots, make sure you run the tool and supply the correct --bundle-adjust-prefix to asp_report.\n\n"
320
338
  )
321
339
  return sections
322
340
 
@@ -400,7 +418,7 @@ def _build_altimetry_earth(ctx: ReportContext, ctx_kwargs_altimetry: dict):
400
418
  asp_dem = ctx.asp_dem
401
419
  sections: List[object] = []
402
420
 
403
- # Parse --atl06sr_time_range into time_range/t0/t1 kwargs
421
+ # Parse --atl06sr-time-range into time_range/t0/t1 kwargs
404
422
  atl06sr_time_range = cfg.atl06sr_time_range
405
423
  atl06sr_time_kwargs = {}
406
424
  if atl06sr_time_range.lower() == "all":
@@ -632,7 +650,7 @@ def _build_altimetry_planetary(ctx: ReportContext, body: str):
632
650
  f" 1. Run: request_planetary_altimetry --dem {asp_dem} --email <your_email>\n"
633
651
  f" 2. Wait for the email with a download link\n"
634
652
  f" 3. Download and unzip the result\n"
635
- f" 4. Re-run asp_report with: --altimetry_csv <path_to_pts_csv.csv>\n"
653
+ f" 4. Re-run asp_report with: --altimetry-csv <path_to_pts_csv.csv>\n"
636
654
  f"\nSkipping {instrument} altimetry plots.\n"
637
655
  f"{'='*60}\n"
638
656
  )
@@ -751,7 +769,7 @@ REPORT_SECTIONS: List[ReportSpec] = [
751
769
  ReportSpec("match_points", lambda ctx: True, _build_match_points),
752
770
  ReportSpec(
753
771
  "bundle_adjust",
754
- lambda ctx: bool(ctx.config.bundle_adjust_directory),
772
+ lambda ctx: bool(ctx.config.bundle_adjust_prefix),
755
773
  _build_bundle_adjust,
756
774
  ),
757
775
  ReportSpec("disparity", lambda ctx: True, _build_disparity),
@@ -766,29 +784,13 @@ REPORT_SECTIONS: List[ReportSpec] = [
766
784
  # ---------------------------------------------------------------------------
767
785
 
768
786
 
769
- def _resolve_plot_altimetry(config: ReportConfig) -> bool:
770
- """Resolve the deprecated --plot_icesat alias against --plot_altimetry."""
771
- plot_altimetry = config.plot_altimetry
772
- if config.plot_icesat is not None:
773
- import warnings
774
-
775
- warnings.warn(
776
- "--plot_icesat is deprecated. Use --plot_altimetry instead.",
777
- DeprecationWarning,
778
- stacklevel=1,
779
- )
780
- plot_icesat = config.plot_icesat
781
- # Convert Click string 'True'/'False' to bool
782
- if isinstance(plot_icesat, str):
783
- plot_icesat = plot_icesat.lower() not in ("false", "0", "no")
784
- plot_altimetry = plot_icesat
785
- return plot_altimetry
786
-
787
-
788
787
  def _setup_context(config: ReportConfig) -> ReportContext:
789
788
  """Resolve paths, projection, DEM metadata, and shared plotters."""
790
789
  directory = os.path.expanduser(config.directory)
791
790
  config.directory = directory
791
+ ba_directory, ba_stem = resolve_directory_or_prefix(
792
+ directory, config.bundle_adjust_prefix
793
+ )
792
794
  if config.reference_dem:
793
795
  config.reference_dem = os.path.expanduser(config.reference_dem)
794
796
  if config.altimetry_csv:
@@ -858,7 +860,7 @@ def _setup_context(config: ReportConfig) -> ReportContext:
858
860
  print(f"\nUsing map projection from DEM: {map_crs}\n")
859
861
  except Exception as e:
860
862
  print(
861
- f"\nError getting projection from DEM: {e}. Using default projection EPSG:4326. If you want a different projection, use the --map_crs flag.\n"
863
+ f"\nError getting projection from DEM: {e}. Using default projection EPSG:4326. If you want a different projection, use the --map-crs flag.\n"
862
864
  )
863
865
  map_crs = "EPSG:4326"
864
866
 
@@ -875,10 +877,8 @@ def _setup_context(config: ReportConfig) -> ReportContext:
875
877
  (float(valid.min()), float(valid.max())) if valid.size else (0, 0)
876
878
  )
877
879
  acq_extra_dirs = [os.path.join(directory, config.stereo_directory)]
878
- if config.bundle_adjust_directory:
879
- acq_extra_dirs.append(
880
- os.path.join(directory, config.bundle_adjust_directory)
881
- )
880
+ if ba_directory:
881
+ acq_extra_dirs.append(os.path.join(directory, ba_directory))
882
882
  acquisition_dates = get_acquisition_dates(
883
883
  directory, extra_dirs=acq_extra_dirs
884
884
  )
@@ -915,7 +915,9 @@ def _setup_context(config: ReportConfig) -> ReportContext:
915
915
  ctx_kwargs=ctx_kwargs,
916
916
  stereo_plotter=stereo_plotter,
917
917
  asp_dem=asp_dem,
918
- plot_altimetry=_resolve_plot_altimetry(config),
918
+ plot_altimetry=config.plot_altimetry,
919
+ ba_directory=ba_directory,
920
+ ba_stem=ba_stem,
919
921
  report_metadata=report_metadata,
920
922
  reuse_clip_windows=reuse_clip_windows,
921
923
  reuse_clip_windows_crs=reuse_clip_windows_crs,
@@ -983,7 +985,8 @@ def run_report(config: ReportConfig) -> str:
983
985
  # Compile report
984
986
  processing_parameters = ProcessingParameters(
985
987
  processing_directory=config.directory,
986
- bundle_adjust_directory=config.bundle_adjust_directory,
988
+ bundle_adjust_directory=ctx.ba_directory,
989
+ bundle_adjust_stem=ctx.ba_stem,
987
990
  stereo_directory=config.stereo_directory,
988
991
  )
989
992
  processing_parameters_dict = processing_parameters.from_log_files()
@@ -84,6 +84,42 @@ def glob_file(directory, *patterns, all_files=False, recursive=False, quiet=Fals
84
84
  return None
85
85
 
86
86
 
87
+ def resolve_directory_or_prefix(base_directory, value):
88
+ """
89
+ Resolve a CLI value that may name a directory or an ASP output prefix.
90
+
91
+ ASP tools take output *prefixes* (``--bundle-adjust-prefix ba/run``), while
92
+ asp_plot historically took the containing *directory* (``ba``). This
93
+ accepts both: an existing directory is returned as-is with no stem, while
94
+ a prefix is split into its directory and run stem so file globs can be
95
+ narrowed to that run (``run-final_residuals_pointmap.csv``).
96
+
97
+ Parameters
98
+ ----------
99
+ base_directory : str
100
+ Directory the value is relative to (the report's ``--directory``).
101
+ value : str or None
102
+ Directory or prefix, relative to ``base_directory``. Trailing slashes
103
+ are stripped.
104
+
105
+ Returns
106
+ -------
107
+ tuple of (str or None, str or None)
108
+ ``(subdirectory, stem)``; ``stem`` is None when ``value`` names a
109
+ directory. A separator-free value that is not an existing directory is
110
+ kept as a directory so downstream "not found" errors point at it.
111
+ """
112
+ if value is None:
113
+ return None, None
114
+ value = value.rstrip("/\\")
115
+ if os.path.isdir(os.path.join(os.path.expanduser(base_directory), value)):
116
+ return value, None
117
+ subdirectory, stem = os.path.split(value)
118
+ if subdirectory and stem:
119
+ return subdirectory, stem
120
+ return value, None
121
+
122
+
87
123
  def find_pair_directories(directory):
88
124
  """
89
125
  Find the per-pair subdirectories of an ASP multi-view stereo run.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "asp_plot"
7
- version = "2.2.0"
7
+ version = "3.0.0"
8
8
  license = {text = "BSD-3-Clause"}
9
9
  authors = [
10
10
  { name="Ben Purinton", email="purinton@uw.edu" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes