asp-plot 0.0.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-0.0.1/LICENSE +28 -0
- asp_plot-0.0.1/PKG-INFO +76 -0
- asp_plot-0.0.1/README.md +61 -0
- asp_plot-0.0.1/asp_plot/__init__.py +5 -0
- asp_plot-0.0.1/asp_plot/bundle_adjust.py +212 -0
- asp_plot-0.0.1/asp_plot/cli/__init__.py +0 -0
- asp_plot-0.0.1/asp_plot/cli/asp_plot.py +184 -0
- asp_plot-0.0.1/asp_plot/processing_parameters.py +121 -0
- asp_plot-0.0.1/asp_plot/scenes.py +68 -0
- asp_plot-0.0.1/asp_plot/stereo.py +289 -0
- asp_plot-0.0.1/asp_plot/utils.py +230 -0
- asp_plot-0.0.1/asp_plot.egg-info/PKG-INFO +76 -0
- asp_plot-0.0.1/asp_plot.egg-info/SOURCES.txt +22 -0
- asp_plot-0.0.1/asp_plot.egg-info/dependency_links.txt +1 -0
- asp_plot-0.0.1/asp_plot.egg-info/top_level.txt +2 -0
- asp_plot-0.0.1/pyproject.toml +22 -0
- asp_plot-0.0.1/setup.cfg +4 -0
- asp_plot-0.0.1/setup.py +13 -0
- asp_plot-0.0.1/tests/__init__.py +5 -0
- asp_plot-0.0.1/tests/test_bundle_adjust.py +35 -0
- asp_plot-0.0.1/tests/test_imports.py +20 -0
- asp_plot-0.0.1/tests/test_processing_parameters.py +35 -0
- asp_plot-0.0.1/tests/test_scenes.py +22 -0
- asp_plot-0.0.1/tests/test_stereo.py +36 -0
asp_plot-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, UW Terrain Analysis and Cryosphere Observation Lab
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
asp_plot-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: asp_plot
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Package for plotting outputs Ames Stereo Pipeline processing
|
|
5
|
+
Author: Ben Purinton
|
|
6
|
+
Author-email: Ben Purinton <purinton@uw.edu>
|
|
7
|
+
Project-URL: Homepage, https://github.com/uw-cryo/asp_plot
|
|
8
|
+
Project-URL: Issues, https://github.com/uw-cryo/asp_plot/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# asp_plot
|
|
17
|
+
|
|
18
|
+
Scripts and notebooks to visualize output from the [NASA Ames Stereo Pipeline (ASP)](https://github.com/NeoGeographyToolkit/StereoPipeline).
|
|
19
|
+
|
|
20
|
+
## Motivation
|
|
21
|
+
|
|
22
|
+
Our objective is to release a modular Python package with a command-line interface (CLI) that can be run automatically on an ASP output directory to prepare a set of standard diagnostic plots, publication-quality output figures, and a pdf report with relevant information, similar to the reports prepared by many commercial SfM software packages (e.g., Agisoft Metashape, Pix4DMapper).
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Status
|
|
26
|
+
|
|
27
|
+
This is a work in progress.
|
|
28
|
+
|
|
29
|
+
The directory `original_code/` contains initial notebooks compiled from recent projects using sample stereo images from the Maxar WorldView, Planet SkySat-C and BlackSky Global constellations.
|
|
30
|
+
|
|
31
|
+
The functionality of these notebooks is being ported to the `asp_plot/` directory, which is the package `asp_plot`.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Installing and testing the package
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
$ git clone git@github.com:uw-cryo/asp_plot.git
|
|
38
|
+
$ cd asp_plot
|
|
39
|
+
$ conda env create -f environment.yml
|
|
40
|
+
$ conda activate asp_plot
|
|
41
|
+
$ pip install -e .
|
|
42
|
+
$ python3 setup.py install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To ensure the install was successful, tests can be run with:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ pytest
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Notebook example usage
|
|
52
|
+
|
|
53
|
+
Examples of the modular usage of the package can be found in the `notebooks/` directory.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## CLI usage
|
|
57
|
+
|
|
58
|
+
A full report and individual plots can be output via the command-line:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
$ asp_plot --directory ./asp_processing \
|
|
62
|
+
--bundle_adjust_directory ba \
|
|
63
|
+
--stereo_directory stereo \
|
|
64
|
+
--map_crs EPSG:32604 \
|
|
65
|
+
--reference_dem ref_dem.tif \
|
|
66
|
+
--plots_directory asp_plots \
|
|
67
|
+
--report_filename asp_plot_report.pdf
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
$ asp_plot --help
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
for details (and defaults) of the command-line flags.
|
asp_plot-0.0.1/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# asp_plot
|
|
2
|
+
|
|
3
|
+
Scripts and notebooks to visualize output from the [NASA Ames Stereo Pipeline (ASP)](https://github.com/NeoGeographyToolkit/StereoPipeline).
|
|
4
|
+
|
|
5
|
+
## Motivation
|
|
6
|
+
|
|
7
|
+
Our objective is to release a modular Python package with a command-line interface (CLI) that can be run automatically on an ASP output directory to prepare a set of standard diagnostic plots, publication-quality output figures, and a pdf report with relevant information, similar to the reports prepared by many commercial SfM software packages (e.g., Agisoft Metashape, Pix4DMapper).
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
This is a work in progress.
|
|
13
|
+
|
|
14
|
+
The directory `original_code/` contains initial notebooks compiled from recent projects using sample stereo images from the Maxar WorldView, Planet SkySat-C and BlackSky Global constellations.
|
|
15
|
+
|
|
16
|
+
The functionality of these notebooks is being ported to the `asp_plot/` directory, which is the package `asp_plot`.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Installing and testing the package
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ git clone git@github.com:uw-cryo/asp_plot.git
|
|
23
|
+
$ cd asp_plot
|
|
24
|
+
$ conda env create -f environment.yml
|
|
25
|
+
$ conda activate asp_plot
|
|
26
|
+
$ pip install -e .
|
|
27
|
+
$ python3 setup.py install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
To ensure the install was successful, tests can be run with:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
$ pytest
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Notebook example usage
|
|
37
|
+
|
|
38
|
+
Examples of the modular usage of the package can be found in the `notebooks/` directory.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## CLI usage
|
|
42
|
+
|
|
43
|
+
A full report and individual plots can be output via the command-line:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$ asp_plot --directory ./asp_processing \
|
|
47
|
+
--bundle_adjust_directory ba \
|
|
48
|
+
--stereo_directory stereo \
|
|
49
|
+
--map_crs EPSG:32604 \
|
|
50
|
+
--reference_dem ref_dem.tif \
|
|
51
|
+
--plots_directory asp_plots \
|
|
52
|
+
--report_filename asp_plot_report.pdf
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
$ asp_plot --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
for details (and defaults) of the command-line flags.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import geopandas as gpd
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
from matplotlib.lines import Line2D
|
|
7
|
+
import contextily as ctx
|
|
8
|
+
from asp_plot.utils import ColorBar, Plotter, save_figure
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReadResiduals:
|
|
12
|
+
def __init__(self, directory, bundle_adjust_directory):
|
|
13
|
+
self.directory = directory
|
|
14
|
+
self.bundle_adjust_directory = bundle_adjust_directory
|
|
15
|
+
|
|
16
|
+
def get_init_final_residuals_csvs(self):
|
|
17
|
+
filenames = [
|
|
18
|
+
"*-initial_residuals_pointmap.csv",
|
|
19
|
+
"*-final_residuals_pointmap.csv",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
paths = [
|
|
23
|
+
glob.glob(
|
|
24
|
+
os.path.join(self.directory, self.bundle_adjust_directory, filename)
|
|
25
|
+
)[0]
|
|
26
|
+
for filename in filenames
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
for path in paths:
|
|
30
|
+
if not os.path.isfile(path):
|
|
31
|
+
raise ValueError(f"Residuals CSV file not found: {path}")
|
|
32
|
+
|
|
33
|
+
resid_init_path, resid_final_path = paths
|
|
34
|
+
return resid_init_path, resid_final_path
|
|
35
|
+
|
|
36
|
+
def get_residuals_gdf(self, csv_path):
|
|
37
|
+
cols = [
|
|
38
|
+
"lon",
|
|
39
|
+
"lat",
|
|
40
|
+
"height_above_datum",
|
|
41
|
+
"mean_residual",
|
|
42
|
+
"num_observations",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
resid_df = pd.read_csv(csv_path, skiprows=2, names=cols)
|
|
46
|
+
|
|
47
|
+
# Need the astype('str') to handle cases where column has dtype of int (without the # from DEM appended to some rows)
|
|
48
|
+
resid_df["from_DEM"] = (
|
|
49
|
+
resid_df["num_observations"].astype("str").str.contains("# from DEM")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
resid_df["num_observations"] = (
|
|
53
|
+
resid_df["num_observations"]
|
|
54
|
+
.astype("str")
|
|
55
|
+
.str.split("#", expand=True)[0]
|
|
56
|
+
.astype(int)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
resid_gdf = gpd.GeoDataFrame(
|
|
60
|
+
resid_df,
|
|
61
|
+
geometry=gpd.points_from_xy(
|
|
62
|
+
resid_df["lon"], resid_df["lat"], crs="EPSG:4326"
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
resid_gdf.filename = os.path.basename(csv_path)
|
|
67
|
+
return resid_gdf
|
|
68
|
+
|
|
69
|
+
def get_init_final_residuals_gdfs(self):
|
|
70
|
+
resid_init_path, resid_final_path = self.get_init_final_residuals_csvs()
|
|
71
|
+
resid_init_gdf = self.get_residuals_gdf(resid_init_path)
|
|
72
|
+
resid_final_gdf = self.get_residuals_gdf(resid_final_path)
|
|
73
|
+
return resid_init_gdf, resid_final_gdf
|
|
74
|
+
|
|
75
|
+
def get_mapproj_residuals_gdf(self):
|
|
76
|
+
path = glob.glob(
|
|
77
|
+
os.path.join(
|
|
78
|
+
self.directory,
|
|
79
|
+
self.bundle_adjust_directory,
|
|
80
|
+
"*-mapproj_match_offsets.txt",
|
|
81
|
+
)
|
|
82
|
+
)[0]
|
|
83
|
+
if not os.path.isfile(path):
|
|
84
|
+
raise ValueError(f"MapProj Residuals TXT file not found: {path}")
|
|
85
|
+
|
|
86
|
+
cols = ["lon", "lat", "height_above_datum", "mapproj_ip_dist_meters"]
|
|
87
|
+
resid_mapprojected_df = pd.read_csv(path, skiprows=2, names=cols)
|
|
88
|
+
resid_mapprojected_gdf = gpd.GeoDataFrame(
|
|
89
|
+
resid_mapprojected_df,
|
|
90
|
+
geometry=gpd.points_from_xy(
|
|
91
|
+
resid_mapprojected_df["lon"],
|
|
92
|
+
resid_mapprojected_df["lat"],
|
|
93
|
+
crs="EPSG:4326",
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
return resid_mapprojected_gdf
|
|
97
|
+
|
|
98
|
+
def get_propagated_triangulation_uncert_df(self):
|
|
99
|
+
path = glob.glob(
|
|
100
|
+
os.path.join(
|
|
101
|
+
self.directory,
|
|
102
|
+
self.bundle_adjust_directory,
|
|
103
|
+
"*-triangulation_uncertainty.txt",
|
|
104
|
+
)
|
|
105
|
+
)[0]
|
|
106
|
+
if not os.path.isfile(path):
|
|
107
|
+
raise ValueError(f"Triangulation Uncertainty TXT file not found: {path}")
|
|
108
|
+
|
|
109
|
+
cols = [
|
|
110
|
+
"left_image",
|
|
111
|
+
"right_image",
|
|
112
|
+
"horiz_error_median",
|
|
113
|
+
"vert_error_median",
|
|
114
|
+
"horiz_error_mean",
|
|
115
|
+
"vert_error_mean",
|
|
116
|
+
"horiz_error_stddev",
|
|
117
|
+
"vert_error_stddev",
|
|
118
|
+
"num_meas",
|
|
119
|
+
]
|
|
120
|
+
resid_triangulation_uncert_df = pd.read_csv(
|
|
121
|
+
path, sep=" ", skiprows=2, names=cols
|
|
122
|
+
)
|
|
123
|
+
return resid_triangulation_uncert_df
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class PlotResiduals(Plotter):
|
|
127
|
+
def __init__(self, geodataframes, **kwargs):
|
|
128
|
+
super().__init__(**kwargs)
|
|
129
|
+
if not isinstance(geodataframes, list):
|
|
130
|
+
raise ValueError("Input must be a list of GeoDataFrames")
|
|
131
|
+
self.geodataframes = geodataframes
|
|
132
|
+
|
|
133
|
+
def get_residual_stats(self, gdf, column_name="mean_residual"):
|
|
134
|
+
stats = gdf[column_name].quantile([0.25, 0.50, 0.84, 0.95]).round(2).tolist()
|
|
135
|
+
return stats
|
|
136
|
+
|
|
137
|
+
def plot_n_residuals(
|
|
138
|
+
self,
|
|
139
|
+
column_name="mean_residual",
|
|
140
|
+
cbar_label="Mean Residual (m)",
|
|
141
|
+
clip_final=True,
|
|
142
|
+
clim=None,
|
|
143
|
+
common_clim=True,
|
|
144
|
+
cmap="inferno",
|
|
145
|
+
map_crs="EPSG:4326",
|
|
146
|
+
save_dir=None,
|
|
147
|
+
fig_fn=None,
|
|
148
|
+
**ctx_kwargs,
|
|
149
|
+
):
|
|
150
|
+
|
|
151
|
+
# Get rows and columns and create subplots
|
|
152
|
+
n = len(self.geodataframes)
|
|
153
|
+
nrows = (n + 3) // 4
|
|
154
|
+
ncols = min(n, 4)
|
|
155
|
+
if n == 1:
|
|
156
|
+
fig, axa = plt.subplots(1, 1, figsize=(8, 6))
|
|
157
|
+
axa = [axa]
|
|
158
|
+
else:
|
|
159
|
+
fig, axa = plt.subplots(
|
|
160
|
+
nrows, ncols, figsize=(4 * ncols, 3 * nrows), sharex=True, sharey=True
|
|
161
|
+
)
|
|
162
|
+
axa = axa.flatten()
|
|
163
|
+
|
|
164
|
+
# Plot each GeoDataFrame
|
|
165
|
+
for i, gdf in enumerate(self.geodataframes):
|
|
166
|
+
gdf = gdf.sort_values(by=column_name).to_crs(map_crs)
|
|
167
|
+
|
|
168
|
+
if clim is None:
|
|
169
|
+
clim = ColorBar().get_clim(gdf[column_name])
|
|
170
|
+
|
|
171
|
+
if common_clim:
|
|
172
|
+
self.plot_geodataframe(
|
|
173
|
+
ax=axa[i],
|
|
174
|
+
gdf=gdf,
|
|
175
|
+
clim=clim,
|
|
176
|
+
column_name=column_name,
|
|
177
|
+
cbar_label=cbar_label,
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
self.plot_geodataframe(
|
|
181
|
+
ax=axa[i], gdf=gdf, column_name=column_name, cbar_label=cbar_label
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
ctx.add_basemap(ax=axa[i], **ctx_kwargs)
|
|
185
|
+
|
|
186
|
+
if clip_final and i == n - 1:
|
|
187
|
+
axa[i].autoscale(False)
|
|
188
|
+
|
|
189
|
+
# Show some statistics and information
|
|
190
|
+
stats = self.get_residual_stats(gdf, column_name)
|
|
191
|
+
stats_text = f"(n={gdf.shape[0]})\n" + "\n".join(
|
|
192
|
+
f"{quantile*100:.0f}th: {stat}"
|
|
193
|
+
for quantile, stat in zip([0.25, 0.50, 0.84, 0.95], stats)
|
|
194
|
+
)
|
|
195
|
+
axa[i].text(
|
|
196
|
+
0.05,
|
|
197
|
+
0.95,
|
|
198
|
+
stats_text,
|
|
199
|
+
transform=axa[i].transAxes,
|
|
200
|
+
fontsize=8,
|
|
201
|
+
verticalalignment="top",
|
|
202
|
+
bbox=dict(boxstyle="round", facecolor="white", alpha=0.8),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Clean up axes and tighten layout
|
|
206
|
+
for i in range(n, nrows * ncols):
|
|
207
|
+
fig.delaxes(axa[i])
|
|
208
|
+
fig.suptitle(self.title, size=10)
|
|
209
|
+
plt.subplots_adjust(wspace=0.2, hspace=0.4)
|
|
210
|
+
fig.tight_layout()
|
|
211
|
+
if save_dir and fig_fn:
|
|
212
|
+
save_figure(fig, save_dir, fig_fn)
|
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import subprocess
|
|
4
|
+
import click
|
|
5
|
+
import contextily as ctx
|
|
6
|
+
from asp_plot.processing_parameters import ProcessingParameters
|
|
7
|
+
from asp_plot.scenes import ScenePlotter
|
|
8
|
+
from asp_plot.bundle_adjust import ReadResiduals, PlotResiduals
|
|
9
|
+
from asp_plot.stereo import StereoPlotter
|
|
10
|
+
from asp_plot.utils import compile_report
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option(
|
|
15
|
+
"--directory",
|
|
16
|
+
prompt=True,
|
|
17
|
+
default="./",
|
|
18
|
+
help="Directory of ASP processing with scenes and sub-directories for bundle adjustment and stereo. Default: current directory",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--bundle_adjust_directory",
|
|
22
|
+
prompt=True,
|
|
23
|
+
default="ba",
|
|
24
|
+
help="Directory of bundle adjustment files. Default: ba",
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--stereo_directory",
|
|
28
|
+
prompt=True,
|
|
29
|
+
default="stereo",
|
|
30
|
+
help="Directory of stereo files. Default: stereo",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--map_crs",
|
|
34
|
+
prompt=True,
|
|
35
|
+
default="EPSG:4326",
|
|
36
|
+
help="Projection for bundle adjustment plots. Default: EPSG:4326",
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--reference_dem",
|
|
40
|
+
prompt=True,
|
|
41
|
+
default="",
|
|
42
|
+
help="Reference DEM used in ASP processing. Default: ",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"--plots_directory",
|
|
46
|
+
prompt=True,
|
|
47
|
+
default="asp_plots",
|
|
48
|
+
help="Directory to put output plots. Default: asp_plots",
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--report_filename",
|
|
52
|
+
prompt=True,
|
|
53
|
+
default="asp_plot_report.pdf",
|
|
54
|
+
help="PDF file to write out for report. Default: asp_plot_report.pdf",
|
|
55
|
+
)
|
|
56
|
+
def main(
|
|
57
|
+
directory,
|
|
58
|
+
bundle_adjust_directory,
|
|
59
|
+
stereo_directory,
|
|
60
|
+
map_crs,
|
|
61
|
+
reference_dem,
|
|
62
|
+
plots_directory,
|
|
63
|
+
report_filename,
|
|
64
|
+
):
|
|
65
|
+
print(f"\n\nProcessing ASP files in {directory}\n\n")
|
|
66
|
+
|
|
67
|
+
plots_directory = os.path.join(directory, plots_directory)
|
|
68
|
+
os.makedirs(plots_directory, exist_ok=True)
|
|
69
|
+
report_pdf_path = os.path.join(directory, report_filename)
|
|
70
|
+
|
|
71
|
+
# Geometry plot
|
|
72
|
+
try:
|
|
73
|
+
subprocess.run(["dg_geom_plot.py", directory])
|
|
74
|
+
subprocess.run(
|
|
75
|
+
f"mv {directory}/*stereo_geom.png {plots_directory}/00_stereo_geom.png",
|
|
76
|
+
shell=True,
|
|
77
|
+
)
|
|
78
|
+
except:
|
|
79
|
+
print(
|
|
80
|
+
"Could not generate stereo geometry plot, check your path for dg_geom_plot.py"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Scene plot
|
|
84
|
+
plotter = ScenePlotter(directory, stereo_directory, title="Mapprojected Scenes")
|
|
85
|
+
|
|
86
|
+
plotter.plot_orthos(save_dir=plots_directory, fig_fn="01_orthos.png")
|
|
87
|
+
|
|
88
|
+
# Bundle adjustment plots
|
|
89
|
+
residuals = ReadResiduals(directory, bundle_adjust_directory)
|
|
90
|
+
resid_init_gdf, resid_final_gdf = residuals.get_init_final_residuals_gdfs()
|
|
91
|
+
resid_mapprojected_gdf = residuals.get_mapproj_residuals_gdf()
|
|
92
|
+
|
|
93
|
+
ctx_kwargs = {
|
|
94
|
+
"crs": map_crs,
|
|
95
|
+
"source": ctx.providers.Esri.WorldImagery,
|
|
96
|
+
"attribution_size": 0,
|
|
97
|
+
"alpha": 0.5,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
plotter = PlotResiduals(
|
|
101
|
+
[resid_init_gdf, resid_final_gdf],
|
|
102
|
+
lognorm=True,
|
|
103
|
+
title="Bundle Adjust Initial and Final Residuals (Log Scale)",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
plotter.plot_n_residuals(
|
|
107
|
+
column_name="mean_residual",
|
|
108
|
+
cbar_label="Mean Residual (m)",
|
|
109
|
+
map_crs=map_crs,
|
|
110
|
+
save_dir=plots_directory,
|
|
111
|
+
fig_fn="02_ba_residuals_log.png",
|
|
112
|
+
**ctx_kwargs,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
plotter.lognorm = False
|
|
116
|
+
plotter.title = "Bundle Adjust Initial and Final Residuals (Linear Scale)"
|
|
117
|
+
|
|
118
|
+
plotter.plot_n_residuals(
|
|
119
|
+
column_name="mean_residual",
|
|
120
|
+
cbar_label="Mean Residual (m)",
|
|
121
|
+
common_clim=False,
|
|
122
|
+
map_crs=map_crs,
|
|
123
|
+
save_dir=plots_directory,
|
|
124
|
+
fig_fn="03_ba_residuals_linear.png",
|
|
125
|
+
**ctx_kwargs,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
plotter = PlotResiduals(
|
|
129
|
+
[resid_mapprojected_gdf],
|
|
130
|
+
title="Bundle Adjust Midpoint distance between\nfinal interest points projected onto reference DEM",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
plotter.plot_n_residuals(
|
|
134
|
+
column_name="mapproj_ip_dist_meters",
|
|
135
|
+
cbar_label="Interest point distance (m)",
|
|
136
|
+
map_crs=map_crs,
|
|
137
|
+
save_dir=plots_directory,
|
|
138
|
+
fig_fn="04_ba_residuals_mapproj_dist.png",
|
|
139
|
+
**ctx_kwargs,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Stereo plots
|
|
143
|
+
plotter = StereoPlotter(
|
|
144
|
+
directory,
|
|
145
|
+
stereo_directory,
|
|
146
|
+
reference_dem,
|
|
147
|
+
out_dem_gsd=1,
|
|
148
|
+
title="Stereo Match Points",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
plotter.plot_match_points(
|
|
152
|
+
save_dir=plots_directory,
|
|
153
|
+
fig_fn="05_stereo_match_points.png",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
plotter.title = "Disparity (pixels)"
|
|
157
|
+
|
|
158
|
+
plotter.plot_disparity(
|
|
159
|
+
unit="pixels",
|
|
160
|
+
quiver=True,
|
|
161
|
+
save_dir=plots_directory,
|
|
162
|
+
fig_fn="06_disparity_pixels.png",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
plotter.title = "Stereo DEM Results"
|
|
166
|
+
|
|
167
|
+
plotter.plot_dem_results(
|
|
168
|
+
save_dir=plots_directory,
|
|
169
|
+
fig_fn="07_stereo_dem_results.png",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Compile report
|
|
173
|
+
processing_parameters = ProcessingParameters(
|
|
174
|
+
directory, bundle_adjust_directory, stereo_directory
|
|
175
|
+
)
|
|
176
|
+
processing_parameters_dict = processing_parameters.from_log_files()
|
|
177
|
+
|
|
178
|
+
compile_report(plots_directory, processing_parameters_dict, report_pdf_path)
|
|
179
|
+
|
|
180
|
+
print(f"\n\nReport saved to {report_pdf_path}\n\n")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
main()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from asp_plot.utils import save_figure
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProcessingParameters:
|
|
8
|
+
def __init__(self, directory, bundle_adjust_directory, stereo_directory):
|
|
9
|
+
self.directory = directory
|
|
10
|
+
self.bundle_adjust_directory = bundle_adjust_directory
|
|
11
|
+
self.stereo_directory = stereo_directory
|
|
12
|
+
self.processing_parameters_dict = {}
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
self.bundle_adjust_log = glob.glob(
|
|
16
|
+
os.path.join(self.directory, self.bundle_adjust_directory, "*log*.txt")
|
|
17
|
+
)[0]
|
|
18
|
+
self.stereo_log = glob.glob(
|
|
19
|
+
os.path.join(self.directory, self.stereo_directory, "*log-stereo*.txt")
|
|
20
|
+
)[0]
|
|
21
|
+
self.point2dem_log = glob.glob(
|
|
22
|
+
os.path.join(
|
|
23
|
+
self.directory, self.stereo_directory, "*log-point2dem*.txt"
|
|
24
|
+
)
|
|
25
|
+
)[0]
|
|
26
|
+
except:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Could not find log files in bundle adjust and stereo directories\nCheck that these *log*.txt files exist in the directories specified"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def from_log_files(self):
|
|
32
|
+
with open(self.bundle_adjust_log, "r") as file:
|
|
33
|
+
content = file.readlines()
|
|
34
|
+
|
|
35
|
+
bundle_adjust_params = ""
|
|
36
|
+
processing_timestamp = ""
|
|
37
|
+
|
|
38
|
+
for line in content:
|
|
39
|
+
if "bundle_adjust" in line and not bundle_adjust_params:
|
|
40
|
+
bundle_adjust_params = line.strip()
|
|
41
|
+
|
|
42
|
+
if "[ console ]" in line and not processing_timestamp:
|
|
43
|
+
date, time = line.split()[0], line.split()[1]
|
|
44
|
+
processing_timestamp = f"{date}-{time[:5].replace(':', '')}"
|
|
45
|
+
|
|
46
|
+
if bundle_adjust_params and processing_timestamp:
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
with open(self.stereo_log, "r") as file:
|
|
50
|
+
content = file.readlines()
|
|
51
|
+
|
|
52
|
+
stereo_params = ""
|
|
53
|
+
|
|
54
|
+
for line in content:
|
|
55
|
+
if "stereo" in line and not stereo_params:
|
|
56
|
+
stereo_params = line.strip()
|
|
57
|
+
|
|
58
|
+
if stereo_params:
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
with open(self.point2dem_log, "r") as file:
|
|
62
|
+
content = file.readlines()
|
|
63
|
+
|
|
64
|
+
point2dem_params = ""
|
|
65
|
+
|
|
66
|
+
for line in content:
|
|
67
|
+
if "point2dem" in line and not point2dem_params:
|
|
68
|
+
point2dem_params = line.strip()
|
|
69
|
+
|
|
70
|
+
if point2dem_params:
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
self.processing_parameters_dict = {
|
|
74
|
+
"bundle_adjust": bundle_adjust_params,
|
|
75
|
+
"stereo": stereo_params,
|
|
76
|
+
"point2dem": point2dem_params,
|
|
77
|
+
"processing_timestamp": processing_timestamp,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return self.processing_parameters_dict
|
|
81
|
+
|
|
82
|
+
def plot_processing_parameters(self, save_dir=None, fig_fn=None):
|
|
83
|
+
fig, axes = plt.subplots(3, 1, figsize=(10, 6))
|
|
84
|
+
ax1, ax2, ax3 = axes.flatten()
|
|
85
|
+
|
|
86
|
+
ax1.axis("off")
|
|
87
|
+
ax1.text(
|
|
88
|
+
0.5,
|
|
89
|
+
0.5,
|
|
90
|
+
f"Processed on: {self.processing_parameters_dict['processing_timestamp']:}\n\nBundle Adjust:\n{self.processing_parameters_dict['bundle_adjust']:}",
|
|
91
|
+
horizontalalignment="center",
|
|
92
|
+
verticalalignment="center",
|
|
93
|
+
fontsize=10,
|
|
94
|
+
wrap=True,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
ax2.axis("off")
|
|
98
|
+
ax2.text(
|
|
99
|
+
0.5,
|
|
100
|
+
0.5,
|
|
101
|
+
f"Stereo:\n{self.processing_parameters_dict['stereo']:}",
|
|
102
|
+
horizontalalignment="center",
|
|
103
|
+
verticalalignment="center",
|
|
104
|
+
fontsize=10,
|
|
105
|
+
wrap=True,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
ax3.axis("off")
|
|
109
|
+
ax3.text(
|
|
110
|
+
0.5,
|
|
111
|
+
0.5,
|
|
112
|
+
f"point2dem:\n{self.processing_parameters_dict['point2dem']:}",
|
|
113
|
+
horizontalalignment="center",
|
|
114
|
+
verticalalignment="center",
|
|
115
|
+
fontsize=10,
|
|
116
|
+
wrap=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
fig.tight_layout()
|
|
120
|
+
if save_dir and fig_fn:
|
|
121
|
+
save_figure(fig, save_dir, fig_fn)
|