asp-plot 1.17.0__tar.gz → 1.18.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.
- {asp_plot-1.17.0 → asp_plot-1.18.0}/AGENTS.md +8 -3
- {asp_plot-1.17.0 → asp_plot-1.18.0}/CHANGELOG.md +5 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/PKG-INFO +1 -1
- asp_plot-1.18.0/asp_plot/mapproject.py +248 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/processing_parameters.py +36 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/report.py +47 -12
- {asp_plot-1.17.0 → asp_plot-1.18.0}/pyproject.toml +1 -1
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.flake8 +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.github/workflows/release.yml +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.github/workflows/run-tests.yml +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.gitignore +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.pre-commit-config.yaml +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/.readthedocs.yaml +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/LICENSE +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/README.md +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/__init__.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/alignment.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/altimetry.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/altimetry_plots.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/altimetry_source.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/asp_log.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/bodies.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/bundle_adjust.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/__init__.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/asp_plot.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/csm_camera_plot.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/gallery.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/request_planetary_altimetry.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/cli/stereo_geom.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/csm_analysis.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/csm_camera.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/csm_io.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/gallery.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/icesat2_source.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/planetary_source.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/report_captions.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/report_pipeline.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/scenes.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/selections.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/sensors.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/stereo.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/stereo_geometry.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/stereopair_metadata_parser.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/asp_plot/utils.py +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/conda-forge-recipe/meta.yaml +0 -0
- {asp_plot-1.17.0 → asp_plot-1.18.0}/environment.yml +0 -0
|
@@ -4,7 +4,7 @@ This file provides guidance to AI coding agents (Claude Code and others) when wo
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
`asp_plot` (current version: 1.
|
|
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
8
|
|
|
9
9
|
The package supports:
|
|
10
10
|
- Stereo DEM processing visualization
|
|
@@ -129,7 +129,7 @@ The package is organized by functionality, with each module focused on a specifi
|
|
|
129
129
|
- `ReportMetadata`: Dataclass for DEM metadata displayed on the title page (dimensions, GSD, CRS, nodata %, elevation range, DEM filename, reference DEM, acquisition dates). The "Acquisition Date(s)" row is added to the summary table only when `acquisition_dates` is non-empty.
|
|
130
130
|
- `ASPReportPDF`: FPDF subclass with custom header/footer and page numbers
|
|
131
131
|
- `compile_report()`: Assembles title page, Processing Parameters (page 2), figure sections with captions, and any trailing alignment pages into a PDF. Accepts optional `report_command` string to record the CLI invocation. Figures are automatically scaled to fit page dimensions, preventing overflow/cutoff. `sections` is a mixed list of `ReportSection | AlignmentReportPage`; dispatch is by `isinstance` check.
|
|
132
|
-
- `_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).
|
|
132
|
+
- `_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.
|
|
133
133
|
- `_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.
|
|
134
134
|
- Title page displays: processing date, ASP version (from logs), asp_plot version (from package metadata)
|
|
135
135
|
- Page order: title + DEM summary → Processing Parameters → diagnostic figures → (if `--pc_align` ran) alignment report page + aligned-DEM figures.
|
|
@@ -151,9 +151,14 @@ The package is organized by functionality, with each module focused on a specifi
|
|
|
151
151
|
|
|
152
152
|
**`processing_parameters.py`** - `ProcessingParameters` class
|
|
153
153
|
- Delegates all ASP-log parsing to `asp_log.AspLog` (no more inline string surgery; the bare `except:` clauses became directory guards)
|
|
154
|
-
- Extracts command lines, run times, and processing parameters; `from_log_files()` returns a dict including the `asp_version` key
|
|
154
|
+
- Extracts command lines, run times, and processing parameters; `from_log_files()` returns a dict including the `asp_version` key and a `mapproject` key (list of reconstructed mapproject commands; see `mapproject.py`)
|
|
155
155
|
- Used by report generation to document processing settings
|
|
156
156
|
|
|
157
|
+
**`mapproject.py`** - Reconstruct `mapproject` commands from output GeoTIFF metadata (issue #96)
|
|
158
|
+
- ASP's `mapproject` writes **no log file** (unlike `bundle_adjust`/`stereo`/`point2dem`), so `asp_log.py` has nothing to parse for the mapprojection step. Instead of requiring a new ASP `--log` flag, the command is reconstructed **from the output data alone**: ASP stamps `INPUT_IMAGE_FILE` / `CAMERA_FILE` / `DEM_FILE` / `CAMERA_MODEL_TYPE` / `BUNDLE_ADJUST_PREFIX` into each mapprojected GeoTIFF header, and the raster's own CRS / resolution / bounds give `--t_srs` / `--tr` / `--t_projwin`
|
|
159
|
+
- `reconstruct_mapproject_command(raster_path)`: returns the `mapproject ...` string, or `None` if the ASP mapproject tag signature (`REQUIRED_TAGS` = `INPUT_IMAGE_FILE` + `CAMERA_FILE` + `DEM_FILE`, all read back during reconstruction) is absent. Reuses the `utils.Raster` wrapper (free `NotGeoreferencedWarning` suppression + `get_epsg_code()` with the compound-CRS 2D fallback + `get_gsd()`/bounds) rather than re-opening with raw rasterio. `--t_srs` is `EPSG:XXXX` when an EPSG code exists (incl. the 2D fallback), else the quoted PROJ string (custom planetary/local frames, e.g. jitter stereographic); a malformed CRS returns `None` and logs instead of crashing the report. `_format_coord` renders coordinates to 12 significant figures, positionally — clean for large UTM northings *and* full-precision for degree-scale geographic `--tr`, with no scientific notation or float-repr noise. The reconstruction is faithful but **not byte-for-byte re-runnable** (session is the resolved `-t`, an input `--mpp` shows as the resolved `--tr`, output reads the actual filename) — the report flags this with a one-line note
|
|
160
|
+
- `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
|
+
|
|
157
162
|
**`sensors.py`** - Sensor-specific scene metadata readers (issue #25)
|
|
158
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
|
|
159
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
|
|
@@ -5,6 +5,11 @@ 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
|
+
## [1.18.0] - 2026-06-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Reconstruct `mapproject` commands in the PDF report** ([#96](https://github.com/uw-cryo/asp_plot/issues/96)). ASP's `mapproject` does not write a log file the way `bundle_adjust` / `stereo` / `point2dem` do, so the processing-parameters page never documented the mapprojection step. Rather than depend on a new ASP `--log` flag, the new `asp_plot/mapproject.py` reconstructs the command **from the output GeoTIFF metadata alone**: ASP stamps `INPUT_IMAGE_FILE` / `CAMERA_FILE` / `DEM_FILE` / `CAMERA_MODEL_TYPE` / `BUNDLE_ADJUST_PREFIX` into each mapprojected output, and combined with the raster's CRS (`--t_srs`), resolution (`--tr`), and bounds (`--t_projwin`) that is enough to rebuild the invocation. `ProcessingParameters.from_log_files()` now adds a `mapproject` key (a list — one command per mapprojected input scene found), and the report's "Processing Parameters" page renders them under a "Mapproject Command(s)" heading with a note that the values are reconstructed (resolved session/grid). Works across ASTER, WorldView/RPC, and CSM (jitter) sessions, including custom projections without an EPSG code (falls back to the PROJ string). The command(s) are rendered in ASP pipeline order (between bundle_adjust and stereo), and scoped to the run being reported via the stereo command — a non-mapprojected run that shares a parent directory with mapprojected scenes (the `stereo/` + `stereo_no_mapproj/` layout) does not spuriously list a mapproject step.
|
|
12
|
+
|
|
8
13
|
## [1.17.0] - 2026-06-24
|
|
9
14
|
|
|
10
15
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asp_plot
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.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
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Reconstruct ``mapproject`` commands from ASP-mapprojected GeoTIFF metadata.
|
|
2
|
+
|
|
3
|
+
ASP's ``mapproject`` tool does not write a log file the way ``bundle_adjust`` /
|
|
4
|
+
``stereo`` / ``point2dem`` do, so :mod:`asp_plot.asp_log` (which parses those
|
|
5
|
+
logs) has nothing to read for the mapprojection step (issue #96).
|
|
6
|
+
|
|
7
|
+
Fortunately ``mapproject`` stamps everything we need into the *output* GeoTIFF
|
|
8
|
+
header, so the command can be reconstructed from the output data alone -- no new
|
|
9
|
+
ASP ``--log`` flag required. The fields written by ASP are:
|
|
10
|
+
|
|
11
|
+
- ``INPUT_IMAGE_FILE`` -- the image that was mapprojected
|
|
12
|
+
- ``CAMERA_FILE`` -- the camera model
|
|
13
|
+
- ``DEM_FILE`` -- the DEM used as the projection surface
|
|
14
|
+
- ``CAMERA_MODEL_TYPE`` -- the resolved ``--session-type`` / ``-t``
|
|
15
|
+
- ``BUNDLE_ADJUST_PREFIX`` -- the ``--bundle-adjust-prefix`` (``"NONE"`` if unset)
|
|
16
|
+
|
|
17
|
+
combined with the raster's own CRS (``--t_srs``), resolution (``--tr``), and
|
|
18
|
+
bounds (``--t_projwin``).
|
|
19
|
+
|
|
20
|
+
The reconstruction is faithful but *not* byte-for-byte re-runnable: the session
|
|
21
|
+
type is the resolved value (not necessarily what the user typed), an input
|
|
22
|
+
``--mpp`` shows up resolved as ``--tr``, and the output name is read from the
|
|
23
|
+
file itself. Callers that surface this to users (e.g. the PDF report) should say
|
|
24
|
+
so. See ``reconstruct_mapproject_command`` for the exact argv order.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import glob
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
import numpy as np
|
|
32
|
+
import rasterio
|
|
33
|
+
|
|
34
|
+
from asp_plot.utils import Raster
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# GeoTIFF metadata tags ASP's mapproject writes into every output. This trio is
|
|
39
|
+
# the signature we require to treat a raster as a mapproject output -- and the
|
|
40
|
+
# *only* thing we rely on to identify mapprojected files. We deliberately do not
|
|
41
|
+
# match on filename conventions (``*_map.tif`` etc.): the tags are written by ASP
|
|
42
|
+
# itself, are self-validating, and survive a rename, whereas a filename glob
|
|
43
|
+
# would force an external naming convention and miss anything that deviates. All
|
|
44
|
+
# three are read back during reconstruction, so requiring them here also guards
|
|
45
|
+
# the ``tags[...]`` lookups below.
|
|
46
|
+
REQUIRED_TAGS = ("INPUT_IMAGE_FILE", "CAMERA_FILE", "DEM_FILE")
|
|
47
|
+
|
|
48
|
+
# Raster extensions scanned when discovering mapproject outputs in a directory.
|
|
49
|
+
RASTER_EXTENSIONS = ("*.tif", "*.tiff")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _format_coord(value, sig_figs=12):
|
|
53
|
+
"""Format a projwin/resolution coordinate for the CLI.
|
|
54
|
+
|
|
55
|
+
Renders to ``sig_figs`` significant figures, positionally (never scientific
|
|
56
|
+
notation), trimming trailing zeros. Significant figures -- not fixed decimal
|
|
57
|
+
places -- because the two ends of the range matter: GeoTIFF bounds can be
|
|
58
|
+
large UTM northings (~5.2e6) while a geographic-CRS ``--tr`` can be ~5e-6
|
|
59
|
+
degrees. A fixed ``%.6f`` would coarsen the latter to ~1 significant figure;
|
|
60
|
+
plain shortest-repr would surface float noise on the former
|
|
61
|
+
(``3722294.979`` computed as ``3722294.9790000003``). Twelve significant
|
|
62
|
+
figures is well clear of double-precision noise (~16 figures) yet keeps
|
|
63
|
+
clean grid values clean (``0.481`` -> ``0.481``, ``15.0`` -> ``15``).
|
|
64
|
+
"""
|
|
65
|
+
if not np.isfinite(value) or value == 0:
|
|
66
|
+
return "0" if value == 0 else repr(value)
|
|
67
|
+
decimals = sig_figs - 1 - int(np.floor(np.log10(abs(value))))
|
|
68
|
+
return np.format_float_positional(round(value, decimals), trim="-")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def reconstruct_mapproject_command(raster_path):
|
|
72
|
+
"""Reconstruct the ``mapproject`` command for an ASP-mapprojected GeoTIFF.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
raster_path : str
|
|
77
|
+
Path to a GeoTIFF that may be an ASP ``mapproject`` output.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
str or None
|
|
82
|
+
The reconstructed ``mapproject ...`` command line, or ``None`` if the
|
|
83
|
+
file is missing the ASP mapproject metadata signature (i.e. it is not a
|
|
84
|
+
mapproject output, or was written by a tool that did not stamp the
|
|
85
|
+
header).
|
|
86
|
+
|
|
87
|
+
Notes
|
|
88
|
+
-----
|
|
89
|
+
The reconstructed argv order mirrors the ASP CLI::
|
|
90
|
+
|
|
91
|
+
mapproject [-t SESSION] [--t_srs SRS] --tr TR \\
|
|
92
|
+
--t_projwin XMIN YMIN XMAX YMAX \\
|
|
93
|
+
[--bundle-adjust-prefix PREFIX] \\
|
|
94
|
+
DEM_FILE INPUT_IMAGE CAMERA OUTPUT
|
|
95
|
+
|
|
96
|
+
``--t_srs`` is emitted as ``EPSG:XXXX`` when the CRS has an exact EPSG code,
|
|
97
|
+
otherwise as the PROJ string (quoted), so custom planetary/local projections
|
|
98
|
+
(e.g. the stereographic frames used in jitter solving) still round-trip.
|
|
99
|
+
"""
|
|
100
|
+
# Reuse the package Raster wrapper: it suppresses the NotGeoreferencedWarning
|
|
101
|
+
# that the raw (non-georef) input scenes raise during discovery, and it owns
|
|
102
|
+
# the EPSG/GSD/bounds derivation (incl. the compound-CRS EPSG fallback) that
|
|
103
|
+
# _t_srs_token relies on.
|
|
104
|
+
try:
|
|
105
|
+
raster = Raster(raster_path)
|
|
106
|
+
except rasterio.errors.RasterioIOError as e:
|
|
107
|
+
logger.warning("Could not read %s for mapproject metadata: %s", raster_path, e)
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
with raster.ds:
|
|
111
|
+
tags = raster.ds.tags()
|
|
112
|
+
if not all(tags.get(k) for k in REQUIRED_TAGS):
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
parts = ["mapproject"]
|
|
116
|
+
|
|
117
|
+
session = tags.get("CAMERA_MODEL_TYPE")
|
|
118
|
+
if session:
|
|
119
|
+
parts += ["-t", session]
|
|
120
|
+
|
|
121
|
+
t_srs = _t_srs_token(raster)
|
|
122
|
+
if t_srs:
|
|
123
|
+
parts += ["--t_srs", t_srs]
|
|
124
|
+
|
|
125
|
+
parts += ["--tr", _format_coord(raster.get_gsd())]
|
|
126
|
+
bounds = raster.ds.bounds
|
|
127
|
+
parts += [
|
|
128
|
+
"--t_projwin",
|
|
129
|
+
_format_coord(bounds.left),
|
|
130
|
+
_format_coord(bounds.bottom),
|
|
131
|
+
_format_coord(bounds.right),
|
|
132
|
+
_format_coord(bounds.top),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
ba_prefix = tags.get("BUNDLE_ADJUST_PREFIX")
|
|
136
|
+
if ba_prefix and ba_prefix != "NONE":
|
|
137
|
+
parts += ["--bundle-adjust-prefix", ba_prefix]
|
|
138
|
+
|
|
139
|
+
parts += [
|
|
140
|
+
tags["DEM_FILE"],
|
|
141
|
+
tags["INPUT_IMAGE_FILE"],
|
|
142
|
+
tags["CAMERA_FILE"],
|
|
143
|
+
os.path.basename(raster_path),
|
|
144
|
+
]
|
|
145
|
+
return " ".join(parts)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _t_srs_token(raster):
|
|
149
|
+
"""Return the ``--t_srs`` token for a :class:`~asp_plot.utils.Raster`.
|
|
150
|
+
|
|
151
|
+
Prefers a compact ``EPSG:XXXX`` via ``Raster.get_epsg_code()`` (which already
|
|
152
|
+
falls back to the horizontal 2D component for compound / 3D-promoted CRSs);
|
|
153
|
+
failing that, emits the PROJ string (quoted, since it contains spaces) so
|
|
154
|
+
custom projections without an EPSG code -- e.g. the stereographic frames used
|
|
155
|
+
in jitter solving -- still round-trip. A missing or malformed CRS returns
|
|
156
|
+
``None`` (logged) rather than raising, so one odd raster cannot abort report
|
|
157
|
+
generation.
|
|
158
|
+
"""
|
|
159
|
+
crs = raster.ds.crs
|
|
160
|
+
if crs is None:
|
|
161
|
+
return None
|
|
162
|
+
try:
|
|
163
|
+
epsg = raster.get_epsg_code()
|
|
164
|
+
if epsg:
|
|
165
|
+
return f"EPSG:{epsg}"
|
|
166
|
+
proj4 = crs.to_proj4()
|
|
167
|
+
if proj4:
|
|
168
|
+
return f'"{proj4}"'
|
|
169
|
+
except Exception as e: # malformed/exotic CRS -- don't crash the report
|
|
170
|
+
logger.warning("Could not derive --t_srs from CRS: %s", e)
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def find_mapproject_commands(directories, stereo_command=None):
|
|
175
|
+
"""Find ASP-mapprojected outputs across ``directories`` and reconstruct them.
|
|
176
|
+
|
|
177
|
+
Scans each directory (non-recursively) for GeoTIFFs and keeps those carrying
|
|
178
|
+
the ASP mapproject metadata signature (see ``REQUIRED_TAGS``) -- identity is
|
|
179
|
+
decided entirely by the file's own metadata, not by its name, so the result
|
|
180
|
+
is robust to ASP/asp_plot filename conventions. Each kept output is
|
|
181
|
+
reconstructed into a command; results are deduplicated (the same scene can be
|
|
182
|
+
reached via more than one of the passed directories) and returned sorted for
|
|
183
|
+
stable report output.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
directories : iterable of str or None
|
|
188
|
+
Directories to scan (e.g. the processing root, stereo dir, BA dir).
|
|
189
|
+
``None`` entries are skipped.
|
|
190
|
+
stereo_command : str, optional
|
|
191
|
+
The stereo command line for the run being reported. When given, a
|
|
192
|
+
mapprojected output is kept only if its filename appears among the
|
|
193
|
+
stereo inputs (i.e. this run actually consumed it). This scopes the
|
|
194
|
+
result to the run at hand: a non-mapprojected stereo run that shares a
|
|
195
|
+
directory with leftover mapprojected scenes (e.g. ``stereo/`` and
|
|
196
|
+
``stereo_no_mapproj/`` under one parent) no longer picks them up. A
|
|
197
|
+
mapprojected run's ``stereo`` command lists the ``*_map.tif`` inputs; a
|
|
198
|
+
non-mapprojected run lists the raw images instead, so the gate is a
|
|
199
|
+
plain filename-membership test -- no fragile positional parsing. When
|
|
200
|
+
``None`` (or empty), no gating is applied and every discovered output is
|
|
201
|
+
returned.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
list of str
|
|
206
|
+
Reconstructed ``mapproject`` command lines, sorted; empty if none found.
|
|
207
|
+
"""
|
|
208
|
+
# Scope to what this stereo run actually used: a mapprojected output is an
|
|
209
|
+
# input to mapprojected stereo, so its basename matches one of the stereo
|
|
210
|
+
# command's argument basenames. Compare against the set of whole-token
|
|
211
|
+
# basenames (not a raw substring of the command) so a short output name like
|
|
212
|
+
# "run.tif" can't spuriously match "prun.tif" or a path fragment.
|
|
213
|
+
stereo_input_basenames = (
|
|
214
|
+
{os.path.basename(token) for token in stereo_command.split()}
|
|
215
|
+
if stereo_command
|
|
216
|
+
else None
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
seen = set()
|
|
220
|
+
commands = []
|
|
221
|
+
scanned = set()
|
|
222
|
+
for directory in directories:
|
|
223
|
+
if not directory:
|
|
224
|
+
continue
|
|
225
|
+
directory = os.path.expanduser(directory)
|
|
226
|
+
if not os.path.isdir(directory):
|
|
227
|
+
continue
|
|
228
|
+
candidates = set()
|
|
229
|
+
for ext in RASTER_EXTENSIONS:
|
|
230
|
+
candidates.update(glob.glob(os.path.join(directory, ext)))
|
|
231
|
+
for path in sorted(candidates):
|
|
232
|
+
real = os.path.realpath(path)
|
|
233
|
+
if real in scanned:
|
|
234
|
+
continue
|
|
235
|
+
scanned.add(real)
|
|
236
|
+
if (
|
|
237
|
+
stereo_input_basenames is not None
|
|
238
|
+
and os.path.basename(path) not in stereo_input_basenames
|
|
239
|
+
):
|
|
240
|
+
continue
|
|
241
|
+
command = reconstruct_mapproject_command(path)
|
|
242
|
+
# Dedupe on the reconstructed command: distinct left/right scenes
|
|
243
|
+
# differ (different input image + output name), but the same scene
|
|
244
|
+
# copied into two scanned directories yields an identical command.
|
|
245
|
+
if command and command not in seen:
|
|
246
|
+
seen.add(command)
|
|
247
|
+
commands.append(command)
|
|
248
|
+
return sorted(commands)
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
5
|
from asp_plot.asp_log import STEREO_STEP_ORDER, AspLog
|
|
6
|
+
from asp_plot.mapproject import find_mapproject_commands
|
|
6
7
|
from asp_plot.utils import glob_file
|
|
7
8
|
|
|
8
9
|
logging.basicConfig(level=logging.WARNING)
|
|
@@ -213,10 +214,45 @@ class ProcessingParameters:
|
|
|
213
214
|
"stereo_run_time": stereo_run_time,
|
|
214
215
|
"point2dem": point2dem_params,
|
|
215
216
|
"point2dem_run_time": point2dem_run_time,
|
|
217
|
+
"mapproject": self.get_mapproject_commands(stereo_params),
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
return self.processing_parameters_dict
|
|
219
221
|
|
|
222
|
+
def get_mapproject_commands(self, stereo_command=None):
|
|
223
|
+
"""Reconstruct ``mapproject`` commands from mapprojected scene metadata.
|
|
224
|
+
|
|
225
|
+
ASP's ``mapproject`` does not write a log file, but it stamps the
|
|
226
|
+
reconstruction inputs into each output GeoTIFF header
|
|
227
|
+
(issue #96). The mapprojected scenes live alongside the raw inputs in
|
|
228
|
+
the processing root and/or the bundle-adjust directory, so we scan those
|
|
229
|
+
plus the stereo directory.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
stereo_command : str, optional
|
|
234
|
+
The stereo command line for this run. When supplied, only
|
|
235
|
+
mapprojected outputs that the stereo run actually consumed (their
|
|
236
|
+
filename appears in the command) are reported -- so a
|
|
237
|
+
non-mapprojected run that shares a directory with mapprojected
|
|
238
|
+
scenes does not spuriously list a mapproject step.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
list of str
|
|
243
|
+
Reconstructed ``mapproject`` command lines (one per mapprojected
|
|
244
|
+
input image found), sorted; empty if the run was not mapprojected or
|
|
245
|
+
the outputs carry no ASP metadata.
|
|
246
|
+
"""
|
|
247
|
+
return find_mapproject_commands(
|
|
248
|
+
[
|
|
249
|
+
self.processing_directory,
|
|
250
|
+
self.full_ba_directory,
|
|
251
|
+
self.full_stereo_directory,
|
|
252
|
+
],
|
|
253
|
+
stereo_command=stereo_command,
|
|
254
|
+
)
|
|
255
|
+
|
|
220
256
|
def from_bundle_adjust_log(self):
|
|
221
257
|
"""
|
|
222
258
|
Extract parameters from the bundle adjustment log file.
|
|
@@ -190,6 +190,11 @@ def compile_report(
|
|
|
190
190
|
- stereo_run_time: Time to run stereo
|
|
191
191
|
- point2dem: Point2dem command
|
|
192
192
|
- point2dem_run_time: Time to run point2dem
|
|
193
|
+
|
|
194
|
+
Optional keys:
|
|
195
|
+
- mapproject: list of reconstructed mapproject command strings (one per
|
|
196
|
+
mapprojected input scene); rendered on the Processing Parameters page
|
|
197
|
+
when present and non-empty.
|
|
193
198
|
"""
|
|
194
199
|
pdf = ASPReportPDF(report_title=report_title)
|
|
195
200
|
pdf.alias_nb_pages()
|
|
@@ -432,6 +437,16 @@ def _add_alignment_stats_row_table(pdf, stats_row):
|
|
|
432
437
|
pdf.ln(6)
|
|
433
438
|
|
|
434
439
|
|
|
440
|
+
def _render_command_block(pdf, label, cmd):
|
|
441
|
+
"""Render a single bold-labelled, monospace-wrapped command on the PDF."""
|
|
442
|
+
pdf.set_font("Helvetica", "B", 10)
|
|
443
|
+
pdf.cell(0, 7, f"{label} Command:", new_x="LMARGIN", new_y="NEXT")
|
|
444
|
+
pdf.set_font("Courier", "", 7)
|
|
445
|
+
wrapped = textwrap.fill(cmd, width=120)
|
|
446
|
+
pdf.multi_cell(0, 4, wrapped, new_x="LMARGIN", new_y="NEXT")
|
|
447
|
+
pdf.ln(4)
|
|
448
|
+
|
|
449
|
+
|
|
435
450
|
def _add_processing_parameters_page(pdf, params, report_command):
|
|
436
451
|
"""Add the Processing Parameters page (runtime table + commands).
|
|
437
452
|
|
|
@@ -460,19 +475,39 @@ def _add_processing_parameters_page(pdf, params, report_command):
|
|
|
460
475
|
pdf.multi_cell(0, 4, ref_dem)
|
|
461
476
|
pdf.ln(4)
|
|
462
477
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
478
|
+
# Render commands in ASP pipeline order: bundle_adjust -> mapproject ->
|
|
479
|
+
# stereo -> point2dem. mapproject is reconstructed from output metadata
|
|
480
|
+
# (it writes no log), so it carries an explanatory note and may be a list.
|
|
481
|
+
if params.get("bundle_adjust"):
|
|
482
|
+
_render_command_block(pdf, "Bundle Adjust", params["bundle_adjust"])
|
|
483
|
+
|
|
484
|
+
mapproject_cmds = params.get("mapproject") or []
|
|
485
|
+
if mapproject_cmds:
|
|
486
|
+
label = (
|
|
487
|
+
"Mapproject Command" if len(mapproject_cmds) == 1 else "Mapproject Commands"
|
|
488
|
+
)
|
|
489
|
+
pdf.set_font("Helvetica", "B", 10)
|
|
490
|
+
pdf.cell(0, 7, f"{label}:", new_x="LMARGIN", new_y="NEXT")
|
|
491
|
+
pdf.set_font("Helvetica", "I", 7)
|
|
492
|
+
pdf.multi_cell(
|
|
493
|
+
0,
|
|
494
|
+
4,
|
|
495
|
+
"Reconstructed from output GeoTIFF metadata (mapproject writes no "
|
|
496
|
+
"log file); session type and grid size are the resolved values.",
|
|
497
|
+
new_x="LMARGIN",
|
|
498
|
+
new_y="NEXT",
|
|
499
|
+
)
|
|
500
|
+
pdf.set_font("Courier", "", 7)
|
|
501
|
+
for cmd in mapproject_cmds:
|
|
473
502
|
wrapped = textwrap.fill(cmd, width=120)
|
|
474
|
-
pdf.multi_cell(0, 4, wrapped)
|
|
475
|
-
pdf.ln(
|
|
503
|
+
pdf.multi_cell(0, 4, wrapped, new_x="LMARGIN", new_y="NEXT")
|
|
504
|
+
pdf.ln(1)
|
|
505
|
+
pdf.ln(3)
|
|
506
|
+
|
|
507
|
+
if params.get("stereo"):
|
|
508
|
+
_render_command_block(pdf, "Stereo", params["stereo"])
|
|
509
|
+
if params.get("point2dem"):
|
|
510
|
+
_render_command_block(pdf, "point2dem", params["point2dem"])
|
|
476
511
|
|
|
477
512
|
if report_command:
|
|
478
513
|
pdf.set_font("Helvetica", "B", 10)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|