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 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.
@@ -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.
@@ -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,5 @@
1
+ import asp_plot.utils
2
+ import asp_plot.processing_parameters
3
+ import asp_plot.scenes
4
+ import asp_plot.bundle_adjust
5
+ import asp_plot.stereo
@@ -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)