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.
Files changed (32) hide show
  1. {asp_plot-1.8.0 → asp_plot-1.9.0}/.github/workflows/run-tests.yml +0 -1
  2. {asp_plot-1.8.0 → asp_plot-1.9.0}/.gitignore +4 -0
  3. asp_plot-1.9.0/.readthedocs.yaml +25 -0
  4. {asp_plot-1.8.0 → asp_plot-1.9.0}/CHANGELOG.md +26 -0
  5. asp_plot-1.9.0/PKG-INFO +88 -0
  6. asp_plot-1.9.0/README.md +45 -0
  7. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/altimetry.py +3 -3
  8. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/asp_plot.py +14 -2
  9. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/report.py +37 -1
  10. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereo.py +68 -33
  11. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/utils.py +4 -3
  12. asp_plot-1.9.0/environment.yml +13 -0
  13. {asp_plot-1.8.0 → asp_plot-1.9.0}/pyproject.toml +19 -1
  14. asp_plot-1.8.0/PKG-INFO +0 -403
  15. asp_plot-1.8.0/README.md +0 -372
  16. asp_plot-1.8.0/environment.yml +0 -26
  17. {asp_plot-1.8.0 → asp_plot-1.9.0}/.flake8 +0 -0
  18. {asp_plot-1.8.0 → asp_plot-1.9.0}/.github/workflows/release.yml +0 -0
  19. {asp_plot-1.8.0 → asp_plot-1.9.0}/.pre-commit-config.yaml +0 -0
  20. {asp_plot-1.8.0 → asp_plot-1.9.0}/LICENSE +0 -0
  21. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/__init__.py +0 -0
  22. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/alignment.py +0 -0
  23. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/bundle_adjust.py +0 -0
  24. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/__init__.py +0 -0
  25. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/csm_camera_plot.py +0 -0
  26. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/cli/stereo_geom.py +0 -0
  27. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/csm_camera.py +0 -0
  28. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/processing_parameters.py +0 -0
  29. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/scenes.py +0 -0
  30. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereo_geometry.py +0 -0
  31. {asp_plot-1.8.0 → asp_plot-1.9.0}/asp_plot/stereopair_metadata_parser.py +0 -0
  32. {asp_plot-1.8.0 → asp_plot-1.9.0}/conda-forge-recipe/meta.yaml +0 -0
@@ -32,7 +32,6 @@ jobs:
32
32
  conda env create -f environment.yml
33
33
  source $CONDA/etc/profile.d/conda.sh
34
34
  conda activate asp_plot
35
- pip install .
36
35
  - name: Test with pytest
37
36
  run: |
38
37
  source $CONDA/etc/profile.d/conda.sh
@@ -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
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/asp-plot.svg)](https://pypi.org/project/asp-plot/)
47
+ [![conda-forge](https://img.shields.io/conda/vn/conda-forge/asp-plot.svg)](https://anaconda.org/conda-forge/asp-plot)
48
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14263121.svg)](https://doi.org/10.5281/zenodo.14263121)
49
+ [![Documentation](https://readthedocs.org/projects/asp-plot/badge/?version=latest)](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.
@@ -0,0 +1,45 @@
1
+ # asp_plot
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/asp-plot.svg)](https://pypi.org/project/asp-plot/)
4
+ [![conda-forge](https://img.shields.io/conda/vn/conda-forge/asp-plot.svg)](https://anaconda.org/conda-forge/asp-plot)
5
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14263121.svg)](https://doi.org/10.5281/zenodo.14263121)
6
+ [![Documentation](https://readthedocs.org/projects/asp-plot/badge/?version=latest)](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=f"DEM hillshade with {subset_km} km detail subset in second row. If available, corresponding mapprojected ortho image subsets are displayed in the bottom row.",
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
- pdf.image(section.image_path, x=pdf.l_margin, w=usable_width)
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
- orthoimages (if they are map-projected) with match points overlaid.
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
- left_image = Raster(self.left_image_sub_fn).read_array()
311
- right_image = Raster(self.right_image_sub_fn).read_array()
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
- # These are small hacks to make the match point plot work if the images are not
314
- # mapprojected and thus not being shown.
315
- rescale_factor = 1
316
- left_image = np.zeros((1, 1))
317
- right_image = np.zeros((1, 1))
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[1].set_title("Right (scenes shown only if mapprojected)")
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
- match_point_df["x1"] / rescale_factor,
326
- match_point_df["y1"] / rescale_factor,
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
- match_point_df["x2"] / rescale_factor,
336
- match_point_df["y2"] / rescale_factor,
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
- full_gsd = Raster(self.disparity_fn).get_gsd()
432
- rescale_factor = sub_gsd / full_gsd
433
- dx = dx * rescale_factor
434
- dy = dy * rescale_factor
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
- if unit == "meters":
437
- dx = dx * full_gsd
438
- dy = dy * full_gsd
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
- scalebar = ScaleBar(sub_gsd)
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=150):
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, default is 150
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
  -------
@@ -0,0 +1,13 @@
1
+ name: asp_plot
2
+ channels:
3
+ - conda-forge
4
+ dependencies:
5
+ - python>=3.11
6
+ - gdal
7
+ - rasterio
8
+ - rioxarray
9
+ - fiona
10
+ - geopandas
11
+ - pip
12
+ - pip:
13
+ - -e ".[dev,docs]"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "asp_plot"
7
- version = "1.8.0"
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
  ]