asp-plot 1.8.0__tar.gz → 1.9.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.8.0 → asp_plot-1.9.0}/.github/workflows/run-tests.yml +0 -1
- {asp_plot-1.8.0 → asp_plot-1.9.0}/.gitignore +4 -0
- asp_plot-1.9.0/.readthedocs.yaml +25 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/CHANGELOG.md +26 -0
- asp_plot-1.9.0/PKG-INFO +88 -0
- asp_plot-1.9.0/README.md +45 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/altimetry.py +3 -3
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/asp_plot.py +14 -2
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/report.py +37 -1
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereo.py +68 -33
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/utils.py +4 -3
- asp_plot-1.9.0/environment.yml +13 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/pyproject.toml +19 -1
- asp_plot-1.8.0/PKG-INFO +0 -403
- asp_plot-1.8.0/README.md +0 -372
- asp_plot-1.8.0/environment.yml +0 -26
- {asp_plot-1.8.0 → asp_plot-1.9.0}/.flake8 +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/.github/workflows/release.yml +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/.pre-commit-config.yaml +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/LICENSE +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/__init__.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/alignment.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/bundle_adjust.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/__init__.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/csm_camera_plot.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/stereo_geom.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/csm_camera.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/processing_parameters.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/scenes.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereo_geometry.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereopair_metadata_parser.py +0 -0
- {asp_plot-1.8.0 → asp_plot-1.9.0}/conda-forge-recipe/meta.yaml +0 -0
|
@@ -70,6 +70,9 @@ instance/
|
|
|
70
70
|
|
|
71
71
|
# Sphinx documentation
|
|
72
72
|
docs/_build/
|
|
73
|
+
docs/examples/notebooks/
|
|
74
|
+
docs/_extra/
|
|
75
|
+
docs/_static/reports/
|
|
73
76
|
|
|
74
77
|
# PyBuilder
|
|
75
78
|
target/
|
|
@@ -138,3 +141,4 @@ notebooks/**/*.csv
|
|
|
138
141
|
CLAUDE*
|
|
139
142
|
.claude/**
|
|
140
143
|
scripts/
|
|
144
|
+
/*.parquet
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: "ubuntu-24.04"
|
|
5
|
+
tools:
|
|
6
|
+
python: "3.12"
|
|
7
|
+
jobs:
|
|
8
|
+
pre_build:
|
|
9
|
+
- mkdir -p docs/examples/notebooks
|
|
10
|
+
- cp notebooks/WorldView/*.ipynb docs/examples/notebooks/
|
|
11
|
+
- cp notebooks/ASTER/*.ipynb docs/examples/notebooks/
|
|
12
|
+
- cp notebooks/LRO_NAC/*.ipynb docs/examples/notebooks/
|
|
13
|
+
- cp notebooks/Mars_MGS/*.ipynb docs/examples/notebooks/
|
|
14
|
+
- cp notebooks/Mars_MRO/*.ipynb docs/examples/notebooks/
|
|
15
|
+
- mkdir -p docs/_extra/examples/figures
|
|
16
|
+
- cp notebooks/figures/* docs/_extra/examples/figures/
|
|
17
|
+
- mkdir -p docs/_static/reports
|
|
18
|
+
- cp reports/*.pdf docs/_static/reports/
|
|
19
|
+
|
|
20
|
+
sphinx:
|
|
21
|
+
configuration: docs/conf.py
|
|
22
|
+
|
|
23
|
+
python:
|
|
24
|
+
install:
|
|
25
|
+
- requirements: docs/requirements.txt
|
|
@@ -5,6 +5,32 @@ 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.9.0] - 2026-03-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Match points now overlay on non-mapprojected images using alignment transform matrices (`run-align-{L,R}.txt`), replacing the previous blank-right-panel behavior
|
|
12
|
+
- Report command string recorded in PDF report via new `report_command` parameter in `compile_report()`
|
|
13
|
+
- Pixel-unit scalebar for non-mapprojected disparity plots (mapprojected scenes continue to use GSD-based scalebar)
|
|
14
|
+
- Guard with `FileNotFoundError` when alignment matrix files are missing for non-mapprojected match point overlay
|
|
15
|
+
- Warning when `unit="meters"` is passed for non-mapprojected disparity (unsupported, falls back to pixels)
|
|
16
|
+
- Test coverage for non-mapprojected stereo code paths (9 new tests with resampled ASTER test data)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Report figures are now fitted to page dimensions, preventing overflow and cutoff for large/wide figures
|
|
20
|
+
- Report caption reserve is now dynamically calculated from actual caption length instead of a hardcoded 20mm
|
|
21
|
+
- Input Scenes caption updated to explain alignment rotation applied to non-mapprojected imagery
|
|
22
|
+
- Match points right subplot title simplified from "Right (scenes shown only if mapprojected)" to "Right"
|
|
23
|
+
- `save_figure()` default DPI changed from hardcoded 150 to `None` (uses figure's own creation DPI), fixing pixelated ICESat-2 report figures
|
|
24
|
+
- ICESat-2 altimetry figures created at 220 DPI for high-quality PDF embedding
|
|
25
|
+
- CLI parameter values are now quoted with `shlex.quote()` for proper reconstruction of commands with spaces
|
|
26
|
+
- Cleaned up example notebook report links and removed stale PDF files
|
|
27
|
+
- Removed unnecessary `read_align_matrix()` method; alignment matrices are loaded inline via `np.loadtxt()`
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Disparity plot scale for non-mapprojected scenes: GSD-based rescale was producing near-zero values from the identity transform; now skips rescaling and uses pixel-unit scalebar instead
|
|
31
|
+
- Match point plot whitespace for non-mapprojected scenes caused by a 1x1 dummy image plotted underneath scatter points
|
|
32
|
+
- Pixelated ICESat-2 ATL06-SR figures in PDF reports caused by `save_figure()` overriding figure DPI with 150
|
|
33
|
+
|
|
8
34
|
## [1.8.0] - 2026-03-03
|
|
9
35
|
|
|
10
36
|
### Added
|
asp_plot-1.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asp_plot
|
|
3
|
+
Version: 1.9.0
|
|
4
|
+
Summary: Package for plotting outputs Ames Stereo Pipeline processing
|
|
5
|
+
Project-URL: Homepage, https://github.com/uw-cryo/asp_plot
|
|
6
|
+
Project-URL: Documentation, https://asp-plot.readthedocs.io
|
|
7
|
+
Project-URL: Issues, https://github.com/uw-cryo/asp_plot/issues
|
|
8
|
+
Author-email: Ben Purinton <purinton@uw.edu>, David Shean <dshean@uw.edu>, Shashank Bhushan <sbhusha1@umd.edu>
|
|
9
|
+
License: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: click
|
|
16
|
+
Requires-Dist: contextily
|
|
17
|
+
Requires-Dist: fpdf2
|
|
18
|
+
Requires-Dist: gdal
|
|
19
|
+
Requires-Dist: geopandas
|
|
20
|
+
Requires-Dist: matplotlib
|
|
21
|
+
Requires-Dist: matplotlib-scalebar
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: pyproj
|
|
25
|
+
Requires-Dist: rasterio
|
|
26
|
+
Requires-Dist: rioxarray
|
|
27
|
+
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: shapely
|
|
29
|
+
Requires-Dist: sliderule
|
|
30
|
+
Requires-Dist: xarray
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Requires-Dist: myst-nb; extra == 'docs'
|
|
36
|
+
Requires-Dist: sphinx-autoapi; extra == 'docs'
|
|
37
|
+
Requires-Dist: sphinx-autobuild; extra == 'docs'
|
|
38
|
+
Requires-Dist: sphinx-book-theme; extra == 'docs'
|
|
39
|
+
Requires-Dist: sphinx-copybutton; extra == 'docs'
|
|
40
|
+
Requires-Dist: sphinx-design; extra == 'docs'
|
|
41
|
+
Requires-Dist: sphinx>=7; extra == 'docs'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# asp_plot
|
|
45
|
+
|
|
46
|
+
[](https://pypi.org/project/asp-plot/)
|
|
47
|
+
[](https://anaconda.org/conda-forge/asp-plot)
|
|
48
|
+
[](https://doi.org/10.5281/zenodo.14263121)
|
|
49
|
+
[](https://asp-plot.readthedocs.io/en/latest/)
|
|
50
|
+
|
|
51
|
+
A Python package for visualizing output from the [NASA Ames Stereo Pipeline (ASP)](https://github.com/NeoGeographyToolkit/StereoPipeline). Generates diagnostic plots and comprehensive PDF reports for ASP stereo processing results, similar to reports from commercial SfM software like Agisoft Metashape.
|
|
52
|
+
|
|
53
|
+
### **[View some example reports](https://asp-plot.readthedocs.io/en/latest/examples/reports.html)**
|
|
54
|
+
|
|
55
|
+
### **[Full documentation at asp-plot.readthedocs.io](https://asp-plot.readthedocs.io)**
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
conda install -c conda-forge asp-plot
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or with pip:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
pip install asp-plot
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
See the [installation guide](https://asp-plot.readthedocs.io/en/latest/installation.html) for more options.
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
Generate a PDF report from an ASP processing directory:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
asp_plot --directory ./ --stereo_directory stereo
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
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`).
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
- [Example reports](https://asp-plot.readthedocs.io/en/latest/examples/reports.html) — PDF reports for WorldView, ASTER, LRO NAC, and Mars sensors
|
|
84
|
+
- [Example notebooks](https://asp-plot.readthedocs.io/en/latest/examples/index.html) — Modular usage by sensor type
|
|
85
|
+
|
|
86
|
+
## Contributing
|
|
87
|
+
|
|
88
|
+
See the [contributing guide](https://asp-plot.readthedocs.io/en/latest/contributing.html) for development setup, testing, and release process.
|
asp_plot-1.9.0/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# asp_plot
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/asp-plot/)
|
|
4
|
+
[](https://anaconda.org/conda-forge/asp-plot)
|
|
5
|
+
[](https://doi.org/10.5281/zenodo.14263121)
|
|
6
|
+
[](https://asp-plot.readthedocs.io/en/latest/)
|
|
7
|
+
|
|
8
|
+
A Python package for visualizing output from the [NASA Ames Stereo Pipeline (ASP)](https://github.com/NeoGeographyToolkit/StereoPipeline). Generates diagnostic plots and comprehensive PDF reports for ASP stereo processing results, similar to reports from commercial SfM software like Agisoft Metashape.
|
|
9
|
+
|
|
10
|
+
### **[View some example reports](https://asp-plot.readthedocs.io/en/latest/examples/reports.html)**
|
|
11
|
+
|
|
12
|
+
### **[Full documentation at asp-plot.readthedocs.io](https://asp-plot.readthedocs.io)**
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
conda install -c conda-forge asp-plot
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or with pip:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install asp-plot
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
See the [installation guide](https://asp-plot.readthedocs.io/en/latest/installation.html) for more options.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
Generate a PDF report from an ASP processing directory:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
asp_plot --directory ./ --stereo_directory stereo
|
|
34
|
+
```
|
|
35
|
+
|
|
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`).
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
- [Example reports](https://asp-plot.readthedocs.io/en/latest/examples/reports.html) — PDF reports for WorldView, ASTER, LRO NAC, and Mars sensors
|
|
41
|
+
- [Example notebooks](https://asp-plot.readthedocs.io/en/latest/examples/index.html) — Modular usage by sensor type
|
|
42
|
+
|
|
43
|
+
## Contributing
|
|
44
|
+
|
|
45
|
+
See the [contributing guide](https://asp-plot.readthedocs.io/en/latest/contributing.html) for development setup, testing, and release process.
|
|
@@ -1042,7 +1042,7 @@ class Altimetry:
|
|
|
1042
1042
|
atl06sr = self.atl06sr_processing_levels_filtered[key]
|
|
1043
1043
|
atl06sr_sorted = atl06sr.sort_values(by=column_name).to_crs(map_crs)
|
|
1044
1044
|
|
|
1045
|
-
fig, ax = plt.subplots(1, 1, figsize=figsize)
|
|
1045
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, dpi=220)
|
|
1046
1046
|
|
|
1047
1047
|
if plot_dem:
|
|
1048
1048
|
ctx_kwargs = {}
|
|
@@ -1319,7 +1319,7 @@ class Altimetry:
|
|
|
1319
1319
|
print("\nAligned DEM not found.\n")
|
|
1320
1320
|
return
|
|
1321
1321
|
|
|
1322
|
-
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
|
|
1322
|
+
fig, ax = plt.subplots(1, 1, figsize=(6, 4), dpi=220)
|
|
1323
1323
|
|
|
1324
1324
|
for column_name in column_names:
|
|
1325
1325
|
med = atl06sr[column_name].quantile(0.50)
|
|
@@ -1471,7 +1471,7 @@ class Altimetry:
|
|
|
1471
1471
|
f"{cs['name']}: n={cs['n']}, Med={cs['med']:+.2f}, NMAD={cs['nmad']:.2f}"
|
|
1472
1472
|
)
|
|
1473
1473
|
|
|
1474
|
-
fig, ax = plt.subplots(1, 1, figsize=(8, 5))
|
|
1474
|
+
fig, ax = plt.subplots(1, 1, figsize=(8, 5), dpi=220)
|
|
1475
1475
|
|
|
1476
1476
|
xmin = dh.quantile(0.01)
|
|
1477
1477
|
xmax = dh.quantile(0.99)
|
|
@@ -117,6 +117,17 @@ def main(
|
|
|
117
117
|
ICESat-2 comparisons, and more. All plots are combined into a single PDF report
|
|
118
118
|
with processing parameters and summary information.
|
|
119
119
|
"""
|
|
120
|
+
# Reconstruct the asp_plot command for recording in the report
|
|
121
|
+
import shlex
|
|
122
|
+
|
|
123
|
+
click_ctx = click.get_current_context()
|
|
124
|
+
cmd_parts = ["asp_plot"]
|
|
125
|
+
for param in click_ctx.command.params:
|
|
126
|
+
val = click_ctx.params.get(param.name)
|
|
127
|
+
if val is not None and val != param.default:
|
|
128
|
+
cmd_parts.append(f"--{param.name} {shlex.quote(str(val))}")
|
|
129
|
+
report_command = " ".join(cmd_parts)
|
|
130
|
+
|
|
120
131
|
print(f"\nProcessing ASP files in {directory}\n")
|
|
121
132
|
|
|
122
133
|
plots_directory = os.path.join(directory, "tmp_asp_report_plots/")
|
|
@@ -205,7 +216,7 @@ def main(
|
|
|
205
216
|
ReportSection(
|
|
206
217
|
title="Input Scenes",
|
|
207
218
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
208
|
-
caption="Left and right input scenes used for stereo processing.",
|
|
219
|
+
caption="Left and right input scenes used for stereo processing. Non-mapprojected scenes are shown after ASP's alignment step (e.g., affineepipolar), which rotates images to create horizontal epipolar lines for correlation. Mapprojected scenes require no pre-alignment and are displayed in their map-projected orientation.",
|
|
209
220
|
)
|
|
210
221
|
)
|
|
211
222
|
|
|
@@ -364,7 +375,7 @@ def main(
|
|
|
364
375
|
ReportSection(
|
|
365
376
|
title="Detailed Hillshade",
|
|
366
377
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
367
|
-
caption=
|
|
378
|
+
caption="DEM hillshade. If the intersection error is available, zoomed subsets selected from low, medium, and high (left to right) uncertainty areas are displayed in the second row. If the mapprojected image is available, corresponding ortho image subsets are displayed in the bottom row.",
|
|
368
379
|
)
|
|
369
380
|
)
|
|
370
381
|
|
|
@@ -463,6 +474,7 @@ def main(
|
|
|
463
474
|
report_pdf_path,
|
|
464
475
|
report_title=report_title,
|
|
465
476
|
report_metadata=report_metadata,
|
|
477
|
+
report_command=report_command,
|
|
466
478
|
)
|
|
467
479
|
|
|
468
480
|
shutil.rmtree(plots_directory)
|
|
@@ -4,6 +4,7 @@ import textwrap
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
6
|
from fpdf import FPDF
|
|
7
|
+
from PIL import Image
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
@@ -95,6 +96,7 @@ def compile_report(
|
|
|
95
96
|
report_pdf_path,
|
|
96
97
|
report_title="ASP Output Quality Report",
|
|
97
98
|
report_metadata=None,
|
|
99
|
+
report_command=None,
|
|
98
100
|
):
|
|
99
101
|
"""
|
|
100
102
|
Compile a PDF report with ASP processing results and plots.
|
|
@@ -115,6 +117,8 @@ def compile_report(
|
|
|
115
117
|
Title for the report. Default is "ASP Output Quality Report".
|
|
116
118
|
report_metadata : ReportMetadata, optional
|
|
117
119
|
DEM metadata for the title page summary table. Default is None.
|
|
120
|
+
report_command : str, optional
|
|
121
|
+
The asp_plot CLI command used to generate this report. Default is None.
|
|
118
122
|
|
|
119
123
|
Returns
|
|
120
124
|
-------
|
|
@@ -191,7 +195,31 @@ def compile_report(
|
|
|
191
195
|
pdf.ln(2)
|
|
192
196
|
|
|
193
197
|
usable_width = pdf.w - pdf.l_margin - pdf.r_margin
|
|
194
|
-
|
|
198
|
+
# Reserve space for caption below the image and bottom margin.
|
|
199
|
+
# Caption font is 9pt with ~80 chars/line at usable_width; 5mm per line + spacing.
|
|
200
|
+
if section.caption:
|
|
201
|
+
caption_text = f"Figure {section.figure_number}: {section.caption}"
|
|
202
|
+
estimated_lines = max(1, -(-len(caption_text) // 80)) # ceil division
|
|
203
|
+
caption_reserve = estimated_lines * 5 + 8
|
|
204
|
+
else:
|
|
205
|
+
caption_reserve = 0
|
|
206
|
+
usable_height = pdf.h - pdf.get_y() - pdf.b_margin - caption_reserve
|
|
207
|
+
|
|
208
|
+
# Determine image dimensions that fit within usable area
|
|
209
|
+
with Image.open(section.image_path) as img:
|
|
210
|
+
img_w, img_h = img.size
|
|
211
|
+
aspect = img_h / img_w
|
|
212
|
+
render_w = usable_width
|
|
213
|
+
render_h = render_w * aspect
|
|
214
|
+
if render_h > usable_height:
|
|
215
|
+
render_h = usable_height
|
|
216
|
+
render_w = render_h / aspect
|
|
217
|
+
|
|
218
|
+
pdf.image(
|
|
219
|
+
section.image_path,
|
|
220
|
+
x=pdf.l_margin + (usable_width - render_w) / 2,
|
|
221
|
+
w=render_w,
|
|
222
|
+
)
|
|
195
223
|
|
|
196
224
|
if section.caption:
|
|
197
225
|
pdf.ln(3)
|
|
@@ -229,6 +257,14 @@ def compile_report(
|
|
|
229
257
|
pdf.multi_cell(0, 4, wrapped)
|
|
230
258
|
pdf.ln(4)
|
|
231
259
|
|
|
260
|
+
if report_command:
|
|
261
|
+
pdf.set_font("Helvetica", "B", 10)
|
|
262
|
+
pdf.cell(0, 7, "Report Generation Command:", new_x="LMARGIN", new_y="NEXT")
|
|
263
|
+
pdf.set_font("Courier", "", 7)
|
|
264
|
+
wrapped = textwrap.fill(report_command, width=120)
|
|
265
|
+
pdf.multi_cell(0, 4, wrapped)
|
|
266
|
+
pdf.ln(4)
|
|
267
|
+
|
|
232
268
|
pdf.output(report_pdf_path)
|
|
233
269
|
|
|
234
270
|
|
|
@@ -126,6 +126,8 @@ class StereoPlotter(Plotter):
|
|
|
126
126
|
self.orthos = False if Raster(self.left_image_fn).transform is None else True
|
|
127
127
|
self.left_image_sub_fn = glob_file(self.full_directory, "*-L_sub.tif")
|
|
128
128
|
self.right_image_sub_fn = glob_file(self.full_directory, "*-R_sub.tif")
|
|
129
|
+
self.align_left_fn = glob_file(self.full_directory, "*-align-L.txt")
|
|
130
|
+
self.align_right_fn = glob_file(self.full_directory, "*-align-R.txt")
|
|
129
131
|
|
|
130
132
|
# There may be multiple match files if stereo was run with --num-matches-from-disparity.
|
|
131
133
|
# In that case, filter out the match file with `-disp-` in filename.
|
|
@@ -270,7 +272,11 @@ class StereoPlotter(Plotter):
|
|
|
270
272
|
Plot match points between the left and right images.
|
|
271
273
|
|
|
272
274
|
Creates a figure with two subplots showing the left and right
|
|
273
|
-
|
|
275
|
+
subsampled images with match points overlaid as small red circles.
|
|
276
|
+
For mapprojected scenes, match points are rescaled using the GSD ratio.
|
|
277
|
+
For non-mapprojected scenes, match points are transformed from original
|
|
278
|
+
to aligned coordinate space using the alignment matrices, then rescaled
|
|
279
|
+
to the subsampled image dimensions.
|
|
274
280
|
|
|
275
281
|
Parameters
|
|
276
282
|
----------
|
|
@@ -283,12 +289,6 @@ class StereoPlotter(Plotter):
|
|
|
283
289
|
-------
|
|
284
290
|
None
|
|
285
291
|
Displays the plot and optionally saves it
|
|
286
|
-
|
|
287
|
-
Notes
|
|
288
|
-
-----
|
|
289
|
-
If the images are not map-projected, only the match points are shown,
|
|
290
|
-
not the underlying images. The match points are displayed as small red
|
|
291
|
-
circles on both images.
|
|
292
292
|
"""
|
|
293
293
|
match_point_df = self.get_match_point_df()
|
|
294
294
|
|
|
@@ -299,31 +299,54 @@ class StereoPlotter(Plotter):
|
|
|
299
299
|
and self.right_image_sub_fn
|
|
300
300
|
and match_point_df is not None
|
|
301
301
|
):
|
|
302
|
-
# If the images are not mapprojected, we only show the distribution of match points
|
|
303
|
-
# and not the underlying images, which are rotated and difficult to plot.
|
|
304
|
-
# We can revisit plotting the non-mapprojected images later, but it is challenging,
|
|
305
|
-
# and likely not worthwhile, as the distribution of match points is what we are interested in.
|
|
306
302
|
if self.orthos:
|
|
307
303
|
full_gsd = Raster(self.left_image_fn).get_gsd()
|
|
308
304
|
sub_gsd = Raster(self.left_image_sub_fn).get_gsd()
|
|
309
305
|
rescale_factor = sub_gsd / full_gsd
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
left_x = match_point_df["x1"] / rescale_factor
|
|
307
|
+
left_y = match_point_df["y1"] / rescale_factor
|
|
308
|
+
right_x = match_point_df["x2"] / rescale_factor
|
|
309
|
+
right_y = match_point_df["y2"] / rescale_factor
|
|
312
310
|
else:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
if not self.align_left_fn or not self.align_right_fn:
|
|
312
|
+
raise FileNotFoundError(
|
|
313
|
+
"Alignment matrix files (run-align-{L,R}.txt) not found. "
|
|
314
|
+
"These are required to overlay match points on non-mapprojected images."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
full_width = Raster(self.left_image_fn).ds.width
|
|
318
|
+
sub_width = Raster(self.left_image_sub_fn).ds.width
|
|
319
|
+
rescale_factor = full_width / sub_width
|
|
320
|
+
|
|
321
|
+
# Transform match points from original to aligned coordinate space
|
|
322
|
+
align_L = np.loadtxt(self.align_left_fn)
|
|
323
|
+
align_R = np.loadtxt(self.align_right_fn)
|
|
324
|
+
|
|
325
|
+
n = len(match_point_df)
|
|
326
|
+
ones = np.ones(n)
|
|
327
|
+
|
|
328
|
+
left_pts = np.vstack([match_point_df["x1"], match_point_df["y1"], ones])
|
|
329
|
+
left_aligned = align_L @ left_pts
|
|
330
|
+
left_x = left_aligned[0] / rescale_factor
|
|
331
|
+
left_y = left_aligned[1] / rescale_factor
|
|
332
|
+
|
|
333
|
+
right_pts = np.vstack(
|
|
334
|
+
[match_point_df["x2"], match_point_df["y2"], ones]
|
|
335
|
+
)
|
|
336
|
+
right_aligned = align_R @ right_pts
|
|
337
|
+
right_x = right_aligned[0] / rescale_factor
|
|
338
|
+
right_y = right_aligned[1] / rescale_factor
|
|
318
339
|
|
|
340
|
+
left_image = Raster(self.left_image_sub_fn).read_array()
|
|
341
|
+
right_image = Raster(self.right_image_sub_fn).read_array()
|
|
319
342
|
self.plot_array(ax=axa[0], array=left_image, cmap="gray", add_cbar=False)
|
|
320
|
-
axa[0].set_title(f"Left (n={match_point_df.shape[0]})")
|
|
321
343
|
self.plot_array(ax=axa[1], array=right_image, cmap="gray", add_cbar=False)
|
|
322
|
-
axa[
|
|
344
|
+
axa[0].set_title(f"Left (n={match_point_df.shape[0]})")
|
|
345
|
+
axa[1].set_title("Right")
|
|
323
346
|
|
|
324
347
|
axa[0].scatter(
|
|
325
|
-
|
|
326
|
-
|
|
348
|
+
left_x,
|
|
349
|
+
left_y,
|
|
327
350
|
color="r",
|
|
328
351
|
marker="o",
|
|
329
352
|
facecolor="none",
|
|
@@ -332,8 +355,8 @@ class StereoPlotter(Plotter):
|
|
|
332
355
|
axa[0].set_aspect("equal")
|
|
333
356
|
|
|
334
357
|
axa[1].scatter(
|
|
335
|
-
|
|
336
|
-
|
|
358
|
+
right_x,
|
|
359
|
+
right_y,
|
|
337
360
|
color="r",
|
|
338
361
|
marker="o",
|
|
339
362
|
facecolor="none",
|
|
@@ -418,7 +441,6 @@ class StereoPlotter(Plotter):
|
|
|
418
441
|
|
|
419
442
|
if self.disparity_sub_fn and self.disparity_fn:
|
|
420
443
|
raster = Raster(self.disparity_sub_fn)
|
|
421
|
-
sub_gsd = raster.get_gsd()
|
|
422
444
|
dx = raster.read_array(b=1)
|
|
423
445
|
dy = raster.read_array(b=2)
|
|
424
446
|
|
|
@@ -428,14 +450,24 @@ class StereoPlotter(Plotter):
|
|
|
428
450
|
dx.mask = combined_mask
|
|
429
451
|
dy.mask = combined_mask
|
|
430
452
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
453
|
+
# Rescale disparity from subsampled to full-res pixel coordinates.
|
|
454
|
+
# Only meaningful for georeferenced (mapprojected) data where the
|
|
455
|
+
# GSD ratio reflects the actual downsampling factor. For non-georeferenced
|
|
456
|
+
# data the transform is identity/near-zero, so skip rescaling.
|
|
457
|
+
if not self.orthos and unit == "meters":
|
|
458
|
+
logger.warning(
|
|
459
|
+
"Disparity unit 'meters' not supported for non-mapprojected scenes; using pixels."
|
|
460
|
+
)
|
|
461
|
+
if self.orthos:
|
|
462
|
+
sub_gsd = raster.get_gsd()
|
|
463
|
+
full_gsd = Raster(self.disparity_fn).get_gsd()
|
|
464
|
+
rescale_factor = sub_gsd / full_gsd
|
|
465
|
+
dx = dx * rescale_factor
|
|
466
|
+
dy = dy * rescale_factor
|
|
435
467
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
468
|
+
if unit == "meters":
|
|
469
|
+
dx = dx * full_gsd
|
|
470
|
+
dy = dy * full_gsd
|
|
439
471
|
|
|
440
472
|
if remove_bias:
|
|
441
473
|
dx_offset = np.ma.median(dx)
|
|
@@ -465,7 +497,10 @@ class StereoPlotter(Plotter):
|
|
|
465
497
|
dy_q = dy[::stride, ::stride]
|
|
466
498
|
axa[2].quiver(ix, iy, dx_q, dy_q, color="white")
|
|
467
499
|
|
|
468
|
-
|
|
500
|
+
if self.orthos:
|
|
501
|
+
scalebar = ScaleBar(raster.get_gsd())
|
|
502
|
+
else:
|
|
503
|
+
scalebar = ScaleBar(1, units="px", dimension="pixel-length")
|
|
469
504
|
axa[0].add_artist(scalebar)
|
|
470
505
|
axa[0].set_title("x offset")
|
|
471
506
|
axa[1].set_title("y offset")
|
|
@@ -92,7 +92,7 @@ def show_existing_figure(filename):
|
|
|
92
92
|
print(f"Figure not found: {filename}")
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def save_figure(fig, save_dir=None, fig_fn=None, dpi=
|
|
95
|
+
def save_figure(fig, save_dir=None, fig_fn=None, dpi=None):
|
|
96
96
|
"""
|
|
97
97
|
Save a matplotlib figure to a file.
|
|
98
98
|
|
|
@@ -107,8 +107,9 @@ def save_figure(fig, save_dir=None, fig_fn=None, dpi=150):
|
|
|
107
107
|
Directory to save the figure in
|
|
108
108
|
fig_fn : str, optional
|
|
109
109
|
Filename for the saved figure
|
|
110
|
-
dpi : int, optional
|
|
111
|
-
Resolution in dots per inch
|
|
110
|
+
dpi : int or None, optional
|
|
111
|
+
Resolution in dots per inch. Default is None, which uses the
|
|
112
|
+
figure's own DPI setting.
|
|
112
113
|
|
|
113
114
|
Returns
|
|
114
115
|
-------
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "asp_plot"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.9.0"
|
|
8
8
|
license = {text = "BSD-3-Clause"}
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Ben Purinton", email="purinton@uw.edu" },
|
|
@@ -43,8 +43,24 @@ asp_plot = "asp_plot.cli.asp_plot:main"
|
|
|
43
43
|
csm_camera_plot = "asp_plot.cli.csm_camera_plot:main"
|
|
44
44
|
stereo_geom = "asp_plot.cli.stereo_geom:main"
|
|
45
45
|
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest",
|
|
49
|
+
"pre-commit",
|
|
50
|
+
]
|
|
51
|
+
docs = [
|
|
52
|
+
"sphinx>=7",
|
|
53
|
+
"myst-nb",
|
|
54
|
+
"sphinx-autoapi",
|
|
55
|
+
"sphinx-design",
|
|
56
|
+
"sphinx-copybutton",
|
|
57
|
+
"sphinx-book-theme",
|
|
58
|
+
"sphinx-autobuild",
|
|
59
|
+
]
|
|
60
|
+
|
|
46
61
|
[project.urls]
|
|
47
62
|
Homepage = "https://github.com/uw-cryo/asp_plot"
|
|
63
|
+
Documentation = "https://asp-plot.readthedocs.io"
|
|
48
64
|
Issues = "https://github.com/uw-cryo/asp_plot/issues"
|
|
49
65
|
|
|
50
66
|
[tool.hatch.build.targets.sdist]
|
|
@@ -53,4 +69,6 @@ exclude = [
|
|
|
53
69
|
"notebooks/",
|
|
54
70
|
"original_code/",
|
|
55
71
|
"scratch/",
|
|
72
|
+
"reports/",
|
|
73
|
+
"docs/",
|
|
56
74
|
]
|