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.
Files changed (30) hide show
  1. {asp_plot-1.12.0 → asp_plot-1.12.1}/.gitignore +1 -0
  2. {asp_plot-1.12.0 → asp_plot-1.12.1}/CHANGELOG.md +15 -0
  3. {asp_plot-1.12.0 → asp_plot-1.12.1}/PKG-INFO +1 -1
  4. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/asp_plot.py +22 -15
  5. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/report.py +10 -1
  6. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/utils.py +72 -0
  7. {asp_plot-1.12.0 → asp_plot-1.12.1}/pyproject.toml +1 -1
  8. {asp_plot-1.12.0 → asp_plot-1.12.1}/.flake8 +0 -0
  9. {asp_plot-1.12.0 → asp_plot-1.12.1}/.github/workflows/release.yml +0 -0
  10. {asp_plot-1.12.0 → asp_plot-1.12.1}/.github/workflows/run-tests.yml +0 -0
  11. {asp_plot-1.12.0 → asp_plot-1.12.1}/.pre-commit-config.yaml +0 -0
  12. {asp_plot-1.12.0 → asp_plot-1.12.1}/.readthedocs.yaml +0 -0
  13. {asp_plot-1.12.0 → asp_plot-1.12.1}/LICENSE +0 -0
  14. {asp_plot-1.12.0 → asp_plot-1.12.1}/README.md +0 -0
  15. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/__init__.py +0 -0
  16. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/alignment.py +0 -0
  17. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/altimetry.py +0 -0
  18. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/bundle_adjust.py +0 -0
  19. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/__init__.py +0 -0
  20. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/csm_camera_plot.py +0 -0
  21. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/request_planetary_altimetry.py +0 -0
  22. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/cli/stereo_geom.py +0 -0
  23. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/csm_camera.py +0 -0
  24. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/processing_parameters.py +0 -0
  25. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/scenes.py +0 -0
  26. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereo.py +0 -0
  27. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereo_geometry.py +0 -0
  28. {asp_plot-1.12.0 → asp_plot-1.12.1}/asp_plot/stereopair_metadata_parser.py +0 -0
  29. {asp_plot-1.12.0 → asp_plot-1.12.1}/conda-forge-recipe/meta.yaml +0 -0
  30. {asp_plot-1.12.0 → asp_plot-1.12.1}/environment.yml +0 -0
@@ -142,3 +142,4 @@ CLAUDE*
142
142
  .claude/**
143
143
  scripts/
144
144
  /*.parquet
145
+ reports/regenerate_reports.sh
@@ -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.0
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 require no pre-alignment and are displayed in their map-projected orientation.",
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 identified during stereo correlation.",
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
- # ---- Detailed Hillshade ----
407
+ # ---- Disparity ----
401
408
  fig_fn = f"{next(figure_counter):02}.png"
402
- stereo_plotter.title = "Hillshade with details"
403
- stereo_plotter.plot_detailed_hillshade(
404
- subset_km=subset_km, save_dir=plots_directory, fig_fn=fig_fn
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="Detailed Hillshade",
415
+ title="Disparity",
409
416
  image_path=os.path.join(plots_directory, fig_fn),
410
- 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.",
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
- # ---- Disparity ----
433
+ # ---- Detailed Hillshade ----
427
434
  fig_fn = f"{next(figure_counter):02}.png"
428
- stereo_plotter.title = "Disparity (pixels)"
429
- stereo_plotter.plot_disparity(
430
- unit="pixels", quiver=True, save_dir=plots_directory, fig_fn=fig_fn
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="Disparity",
441
+ title="Detailed Hillshade",
435
442
  image_path=os.path.join(plots_directory, fig_fn),
436
- caption="Horizontal and vertical disparity maps in pixels with quiver overlay.",
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "asp_plot"
7
- version = "1.12.0"
7
+ version = "1.12.1"
8
8
  license = {text = "BSD-3-Clause"}
9
9
  authors = [
10
10
  { name="Ben Purinton", email="purinton@uw.edu" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes