asp-plot 1.18.1__tar.gz → 2.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 (56) hide show
  1. asp_plot-2.0.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  2. asp_plot-2.0.0/.github/ISSUE_TEMPLATE/problem-report.yml +44 -0
  3. asp_plot-2.0.0/.github/workflows/claude-code-review.yml +39 -0
  4. asp_plot-2.0.0/.github/workflows/claude.yml +49 -0
  5. {asp_plot-1.18.1 → asp_plot-2.0.0}/.gitignore +3 -3
  6. {asp_plot-1.18.1 → asp_plot-2.0.0}/.readthedocs.yaml +1 -0
  7. asp_plot-2.0.0/AGENTS.md +79 -0
  8. asp_plot-1.18.1/AGENTS.md → asp_plot-2.0.0/ARCHITECTURE.md +41 -152
  9. {asp_plot-1.18.1 → asp_plot-2.0.0}/CHANGELOG.md +36 -0
  10. asp_plot-2.0.0/CLAUDE.md +1 -0
  11. {asp_plot-1.18.1 → asp_plot-2.0.0}/PKG-INFO +5 -5
  12. {asp_plot-1.18.1 → asp_plot-2.0.0}/README.md +3 -3
  13. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/altimetry_plots.py +13 -7
  14. asp_plot-1.18.1/asp_plot/cli/asp_plot.py → asp_plot-2.0.0/asp_plot/cli/asp_report.py +2 -2
  15. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/cli/request_planetary_altimetry.py +3 -3
  16. asp_plot-2.0.0/asp_plot/cli/stereo_geom.py +104 -0
  17. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/report.py +13 -5
  18. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/report_captions.py +2 -2
  19. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/report_pipeline.py +60 -41
  20. asp_plot-2.0.0/asp_plot/scenes.py +296 -0
  21. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/selections.py +1 -1
  22. asp_plot-2.0.0/asp_plot/sensors.py +1166 -0
  23. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/stereo.py +261 -99
  24. asp_plot-2.0.0/asp_plot/stereo_geometry.py +757 -0
  25. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/stereopair_metadata_parser.py +117 -16
  26. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/utils.py +203 -47
  27. {asp_plot-1.18.1 → asp_plot-2.0.0}/conda-forge-recipe/meta.yaml +2 -2
  28. {asp_plot-1.18.1 → asp_plot-2.0.0}/pyproject.toml +5 -3
  29. asp_plot-1.18.1/asp_plot/cli/stereo_geom.py +0 -73
  30. asp_plot-1.18.1/asp_plot/scenes.py +0 -193
  31. asp_plot-1.18.1/asp_plot/sensors.py +0 -593
  32. asp_plot-1.18.1/asp_plot/stereo_geometry.py +0 -471
  33. {asp_plot-1.18.1 → asp_plot-2.0.0}/.flake8 +0 -0
  34. {asp_plot-1.18.1 → asp_plot-2.0.0}/.github/workflows/release.yml +0 -0
  35. {asp_plot-1.18.1 → asp_plot-2.0.0}/.github/workflows/run-tests.yml +0 -0
  36. {asp_plot-1.18.1 → asp_plot-2.0.0}/.pre-commit-config.yaml +0 -0
  37. {asp_plot-1.18.1 → asp_plot-2.0.0}/LICENSE +0 -0
  38. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/__init__.py +0 -0
  39. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/alignment.py +0 -0
  40. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/altimetry.py +0 -0
  41. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/altimetry_source.py +0 -0
  42. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/asp_log.py +0 -0
  43. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/bodies.py +0 -0
  44. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/bundle_adjust.py +0 -0
  45. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/cli/__init__.py +0 -0
  46. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/cli/csm_camera_plot.py +0 -0
  47. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/cli/gallery.py +0 -0
  48. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/csm_analysis.py +0 -0
  49. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/csm_camera.py +0 -0
  50. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/csm_io.py +0 -0
  51. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/gallery.py +0 -0
  52. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/icesat2_source.py +0 -0
  53. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/mapproject.py +0 -0
  54. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/planetary_source.py +0 -0
  55. {asp_plot-1.18.1 → asp_plot-2.0.0}/asp_plot/processing_parameters.py +0 -0
  56. {asp_plot-1.18.1 → asp_plot-2.0.0}/environment.yml +0 -0
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Questions about ASP itself
4
+ url: https://groups.google.com/g/ames-stereo-pipeline-support
5
+ about: The upstream ASP support forum, read by the ASP developers.
@@ -0,0 +1,44 @@
1
+ name: Problem report
2
+ description: Something in asp_plot is broken, wrong, or unclear
3
+ body:
4
+ - type: input
5
+ id: location
6
+ attributes:
7
+ label: Where did you hit this?
8
+ description: A docs page URL, a CLI command, an example notebook, or a module/function.
9
+ placeholder: e.g. `asp_report --directory ...`, or the stereo_geom example notebook
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: what-happened
14
+ attributes:
15
+ label: What happened?
16
+ description: What you did, what you expected, and what happened instead. Paste the full command and error output as text where possible.
17
+ validations:
18
+ required: true
19
+ - type: input
20
+ id: version
21
+ attributes:
22
+ label: asp_plot version
23
+ description: From `python -c "import asp_plot; print(asp_plot.__version__)"`.
24
+ placeholder: e.g. 1.1.0
25
+ validations:
26
+ required: false
27
+ - type: dropdown
28
+ id: install
29
+ attributes:
30
+ label: How did you install asp_plot?
31
+ options:
32
+ - conda-forge
33
+ - pip (PyPI)
34
+ - From source (git)
35
+ - Just reading the docs
36
+ validations:
37
+ required: false
38
+ - type: textarea
39
+ id: extra
40
+ attributes:
41
+ label: Anything else?
42
+ description: Screenshots, log files, links, ASP version.
43
+ validations:
44
+ required: false
@@ -0,0 +1,39 @@
1
+ name: Claude Code Review
2
+
3
+ # Runs once when a draft PR is marked ready for review.
4
+ # For further reviews, mention @claude in a PR comment (see claude.yml).
5
+ on:
6
+ pull_request:
7
+ types: [ready_for_review]
8
+
9
+ jobs:
10
+ claude-review:
11
+ # Optional: Filter by PR author
12
+ # if: |
13
+ # github.event.pull_request.user.login == 'external-contributor' ||
14
+ # github.event.pull_request.user.login == 'new-developer' ||
15
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
16
+
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ pull-requests: read
21
+ issues: read
22
+ id-token: write
23
+
24
+ steps:
25
+ - name: Checkout repository
26
+ uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 1
29
+
30
+ - name: Run Claude Code Review
31
+ id: claude-review
32
+ uses: anthropics/claude-code-action@v1
33
+ with:
34
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
35
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
36
+ plugins: 'code-review@claude-code-plugins'
37
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
38
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
39
+ # or https://code.claude.com/docs/en/cli-reference for available options
@@ -0,0 +1,49 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ pull-requests: read
24
+ issues: read
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Run Claude Code
34
+ id: claude
35
+ uses: anthropics/claude-code-action@v1
36
+ with:
37
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
+
39
+ # This is an optional setting that allows Claude to read CI results on PRs
40
+ additional_permissions: |
41
+ actions: read
42
+
43
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
44
+ # prompt: 'Update the pull request description to include a summary of changes.'
45
+
46
+ # Optional: Add claude_args to customize behavior and configuration
47
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
+ # or https://code.claude.com/docs/en/cli-reference for available options
49
+ # claude_args: '--allowed-tools Bash(gh pr *)'
@@ -138,14 +138,14 @@ dmypy.json
138
138
  scratch/
139
139
  notebooks/**/*.parquet
140
140
  notebooks/**/*.csv
141
- CLAUDE*
141
+ CLAUDE/
142
142
  .claude/**
143
143
  scripts/
144
144
  /*.parquet
145
145
  reports/regenerate_reports.sh
146
- # Figure-selections sidecars written next to each report by `asp_plot`. These are
146
+ # Figure-selections sidecars written next to each report by `asp_report`. These are
147
147
  # regeneration byproducts that hardcode absolute local paths (dem_filename,
148
148
  # parquet_cache), so they are not committed; a sanitized example is documented in
149
- # docs/cli/asp_plot.md instead.
149
+ # docs/cli/asp_report.md instead.
150
150
  reports/*_figure_selections.yml
151
151
  *.csv
@@ -8,6 +8,7 @@ build:
8
8
  pre_build:
9
9
  - mkdir -p docs/examples/notebooks
10
10
  - cp notebooks/WorldView/*.ipynb docs/examples/notebooks/
11
+ - cp notebooks/Pleiades/*.ipynb docs/examples/notebooks/
11
12
  - cp notebooks/ASTER/*.ipynb docs/examples/notebooks/
12
13
  - cp notebooks/LRO_NAC/*.ipynb docs/examples/notebooks/
13
14
  - cp notebooks/Mars_MGS/*.ipynb docs/examples/notebooks/
@@ -0,0 +1,79 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents (Claude Code and others) and new contributors. This file holds only what you can't infer from the code: commands, gotchas, external dependencies, and process. For the module-by-module codebase map and design rationale, read [ARCHITECTURE.md](ARCHITECTURE.md) on demand — and keep both files in sync with the code.
4
+
5
+ ## Project Overview
6
+
7
+ `asp_plot` is a Python package for visualizing output from the NASA Ames Stereo Pipeline (ASP): diagnostic plots and comprehensive PDF reports covering stereo DEM results, bundle adjustment, CSM camera models, stereo acquisition geometry, DEM galleries, and altimetry comparison/alignment (ICESat-2 for Earth, LOLA for Moon, MOLA for Mars). Handles terrestrial and planetary sensors. Requires Python >= 3.11; published on PyPI and conda-forge. The version lives in `pyproject.toml` (exposed as `asp_plot.__version__`).
8
+
9
+ ## Development Commands
10
+
11
+ ```bash
12
+ # Environment (installs the package editable with dev+docs extras)
13
+ conda env create -f environment.yml
14
+ conda activate asp_plot
15
+ pre-commit install # REQUIRED for development
16
+
17
+ # Tests
18
+ pytest # all tests; add -s to see print output
19
+ pytest tests/test_stereo.py::test_name # one file / one test
20
+
21
+ # Lint/format (pre-commit runs these on commit; manually:)
22
+ pre-commit run --all-files # black + flake8 + isort (profile: black)
23
+
24
+ # Rebuild after changing CLI tools or entry points
25
+ pip install -e ".[dev]"
26
+ ```
27
+
28
+ Flake8 config is in `.flake8` (extends ignore: E203, E701); pre-commit further ignores E501, E722, E207.
29
+
30
+ To build the docs locally (Sphinx + MyST; hosted on ReadTheDocs, auto-built on push to `main`):
31
+
32
+ ```bash
33
+ # One-time: copy notebooks, reports, and figures for local preview
34
+ mkdir -p docs/examples/notebooks && cp notebooks/**/*.ipynb docs/examples/notebooks/
35
+ mkdir -p docs/_static/reports && cp reports/*.pdf docs/_static/reports/
36
+ mkdir -p docs/_extra/examples/figures && cp notebooks/figures/* docs/_extra/examples/figures/
37
+
38
+ sphinx-autobuild docs docs/_build/html --open-browser # or sphinx-build for a one-off
39
+ ```
40
+
41
+ ## Gotchas
42
+
43
+ - **ASP tools must be on PATH** for the workflows that wrap them: `stereo`/`parallel_stereo`, `bundle_adjust`, `point2dem`, `pc_align`, `geodiff`, `dg_mosaic` (called via `run_subprocess_command()`). **Append** the ASP bin directory to PATH rather than prepending: the ASP release bundles its own `python`, which would shadow your environment's interpreter and break imports.
44
+ - **Internet is required** for basemaps (contextily/Esri tiles), ICESat-2 requests (SlideRule), and ESA WorldCover sampling (public AWS S3 COGs). Tests must not depend on the network — basemap fetches are stubbed (#151).
45
+ - **Mars altimetry needs the `*_pts_csv.csv`** (with `PLANET_RAD`), never the `*_topo_csv.csv`: MOLA TOPOGRAPHY is referenced to the oblate areoid while ASP DEMs use the spherical IAU datum — a latitude-dependent offset up to ~10 km that pc_align cannot remove. The loader rejects the topo file with an explanatory error.
46
+ - **ASP's `mapproject` writes no log file**; its command is reconstructed from output GeoTIFF metadata (`mapproject.py`), not parsed from logs like the other tools.
47
+ - **Attribution vs sensor naming is deliberate**: copyright/attribution names the rights-holder (`detect_satellite_attribution()` → `"Vantor"` or `"Airbus DS"`), while readers in `sensors.py` are named for the satellite family (WorldView, Pleiades). Don't reconcile them into one name (#137).
48
+ - **Package vs CLI naming is a deliberate split** (v2.0.0, #165): the *package* is `asp_plot` (`import asp_plot`, `pip install asp-plot`), the report *command* is `asp_report` (`asp_plot/cli/asp_report.py`). There is no `asp_plot` console script and no alias — don't "fix" either name to match the other. The other four CLIs (`stereo_geom`, `csm_camera_plot`, `request_planetary_altimetry`, `gallery`) are unprefixed and unchanged.
49
+ - **Airbus DIMAP quaternions are scalar-first** (`Q0` = scalar); they are reordered to the scalar-last `q1..q4` layout the roll/pitch/yaw code expects in `PleiadesMetadata.getAtt_df()`. Don't "fix" the reorder.
50
+ - **ASP multiview triangulation of mapprojected images needs `ISISROOT`** (observed with ASP 3.8.0-alpha, non-ISIS `-t pleiades` session): the joint triangulation aborts with an uncatchable `Isis::IException` (`$ISISROOT/IsisPreferences was not found`) that surfaces as a generic "Failed to run"/killed job. Workaround: `export ISISROOT=<ASP install root>` (the release bundles `IsisPreferences` there). Pair runs and raw-image multiview runs are unaffected; full write-up in a PR #155 comment.
51
+
52
+ ## External Data Sources
53
+
54
+ - **ICESat-2 ATL06-SR** (Earth): requested through the SlideRule API; results cached as parquet next to the report.
55
+ - **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.
56
+
57
+ ## Testing
58
+
59
+ Tests are in `tests/` with sample data in `tests/test_data/` (synthetic rasters, XML camera files, BA CSVs, ICESat-2 parquet, pc_align outputs, jitter data). Most modules have a matching `tests/test_<module>.py`; `test_imports.py` verifies everything imports. Some fixture derivatives (e.g. match-point CSVs next to `.match` files) are gitignored and regenerate during test runs — untracked files appearing under `tests/test_data/` after `pytest` are expected, don't commit them. Example notebooks in `notebooks/` are organized by sensor (WorldView, Pleiades, ASTER, LRO_NAC, Mars_MGS, Mars_MRO) — see ARCHITECTURE.md for what each demonstrates.
60
+
61
+ ## Versioning and Release Process
62
+
63
+ Follow semantic versioning. To release:
64
+ 1. Update version in `pyproject.toml`
65
+ 2. Update `CHANGELOG.md` with version and date
66
+ 3. Merge to `main`
67
+
68
+ The rest is automated: `release.yml` detects the version bump, creates a GitHub Release + tag, and publishes to PyPI via OIDC trusted publishing; conda-forge's autotick bot then opens a feedstock PR. PyPI trusted publishing and the conda-forge feedstock are already configured (reference recipe in `conda-forge-recipe/meta.yaml`).
69
+
70
+ ## Common File Patterns
71
+
72
+ ASP output files follow specific naming patterns (find them with the `glob_file()` utility):
73
+ - DEMs: `*-DEM.tif` or `*_dem.tif`
74
+ - Disparity: `*-F.tif`
75
+ - Match files: `*.match`
76
+ - Bundle adjust residuals: `*-initial_residuals_pointmap.csv`, `*-final_residuals_pointmap.csv`
77
+ - Log files: `*log-bundle_adjust*.txt`, `*log-stereo*.txt`, `*log-point2dem*.txt`
78
+
79
+ **Multi-view (>2 scene) runs** keep only the joint products (`*-PC.tif`, `*-DEM.tif`, `*-IntersectionErr.tif`) at the stereo-directory top level; the per-pair intermediates live one level down in `<prefix>-pairN/` (`N-L_sub.tif`, `N-R_sub.tif`, `N-D_sub.tif`, the `.match` file, `N-align-{L,R}.txt`, and an `N-stereo.default` config copy naming that pair's images). Discover them with `find_pair_directories()` rather than globbing — code that assumes the flat pair layout silently degrades to "missing files" placeholders (#160).
@@ -1,88 +1,8 @@
1
- # AGENTS.md
1
+ # Architecture
2
2
 
3
- This file provides guidance to AI coding agents (Claude Code and others) when working with code in this repository. It is the git-tracked onboarding reference for the package's architecture; keep it in sync with the code.
3
+ A module-by-module map of `asp_plot` and its design patterns, for contributors and AI coding agents. Read this on demand when deciding where a change belongs, or why a module is shaped the way it is. Day-to-day commands, gotchas, and process live in [AGENTS.md](AGENTS.md). Keep this file in sync with the code when you add or restructure a module.
4
4
 
5
- ## Project Overview
6
-
7
- `asp_plot` (current version: 1.18.0) is a Python package for visualizing output from the NASA Ames Stereo Pipeline (ASP). It processes stereo satellite imagery results (both terrestrial and planetary), generates diagnostic plots, and creates comprehensive PDF reports similar to those from commercial SfM software like Agisoft Metashape. Requires Python >= 3.11. Published on PyPI and conda-forge.
8
-
9
- The package supports:
10
- - Stereo DEM processing visualization
11
- - Bundle adjustment analysis
12
- - CSM camera model comparisons (for tools like bundle_adjust and jitter_solve)
13
- - Altimetry comparisons: ICESat-2 ATL06-SR (Earth), LOLA (Moon), MOLA (Mars)
14
- - Stereo geometry visualization from satellite XML metadata (Earth-based sensors)
15
- - Non-terrestrial/planetary processing (tested with LRO NAC, Mars MRO CTX/HiRISE, Mars MGS MOC, and ASTER)
16
- - Gallery plotting of many DEMs as a grid of thumbnails sharing one color scale
17
-
18
- ## Development Commands
19
-
20
- ### Environment Setup
21
- ```bash
22
- # Create and activate conda environment (installs package with dev+docs extras)
23
- conda env create -f environment.yml
24
- conda activate asp_plot
25
-
26
- # Install pre-commit hooks (REQUIRED for development)
27
- pre-commit install
28
- ```
29
-
30
- ### Testing
31
- ```bash
32
- # Run all tests
33
- pytest
34
-
35
- # Run tests with output visible (useful for debugging)
36
- pytest -s
37
-
38
- # Run a specific test file
39
- pytest tests/test_stereo.py
40
-
41
- # Run a specific test function
42
- pytest tests/test_stereo.py::test_function_name
43
- ```
44
-
45
- ### Linting and Formatting
46
- Pre-commit hooks automatically run on commit. To run manually:
47
- ```bash
48
- # Run all pre-commit hooks
49
- pre-commit run --all-files
50
-
51
- # Format code with black
52
- black asp_plot/ tests/
53
-
54
- # Lint with flake8
55
- flake8 asp_plot/ tests/
56
-
57
- # Sort imports with isort (profile: black)
58
- isort --profile black asp_plot/ tests/
59
- ```
60
-
61
- Flake8 configuration is in `.flake8` (extends ignore: E203, E701). Pre-commit further extends ignore to: E501, E722, E203, E207.
62
-
63
- ### Rebuilding the Package
64
- When making changes to CLI tools or entry points:
65
- ```bash
66
- pip install -e ".[dev]"
67
- ```
68
-
69
- ### Building Docs Locally
70
- ```bash
71
- # One-time: copy notebooks, reports, and figures for local preview
72
- mkdir -p docs/examples/notebooks && cp notebooks/**/*.ipynb docs/examples/notebooks/
73
- mkdir -p docs/_static/reports && cp reports/*.pdf docs/_static/reports/
74
- mkdir -p docs/_extra/examples/figures && cp notebooks/figures/* docs/_extra/examples/figures/
75
-
76
- # Build once
77
- sphinx-build docs docs/_build/html
78
-
79
- # Or live-reloading preview
80
- sphinx-autobuild docs docs/_build/html --open-browser
81
- ```
82
-
83
- ## Code Architecture
84
-
85
- ### Core Module Structure
5
+ ## Core Module Structure
86
6
 
87
7
  **`__init__.py`** exports `__version__` via `importlib.metadata` (reads from `pyproject.toml` at install time, falls back to `"unknown"`).
88
8
 
@@ -104,16 +24,17 @@ The package is organized by functionality, with each module focused on a specifi
104
24
  - `get_bounds()`: Returns bounds using `self.ds.bounds` (rasterio), with optional lat/lon transform
105
25
  - `_mask_nodata()`: Private helper that handles both undeclared nodata (inferred from pixel data) and invalid values (inf/nan) — needed because rasterio's `masked=True` alone doesn't catch undeclared nodata in ASP outputs
106
26
  - `_load_and_diff_rasters_da()`: Private static method returning xarray DataArray for raster differencing; used internally by both `load_and_diff_rasters()` (converts to numpy) and `compute_difference()` (uses `.rio.to_raster()` for saving)
107
- - `Plotter`: Base class for all plotting classes with common matplotlib setup. The scaffold added in #129 centralizes the boilerplate that was copy-pasted across plot methods: `save(fig, save_dir, fig_fn, ...)` (the `tight_layout()` + conditional-save tail), `plot_missing(ax, message)` (the "required files are missing" placeholder), and `plot_array(..., copyright=True)` which draws the Vantor/WorldView overlay from `self.is_vantor` so the check no longer threads through every call site
27
+ - `Plotter`: Base class for all plotting classes with common matplotlib setup. The scaffold added in #129 centralizes the boilerplate that was copy-pasted across plot methods: `save(fig, save_dir, fig_fn, ...)` (the `tight_layout()` + conditional-save tail), `plot_missing(ax, message)` (the "required files are missing" placeholder), and `plot_array(..., copyright=True)` which draws the rights-holder overlay from `self.attribution` (a string like `"Vantor"` or `"Airbus DS"`, or None) so the check no longer threads through every call site
108
28
  - `ColorBar`: Handles colorbar creation and formatting. `get_norm(clim=...)` takes an explicit clim so callers (e.g. `plot_geodataframe`) keep clim local instead of mutating `self.cb.clim`
109
- - File utilities: `glob_file()`, `save_figure()`, `show_existing_figure()`
29
+ - File utilities: `glob_file()` (with `quiet=` for lookups whose absence is an expected layout), `save_figure()`, `show_existing_figure()`
30
+ - Multi-view layout helpers (issue #160): `find_pair_directories()` finds the `<prefix>-pairN/` subdirectories an ASP multi-view run keeps its per-pair products in; `get_pair_images()` recovers a pair's left/right image names from its `N-stereo.default` config copy (the `N-info.txt` lists *all* images of the joint run, so it is deliberately not used); `describe_pair()` builds the `"Pair N: <left> ↔ <right>"` label used on per-pair figures
110
31
  - Coordinate utilities: `get_utm_epsg()` for determining UTM EPSG from lon/lat, `get_planetary_bounds()` for DEM bounds in planetocentric 0-360 lon/lat
111
32
  - Planetary body detection: `detect_planetary_body(dem_fn)` returns `"earth"`, `"moon"`, or `"mars"` by inspecting CRS WKT DATUM/ELLIPSOID fields
112
33
  - Subprocess utilities: `run_subprocess_command()`
113
- - Vantor/copyright utilities (an **attribution** concern — named for the rights-holder — kept deliberately distinct from sensor/reader **identity**, the WorldView-named abstraction in `sensors.py`; #137):
114
- - `detect_vantor_satellite(directory)`: True when an XML camera file's `SATID` matches `VANTOR_SATID_PREFIXES` any DigitalGlobe→Maxar→Vantor-owned satellite, i.e. the WorldView family (`WV*`, incl. Legion `WVLG`), GeoEye (`GE*`), QuickBird (`QB*`), IKONOS (`IK*`) not just WorldView. Gates the Vantor` overlay via `Plotter.is_vantor`
115
- - `add_copyright_overlay(ax)`: Adds "© Vantor {year}" text overlay to bottom-right of matplotlib axes
116
- - Scene metadata: `get_acquisition_dates(directory, extra_dirs=None)` reads `FIRSTLINETIME` from WorldView/Maxar XMLs and parses the capture timestamp from `AST_L1A_...` file/directory names. Returns a sorted, deduplicated list of `"YYYY-MM-DD HH:MM:SS UTC"` strings; empty if nothing is found. Used by the CLI to populate `ReportMetadata.acquisition_dates`.
34
+ - Copyright/attribution utilities (an **attribution** concern — named for the rights-holder — kept deliberately distinct from sensor/reader **identity** in `sensors.py`; #137):
35
+ - `detect_satellite_attribution(directory)`: returns the rights-holder name or None — `"Vantor"` when an XML camera file's `SATID` matches `VANTOR_SATID_PREFIXES` (the DigitalGlobe→Maxar→Vantor heritage: WorldView `WV*` incl. Legion `WVLG`, GeoEye `GE*`, QuickBird `QB*`, IKONOS `IK*`), `"Airbus DS"` when an Airbus DIMAP document (root tag `Dimap_Document`; Pléiades/Pléiades Neo/SPOT) is present. Gates the copyright overlay via `Plotter.attribution`. `detect_vantor_satellite(directory)` remains as a bool wrapper for backward compatibility
36
+ - `add_copyright_overlay(ax, attribution="Vantor")`: Adds "© {attribution} {year}" text overlay to bottom-right of matplotlib axes
37
+ - Scene metadata: `get_acquisition_dates(directory, extra_dirs=None)` reads `FIRSTLINETIME` from WorldView/Maxar XMLs, falls back to the DIMAP refined-model start time (`Refined_Model/Time/Time_Range/START`, via `_dimap_acquisition_datetime()`) for Airbus products, and parses the capture timestamp from `AST_L1A_...` file/directory names. Returns a sorted, deduplicated list of `"YYYY-MM-DD HH:MM:SS UTC"` strings; empty if nothing is found. Used by the CLI to populate `ReportMetadata.acquisition_dates`.
117
38
 
118
39
  **`selections.py`** - Reproducible "figure selections" for run-to-run comparison (issue #121)
119
40
  - `FigureSelections` dataclass mirroring the YAML sidecar schema (`detailed_hillshade` clips + `icesat2` track/segments/parquet/request); `to_dict()`/`from_dict()`
@@ -121,7 +42,7 @@ The package is organized by functionality, with each module focused on a specifi
121
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
122
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
123
44
  - Deliberately imports nothing from `report.py` / `fpdf`, so it is safe to use from notebooks
124
- - The `asp_plot` 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
125
46
 
126
47
  **`report.py`** - PDF report generation using fpdf2
127
48
  - `ReportSection`: Dataclass representing a report figure (title, image path, caption)
@@ -135,11 +56,12 @@ The package is organized by functionality, with each module focused on a specifi
135
56
  - Page order: title + DEM summary → Processing Parameters → diagnostic figures → (if `--pc_align` ran) alignment report page + aligned-DEM figures.
136
57
  - `report.py` is fed declaratively by `report_pipeline.py`; it was **not** rewritten in #128.
137
58
 
138
- **`report_pipeline.py`** - Declarative report pipeline behind the `asp_plot` CLI (issue #128)
59
+ **`report_pipeline.py`** - Declarative report pipeline behind the `asp_report` CLI (issue #128)
139
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)
140
61
  - `run_report(config)`: importable/callable from notebooks and tests with no Click context; returns the written PDF path
141
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
142
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
+ - `_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)
143
65
 
144
66
  **`report_captions.py`** - Caption + description text for the report sections (issue #128)
145
67
  - All section captions and the Earth/planetary alignment descriptions as module constants / small builder functions, moved out of the CLI as a data module (verified byte-identical to the pre-refactor strings via AST comparison)
@@ -160,16 +82,17 @@ The package is organized by functionality, with each module focused on a specifi
160
82
  - `find_mapproject_commands(directories, stereo_command=None)`: scans dirs (processing root, BA dir, stereo dir) for all `*.tif`/`*.tiff` and keeps those carrying the tag signature — **identity is decided by the file's own metadata, never by filename**, so there is no naming-convention dependency (reading a GeoTIFF header is cheap; the `NotGeoreferencedWarning` from raw non-georef inputs is silenced). Dedupes by the reconstructed command string (identical scene reached via two dirs collapses; distinct left/right both show). When `stereo_command` is given, a discovered output is kept only if its filename appears in that command — this scopes the result to the run being reported, so a non-mapprojected run sharing a parent dir with mapprojected scenes (the `stereo/` + `stereo_no_mapproj/` layout) does **not** spuriously list a mapproject step. It's a whole-token basename membership test (the output basename must equal one of the stereo command's argument basenames — not a raw substring, so `run.tif` can't match `prun.tif`), not positional parsing. `ProcessingParameters.get_mapproject_commands(stereo_command)` passes the parsed stereo command; `report.py` renders the results under "Mapproject Command(s)" on the Processing Parameters page (via the module-level `_render_command_block` helper, shared with the bundle_adjust/stereo/point2dem commands)
161
83
 
162
84
  **`sensors.py`** - Sensor-specific scene metadata readers (issue #25)
163
- - `SensorMetadata` ABC defining the reader interface (`detect` + `get_scene_dicts`) and the sensor-agnostic scene-dict schema, mirroring the `bodies.py` registry pattern so adding ASTER/HiRISE/etc. is a new subclass with no change to the geometry code
164
- - `WorldViewMetadata(SensorMetadata)`: the WorldView/Maxar XML logic (file discovery, `dg_mosaic` tiling, per-scene extraction, ephemeris/attitude/footprint) moved verbatim out of the parser. Named after the satellite family rather than the (twice-renamed: DigitalGlobe → Maxar → Vantor) company; the same format also covers GeoEye-1/QuickBird/IKONOS
165
- - `SENSORS` registry + `sensor_for_directory()` detection
85
+ - `SensorMetadata` ABC defining the reader interface (`detect` + `detect_files` + `get_scene_dicts`) and the sensor-agnostic scene-dict schema, mirroring the `bodies.py` registry pattern so adding ASTER/HiRISE/etc. is a new subclass with no change to the geometry code
86
+ - `WorldViewMetadata(SensorMetadata)`: the WorldView/Maxar XML logic (file discovery, `dg_mosaic` tiling, per-scene extraction, ephemeris/attitude/footprint) moved verbatim out of the parser. Named after the satellite family rather than the (twice-renamed: DigitalGlobe → Maxar → Vantor) company; the same format also covers GeoEye-1/QuickBird/IKONOS. Constructible from either a `directory=` (recursive discovery) or an explicit `image_list=` (already-resolved XML paths); non-camera files (`README.XML`, ortho) are filtered out either way
87
+ - `PleiadesMetadata(SensorMetadata)`: Airbus Pléiades / Pléiades Neo DIMAP v2 reader. One scene per `DIM_*.XML` product (no tiling/mosaicking); `RPC_*.XML` sidecars share the DIMAP root tag but are filtered out by `METADATA_SUBPROFILE != PRODUCT` (cheap `iterparse` check). Mean view/sun angles and GSD are averaged over the nine `Located_Geometric_Values` blocks; `meansatel` = 90° − mean incidence. Ephemeris `Point_List` carries per-point `TIME` values (no fixed interval like WorldView). Airbus quaternions are **scalar-first** (`Q0`) and are reordered to the scalar-last `q1..q4` layout the roll/pitch/yaw code expects. DIMAP has no ephemeris/attitude covariance and no scan direction/TDI: `cov_*` columns are NaN and `scandir`/`tdi` are None (consumers treat these as "not provided")
88
+ - `SENSORS` registry + two detection entry points: `sensor_for_directory()` (scans a directory) and `sensor_for_inputs()` (accepts a mixed list of files/dirs/globs). Directory selection is **two-pass** — every sensor gets a shallow (top-level) `detect()` first, then a recursive pass — so a sensor whose metadata sits at the top level wins over one matching only inside a nested delivery, regardless of registry order; within a pass, order matters (`PleiadesMetadata` before the any-XML-matching `WorldViewMetadata`). `sensor_for_inputs()` routes through `resolve_xml_inputs()`, which expands directories (recursively), globs, and plain paths into a deduped, sorted XML list — this is what lets `stereo_geom *.XML` / explicit files / a delivery dir all work without a fixed structure
166
89
 
167
90
  **`stereopair_metadata_parser.py`** - `StereopairMetadataParser` class
168
- - Now a **sensor-agnostic orchestrator** (issue #25): detects a reader via `sensor_for_directory()`, delegates scene discovery/extraction to it, and keeps only the pair-level geometry
91
+ - Now a **sensor-agnostic orchestrator** (issue #25): detects a reader via `sensor_for_directory()` (from `directory=`) or `sensor_for_inputs()` (from a mixed `inputs=` list of files/dirs/globs), delegates scene discovery/extraction to it, and keeps only the pair-level geometry
169
92
  - Computes stereo geometry metrics: convergence angle, base-to-height ratio, bisector elevation, asymmetry
170
93
  - Provides spatial utilities: `get_pair_utm_epsg()`, `get_intersection_bounds()`, `get_scene_bounds()`
171
94
  - `get_catid_dicts()` returns dictionaries per catalog ID with ephemeris, attitude, and geometry data (sourced from the sensor reader)
172
- - Currently supports pairs; N-scene support is planned
95
+ - **N-scene aware** (issue #73): `get_pair_dict()` is the exact-two-scene entry point (raises otherwise); `get_pair_dicts()` returns one pair dict per N-choose-2 combination so >2 scenes can be assessed pairwise. `get_pair_map_projection()` falls back to the footprint union when a pair does not overlap, and `get_pair_intersection()` tolerates a `None` intersection (non-overlapping pairs report `intersection_area = None`)
173
96
 
174
97
  **`stereo.py`** - `StereoPlotter` class (inherits from `Plotter`)
175
98
  - Visualizes ASP stereo processing results
@@ -178,7 +101,8 @@ The package is organized by functionality, with each module focused on a specifi
178
101
  - Supports both map-projected and raw (non-georeferenced) imagery
179
102
  - Detects map-projection status via `Raster.transform` check
180
103
  - For non-mapprojected scenes: match points are overlaid on images using alignment transform matrices (`run-align-{L,R}.txt` loaded via `np.loadtxt`), and disparity plots use pixel-unit scalebar instead of GSD-based
181
- - Detects Vantor (WorldView) satellite via `is_vantor` attribute; adds copyright overlay to optical imagery in `plot_match_points()` and `plot_detailed_hillshade()`
104
+ - **Multi-view aware** (issue #160): `StereoFiles` resolves each `<prefix>-pairN/` subdirectory into a `PairStereoFiles` (field names mirror the top-level attributes so the per-figure helpers consume either, duck-typed); `plot_match_points()` / `plot_disparity()` render one figure per pair (`<stem>_pairN.png`) and return the saved filename list — `[fig_fn]` for a standard run — mirroring the `stereo_geom_plot()` N-scene contract
105
+ - Detects the imagery rights-holder via the `attribution` attribute (`detect_satellite_attribution`); adds the copyright overlay to optical imagery in `plot_match_points()` and `plot_detailed_hillshade()`
182
106
  - `plot_detailed_hillshade()` auto-selects three subset clips from intersection-error variance (low/medium/high) via `_auto_hillshade_clip_offsets()`. Accepts `clip_windows` (DEM-CRS bboxes) + `clip_windows_crs` to pin/replay clips for run-to-run comparison (issue #121); records the boxes it drew on `self.detailed_hillshade_clips`. Out-of-bounds pinned boxes warn and fall back to auto.
183
107
  - Key methods: `plot_dem_results()`, `plot_disparity()`, `plot_match_points()`, `plot_detailed_hillshade()`
184
108
 
@@ -251,16 +175,18 @@ The package is organized by functionality, with each module focused on a specifi
251
175
  **`stereo_geometry.py`** - `StereoGeometryPlotter` class
252
176
  - **Composes** a `StereopairMetadataParser` via `self.parser` rather than subclassing it (issue #25); imports `estim_satellite_orientation` from `csm_io` directly
253
177
  - Visualizes stereo acquisition geometry from XML metadata
254
- - `dg_geom_plot()`: Creates skyplot (satellite viewing angles) and map view (footprints)
255
- - `satellite_position_orientation_plot()`: Creates 3x2 figure showing position covariance, roll/pitch/yaw orientation, and attitude covariance for each scene
178
+ - `camera_files_from_stereo_run()`: recovers the camera metadata files named in a stereo run's command (parsed from the newest `*log-stereo*.txt` via `AspLog`, so a rerun into the same directory wins) so the report's geometry section covers only the run's scenes, not every camera file in the directory; returns None (→ directory fallback) when fewer than two cameras resolve (CSM `.json` cameras, unresolvable paths)
179
+ - `stereo_geom_plot()`: Creates skyplot (satellite viewing angles) and map view (footprints)
180
+ - `satellite_position_orientation_plot()`: Creates a 3-row × N-column figure (one column per scene) showing position covariance, roll/pitch/yaw orientation, and attitude covariance. Sensors without covariance data (e.g. Pléiades DIMAP) get plain position markers and an annotated "not provided" panel; scene labels omit scan/TDI when the sensor has none
256
181
 
257
182
  **`scenes.py`** - `ScenePlotter` class (inherits from `Plotter`)
258
183
  - Plots individual input scenes (satellite/spacecraft images)
259
184
  - Works with any sensor (terrestrial or planetary)
260
185
  - Automatically detects and indicates whether scenes are map-projected or raw
261
186
  - Displays filenames rather than sensor-specific metadata
262
- - Detects Vantor (WorldView) satellite via `is_vantor` attribute; adds copyright overlay to scene images in `plot_scenes()`
263
- - Key method: `plot_scenes()` (formerly `plot_orthos()` prior to v1.2.0)
187
+ - Detects the imagery rights-holder via the `attribution` attribute (`detect_satellite_attribution`); adds the copyright overlay to scene images in `plot_scenes()`
188
+ - Key method: `plot_scenes()` (formerly `plot_orthos()` prior to v1.2.0); returns the saved filename list
189
+ - **Multi-view aware** (issue #160): when `<prefix>-pairN/` subdirectories are present, `SceneFiles` resolves per-pair `PairSceneFiles` from them (pairs win over any stale top-level sub-sampled scenes, matching `StereoFiles`) and `plot_scenes()` renders one figure per pair ("Left (reference)" panel labeling); the "missing files" placeholder remains the fallback
264
190
  - Used in comprehensive reports to show source imagery
265
191
 
266
192
  **`gallery.py`** - `GalleryPlotter` class (inherits from `Plotter`)
@@ -271,13 +197,13 @@ The package is organized by functionality, with each module focused on a specifi
271
197
  - Per-panel titles use the full filename, auto-shrunk to fit the panel by *measuring* rendered text width with an Agg renderer (`_fit_titles()`); falls back to a character-count heuristic (`_fit_title_fontsize()`) if measurement fails
272
198
  - Output detail is read at ~`GALLERY_TARGET_PX` (1200) px/panel and save dpi is matched to it for crisp zoom, then dpi is capped to a pixel budget so the PNG stays under `max_filesize_mb` (default 10) regardless of raster count
273
199
  - Key method: `plot_gallery()`; static helper `_grid_shape(n, aspect)` picks the most square-in-display grid
274
- - Not wired into the main `asp_plot` PDF report (standalone class + `gallery` CLI)
200
+ - Not wired into the main `asp_report` PDF report (standalone class + `gallery` CLI)
275
201
 
276
- ### CLI Tools
202
+ ## CLI Tools
277
203
 
278
204
  All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
279
205
 
280
- **`asp_plot.py`** - Main CLI tool (`asp_plot` command)
206
+ **`asp_report.py`** - Main CLI tool (`asp_report` command)
281
207
  - Generates comprehensive PDF reports of ASP processing
282
208
  - 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
283
209
  - Accepts directories for stereo and bundle_adjust outputs
@@ -299,8 +225,8 @@ All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
299
225
  - `--plot_geometry`: Plot stereo geometry (default: True; disable for planetary missions)
300
226
  - `--subset_km`: Hillshade subset size in km (default: 1.0)
301
227
  - `--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).
302
- - `--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_plot.md`.
303
- - `--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
228
+ - `--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`.
229
+ - `--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`
304
230
  - `--report_title`: Custom report title (default: directory name)
305
231
 
306
232
  **`csm_camera_plot.py`** - CSM camera comparison tool (`csm_camera_plot` command)
@@ -315,12 +241,14 @@ All CLI tools are in `asp_plot/cli/` and use Click for argument parsing:
315
241
  - `--email`: Email for notification when query finishes (required)
316
242
  - `--channels`: LOLA detector channels (Moon only, default `tffff` = channel 1 only)
317
243
  - Saves request metadata as `altimetry_request_info.yml` alongside the DEM
318
- - Workflow: submit query → receive email → download/unzip → pass `*_pts_csv.csv` (Mars) or `*_topo_simple_csv.csv`/`*_pts_csv.csv` (Moon) to `asp_plot --altimetry_csv`
244
+ - 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`
319
245
 
320
246
  **`stereo_geom.py`** - Stereo geometry visualization tool (`stereo_geom` command)
321
247
  - Wrapper for `StereoGeometryPlotter`
322
248
  - Creates skyplot and map view from XML camera files
323
249
  - Supports multiple XMLs with automatic mosaicking
250
+ - Accepts positional `INPUTS` (any mix of XML files, directories, and globs — e.g. `stereo_geom *.XML`); falls back to `--directory` when no positional inputs are given. Both paths funnel into the same plotter (`inputs=` vs `directory=`)
251
+ - **N-scene output** (issue #73): two scenes → one `<name>_stereo_geom.png` (unchanged). More than two → one color-coded overview figure (`_overview.png`) plus one figure per pair (`_<catidA>_<catidB>.png`, or `_pairN.png` if a CATID is missing), each with full pairwise stats. `stereo_geom_plot()` dispatches on scene count and returns the list of saved filenames; `StereoGeometryPlotter` adds `_render_pair`/`_render_overview`. Map views keep the full (autoscaled) extent so all satellite ephemeris tracks are visible (off-nadir scenes have tracks tens to hundreds of km from the footprints), with tracks drawn on top of the semi-transparent footprints; `_add_basemap_safe` falls back to a coarse zoom so the wide-extent basemap fetch doesn't fail on contextily's negative auto-zoom
324
252
 
325
253
  **`gallery.py`** - DEM gallery tool (`gallery` command)
326
254
  - Wrapper for `GalleryPlotter`; lays out many DEMs as a grid sharing one color scale
@@ -346,7 +274,7 @@ docs/
346
274
  conf.py # Sphinx configuration
347
275
  index.md # Landing page with sphinx-design cards
348
276
  installation.md # conda/pip/source install
349
- cli/ # CLI tool docs (asp_plot, stereo_geom, csm_camera_plot, request_planetary_altimetry, gallery)
277
+ cli/ # CLI tool docs (asp_report, stereo_geom, csm_camera_plot, request_planetary_altimetry, gallery)
350
278
  examples/
351
279
  index.md # Notebook gallery with cards by sensor
352
280
  reports.md # PDF reports embedded as iframes
@@ -367,7 +295,7 @@ docs/
367
295
  - **`html_extra_path`** is used to serve `notebooks/figures/` at the correct relative path for notebook `<img src>` references.
368
296
  - **`docs/` is excluded from sdist** in `pyproject.toml` so docs never ship in the PyPI/conda package.
369
297
  - **Changelog** uses `{include} ../CHANGELOG.md` so there's one source of truth.
370
- - **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_plot --report_filename` writes (so the URL on RTD actually resolves).
298
+ - **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).
371
299
 
372
300
  ### Dependencies
373
301
 
@@ -385,17 +313,7 @@ Docs dependencies are in `pyproject.toml` under `[project.optional-dependencies]
385
313
 
386
314
  **External Tool Integration**: The package wraps ASP command-line tools (pc_align, geodiff, dg_mosaic) via `run_subprocess_command()`.
387
315
 
388
- **Report Generation**: The main `asp_plot` CLI uses `compile_report()` to combine individual plots into a single PDF with metadata tables.
389
-
390
- ## ASP Tool Dependencies
391
-
392
- This package is designed to work with outputs from the NASA Ames Stereo Pipeline. Key ASP tools used:
393
- - `stereo` / `parallel_stereo`: Main stereo processing (generates DEMs, disparity maps, match files)
394
- - `bundle_adjust`: Camera optimization (generates residual pointmaps)
395
- - `pc_align`: Point cloud alignment (used for DEM-to-altimetry alignment)
396
- - `geodiff`: Generates difference statistics between DEMs
397
- - `dg_mosaic`: Mosaics multiple XML files for tiled imagery
398
- - `point2dem`: Converts point clouds to DEMs
316
+ **Report Generation**: The main `asp_report` CLI uses `compile_report()` to combine individual plots into a single PDF with metadata tables.
399
317
 
400
318
  ## External Data Sources
401
319
 
@@ -409,41 +327,12 @@ This package is designed to work with outputs from the NASA Ames Stereo Pipeline
409
327
 
410
328
  **Basemaps**: Uses `contextily` to fetch Esri WorldImagery tiles (requires internet). Can be disabled with `--add_basemap False`. Automatically skipped for planetary (non-Earth) DEMs.
411
329
 
412
- ## Testing
413
-
414
- Tests are in `tests/` with sample data in `tests/test_data/`. Most modules have corresponding test files (e.g., `test_stereo.py` for `stereo.py`). Test data includes synthetic rasters, XML camera files, bundle adjustment CSVs, ICESat-2 parquet files, pc_align outputs, and jitter correction data. There is also a `test_imports.py` that verifies all modules can be imported.
330
+ ## Example Notebooks
415
331
 
416
332
  Example notebooks demonstrating modular usage are organized by sensor type:
417
- - `notebooks/WorldView/` - DigitalGlobe/Maxar WorldView examples (Earth-based). Includes a no-mapprojection variant (`worldview_spacenet_atlanta_stereo_without_mapprojection.ipynb`) that runs `parallel_stereo --alignment-method affineepipolar` on the raw `*_corr.tif` images instead of `*_corr_map.tif`. To keep the comparison ROI matched to the mapprojected example, that notebook documents an inverse-RPC trick: open the original `.tif` (which carries RPC metadata; the `wv_correct` `_corr.tif` does not) with GDAL, build a `gdal.Transformer(["METHOD=RPC", "RPC_HEIGHT=<m>"])`, call `TransformPoint(1, lon, lat, h)` (direction `1` = inverse: ground → pixel) on the four corners of the mapprojected notebook's UTM `t_projwin`, take the bounding box, and pass the result as `--left-image-crop-win`. This reuses the existing `ba/` outputs and writes to `stereo_no_mapproj/` so both runs coexist.
333
+ - `notebooks/WorldView/` - WorldView examples (Earth-based). `worldview_spacenet_ucsd_stereo.ipynb` is the classic two-scene pair example; `worldview_utqiagvik_stereo.ipynb` (excluded from the docs build) and `worldview_uyuni_jitter_plots.ipynb` cover comprehensive modular usage and jitter plots; the two `*_scene_selection.ipynb` notebooks pick which scenes to process with `stereo_geom` before any ASP run. The Atlanta example (`worldview_spacenet_atlanta_mvs.ipynb`) is a 3-scene same-pass multi-view run (`parallel_stereo --alignment-method affineepipolar` on the `wv_correct`ed `*_corr.tif` images, nadir13 reference) compared against the ASP-docs-recommended alternative: the three pairwise runs merged with `dem_mosaic` (coverage, DEM difference, ICESat-2 residuals). Two crop-window tricks keep the ROIs matched: the reference crop window in mapprojected/ground coordinates converts to image pixels via GDAL's inverse RPC transform on the *original* `.tif` (the `wv_correct` `_corr.tif` carries no RPC metadata) `gdal.Transformer(["METHOD=RPC", "RPC_HEIGHT=<m>"])` + `TransformPoint(1, lon, lat, h)` on the corners; and the `--left-image-crop-win` for a pair whose left image is *not* the reference is derived from the `bundle_adjust` clean match points that fall inside the reference crop window (no RPCs needed at all).
334
+ - `notebooks/Pleiades/` - Airbus Pléiades Neo tri-stereo example over Marseille (Earth-based): DIMAP stereo-geometry analysis, bundle adjustment of all three scenes, and a 3-image multi-view stereo run (nadir reference, `--left-image-crop-win` derived from the DIMAP `Dataset_Extent` vertices; the ASP docs discourage MVS in favor of pairwise + `dem_mosaic`, which the notebook cites)
418
335
  - `notebooks/ASTER/` - ASTER examples with map-projection and jitter correction (Earth-based)
419
336
  - `notebooks/LRO_NAC/` - Lunar Reconnaissance Orbiter Narrow Angle Camera examples (Lunar)
420
337
  - `notebooks/Mars_MGS/` - Mars Global Surveyor MOC NA examples (Mars). Single notebook covers both mapprojected (`cam2map4stereo.py` first) and non-mapprojected (`--alignment-method affineepipolar`) variants of the M0100115 / E0201461 pair, mirroring the ASTER mapproj/non-mapproj layout
421
338
  - `notebooks/Mars_MRO/` - Mars Reconnaissance Orbiter CTX and HiRISE examples (Mars)
422
-
423
- ## Versioning and Release Process
424
-
425
- Follow semantic versioning (MAJOR.MINOR.PATCH). To release:
426
- 1. Update version in `pyproject.toml`
427
- 2. Update `CHANGELOG.md` with version and date
428
- 3. Merge to `main`
429
-
430
- The rest is automated:
431
- - `release.yml` detects the version bump, creates a GitHub Release + tag, then builds and publishes to PyPI via OIDC trusted publishing (uses `softprops/action-gh-release@v2`)
432
- - conda-forge's `regro-cf-autotick-bot` detects the new PyPI version and opens a feedstock PR automatically
433
-
434
- **One-time setup** (already completed):
435
- - PyPI trusted publisher configured at pypi.org for `release.yml` (environment name left blank)
436
- - conda-forge feedstock created via `staged-recipes` PR (reference recipe in `conda-forge-recipe/meta.yaml`)
437
- - All runtime dependencies declared in `pyproject.toml` (`pip install asp-plot` installs all deps)
438
- - Documentation hosted on ReadTheDocs (auto-builds on push to `main`)
439
-
440
- ## Common File Patterns
441
-
442
- ASP output files follow specific naming patterns:
443
- - DEMs: `*-DEM.tif` or `*_dem.tif`
444
- - Disparity: `*-F.tif` (disparity map)
445
- - Match files: `*.match`
446
- - Bundle adjust residuals: `*-initial_residuals_pointmap.csv`, `*-final_residuals_pointmap.csv`
447
- - Log files: `*log-bundle_adjust*.txt`, `*log-stereo*.txt`, `*log-point2dem*.txt`
448
-
449
- Use `glob_file()` utility to find files matching these patterns.