asp-plot 1.12.0__tar.gz → 1.12.1__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.12.0 → asp_plot-1.12.1}/.gitignore +1 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/CHANGELOG.md +15 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/PKG-INFO +1 -1
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/asp_plot.py +22 -15
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/report.py +10 -1
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/utils.py +72 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/pyproject.toml +1 -1
- {asp_plot-1.12.0 → asp_plot-1.12.1}/.flake8 +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/.github/workflows/release.yml +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/.github/workflows/run-tests.yml +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/.pre-commit-config.yaml +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/.readthedocs.yaml +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/LICENSE +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/README.md +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/__init__.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/alignment.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/altimetry.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/bundle_adjust.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/__init__.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/csm_camera_plot.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/request_planetary_altimetry.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/stereo_geom.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/csm_camera.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/processing_parameters.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/scenes.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereo.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereo_geometry.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereopair_metadata_parser.py +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/conda-forge-recipe/meta.yaml +0 -0
- {asp_plot-1.12.0 → asp_plot-1.12.1}/environment.yml +0 -0
|
@@ -5,6 +5,21 @@ 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.12.1] - 2026-04-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Report panel order**: Disparity maps now follow the Bundle Adjust panels, and DEM Results precedes Detailed Hillshade (was: Hillshade → DEM Results → Disparity).
|
|
12
|
+
- **Input Scenes caption** clarifies that mapprojected scenes are RPC-orthorectified against a reference DEM to roughly pre-align the stereo pair prior to correlation (reducing disparity search range), which addresses confusion for readers coming from non-ASP photogrammetry workflows.
|
|
13
|
+
- **Match Points caption** notes these come from `stereo_corr`'s initial interest point matching step (used to set search windows), not dense correlation.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Acquisition Date(s)** row on the DEM Summary title-page table, populated when recoverable from scene metadata.
|
|
17
|
+
- `get_acquisition_dates()` helper in `utils.py`: reads `FIRSTLINETIME` from WorldView/Maxar XMLs and parses the capture timestamp from `AST_L1A_...` file/directory names. Deduplicates and sorts; returns an empty list if no date can be found, in which case the summary-table row is omitted.
|
|
18
|
+
- Unit tests for `get_acquisition_dates()` covering WorldView XMLs, ASTER filenames (top-level and in subdirectories), dedupe, and sorting of multi-date pairs.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `--report_filename` accepts absolute and relative paths (not just bare filenames), and `~` in CLI path arguments is expanded ([#113](https://github.com/uw-cryo/asp_plot/pull/113)).
|
|
22
|
+
|
|
8
23
|
## [1.12.0] - 2026-04-10
|
|
9
24
|
|
|
10
25
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asp_plot
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.1
|
|
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
|
|
@@ -13,7 +13,7 @@ from asp_plot.report import ReportMetadata, ReportSection, compile_report
|
|
|
13
13
|
from asp_plot.scenes import ScenePlotter
|
|
14
14
|
from asp_plot.stereo import StereoPlotter
|
|
15
15
|
from asp_plot.stereo_geometry import StereoGeometryPlotter
|
|
16
|
-
from asp_plot.utils import Raster, detect_planetary_body
|
|
16
|
+
from asp_plot.utils import Raster, detect_planetary_body, get_acquisition_dates
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@click.command()
|
|
@@ -217,6 +217,12 @@ def main(
|
|
|
217
217
|
elev_range = (
|
|
218
218
|
(float(valid.min()), float(valid.max())) if valid.size else (0, 0)
|
|
219
219
|
)
|
|
220
|
+
acq_extra_dirs = [os.path.join(directory, stereo_directory)]
|
|
221
|
+
if bundle_adjust_directory:
|
|
222
|
+
acq_extra_dirs.append(os.path.join(directory, bundle_adjust_directory))
|
|
223
|
+
acquisition_dates = get_acquisition_dates(
|
|
224
|
+
directory, extra_dirs=acq_extra_dirs
|
|
225
|
+
)
|
|
220
226
|
report_metadata = ReportMetadata(
|
|
221
227
|
dem_dimensions=(dem_raster.ds.width, dem_raster.ds.height),
|
|
222
228
|
dem_gsd_m=dem_raster.get_gsd(),
|
|
@@ -225,6 +231,7 @@ def main(
|
|
|
225
231
|
dem_elevation_range=elev_range,
|
|
226
232
|
dem_filename=os.path.basename(asp_dem),
|
|
227
233
|
reference_dem=reference_dem or "",
|
|
234
|
+
acquisition_dates=acquisition_dates,
|
|
228
235
|
)
|
|
229
236
|
except Exception as e:
|
|
230
237
|
print(f"\nCould not collect DEM metadata: {e}\n")
|
|
@@ -248,7 +255,7 @@ def main(
|
|
|
248
255
|
ReportSection(
|
|
249
256
|
title="Input Scenes",
|
|
250
257
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
251
|
-
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
|
|
258
|
+
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 have been orthorectified with RPCs against a reference DEM to roughly align the two images prior to correlation, which reduces the disparity search range; they are displayed here in their map-projected orientation.",
|
|
252
259
|
)
|
|
253
260
|
)
|
|
254
261
|
|
|
@@ -273,7 +280,7 @@ def main(
|
|
|
273
280
|
ReportSection(
|
|
274
281
|
title="Match Points",
|
|
275
282
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
276
|
-
caption="Interest point matches between left and right images
|
|
283
|
+
caption="Interest point matches between left and right images. These are produced by stereo_corr during its initial interest point matching step, which is used to set the search windows for subsequent dense correlation (not the dense correlation matches themselves).",
|
|
277
284
|
)
|
|
278
285
|
)
|
|
279
286
|
|
|
@@ -397,17 +404,17 @@ def main(
|
|
|
397
404
|
f"\n\nNo bundle adjustment files found in directory {os.path.join(directory, bundle_adjust_directory):}. If you want bundle adjustment plots, make sure you run the tool and supply the correct directory to asp_plot.\n\n"
|
|
398
405
|
)
|
|
399
406
|
|
|
400
|
-
# ----
|
|
407
|
+
# ---- Disparity ----
|
|
401
408
|
fig_fn = f"{next(figure_counter):02}.png"
|
|
402
|
-
stereo_plotter.title = "
|
|
403
|
-
stereo_plotter.
|
|
404
|
-
|
|
409
|
+
stereo_plotter.title = "Disparity (pixels)"
|
|
410
|
+
stereo_plotter.plot_disparity(
|
|
411
|
+
unit="pixels", quiver=True, save_dir=plots_directory, fig_fn=fig_fn
|
|
405
412
|
)
|
|
406
413
|
sections.append(
|
|
407
414
|
ReportSection(
|
|
408
|
-
title="
|
|
415
|
+
title="Disparity",
|
|
409
416
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
410
|
-
caption="
|
|
417
|
+
caption="Horizontal and vertical disparity maps in pixels with quiver overlay.",
|
|
411
418
|
)
|
|
412
419
|
)
|
|
413
420
|
|
|
@@ -423,17 +430,17 @@ def main(
|
|
|
423
430
|
)
|
|
424
431
|
)
|
|
425
432
|
|
|
426
|
-
# ----
|
|
433
|
+
# ---- Detailed Hillshade ----
|
|
427
434
|
fig_fn = f"{next(figure_counter):02}.png"
|
|
428
|
-
stereo_plotter.title = "
|
|
429
|
-
stereo_plotter.
|
|
430
|
-
|
|
435
|
+
stereo_plotter.title = "Hillshade with details"
|
|
436
|
+
stereo_plotter.plot_detailed_hillshade(
|
|
437
|
+
subset_km=subset_km, save_dir=plots_directory, fig_fn=fig_fn
|
|
431
438
|
)
|
|
432
439
|
sections.append(
|
|
433
440
|
ReportSection(
|
|
434
|
-
title="
|
|
441
|
+
title="Detailed Hillshade",
|
|
435
442
|
image_path=os.path.join(plots_directory, fig_fn),
|
|
436
|
-
caption="
|
|
443
|
+
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.",
|
|
437
444
|
)
|
|
438
445
|
)
|
|
439
446
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import textwrap
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
|
|
6
6
|
from fpdf import FPDF
|
|
7
7
|
from PIL import Image
|
|
@@ -51,6 +51,9 @@ class ReportMetadata:
|
|
|
51
51
|
DEM filename.
|
|
52
52
|
reference_dem : str
|
|
53
53
|
Reference DEM path or description.
|
|
54
|
+
acquisition_dates : list of str
|
|
55
|
+
Scene acquisition date strings (e.g. "2017-07-31 19:07:28 UTC") when
|
|
56
|
+
recoverable from scene metadata. Empty list if not found.
|
|
54
57
|
"""
|
|
55
58
|
|
|
56
59
|
dem_dimensions: tuple = (0, 0)
|
|
@@ -60,6 +63,7 @@ class ReportMetadata:
|
|
|
60
63
|
dem_elevation_range: tuple = (0, 0)
|
|
61
64
|
dem_filename: str = ""
|
|
62
65
|
reference_dem: str = ""
|
|
66
|
+
acquisition_dates: list = field(default_factory=list)
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
class ASPReportPDF(FPDF):
|
|
@@ -299,6 +303,11 @@ def _add_metadata_table(pdf, metadata):
|
|
|
299
303
|
rows.append(("Elevation Range (m)", f"{lo:.1f} to {hi:.1f}"))
|
|
300
304
|
if metadata.reference_dem:
|
|
301
305
|
rows.append(("Reference DEM", metadata.reference_dem))
|
|
306
|
+
if metadata.acquisition_dates:
|
|
307
|
+
if len(metadata.acquisition_dates) == 1:
|
|
308
|
+
rows.append(("Acquisition Date", metadata.acquisition_dates[0]))
|
|
309
|
+
else:
|
|
310
|
+
rows.append(("Acquisition Dates", "; ".join(metadata.acquisition_dates)))
|
|
302
311
|
|
|
303
312
|
if not rows:
|
|
304
313
|
return
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import re
|
|
5
5
|
import subprocess
|
|
6
6
|
import warnings
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
|
|
8
9
|
import contextily as ctx
|
|
9
10
|
import matplotlib.colors
|
|
@@ -1227,6 +1228,77 @@ def detect_vantor_satellite(directory):
|
|
|
1227
1228
|
return False
|
|
1228
1229
|
|
|
1229
1230
|
|
|
1231
|
+
def get_acquisition_dates(directory, extra_dirs=None):
|
|
1232
|
+
"""Extract scene acquisition date(s) from metadata in a processing directory.
|
|
1233
|
+
|
|
1234
|
+
Looks for WorldView/Maxar-style XML camera files (using the ``FIRSTLINETIME``
|
|
1235
|
+
tag) and ASTER L1A file or directory names (which encode the capture date in
|
|
1236
|
+
the filename: ``AST_L1A_<prodcode><MMDDYYYY><HHMMSS>_...``). Returns a sorted,
|
|
1237
|
+
deduplicated list of date strings. An empty list is returned if nothing is
|
|
1238
|
+
found.
|
|
1239
|
+
|
|
1240
|
+
Parameters
|
|
1241
|
+
----------
|
|
1242
|
+
directory : str
|
|
1243
|
+
Top-level ASP processing directory to search (non-recursive for XMLs,
|
|
1244
|
+
recursive for ASTER L1A filenames).
|
|
1245
|
+
extra_dirs : list of str, optional
|
|
1246
|
+
Additional directories to search non-recursively for XML files (e.g. a
|
|
1247
|
+
stereo or bundle-adjust subdirectory).
|
|
1248
|
+
|
|
1249
|
+
Returns
|
|
1250
|
+
-------
|
|
1251
|
+
list of str
|
|
1252
|
+
Acquisition datetime strings formatted as ``YYYY-MM-DD HH:MM:SS UTC``.
|
|
1253
|
+
"""
|
|
1254
|
+
directory = os.path.expanduser(directory)
|
|
1255
|
+
dates = {}
|
|
1256
|
+
|
|
1257
|
+
search_dirs = [directory]
|
|
1258
|
+
if extra_dirs:
|
|
1259
|
+
for d in extra_dirs:
|
|
1260
|
+
if d and d not in search_dirs:
|
|
1261
|
+
search_dirs.append(d)
|
|
1262
|
+
|
|
1263
|
+
# WorldView/Maxar XMLs: FIRSTLINETIME
|
|
1264
|
+
for d in search_dirs:
|
|
1265
|
+
try:
|
|
1266
|
+
xml_files = glob_file(d, "*.[Xx][Mm][Ll]", all_files=True)
|
|
1267
|
+
except Exception:
|
|
1268
|
+
xml_files = None
|
|
1269
|
+
if not xml_files:
|
|
1270
|
+
continue
|
|
1271
|
+
xml_files = [f for f in xml_files if not re.search(r".*ortho.*\.xml", f)]
|
|
1272
|
+
for xml_file in xml_files:
|
|
1273
|
+
try:
|
|
1274
|
+
first_line_time = get_xml_tag(xml_file, "FIRSTLINETIME")
|
|
1275
|
+
dt = datetime.strptime(first_line_time, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
1276
|
+
except (ValueError, Exception):
|
|
1277
|
+
continue
|
|
1278
|
+
key = dt.replace(microsecond=0).isoformat()
|
|
1279
|
+
dates.setdefault(key, dt.replace(microsecond=0))
|
|
1280
|
+
|
|
1281
|
+
# ASTER L1A: capture date encoded in filename
|
|
1282
|
+
# Pattern: AST_L1A_<3-digit production code><MM><DD><YYYY><HH><MM><SS>_...
|
|
1283
|
+
aster_re = re.compile(r"AST_L1A_\d{3}(\d{2})(\d{2})(\d{4})(\d{2})(\d{2})(\d{2})")
|
|
1284
|
+
skip_dirs = {"tmp_asp_report_plots", ".git", "__pycache__"}
|
|
1285
|
+
for root, dirnames, filenames in os.walk(directory):
|
|
1286
|
+
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
1287
|
+
for name in list(dirnames) + list(filenames):
|
|
1288
|
+
m = aster_re.search(name)
|
|
1289
|
+
if not m:
|
|
1290
|
+
continue
|
|
1291
|
+
mo, day, yr, hh, mi, ss = (int(x) for x in m.groups())
|
|
1292
|
+
try:
|
|
1293
|
+
dt = datetime(yr, mo, day, hh, mi, ss)
|
|
1294
|
+
except ValueError:
|
|
1295
|
+
continue
|
|
1296
|
+
key = dt.isoformat()
|
|
1297
|
+
dates.setdefault(key, dt)
|
|
1298
|
+
|
|
1299
|
+
return [dt.strftime("%Y-%m-%d %H:%M:%S UTC") for dt in sorted(dates.values())]
|
|
1300
|
+
|
|
1301
|
+
|
|
1230
1302
|
def add_copyright_overlay(ax):
|
|
1231
1303
|
"""Add Vantor copyright text overlay to the bottom-right of a matplotlib axes.
|
|
1232
1304
|
|
|
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
|