cars 1.0.0rc3__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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 (220) hide show
  1. cars/__init__.py +74 -0
  2. cars/applications/__init__.py +40 -0
  3. cars/applications/application.py +117 -0
  4. cars/applications/application_constants.py +29 -0
  5. cars/applications/application_template.py +146 -0
  6. cars/applications/auxiliary_filling/__init__.py +29 -0
  7. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +105 -0
  8. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
  9. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -0
  10. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
  11. cars/applications/dem_generation/__init__.py +30 -0
  12. cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
  13. cars/applications/dem_generation/bulldozer_config/base_config.yaml +46 -0
  14. cars/applications/dem_generation/bulldozer_dem_app.py +641 -0
  15. cars/applications/dem_generation/bulldozer_memory.py +55 -0
  16. cars/applications/dem_generation/dem_generation_algo.py +107 -0
  17. cars/applications/dem_generation/dem_generation_constants.py +32 -0
  18. cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
  19. cars/applications/dense_match_filling/__init__.py +30 -0
  20. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
  21. cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
  22. cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
  23. cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
  24. cars/applications/dense_match_filling/zero_padding_app.py +302 -0
  25. cars/applications/dense_matching/__init__.py +30 -0
  26. cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
  27. cars/applications/dense_matching/census_mccnn_sgm_app.py +1461 -0
  28. cars/applications/dense_matching/cpp/__init__.py +0 -0
  29. cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-313-x86_64-linux-gnu.so +0 -0
  30. cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
  31. cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
  32. cars/applications/dense_matching/cpp/meson.build +9 -0
  33. cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
  34. cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
  35. cars/applications/dense_matching/dense_matching_algo.py +401 -0
  36. cars/applications/dense_matching/dense_matching_constants.py +89 -0
  37. cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
  38. cars/applications/dense_matching/disparity_grid_algo.py +597 -0
  39. cars/applications/dense_matching/loaders/__init__.py +23 -0
  40. cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
  41. cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
  42. cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
  43. cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
  44. cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
  45. cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
  46. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  47. cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
  48. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  49. cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
  50. cars/applications/dsm_filling/__init__.py +32 -0
  51. cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
  52. cars/applications/dsm_filling/border_interpolation_app.py +278 -0
  53. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  54. cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
  55. cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
  56. cars/applications/dsm_merging/__init__.py +28 -0
  57. cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
  58. cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
  59. cars/applications/grid_correction/__init__.py +30 -0
  60. cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
  61. cars/applications/grid_correction/grid_correction_app.py +557 -0
  62. cars/applications/grid_generation/__init__.py +30 -0
  63. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  64. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  65. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  66. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  67. cars/applications/grid_generation/transform_grid.py +88 -0
  68. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  69. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  70. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  71. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  72. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  73. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  74. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  75. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  76. cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
  77. cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
  78. cars/applications/rasterization/__init__.py +30 -0
  79. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  80. cars/applications/rasterization/rasterization_algo.py +534 -0
  81. cars/applications/rasterization/rasterization_constants.py +38 -0
  82. cars/applications/rasterization/rasterization_wrappers.py +639 -0
  83. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  84. cars/applications/resampling/__init__.py +28 -0
  85. cars/applications/resampling/abstract_resampling_app.py +187 -0
  86. cars/applications/resampling/bicubic_resampling_app.py +760 -0
  87. cars/applications/resampling/resampling_algo.py +590 -0
  88. cars/applications/resampling/resampling_constants.py +36 -0
  89. cars/applications/resampling/resampling_wrappers.py +309 -0
  90. cars/applications/sensors_subsampling/__init__.py +32 -0
  91. cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
  92. cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
  93. cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
  94. cars/applications/sparse_matching/__init__.py +30 -0
  95. cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
  96. cars/applications/sparse_matching/sift_app.py +724 -0
  97. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  98. cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
  99. cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
  100. cars/applications/triangulation/__init__.py +32 -0
  101. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  102. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  103. cars/applications/triangulation/pc_transform.py +552 -0
  104. cars/applications/triangulation/triangulation_algo.py +371 -0
  105. cars/applications/triangulation/triangulation_constants.py +38 -0
  106. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  107. cars/bundleadjustment.py +750 -0
  108. cars/cars.py +179 -0
  109. cars/conf/__init__.py +23 -0
  110. cars/conf/geoid/egm96.grd +0 -0
  111. cars/conf/geoid/egm96.grd.hdr +15 -0
  112. cars/conf/input_parameters.py +156 -0
  113. cars/conf/mask_cst.py +35 -0
  114. cars/core/__init__.py +23 -0
  115. cars/core/cars_logging.py +402 -0
  116. cars/core/constants.py +191 -0
  117. cars/core/constants_disparity.py +50 -0
  118. cars/core/datasets.py +140 -0
  119. cars/core/geometry/__init__.py +27 -0
  120. cars/core/geometry/abstract_geometry.py +1130 -0
  121. cars/core/geometry/shareloc_geometry.py +604 -0
  122. cars/core/inputs.py +568 -0
  123. cars/core/outputs.py +176 -0
  124. cars/core/preprocessing.py +722 -0
  125. cars/core/projection.py +843 -0
  126. cars/core/roi_tools.py +215 -0
  127. cars/core/tiling.py +774 -0
  128. cars/core/utils.py +164 -0
  129. cars/data_structures/__init__.py +23 -0
  130. cars/data_structures/cars_dataset.py +1544 -0
  131. cars/data_structures/cars_dict.py +74 -0
  132. cars/data_structures/corresponding_tiles_tools.py +186 -0
  133. cars/data_structures/dataframe_converter.py +185 -0
  134. cars/data_structures/format_transformation.py +297 -0
  135. cars/devibrate.py +689 -0
  136. cars/extractroi.py +264 -0
  137. cars/orchestrator/__init__.py +23 -0
  138. cars/orchestrator/achievement_tracker.py +125 -0
  139. cars/orchestrator/cluster/__init__.py +37 -0
  140. cars/orchestrator/cluster/abstract_cluster.py +250 -0
  141. cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
  142. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  143. cars/orchestrator/cluster/dask_config/README.md +94 -0
  144. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  145. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  146. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  147. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  148. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  149. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  150. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  151. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  152. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  153. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  154. cars/orchestrator/cluster/log_wrapper.py +728 -0
  155. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  156. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  157. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  158. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  159. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  160. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
  161. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  162. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  163. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  164. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  165. cars/orchestrator/memory_tools.py +47 -0
  166. cars/orchestrator/orchestrator.py +755 -0
  167. cars/orchestrator/orchestrator_constants.py +29 -0
  168. cars/orchestrator/registry/__init__.py +23 -0
  169. cars/orchestrator/registry/abstract_registry.py +143 -0
  170. cars/orchestrator/registry/compute_registry.py +106 -0
  171. cars/orchestrator/registry/id_generator.py +116 -0
  172. cars/orchestrator/registry/replacer_registry.py +213 -0
  173. cars/orchestrator/registry/saver_registry.py +363 -0
  174. cars/orchestrator/registry/unseen_registry.py +118 -0
  175. cars/orchestrator/tiles_profiler.py +279 -0
  176. cars/pipelines/__init__.py +26 -0
  177. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  178. cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
  179. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  180. cars/pipelines/default/__init__.py +26 -0
  181. cars/pipelines/default/default_pipeline.py +1095 -0
  182. cars/pipelines/filling/__init__.py +26 -0
  183. cars/pipelines/filling/filling.py +981 -0
  184. cars/pipelines/formatting/__init__.py +26 -0
  185. cars/pipelines/formatting/formatting.py +190 -0
  186. cars/pipelines/merging/__init__.py +26 -0
  187. cars/pipelines/merging/merging.py +439 -0
  188. cars/pipelines/parameters/__init__.py +0 -0
  189. cars/pipelines/parameters/advanced_parameters.py +256 -0
  190. cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
  191. cars/pipelines/parameters/application_parameters.py +72 -0
  192. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  193. cars/pipelines/parameters/dsm_inputs.py +349 -0
  194. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  195. cars/pipelines/parameters/output_constants.py +52 -0
  196. cars/pipelines/parameters/output_parameters.py +435 -0
  197. cars/pipelines/parameters/sensor_inputs.py +859 -0
  198. cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
  199. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  200. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  201. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  202. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  203. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  204. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  205. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  206. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  207. cars/pipelines/pipeline.py +119 -0
  208. cars/pipelines/pipeline_constants.py +38 -0
  209. cars/pipelines/pipeline_template.py +135 -0
  210. cars/pipelines/subsampling/__init__.py +26 -0
  211. cars/pipelines/subsampling/subsampling.py +358 -0
  212. cars/pipelines/surface_modeling/__init__.py +26 -0
  213. cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
  214. cars/pipelines/tie_points/__init__.py +26 -0
  215. cars/pipelines/tie_points/tie_points.py +536 -0
  216. cars/starter.py +167 -0
  217. cars-1.0.0rc3.dist-info/METADATA +289 -0
  218. cars-1.0.0rc3.dist-info/RECORD +220 -0
  219. cars-1.0.0rc3.dist-info/WHEEL +6 -0
  220. cars-1.0.0rc3.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,2098 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf8
3
+ #
4
+ # Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
5
+ #
6
+ # This file is part of CARS
7
+ # (see https://github.com/CNES/cars).
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ # pylint: disable=too-many-lines
22
+ # attribute-defined-outside-init is disabled so that we can create and use
23
+ # attributes however we need, to stick to the "everything is attribute" logic
24
+ # introduced in issue#895
25
+ # pylint: disable=attribute-defined-outside-init
26
+ # pylint: disable=too-many-nested-blocks
27
+ # pylint: disable=C0302
28
+ """
29
+ CARS surface modeling pipeline class file
30
+ """
31
+ # Standard imports
32
+ from __future__ import print_function
33
+
34
+ import copy
35
+ import logging
36
+ import os
37
+ import warnings
38
+ from collections import OrderedDict
39
+
40
+ import numpy as np
41
+ import rasterio
42
+ from json_checker import Checker, OptionalKey
43
+ from rasterio.errors import NodataShadowWarning
44
+
45
+ import cars.applications.sparse_matching.sparse_matching_constants as sm_cst
46
+ from cars import __version__
47
+
48
+ # CARS imports
49
+ from cars.applications import application_constants
50
+ from cars.applications.application import Application
51
+ from cars.applications.dem_generation import (
52
+ dem_generation_wrappers as dem_wrappers,
53
+ )
54
+ from cars.core import preprocessing, projection, roi_tools
55
+ from cars.core.geometry.abstract_geometry import AbstractGeometry
56
+ from cars.core.inputs import get_descriptions_bands
57
+ from cars.core.utils import safe_makedirs
58
+ from cars.data_structures import cars_dataset
59
+ from cars.orchestrator import orchestrator
60
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
61
+ from cars.pipelines.parameters import advanced_parameters
62
+ from cars.pipelines.parameters import advanced_parameters_constants as adv_cst
63
+ from cars.pipelines.parameters import application_parameters
64
+ from cars.pipelines.parameters import output_constants as out_cst
65
+ from cars.pipelines.parameters import output_parameters, sensor_inputs
66
+ from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
67
+ from cars.pipelines.parameters.advanced_parameters_constants import (
68
+ USE_ENDOGENOUS_DEM,
69
+ )
70
+ from cars.pipelines.parameters.output_constants import AUXILIARY
71
+ from cars.pipelines.pipeline import Pipeline
72
+ from cars.pipelines.pipeline_constants import (
73
+ ADVANCED,
74
+ APPLICATIONS,
75
+ INPUT,
76
+ ORCHESTRATOR,
77
+ OUTPUT,
78
+ TIE_POINTS,
79
+ )
80
+ from cars.pipelines.pipeline_template import PipelineTemplate
81
+ from cars.pipelines.tie_points.tie_points import TiePointsPipeline
82
+
83
+ PIPELINE = "surface_modeling"
84
+
85
+
86
+ @Pipeline.register(PIPELINE)
87
+ class SurfaceModelingPipeline(PipelineTemplate):
88
+ """
89
+ SurfaceModelingPipeline
90
+ """
91
+
92
+ # pylint: disable=too-many-instance-attributes
93
+
94
+ def __init__(
95
+ self,
96
+ conf,
97
+ config_dir=None,
98
+ ): # noqa: C901
99
+ """
100
+ Creates pipeline
101
+
102
+ Directly creates class attributes:
103
+ used_conf
104
+ generate_terrain_products
105
+ debug_with_roi
106
+ save_output_dsm
107
+ save_output_depth_map
108
+ save_output_point_clouds
109
+ geom_plugin_without_dem_and_geoid
110
+ geom_plugin_with_dem_and_geoid
111
+
112
+ :param pipeline_name: name of the pipeline.
113
+ :type pipeline_name: str
114
+ :param cfg: configuration {'matching_cost_method': value}
115
+ :type cfg: dictionary
116
+ :param config_dir: path to dir containing json/yaml
117
+ :type config_dir: str
118
+ """
119
+
120
+ # Used conf
121
+ self.used_conf = {}
122
+ # refined conf
123
+ self.refined_conf = {}
124
+
125
+ # Transform relative path to absolute path
126
+ if config_dir is not None:
127
+ config_dir = os.path.abspath(config_dir)
128
+ self.config_dir = config_dir
129
+
130
+ # Check global conf
131
+ self.check_global_schema(conf)
132
+
133
+ if PIPELINE in conf:
134
+ self.check_pipeline_conf(conf)
135
+
136
+ self.out_dir = conf[OUTPUT][out_cst.OUT_DIRECTORY]
137
+
138
+ # Check conf orchestrator
139
+ self.used_conf[ORCHESTRATOR] = self.check_orchestrator(
140
+ conf.get(ORCHESTRATOR, None)
141
+ )
142
+
143
+ # Check conf inputs
144
+ inputs = self.check_inputs(conf[INPUT], config_dir=config_dir)
145
+ self.used_conf[INPUT] = inputs
146
+ self.refined_conf[INPUT] = copy.deepcopy(inputs)
147
+
148
+ initial_elevation = (
149
+ inputs[sens_cst.INITIAL_ELEVATION]["dem"] is not None
150
+ )
151
+ self.elevation_delta_lower_bound = -500 if initial_elevation else -1000
152
+ self.elevation_delta_upper_bound = 1000 if initial_elevation else 9000
153
+
154
+ self.dem_scaling_coeff = None
155
+ if inputs[sens_cst.LOW_RES_DSM] is not None:
156
+ low_res_dsm = rasterio.open(inputs[sens_cst.LOW_RES_DSM])
157
+ self.dem_scaling_coeff = np.mean(low_res_dsm.res) * 2
158
+
159
+ # Init tie points pipelines
160
+ if TIE_POINTS in conf and conf[TIE_POINTS] is None:
161
+ self.tie_points_pipelines = None
162
+ else:
163
+ self.tie_points_pipelines = {}
164
+ for key1, key2 in inputs[sens_cst.PAIRING]:
165
+ pair_key = key1 + "_" + key2
166
+ tie_points_config = {}
167
+ tie_points_input = {}
168
+ tie_points_input[sens_cst.SENSORS] = {
169
+ key1: inputs[sens_cst.SENSORS][key1],
170
+ key2: inputs[sens_cst.SENSORS][key2],
171
+ }
172
+ tie_points_input[sens_cst.PAIRING] = [[key1, key2]]
173
+ tie_points_input[sens_cst.LOADERS] = inputs[sens_cst.LOADERS]
174
+ tie_points_input[sens_cst.INITIAL_ELEVATION] = inputs[
175
+ sens_cst.INITIAL_ELEVATION
176
+ ]
177
+ tie_points_config[INPUT] = tie_points_input
178
+ tie_points_output = os.path.join(
179
+ self.out_dir, TIE_POINTS, pair_key
180
+ )
181
+ tie_points_config["output"] = {"directory": tie_points_output}
182
+ if TIE_POINTS in conf:
183
+ tie_points_config[TIE_POINTS] = conf[TIE_POINTS]
184
+ self.tie_points_pipelines[pair_key] = TiePointsPipeline(
185
+ tie_points_config,
186
+ config_dir=self.config_dir,
187
+ )
188
+ self.used_conf[TIE_POINTS] = {}
189
+ self.used_conf[TIE_POINTS][APPLICATIONS] = (
190
+ self.tie_points_pipelines[pair_key].used_conf[TIE_POINTS][
191
+ APPLICATIONS
192
+ ]
193
+ )
194
+ self.used_conf[TIE_POINTS][ADVANCED] = (
195
+ self.tie_points_pipelines[pair_key].used_conf[TIE_POINTS][
196
+ ADVANCED
197
+ ]
198
+ )
199
+
200
+ # Check advanced parameters
201
+ # TODO static method in the base class
202
+ output_dem_dir = os.path.join(
203
+ self.out_dir, "dump_dir", "initial_elevation"
204
+ )
205
+ pipeline_conf = conf.get(PIPELINE, {})
206
+ self.used_conf[PIPELINE] = {}
207
+ safe_makedirs(output_dem_dir)
208
+ (
209
+ inputs,
210
+ advanced,
211
+ self.geometry_plugin,
212
+ self.geom_plugin_without_dem_and_geoid,
213
+ self.geom_plugin_with_dem_and_geoid,
214
+ self.scaling_coeff,
215
+ self.land_cover_map,
216
+ self.classification_to_config_mapping,
217
+ ) = advanced_parameters.check_advanced_parameters(
218
+ inputs,
219
+ pipeline_conf.get(ADVANCED, {}),
220
+ output_dem_dir=output_dem_dir,
221
+ )
222
+
223
+ self.used_conf[PIPELINE][ADVANCED] = advanced
224
+
225
+ self.refined_conf[ADVANCED] = copy.deepcopy(advanced)
226
+ # Refined conf: resolutions 1
227
+ self.refined_conf[ADVANCED][adv_cst.RESOLUTIONS] = [1]
228
+
229
+ # Get ROI
230
+ (
231
+ self.input_roi_poly,
232
+ self.input_roi_epsg,
233
+ ) = roi_tools.generate_roi_poly_from_inputs(
234
+ self.used_conf[INPUT][sens_cst.ROI]
235
+ )
236
+
237
+ self.debug_with_roi = self.used_conf[PIPELINE][ADVANCED][
238
+ adv_cst.DEBUG_WITH_ROI
239
+ ]
240
+
241
+ # Check conf output
242
+ (
243
+ output,
244
+ self.scaling_coeff,
245
+ ) = self.check_output(inputs, conf[OUTPUT], self.scaling_coeff)
246
+
247
+ self.used_conf[OUTPUT] = output
248
+ self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
249
+ self.dump_dir = os.path.join(self.out_dir, "dump_dir")
250
+
251
+ self.refined_conf[OUTPUT] = copy.deepcopy(output)
252
+
253
+ prod_level = output[out_cst.PRODUCT_LEVEL]
254
+
255
+ self.save_output_dsm = "dsm" in prod_level
256
+ self.save_output_depth_map = "depth_map" in prod_level
257
+ self.save_output_point_cloud = "point_cloud" in prod_level
258
+
259
+ self.output_level_none = not (
260
+ self.save_output_dsm
261
+ or self.save_output_depth_map
262
+ or self.save_output_point_cloud
263
+ )
264
+
265
+ # Used classification values, for filling -> will be masked
266
+ self.used_classif_values_for_filling = self.get_classif_values_filling(
267
+ self.used_conf[INPUT]
268
+ )
269
+
270
+ self.phasing = self.used_conf[PIPELINE][ADVANCED][adv_cst.PHASING]
271
+
272
+ self.compute_depth_map = not self.output_level_none
273
+
274
+ if self.output_level_none:
275
+ self.infer_conditions_from_applications(conf[PIPELINE])
276
+
277
+ self.save_all_intermediate_data = self.used_conf[PIPELINE][ADVANCED][
278
+ adv_cst.SAVE_INTERMEDIATE_DATA
279
+ ]
280
+
281
+ self.save_all_point_clouds_by_pair = self.used_conf[OUTPUT].get(
282
+ out_cst.SAVE_BY_PAIR, False
283
+ )
284
+
285
+ # Check conf application
286
+ application_conf = self.check_applications(
287
+ pipeline_conf.get(APPLICATIONS, {})
288
+ )
289
+
290
+ # Check conf application vs inputs application
291
+ application_conf = self.check_applications_with_inputs(
292
+ self.used_conf[INPUT], application_conf
293
+ )
294
+
295
+ self.used_conf[PIPELINE][APPLICATIONS] = application_conf
296
+
297
+ def check_pipeline_conf(self, conf):
298
+ """
299
+ Check pipeline configuration
300
+ """
301
+ # Validate inputs
302
+ pipeline_schema = {
303
+ OptionalKey(ADVANCED): dict,
304
+ OptionalKey(APPLICATIONS): dict,
305
+ }
306
+
307
+ checker_inputs = Checker(pipeline_schema)
308
+ checker_inputs.validate(conf[PIPELINE])
309
+
310
+ def quit_on_app(self, app_name):
311
+ """
312
+ Returns whether the pipeline should end after
313
+ the application was called.
314
+
315
+ Only works if the output_level is empty, so that
316
+ the control is instead given to
317
+ """
318
+
319
+ if not self.output_level_none:
320
+ # custom quit step was not set, never quit early
321
+ return False
322
+
323
+ return self.app_values[app_name] >= self.last_application_to_run
324
+
325
+ def infer_conditions_from_applications(self, conf):
326
+ """
327
+ Fills the condition booleans used later in the pipeline by going
328
+ through the applications and infering which application we should
329
+ end the pipeline on.
330
+ """
331
+
332
+ self.last_application_to_run = 0
333
+
334
+ sensor_to_depth_apps = {
335
+ "dem_generation": 1,
336
+ "grid_generation": 2,
337
+ "grid_correction": 3,
338
+ "resampling": 4,
339
+ "ground_truth_reprojection": 7,
340
+ "dense_matching": 9,
341
+ "dense_match_filling": 10,
342
+ "triangulation": 12,
343
+ "point_cloud_outlier_removal.1": 13,
344
+ "point_cloud_outlier_removal.2": 14,
345
+ }
346
+
347
+ depth_to_dsm_apps = {
348
+ "point_cloud_rasterization": 16,
349
+ "dsm_filling.1": 18,
350
+ "dsm_filling.2": 19,
351
+ "dsm_filling.3": 20,
352
+ "auxiliary_filling": 21,
353
+ }
354
+
355
+ self.app_values = {}
356
+ self.app_values.update(sensor_to_depth_apps)
357
+ self.app_values.update(depth_to_dsm_apps)
358
+
359
+ app_conf = conf.get(APPLICATIONS, {})
360
+ for key in app_conf:
361
+
362
+ if adv_cst.SAVE_INTERMEDIATE_DATA not in app_conf[key]:
363
+ continue
364
+
365
+ if not app_conf[key][adv_cst.SAVE_INTERMEDIATE_DATA]:
366
+ continue
367
+
368
+ if key in sensor_to_depth_apps:
369
+ self.compute_depth_map = True
370
+ self.last_application_to_run = max(
371
+ self.last_application_to_run, self.app_values[key]
372
+ )
373
+
374
+ elif key in depth_to_dsm_apps:
375
+ self.compute_depth_map = True
376
+
377
+ # enabled to start the depth map to dsm process
378
+ self.save_output_dsm = True
379
+
380
+ self.last_application_to_run = max(
381
+ self.last_application_to_run, self.app_values[key]
382
+ )
383
+
384
+ else:
385
+ warn_msg = (
386
+ "The application {} was not recognized. Its configuration"
387
+ "will be ignored."
388
+ ).format(key)
389
+ logging.warning(warn_msg)
390
+
391
+ if not (self.compute_depth_map or self.save_output_dsm):
392
+ log_msg = (
393
+ "No product level was given. CARS has not detected any "
394
+ "data you wish to save. No computation will be done."
395
+ )
396
+ logging.info(log_msg)
397
+ else:
398
+ log_msg = (
399
+ "No product level was given. CARS has detected that you "
400
+ + "wish to run up to the {} application.".format(
401
+ next(
402
+ k
403
+ for k, v in self.app_values.items()
404
+ if v == self.last_application_to_run
405
+ )
406
+ )
407
+ )
408
+ logging.warning(log_msg)
409
+
410
+ @staticmethod
411
+ def check_inputs(conf, config_dir=None):
412
+ """
413
+ Check the inputs given
414
+
415
+ :param conf: configuration of inputs
416
+ :type conf: dict
417
+ :param config_dir: directory of used json/yaml, if
418
+ user filled paths with relative paths
419
+ :type config_dir: str
420
+
421
+ :return: overloaded inputs
422
+ :rtype: dict
423
+ """
424
+ return sensor_inputs.sensors_check_inputs(conf, config_dir=config_dir)
425
+
426
+ def save_configurations(self):
427
+ """
428
+ Save used_conf and refined_conf configurations
429
+ """
430
+
431
+ cars_dataset.save_dict(
432
+ self.used_conf,
433
+ os.path.join(self.out_dir, "current_res_used_conf.yaml"),
434
+ )
435
+ cars_dataset.save_dict(
436
+ self.refined_conf,
437
+ os.path.join(self.out_dir, "refined_conf.yaml"),
438
+ )
439
+
440
+ def check_output(self, inputs, conf, scaling_coeff):
441
+ """
442
+ Check the output given
443
+
444
+ :param conf: configuration of output
445
+ :type conf: dict
446
+ :param scaling_coeff: scaling factor for resolution
447
+ :type scaling_coeff: float
448
+ :return: overloader output
449
+ :rtype: dict
450
+ """
451
+ return output_parameters.check_output_parameters(
452
+ inputs, conf, scaling_coeff
453
+ )
454
+
455
+ def check_applications( # noqa: C901 : too complex
456
+ self,
457
+ conf,
458
+ ):
459
+ """
460
+ Check the given configuration for applications,
461
+ and generates needed applications for pipeline.
462
+
463
+ :param conf: configuration of applications
464
+ :type conf: dict
465
+ """
466
+ scaling_coeff = self.scaling_coeff
467
+
468
+ needed_applications = application_parameters.get_needed_apps(
469
+ True,
470
+ self.save_output_dsm,
471
+ self.save_output_point_cloud,
472
+ conf,
473
+ )
474
+
475
+ # Check if all specified applications are used
476
+ # Application in terrain_application are note used in
477
+ # the sensors_to_dense_depth_maps pipeline
478
+ for app_key in conf.keys():
479
+ if app_key not in needed_applications:
480
+ msg = (
481
+ f"No {app_key} application used in the "
482
+ + "default Cars pipeline"
483
+ )
484
+ logging.error(msg)
485
+ raise NameError(msg)
486
+
487
+ # Initialize used config
488
+ used_conf = {}
489
+
490
+ for app_key in needed_applications:
491
+ used_conf[app_key] = conf.get(app_key, {})
492
+ if used_conf[app_key] is None:
493
+ continue
494
+ used_conf[app_key]["save_intermediate_data"] = (
495
+ self.save_all_intermediate_data
496
+ or used_conf[app_key].get("save_intermediate_data", False)
497
+ )
498
+
499
+ self.epipolar_grid_generation_application = None
500
+ self.resampling_application = None
501
+ self.ground_truth_reprojection = None
502
+ self.dense_match_filling = None
503
+ self.sparse_mtch_app = None
504
+ self.dense_matching_app = None
505
+ self.triangulation_application = None
506
+ self.dem_generation_application = None
507
+ self.pc_outlier_removal_apps = {}
508
+ self.rasterization_application = None
509
+ self.pc_fusion_application = None
510
+ self.grid_correction_app = None
511
+
512
+ # Epipolar grid generation
513
+ self.epipolar_grid_generation_application = Application(
514
+ "grid_generation",
515
+ cfg=used_conf.get("grid_generation", {}),
516
+ scaling_coeff=scaling_coeff,
517
+ )
518
+ used_conf["grid_generation"] = (
519
+ self.epipolar_grid_generation_application.get_conf()
520
+ )
521
+
522
+ # Epipolar grid correction
523
+ self.grid_correction_app = Application(
524
+ "grid_correction",
525
+ cfg=used_conf.get("grid_correction", {}),
526
+ )
527
+ used_conf["grid_correction"] = self.grid_correction_app.get_conf()
528
+
529
+ # image resampling
530
+ self.resampling_application = Application(
531
+ "resampling",
532
+ cfg=used_conf.get("resampling", {}),
533
+ scaling_coeff=scaling_coeff,
534
+ )
535
+ used_conf["resampling"] = self.resampling_application.get_conf()
536
+
537
+ # ground truth disparity map computation
538
+ if self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
539
+ used_conf["ground_truth_reprojection"][
540
+ "save_intermediate_data"
541
+ ] = True
542
+
543
+ if isinstance(
544
+ self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM],
545
+ str,
546
+ ):
547
+ self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM] = {
548
+ "dsm": self.used_conf[PIPELINE][ADVANCED][
549
+ adv_cst.GROUND_TRUTH_DSM
550
+ ]
551
+ }
552
+
553
+ self.ground_truth_reprojection = Application(
554
+ "ground_truth_reprojection",
555
+ cfg=used_conf.get("ground_truth_reprojection", {}),
556
+ scaling_coeff=scaling_coeff,
557
+ )
558
+
559
+ # disparity filling
560
+ self.dense_match_filling = Application(
561
+ "dense_match_filling",
562
+ cfg=used_conf.get(
563
+ "dense_match_filling",
564
+ {"method": "zero_padding"},
565
+ ),
566
+ scaling_coeff=scaling_coeff,
567
+ )
568
+ used_conf["dense_match_filling"] = self.dense_match_filling.get_conf()
569
+
570
+ # Matching
571
+ generate_performance_map = bool(
572
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][
573
+ out_cst.AUX_PERFORMANCE_MAP
574
+ ]
575
+ )
576
+
577
+ generate_ambiguity = (
578
+ self.used_conf[OUTPUT]
579
+ .get(out_cst.AUXILIARY, {})
580
+ .get(out_cst.AUX_AMBIGUITY, False)
581
+ )
582
+ dense_matching_config = used_conf.get("dense_matching", {})
583
+ if generate_ambiguity is True:
584
+ dense_matching_config["generate_ambiguity"] = True
585
+
586
+ if (
587
+ generate_performance_map is True
588
+ and dense_matching_config.get("performance_map_method", None)
589
+ is None
590
+ ):
591
+ dense_matching_config["performance_map_method"] = "risk"
592
+
593
+ # particular case for some epipolar resolutions
594
+ if not dense_matching_config:
595
+ used_conf["dense_matching"]["performance_map_method"] = [
596
+ "risk",
597
+ "intervals",
598
+ ]
599
+
600
+ self.dense_matching_app = Application(
601
+ "dense_matching",
602
+ cfg=dense_matching_config,
603
+ scaling_coeff=scaling_coeff,
604
+ )
605
+ used_conf["dense_matching"] = self.dense_matching_app.get_conf()
606
+
607
+ # Triangulation
608
+ self.triangulation_application = Application(
609
+ "triangulation",
610
+ cfg=used_conf.get("triangulation", {}),
611
+ scaling_coeff=scaling_coeff,
612
+ )
613
+ used_conf["triangulation"] = self.triangulation_application.get_conf()
614
+
615
+ # MNT generation
616
+ self.dem_generation_application = Application(
617
+ "dem_generation",
618
+ cfg=used_conf.get("dem_generation", {}),
619
+ scaling_coeff=(
620
+ self.dem_scaling_coeff
621
+ if self.dem_scaling_coeff is not None
622
+ else scaling_coeff
623
+ ),
624
+ )
625
+ used_conf["dem_generation"] = self.dem_generation_application.get_conf()
626
+
627
+ for app_key, app_conf in used_conf.items():
628
+ if not app_key.startswith("point_cloud_outlier_removal"):
629
+ continue
630
+
631
+ if app_conf is None:
632
+ self.pc_outlier_removal_apps = {}
633
+ # keep over multiple runs
634
+ used_conf["point_cloud_outlier_removal"] = None
635
+ break
636
+
637
+ if app_key in self.pc_outlier_removal_apps:
638
+ msg = (
639
+ f"The key {app_key} is defined twice in the input "
640
+ "configuration."
641
+ )
642
+ logging.error(msg)
643
+ raise NameError(msg)
644
+
645
+ if app_key[27:] == ".1":
646
+ app_conf.setdefault("method", "small_components")
647
+ if app_key[27:] == ".2":
648
+ app_conf.setdefault("method", "statistical")
649
+
650
+ self.pc_outlier_removal_apps[app_key] = Application(
651
+ "point_cloud_outlier_removal",
652
+ cfg=app_conf,
653
+ scaling_coeff=scaling_coeff,
654
+ )
655
+ used_conf[app_key] = self.pc_outlier_removal_apps[
656
+ app_key
657
+ ].get_conf()
658
+
659
+ methods_str = "\n".join(
660
+ f" - {k}={a.used_method}"
661
+ for k, a in self.pc_outlier_removal_apps.items()
662
+ )
663
+ logging.info(
664
+ "{} point cloud outlier removal apps registered:\n{}".format(
665
+ len(self.pc_outlier_removal_apps), methods_str
666
+ )
667
+ )
668
+
669
+ if self.save_output_dsm:
670
+ # Rasterization
671
+ self.rasterization_application = Application(
672
+ "point_cloud_rasterization",
673
+ cfg=used_conf.get("point_cloud_rasterization", {}),
674
+ scaling_coeff=scaling_coeff,
675
+ )
676
+ used_conf["point_cloud_rasterization"] = (
677
+ self.rasterization_application.get_conf()
678
+ )
679
+
680
+ return used_conf
681
+
682
+ def get_classif_values_filling(self, inputs):
683
+ """
684
+ Get values in classif, used for filling
685
+
686
+ :param inputs: inputs
687
+ :type inputs: dict
688
+
689
+ :return: list of values
690
+ :rtype: list
691
+ """
692
+
693
+ filling_classif_values = []
694
+
695
+ if sens_cst.FILLING not in inputs or inputs[sens_cst.FILLING] is None:
696
+ logging.info("No filling in input configuration")
697
+ return None
698
+
699
+ filling_classif_values = []
700
+ for _, classif_values in inputs[sens_cst.FILLING].items():
701
+ # Add new value to filling bands
702
+ if classif_values is not None:
703
+ if isinstance(classif_values, str):
704
+ classif_values = [classif_values]
705
+ filling_classif_values += classif_values
706
+
707
+ simplified_list = list(OrderedDict.fromkeys(filling_classif_values))
708
+ res_as_string_list = [str(value) for value in simplified_list]
709
+ return res_as_string_list
710
+
711
+ def check_applications_with_inputs( # noqa: C901 : too complex
712
+ self, inputs_conf, application_conf
713
+ ):
714
+ """
715
+ Check for each application the input and output configuration
716
+ consistency
717
+
718
+ :param inputs_conf: inputs checked configuration
719
+ :type inputs_conf: dict
720
+ :param application_conf: application checked configuration
721
+ :type application_conf: dict
722
+ """
723
+
724
+ # check classification application parameter compare
725
+ # to each sensors inputs classification list
726
+ for application_key in application_conf:
727
+ if application_conf[application_key] is None:
728
+ continue
729
+ if "classification" in application_conf[application_key]:
730
+ for item in inputs_conf["sensors"]:
731
+ if "classification" in inputs_conf["sensors"][item].keys():
732
+ if inputs_conf["sensors"][item]["classification"]:
733
+ descriptions = get_descriptions_bands(
734
+ inputs_conf["sensors"][item]["classification"]
735
+ )
736
+ if application_conf[application_key][
737
+ "classification"
738
+ ] and not set(
739
+ application_conf[application_key][
740
+ "classification"
741
+ ]
742
+ ).issubset(
743
+ set(descriptions) | {"nodata"}
744
+ ):
745
+ raise RuntimeError(
746
+ "The {} bands description {} ".format(
747
+ inputs_conf["sensors"][item][
748
+ "classification"
749
+ ],
750
+ list(descriptions),
751
+ )
752
+ + "and the {} config are not ".format(
753
+ application_key
754
+ )
755
+ + "consistent: {}".format(
756
+ application_conf[application_key][
757
+ "classification"
758
+ ]
759
+ )
760
+ )
761
+ for key1, key2 in inputs_conf["pairing"]:
762
+ corr_cfg = self.dense_matching_app.loader.get_conf()
763
+ nodata_left = inputs_conf["sensors"][key2]["image"]["no_data"]
764
+ nodata_right = inputs_conf["sensors"][key2]["image"]["no_data"]
765
+ bands_left = list(
766
+ inputs_conf["sensors"][key1]["image"]["bands"].keys()
767
+ )
768
+ bands_right = list(
769
+ inputs_conf["sensors"][key2]["image"]["bands"].keys()
770
+ )
771
+ values_classif_left = None
772
+ values_classif_right = None
773
+ if (
774
+ "classification" in inputs_conf["sensors"][key1]
775
+ and inputs_conf["sensors"][key1]["classification"] is not None
776
+ ):
777
+ values_classif_left = inputs_conf["sensors"][key1][
778
+ "classification"
779
+ ]["values"]
780
+ values_classif_left = list(map(str, values_classif_left))
781
+ if (
782
+ "classification" in inputs_conf["sensors"][key2]
783
+ and inputs_conf["sensors"][key2]["classification"] is not None
784
+ ):
785
+ values_classif_right = inputs_conf["sensors"][key2][
786
+ "classification"
787
+ ]["values"]
788
+ values_classif_right = list(map(str, values_classif_right))
789
+ self.dense_matching_app.corr_config = (
790
+ self.dense_matching_app.loader.check_conf(
791
+ corr_cfg,
792
+ nodata_left,
793
+ nodata_right,
794
+ bands_left,
795
+ bands_right,
796
+ values_classif_left,
797
+ values_classif_right,
798
+ )
799
+ )
800
+
801
+ return application_conf
802
+
803
+ def sensor_to_depth_maps(self): # noqa: C901
804
+ """
805
+ Creates the depth map from the sensor images given in the input,
806
+ by following the CARS pipeline's steps.
807
+ """
808
+ # pylint:disable=too-many-return-statements
809
+ inputs = self.used_conf[INPUT]
810
+ output = self.used_conf[OUTPUT]
811
+
812
+ # Initialize epsg for terrain tiles
813
+ self.phasing = self.used_conf[PIPELINE][ADVANCED][adv_cst.PHASING]
814
+
815
+ if self.phasing is not None:
816
+ self.epsg = self.phasing["epsg"]
817
+ else:
818
+ self.epsg = output[out_cst.EPSG]
819
+
820
+ if self.epsg is not None:
821
+ # Compute roi polygon, in output EPSG
822
+ self.roi_poly = preprocessing.compute_roi_poly(
823
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
824
+ )
825
+
826
+ self.resolution = output[out_cst.RESOLUTION]
827
+
828
+ # List of terrain roi corresponding to each epipolar pair
829
+ # Used to generate final terrain roi
830
+ self.list_terrain_roi = []
831
+
832
+ # Polygons representing the intersection of each pair of images
833
+ # Used to fill the final DSM only inside of those Polygons
834
+ self.list_intersection_poly = []
835
+
836
+ # initialize lists of points
837
+ self.list_epipolar_point_clouds = []
838
+ sensor_inputs.load_geomodels(
839
+ inputs, self.geom_plugin_without_dem_and_geoid
840
+ )
841
+ self.list_sensor_pairs = sensor_inputs.generate_pairs(
842
+ self.used_conf[INPUT]
843
+ )
844
+ logging.info(
845
+ "Received {} stereo pairs configurations".format(
846
+ len(self.list_sensor_pairs)
847
+ )
848
+ )
849
+
850
+ output_parameters.intialize_product_index(
851
+ self.cars_orchestrator,
852
+ output["product_level"],
853
+ [sensor_pair[0] for sensor_pair in self.list_sensor_pairs],
854
+ )
855
+
856
+ # pairs is a dict used to store the CarsDataset of
857
+ # all pairs, easily retrievable with pair keys
858
+ self.pairs = {}
859
+
860
+ # triangulated_matches_list is used to store triangulated matches
861
+ # used in dem generation
862
+ self.triangulated_matches_list = []
863
+
864
+ save_corrected_grid = (
865
+ self.epipolar_grid_generation_application.get_save_grids()
866
+ )
867
+
868
+ # Compute dems
869
+ dems = {}
870
+ if self.used_conf[INPUT][sens_cst.LOW_RES_DSM] is not None:
871
+ # Create dsm directory
872
+ dsm_dir = os.path.join(
873
+ self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY],
874
+ "dsm",
875
+ )
876
+ safe_makedirs(dsm_dir)
877
+
878
+ # DSM file name
879
+ dsm_file_name = self.used_conf[INPUT][sens_cst.LOW_RES_DSM]
880
+
881
+ dem_generation_output_dir = os.path.join(
882
+ self.dump_dir, "dem_generation"
883
+ )
884
+ safe_makedirs(dem_generation_output_dir)
885
+
886
+ # Use initial elevation if provided, and generate dems
887
+ _, paths, _ = self.dem_generation_application.run(
888
+ dsm_file_name,
889
+ dem_generation_output_dir,
890
+ input_geoid=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
891
+ sens_cst.GEOID
892
+ ],
893
+ output_geoid=self.used_conf[OUTPUT][out_cst.OUT_GEOID],
894
+ initial_elevation=(
895
+ self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
896
+ sens_cst.DEM_PATH
897
+ ]
898
+ ),
899
+ default_alt=self.geom_plugin_with_dem_and_geoid.default_alt,
900
+ cars_orchestrator=self.cars_orchestrator,
901
+ )
902
+
903
+ if self.quit_on_app("dem_generation"):
904
+ return True
905
+
906
+ dem_median = paths["dem_median"]
907
+ dem_min = paths["dem_min"]
908
+ dem_max = paths["dem_max"]
909
+
910
+ dems["dem_median"] = dem_median
911
+ dems["dem_min"] = dem_min
912
+ dems["dem_max"] = dem_max
913
+
914
+ # Override initial elevation
915
+ if (
916
+ inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None
917
+ or "dem_median"
918
+ in inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH]
919
+ ):
920
+ inputs[sens_cst.INITIAL_ELEVATION][
921
+ sens_cst.DEM_PATH
922
+ ] = dem_median
923
+ elif (
924
+ inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH]
925
+ is not None
926
+ and self.used_conf[PIPELINE][ADVANCED][USE_ENDOGENOUS_DEM]
927
+ ):
928
+ inputs[sens_cst.INITIAL_ELEVATION][
929
+ sens_cst.DEM_PATH
930
+ ] = dem_median
931
+
932
+ # Check advanced parameters with new initial elevation
933
+ output_dem_dir = os.path.join(
934
+ self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY],
935
+ "dump_dir",
936
+ "initial_elevation",
937
+ )
938
+ safe_makedirs(output_dem_dir)
939
+ (
940
+ inputs,
941
+ _,
942
+ self.geometry_plugin,
943
+ self.geom_plugin_without_dem_and_geoid,
944
+ self.geom_plugin_with_dem_and_geoid,
945
+ _,
946
+ _,
947
+ _,
948
+ ) = advanced_parameters.check_advanced_parameters(
949
+ inputs,
950
+ self.used_conf.get(PIPELINE, {}).get(ADVANCED, {}),
951
+ output_dem_dir=output_dem_dir,
952
+ )
953
+
954
+ for (
955
+ pair_key,
956
+ sensor_image_left,
957
+ sensor_image_right,
958
+ ) in self.list_sensor_pairs:
959
+ # initialize pairs for current pair
960
+ self.pairs[pair_key] = {}
961
+ self.pairs[pair_key]["sensor_image_left"] = sensor_image_left
962
+ self.pairs[pair_key]["sensor_image_right"] = sensor_image_right
963
+
964
+ # Run applications
965
+
966
+ # Run grid generation
967
+ # We generate grids with dem if it is provided.
968
+ # If not provided, grid are generated without dem and a dem
969
+ # will be generated, to use later for a new grid generation**
970
+
971
+ if inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None:
972
+ geom_plugin = self.geom_plugin_without_dem_and_geoid
973
+
974
+ else:
975
+ geom_plugin = self.geom_plugin_with_dem_and_geoid
976
+
977
+ # Generate rectification grids
978
+ (
979
+ self.pairs[pair_key]["grid_left"],
980
+ self.pairs[pair_key]["grid_right"],
981
+ ) = self.epipolar_grid_generation_application.run(
982
+ self.pairs[pair_key]["sensor_image_left"],
983
+ self.pairs[pair_key]["sensor_image_right"],
984
+ geom_plugin,
985
+ orchestrator=self.cars_orchestrator,
986
+ pair_folder=os.path.join(
987
+ self.dump_dir,
988
+ "epipolar_grid_generation",
989
+ "initial",
990
+ pair_key,
991
+ ),
992
+ pair_key=pair_key,
993
+ )
994
+
995
+ if self.quit_on_app("grid_generation"):
996
+ continue # keep iterating over pairs, but don't go further
997
+
998
+ # Prepare tie point pipeline
999
+
1000
+ # Update tie points pipeline with rectification grids
1001
+ if self.tie_points_pipelines is not None:
1002
+ tie_points_config = self.tie_points_pipelines[
1003
+ pair_key
1004
+ ].used_conf
1005
+ image_keys = list(tie_points_config[INPUT][sens_cst.SENSORS])
1006
+ tie_points_config[INPUT][sens_cst.RECTIFICATION_GRIDS] = {
1007
+ image_keys[0]: self.pairs[pair_key]["grid_left"],
1008
+ image_keys[1]: self.pairs[pair_key]["grid_right"],
1009
+ }
1010
+ tie_points_pipeline = TiePointsPipeline(
1011
+ tie_points_config,
1012
+ config_dir=self.config_dir,
1013
+ )
1014
+ sparse_mtch_app = tie_points_pipeline.sparse_matching_app
1015
+
1016
+ tie_points_output = tie_points_config[OUTPUT][
1017
+ out_cst.OUT_DIRECTORY
1018
+ ]
1019
+
1020
+ # If dem are provided, launch disparity grids generation
1021
+ disp_range_grid_dir = os.path.join(
1022
+ tie_points_output, "disparity_grids"
1023
+ )
1024
+ disp_range_grid = None
1025
+ if dems:
1026
+ disp_range_grid = (
1027
+ self.dense_matching_app.generate_disparity_grids(
1028
+ self.pairs[pair_key]["sensor_image_right"],
1029
+ self.pairs[pair_key]["grid_right"],
1030
+ self.geom_plugin_with_dem_and_geoid,
1031
+ dem_min=dem_min,
1032
+ dem_max=dem_max,
1033
+ pair_folder=disp_range_grid_dir,
1034
+ orchestrator=self.cars_orchestrator,
1035
+ )
1036
+ )
1037
+
1038
+ # Launch tie points pipeline
1039
+ tie_points_pipeline.run(
1040
+ disp_range_grid=disp_range_grid,
1041
+ log_dir=self.log_dir,
1042
+ cars_orchestrator=self.cars_orchestrator,
1043
+ )
1044
+ self.pairs[pair_key]["matches_array"] = np.load(
1045
+ os.path.join(
1046
+ tie_points_output, pair_key, "filtered_matches.npy"
1047
+ )
1048
+ )
1049
+
1050
+ minimum_nb_matches = (
1051
+ self.grid_correction_app.get_minimum_nb_matches()
1052
+ )
1053
+ nb_matches = self.pairs[pair_key]["matches_array"].shape[0]
1054
+ save_matches = sparse_mtch_app.get_save_matches()
1055
+
1056
+ if nb_matches > minimum_nb_matches:
1057
+ # Compute grid correction
1058
+ (self.pairs[pair_key]["corrected_grid_right"], _, _, _) = (
1059
+ self.grid_correction_app.run(
1060
+ self.pairs[pair_key]["matches_array"],
1061
+ self.pairs[pair_key]["grid_right"],
1062
+ save_matches=save_matches,
1063
+ pair_folder=os.path.join(
1064
+ self.dump_dir,
1065
+ "grid_correction",
1066
+ "initial",
1067
+ pair_key,
1068
+ ),
1069
+ pair_key=pair_key,
1070
+ orchestrator=self.cars_orchestrator,
1071
+ save_corrected_grid=save_corrected_grid,
1072
+ )
1073
+ )
1074
+ else:
1075
+ logging.warning(
1076
+ "Grid correction is not applied because number of "
1077
+ "matches found ({}) is less than minimum number of "
1078
+ "matches required for grid correction ({})".format(
1079
+ nb_matches,
1080
+ minimum_nb_matches,
1081
+ )
1082
+ )
1083
+ self.pairs[pair_key]["corrected_grid_right"] = self.pairs[
1084
+ pair_key
1085
+ ]["grid_right"]
1086
+ else:
1087
+ self.pairs[pair_key]["corrected_grid_right"] = self.pairs[
1088
+ pair_key
1089
+ ]["grid_right"]
1090
+
1091
+ self.pairs[pair_key]["corrected_grid_left"] = self.pairs[pair_key][
1092
+ "grid_left"
1093
+ ]
1094
+
1095
+ # Shrink disparity intervals according to SIFT disparities
1096
+ disp_to_alt_ratio = self.pairs[pair_key]["grid_left"][
1097
+ "disp_to_alt_ratio"
1098
+ ]
1099
+ if self.tie_points_pipelines is not None:
1100
+ disp_bounds_params = sparse_mtch_app.disparity_bounds_estimation
1101
+ matches = self.pairs[pair_key]["matches_array"]
1102
+ if disp_bounds_params["activated"] and matches.shape[0] > 0:
1103
+ sift_disp = matches[:, 2] - matches[:, 0]
1104
+ disp_min = np.percentile(
1105
+ sift_disp, disp_bounds_params["percentile"]
1106
+ )
1107
+ disp_max = np.percentile(
1108
+ sift_disp, 100 - disp_bounds_params["percentile"]
1109
+ )
1110
+ logging.info(
1111
+ "Global disparity interval without margin : "
1112
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1113
+ )
1114
+ disp_min -= (
1115
+ disp_bounds_params["upper_margin"] / disp_to_alt_ratio
1116
+ )
1117
+ disp_max += (
1118
+ disp_bounds_params["lower_margin"] / disp_to_alt_ratio
1119
+ )
1120
+ logging.info(
1121
+ "Global disparity interval with margin : "
1122
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1123
+ )
1124
+ else:
1125
+ disp_min = (
1126
+ -self.elevation_delta_upper_bound / disp_to_alt_ratio
1127
+ )
1128
+ disp_max = (
1129
+ -self.elevation_delta_lower_bound / disp_to_alt_ratio
1130
+ )
1131
+ logging.info(
1132
+ "Global disparity interval : "
1133
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1134
+ )
1135
+ else:
1136
+ disp_min = -self.elevation_delta_upper_bound / disp_to_alt_ratio
1137
+ disp_max = -self.elevation_delta_lower_bound / disp_to_alt_ratio
1138
+ logging.info(
1139
+ "Global disparity interval : "
1140
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1141
+ )
1142
+
1143
+ if self.epsg is None:
1144
+ # compute epsg
1145
+ # Epsg uses global disparity min and max
1146
+ self.epsg = preprocessing.compute_epsg(
1147
+ self.pairs[pair_key]["sensor_image_left"],
1148
+ self.pairs[pair_key]["sensor_image_right"],
1149
+ self.pairs[pair_key]["corrected_grid_left"],
1150
+ self.pairs[pair_key]["corrected_grid_right"],
1151
+ self.geom_plugin_with_dem_and_geoid,
1152
+ disp_min=0,
1153
+ disp_max=0,
1154
+ )
1155
+ # Compute roi polygon, in input EPSG
1156
+ self.roi_poly = preprocessing.compute_roi_poly(
1157
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
1158
+ )
1159
+
1160
+ # Clean grids at the end of processing if required. Note that this will
1161
+ # also clean refined grids
1162
+ if not save_corrected_grid:
1163
+ self.cars_orchestrator.add_to_clean(
1164
+ os.path.join(self.dump_dir, "grid_correction")
1165
+ )
1166
+ # grids file are already cleaned in the application, but the tree
1167
+ # structure should also be cleaned
1168
+ if not save_corrected_grid:
1169
+
1170
+ self.cars_orchestrator.add_to_clean(
1171
+ os.path.join(self.dump_dir, "epipolar_grid_generation")
1172
+ )
1173
+
1174
+ # quit if any app in the loop over the pairs was the last one
1175
+ if self.quit_on_app("grid_generation") or self.quit_on_app(
1176
+ "resampling"
1177
+ ):
1178
+ return True
1179
+
1180
+ # Define param
1181
+ use_global_disp_range = self.dense_matching_app.use_global_disp_range
1182
+
1183
+ self.pairs_names = [
1184
+ pair_name for pair_name, _, _ in self.list_sensor_pairs
1185
+ ]
1186
+
1187
+ for _, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
1188
+ # Geometry plugin with dem will be used for the grid generation
1189
+ geom_plugin = self.geom_plugin_with_dem_and_geoid
1190
+
1191
+ # saved used configuration
1192
+ self.save_configurations()
1193
+
1194
+ # Generate min and max disp grids
1195
+ # Global disparity min and max will be computed from
1196
+ # these grids
1197
+ dense_matching_pair_folder = os.path.join(
1198
+ self.dump_dir, "dense_matching", pair_key
1199
+ )
1200
+
1201
+ if self.which_resolution in ("first", "single") and dems in (
1202
+ None,
1203
+ {},
1204
+ ):
1205
+ dmin = disp_min
1206
+ dmax = disp_max
1207
+ # generate_disparity_grids runs orchestrator.breakpoint()
1208
+ self.pairs[pair_key]["disp_range_grid"] = (
1209
+ self.dense_matching_app.generate_disparity_grids(
1210
+ self.pairs[pair_key]["sensor_image_right"],
1211
+ self.pairs[pair_key]["corrected_grid_right"],
1212
+ self.geom_plugin_with_dem_and_geoid,
1213
+ dmin=dmin,
1214
+ dmax=dmax,
1215
+ pair_folder=dense_matching_pair_folder,
1216
+ orchestrator=self.cars_orchestrator,
1217
+ )
1218
+ )
1219
+
1220
+ updating_infos = {
1221
+ application_constants.APPLICATION_TAG: {
1222
+ sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
1223
+ pair_key: {
1224
+ sm_cst.MINIMUM_DISPARITY_TAG: dmin,
1225
+ sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+ self.cars_orchestrator.update_out_info(updating_infos)
1231
+ else:
1232
+ # Generate min and max disp grids from dems
1233
+ # generate_disparity_grids runs orchestrator.breakpoint()
1234
+ self.pairs[pair_key]["disp_range_grid"] = (
1235
+ self.dense_matching_app.generate_disparity_grids(
1236
+ self.pairs[pair_key]["sensor_image_right"],
1237
+ self.pairs[pair_key]["corrected_grid_right"],
1238
+ self.geom_plugin_with_dem_and_geoid,
1239
+ dem_min=dem_min,
1240
+ dem_max=dem_max,
1241
+ pair_folder=dense_matching_pair_folder,
1242
+ orchestrator=self.cars_orchestrator,
1243
+ )
1244
+ )
1245
+
1246
+ if use_global_disp_range:
1247
+ # Generate min and max disp grids from constants
1248
+ # sensor image is not used here
1249
+ # TODO remove when only local diparity range will be used
1250
+ dmin = self.pairs[pair_key]["disp_range_grid"]["global_min"]
1251
+ dmax = self.pairs[pair_key]["disp_range_grid"]["global_max"]
1252
+
1253
+ # update orchestrator_out_json
1254
+ updating_infos = {
1255
+ application_constants.APPLICATION_TAG: {
1256
+ sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
1257
+ pair_key: {
1258
+ sm_cst.MINIMUM_DISPARITY_TAG: dmin,
1259
+ sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ self.cars_orchestrator.update_out_info(updating_infos)
1265
+
1266
+ # generate_disparity_grids runs orchestrator.breakpoint()
1267
+ self.pairs[pair_key]["disp_range_grid"] = (
1268
+ self.dense_matching_app.generate_disparity_grids(
1269
+ self.pairs[pair_key]["sensor_image_right"],
1270
+ self.pairs[pair_key]["corrected_grid_right"],
1271
+ self.geom_plugin_with_dem_and_geoid,
1272
+ dmin=dmin,
1273
+ dmax=dmax,
1274
+ pair_folder=dense_matching_pair_folder,
1275
+ orchestrator=self.cars_orchestrator,
1276
+ )
1277
+ )
1278
+
1279
+ # saved used configuration
1280
+ self.save_configurations()
1281
+
1282
+ # end of for loop, to finish computing disparity range grids
1283
+
1284
+ for cloud_id, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
1285
+
1286
+ # Generate roi
1287
+ epipolar_roi = preprocessing.compute_epipolar_roi(
1288
+ self.input_roi_poly,
1289
+ self.input_roi_epsg,
1290
+ self.geom_plugin_with_dem_and_geoid,
1291
+ self.pairs[pair_key]["sensor_image_left"],
1292
+ self.pairs[pair_key]["sensor_image_right"],
1293
+ self.pairs[pair_key]["corrected_grid_left"],
1294
+ self.pairs[pair_key]["corrected_grid_right"],
1295
+ os.path.join(self.dump_dir, "compute_epipolar_roi", pair_key),
1296
+ disp_min=self.pairs[pair_key]["disp_range_grid"]["global_min"],
1297
+ disp_max=self.pairs[pair_key]["disp_range_grid"]["global_max"],
1298
+ )
1299
+
1300
+ # Generate new epipolar images
1301
+ # Generated with corrected grids
1302
+ # Optimal size is computed for the worst case scenario
1303
+ # found with epipolar disparity range grids
1304
+
1305
+ (
1306
+ optimum_tile_size,
1307
+ local_tile_optimal_size_fun,
1308
+ ) = self.dense_matching_app.get_optimal_tile_size(
1309
+ self.pairs[pair_key]["disp_range_grid"],
1310
+ self.cars_orchestrator.cluster.checked_conf_cluster[
1311
+ "max_ram_per_worker"
1312
+ ],
1313
+ )
1314
+
1315
+ # Get required bands of third resampling
1316
+ required_bands = self.dense_matching_app.get_required_bands()
1317
+
1318
+ # Add left required bands for texture
1319
+ required_bands["left"] = sorted(
1320
+ set(required_bands["left"]).union(set(self.texture_bands))
1321
+ )
1322
+
1323
+ # Find index of texture band in left_dataset
1324
+ texture_bands_indices = [
1325
+ required_bands["left"].index(band)
1326
+ for band in self.texture_bands
1327
+ ]
1328
+
1329
+ # Get margins used in dense matching,
1330
+ dense_matching_margins_fun = (
1331
+ self.dense_matching_app.get_margins_fun(
1332
+ self.pairs[pair_key]["corrected_grid_left"],
1333
+ self.pairs[pair_key]["disp_range_grid"],
1334
+ )
1335
+ )
1336
+
1337
+ # Run third epipolar resampling
1338
+ (
1339
+ new_epipolar_image_left,
1340
+ new_epipolar_image_right,
1341
+ ) = self.resampling_application.run(
1342
+ self.pairs[pair_key]["sensor_image_left"],
1343
+ self.pairs[pair_key]["sensor_image_right"],
1344
+ self.pairs[pair_key]["corrected_grid_left"],
1345
+ self.pairs[pair_key]["corrected_grid_right"],
1346
+ geom_plugin,
1347
+ orchestrator=self.cars_orchestrator,
1348
+ pair_folder=os.path.join(
1349
+ self.dump_dir, "resampling", "corrected_grid", pair_key
1350
+ ),
1351
+ pair_key=pair_key,
1352
+ margins_fun=dense_matching_margins_fun,
1353
+ tile_width=optimum_tile_size,
1354
+ tile_height=optimum_tile_size,
1355
+ add_classif=True,
1356
+ epipolar_roi=epipolar_roi,
1357
+ required_bands=required_bands,
1358
+ texture_bands=self.texture_bands,
1359
+ )
1360
+ # Run ground truth dsm computation
1361
+ if self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
1362
+ self.used_conf[PIPELINE][APPLICATIONS][
1363
+ "ground_truth_reprojection"
1364
+ ]["save_intermediate_data"] = True
1365
+ new_geomplugin_dsm = AbstractGeometry( # pylint: disable=E0110
1366
+ self.geometry_plugin,
1367
+ dem=self.used_conf[PIPELINE][ADVANCED][
1368
+ adv_cst.GROUND_TRUTH_DSM
1369
+ ][adv_cst.INPUT_GROUND_TRUTH_DSM],
1370
+ geoid=self.used_conf[PIPELINE][ADVANCED][
1371
+ adv_cst.GROUND_TRUTH_DSM
1372
+ ][adv_cst.INPUT_GEOID],
1373
+ scaling_coeff=self.scaling_coeff,
1374
+ )
1375
+ self.ground_truth_reprojection.run(
1376
+ self.pairs[pair_key]["sensor_image_left"],
1377
+ self.pairs[pair_key]["sensor_image_right"],
1378
+ self.pairs[pair_key]["corrected_grid_left"],
1379
+ self.pairs[pair_key]["corrected_grid_right"],
1380
+ new_geomplugin_dsm,
1381
+ self.geom_plugin_with_dem_and_geoid,
1382
+ self.pairs[pair_key]["corrected_grid_left"][
1383
+ "disp_to_alt_ratio"
1384
+ ],
1385
+ self.used_conf[PIPELINE][ADVANCED][
1386
+ adv_cst.GROUND_TRUTH_DSM
1387
+ ][adv_cst.INPUT_AUX_PATH],
1388
+ self.used_conf[PIPELINE][ADVANCED][
1389
+ adv_cst.GROUND_TRUTH_DSM
1390
+ ][adv_cst.INPUT_AUX_INTERP],
1391
+ orchestrator=self.cars_orchestrator,
1392
+ pair_folder=os.path.join(
1393
+ self.dump_dir, "ground_truth_reprojection", pair_key
1394
+ ),
1395
+ )
1396
+
1397
+ if self.epsg is None:
1398
+ # compute epsg
1399
+ # Epsg uses global disparity min and max
1400
+ self.epsg = preprocessing.compute_epsg(
1401
+ self.pairs[pair_key]["sensor_image_left"],
1402
+ self.pairs[pair_key]["sensor_image_right"],
1403
+ self.pairs[pair_key]["corrected_grid_left"],
1404
+ self.pairs[pair_key]["corrected_grid_right"],
1405
+ self.geom_plugin_with_dem_and_geoid,
1406
+ disp_min=self.pairs[pair_key]["disp_range_grid"][
1407
+ "global_min"
1408
+ ],
1409
+ disp_max=self.pairs[pair_key]["disp_range_grid"][
1410
+ "global_max"
1411
+ ],
1412
+ )
1413
+ # Compute roi polygon, in input EPSG
1414
+ self.roi_poly = preprocessing.compute_roi_poly(
1415
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
1416
+ )
1417
+
1418
+ self.vertical_crs = projection.get_output_crs(self.epsg, output)
1419
+
1420
+ if (
1421
+ self.save_output_dsm
1422
+ or self.save_output_point_cloud
1423
+ or self.dense_matching_app.get_method() == "auto"
1424
+ ):
1425
+ # Compute terrain bounding box /roi related to
1426
+ # current images
1427
+ (current_terrain_roi_bbox, intersection_poly) = (
1428
+ preprocessing.compute_terrain_bbox(
1429
+ self.pairs[pair_key]["sensor_image_left"],
1430
+ self.pairs[pair_key]["sensor_image_right"],
1431
+ new_epipolar_image_left,
1432
+ self.pairs[pair_key]["corrected_grid_left"],
1433
+ self.pairs[pair_key]["corrected_grid_right"],
1434
+ self.epsg,
1435
+ self.geom_plugin_with_dem_and_geoid,
1436
+ resolution=self.resolution,
1437
+ disp_min=self.pairs[pair_key]["disp_range_grid"][
1438
+ "global_min"
1439
+ ],
1440
+ disp_max=self.pairs[pair_key]["disp_range_grid"][
1441
+ "global_max"
1442
+ ],
1443
+ roi_poly=(
1444
+ None if self.debug_with_roi else self.roi_poly
1445
+ ),
1446
+ orchestrator=self.cars_orchestrator,
1447
+ pair_key=pair_key,
1448
+ pair_folder=os.path.join(
1449
+ self.dump_dir, "terrain_bbox", pair_key
1450
+ ),
1451
+ check_inputs=False,
1452
+ )
1453
+ )
1454
+ self.list_terrain_roi.append(current_terrain_roi_bbox)
1455
+ self.list_intersection_poly.append(intersection_poly)
1456
+
1457
+ # compute terrain bounds for later use
1458
+ (
1459
+ self.terrain_bounds,
1460
+ self.optimal_terrain_tile_width,
1461
+ ) = preprocessing.compute_terrain_bounds(
1462
+ self.list_terrain_roi,
1463
+ roi_poly=(None if self.debug_with_roi else self.roi_poly),
1464
+ resolution=self.resolution,
1465
+ )
1466
+
1467
+ if self.which_resolution not in ("final", "single"):
1468
+ self.terrain_bounds = dem_wrappers.modify_terrain_bounds(
1469
+ self.terrain_bounds,
1470
+ self.dem_generation_application.margin[0],
1471
+ self.dem_generation_application.margin[1],
1472
+ )
1473
+
1474
+ if self.dense_matching_app.get_method() == "auto":
1475
+ # Copy the initial corr_config in order to keep
1476
+ # the inputs that have already been checked
1477
+ corr_cfg = self.dense_matching_app.corr_config.copy()
1478
+
1479
+ # Find the conf that correspond to the land cover map
1480
+ conf = self.dense_matching_app.loader.find_auto_conf(
1481
+ intersection_poly,
1482
+ self.land_cover_map,
1483
+ self.classification_to_config_mapping,
1484
+ self.epsg,
1485
+ )
1486
+
1487
+ # Update the used_conf if order to reinitialize
1488
+ # the dense matching app
1489
+ # Because we kept the information regarding the ambiguity,
1490
+ # performance_map calculus..
1491
+ self.used_conf[PIPELINE][APPLICATIONS]["dense_matching"][
1492
+ "loader_conf"
1493
+ ] = conf
1494
+ self.used_conf[PIPELINE][APPLICATIONS]["dense_matching"][
1495
+ "method"
1496
+ ] = "custom"
1497
+
1498
+ # Re initialization of the dense matching application
1499
+ self.dense_matching_app = Application(
1500
+ "dense_matching",
1501
+ cfg=self.used_conf[PIPELINE][APPLICATIONS][
1502
+ "dense_matching"
1503
+ ],
1504
+ )
1505
+
1506
+ # Update the corr_config with the inputs that have
1507
+ # already been checked
1508
+ self.dense_matching_app.corr_config["input"] = corr_cfg["input"]
1509
+
1510
+ # Run epipolar matching application
1511
+ epipolar_disparity_map = self.dense_matching_app.run(
1512
+ new_epipolar_image_left,
1513
+ new_epipolar_image_right,
1514
+ local_tile_optimal_size_fun,
1515
+ orchestrator=self.cars_orchestrator,
1516
+ pair_folder=os.path.join(
1517
+ self.dump_dir, "dense_matching", pair_key
1518
+ ),
1519
+ pair_key=pair_key,
1520
+ disp_range_grid=self.pairs[pair_key]["disp_range_grid"],
1521
+ compute_disparity_masks=False,
1522
+ margins_to_keep=sum(
1523
+ app.get_epipolar_margin()
1524
+ for _, app in self.pc_outlier_removal_apps.items()
1525
+ ),
1526
+ texture_bands=texture_bands_indices,
1527
+ classif_bands_to_mask=self.used_classif_values_for_filling,
1528
+ )
1529
+
1530
+ if self.quit_on_app("dense_matching"):
1531
+ continue # keep iterating over pairs, but don't go further
1532
+
1533
+ # Fill with zeros
1534
+ (filled_epipolar_disparity_map) = self.dense_match_filling.run(
1535
+ epipolar_disparity_map,
1536
+ orchestrator=self.cars_orchestrator,
1537
+ pair_folder=os.path.join(
1538
+ self.dump_dir, "dense_match_filling", pair_key
1539
+ ),
1540
+ pair_key=pair_key,
1541
+ )
1542
+
1543
+ if self.quit_on_app("dense_match_filling"):
1544
+ continue # keep iterating over pairs, but don't go further
1545
+
1546
+ if isinstance(output[sens_cst.GEOID], str):
1547
+ output_geoid_path = output[sens_cst.GEOID]
1548
+ elif (
1549
+ isinstance(output[sens_cst.GEOID], bool)
1550
+ and output[sens_cst.GEOID]
1551
+ ):
1552
+ package_path = os.path.dirname(__file__)
1553
+ output_geoid_path = os.path.join(
1554
+ package_path,
1555
+ "..",
1556
+ "..",
1557
+ "conf",
1558
+ sensor_inputs.CARS_GEOID_PATH,
1559
+ )
1560
+ else:
1561
+ # default case : stay on the ellipsoid
1562
+ output_geoid_path = None
1563
+
1564
+ depth_map_dir = None
1565
+ if self.save_output_depth_map:
1566
+ depth_map_dir = os.path.join(
1567
+ self.out_dir, "depth_map", pair_key
1568
+ )
1569
+ safe_makedirs(depth_map_dir)
1570
+
1571
+ point_cloud_dir = None
1572
+ if self.save_output_point_cloud:
1573
+ point_cloud_dir = os.path.join(
1574
+ self.out_dir, "point_cloud", pair_key
1575
+ )
1576
+ safe_makedirs(point_cloud_dir)
1577
+
1578
+ triangulation_point_cloud_dir = (
1579
+ point_cloud_dir
1580
+ if (point_cloud_dir and len(self.pc_outlier_removal_apps) == 0)
1581
+ else None
1582
+ )
1583
+
1584
+ # Run epipolar triangulation application
1585
+ epipolar_point_cloud = self.triangulation_application.run(
1586
+ self.pairs[pair_key]["sensor_image_left"],
1587
+ self.pairs[pair_key]["sensor_image_right"],
1588
+ self.pairs[pair_key]["corrected_grid_left"],
1589
+ self.pairs[pair_key]["corrected_grid_right"],
1590
+ filled_epipolar_disparity_map,
1591
+ self.geom_plugin_without_dem_and_geoid,
1592
+ new_epipolar_image_left,
1593
+ epsg=self.epsg,
1594
+ denoising_overload_fun=None,
1595
+ source_pc_names=self.pairs_names,
1596
+ orchestrator=self.cars_orchestrator,
1597
+ pair_dump_dir=os.path.join(
1598
+ self.dump_dir, "triangulation", pair_key
1599
+ ),
1600
+ pair_key=pair_key,
1601
+ uncorrected_grid_right=self.pairs[pair_key]["grid_right"],
1602
+ geoid_path=output_geoid_path,
1603
+ cloud_id=cloud_id,
1604
+ performance_maps_param=(
1605
+ self.dense_matching_app.get_performance_map_parameters()
1606
+ ),
1607
+ depth_map_dir=depth_map_dir,
1608
+ point_cloud_dir=triangulation_point_cloud_dir,
1609
+ save_output_coordinates=(len(self.pc_outlier_removal_apps) == 0)
1610
+ and (
1611
+ self.save_output_depth_map or self.save_output_point_cloud
1612
+ ),
1613
+ save_output_color=bool(depth_map_dir)
1614
+ and self.auxiliary[out_cst.AUX_IMAGE],
1615
+ save_output_classification=bool(depth_map_dir)
1616
+ and self.auxiliary[out_cst.AUX_CLASSIFICATION],
1617
+ save_output_filling=bool(depth_map_dir)
1618
+ and self.auxiliary[out_cst.AUX_FILLING],
1619
+ save_output_performance_map=bool(depth_map_dir)
1620
+ and self.auxiliary[out_cst.AUX_PERFORMANCE_MAP],
1621
+ save_output_ambiguity=bool(depth_map_dir)
1622
+ and self.auxiliary[out_cst.AUX_AMBIGUITY],
1623
+ )
1624
+
1625
+ if self.quit_on_app("triangulation"):
1626
+ continue # keep iterating over pairs, but don't go further
1627
+
1628
+ filtered_epipolar_point_cloud = epipolar_point_cloud
1629
+ for app_key, app in self.pc_outlier_removal_apps.items():
1630
+
1631
+ app_key_is_last = (
1632
+ app_key == list(self.pc_outlier_removal_apps)[-1]
1633
+ )
1634
+ filtering_depth_map_dir = (
1635
+ depth_map_dir if app_key_is_last else None
1636
+ )
1637
+ filtering_point_cloud_dir = (
1638
+ point_cloud_dir if app_key_is_last else None
1639
+ )
1640
+
1641
+ filtered_epipolar_point_cloud = app.run(
1642
+ filtered_epipolar_point_cloud,
1643
+ depth_map_dir=filtering_depth_map_dir,
1644
+ point_cloud_dir=filtering_point_cloud_dir,
1645
+ dump_dir=os.path.join(
1646
+ self.dump_dir,
1647
+ ( # pylint: disable=inconsistent-quotes
1648
+ f"pc_outlier_removal"
1649
+ f"{str(app_key[27:]).replace('.', '_')}"
1650
+ ),
1651
+ pair_key,
1652
+ ),
1653
+ epsg=self.epsg,
1654
+ orchestrator=self.cars_orchestrator,
1655
+ )
1656
+ if self.quit_on_app("point_cloud_outlier_removal"):
1657
+ continue # keep iterating over pairs, but don't go further
1658
+
1659
+ self.list_epipolar_point_clouds.append(
1660
+ filtered_epipolar_point_cloud
1661
+ )
1662
+
1663
+ # quit if any app in the loop over the pairs was the last one
1664
+ # pylint:disable=too-many-boolean-expressions
1665
+ if (
1666
+ self.quit_on_app("dense_matching")
1667
+ or self.quit_on_app("dense_match_filling")
1668
+ or self.quit_on_app("triangulation")
1669
+ or self.quit_on_app("point_cloud_outlier_removal.1")
1670
+ or self.quit_on_app("point_cloud_outlier_removal.2")
1671
+ ):
1672
+ return True
1673
+
1674
+ return False
1675
+
1676
+ def rasterize_point_cloud(self):
1677
+ """
1678
+ Final step of the pipeline: rasterize the point
1679
+ cloud created in the prior steps.
1680
+ """
1681
+
1682
+ self.rasterization_dump_dir = os.path.join(
1683
+ self.dump_dir, "rasterization"
1684
+ )
1685
+
1686
+ dsm_file_name = (
1687
+ os.path.join(
1688
+ self.out_dir,
1689
+ out_cst.DSM_DIRECTORY,
1690
+ "dsm.tif",
1691
+ )
1692
+ if self.save_output_dsm
1693
+ else None
1694
+ )
1695
+
1696
+ weights_file_name = (
1697
+ os.path.join(
1698
+ self.out_dir,
1699
+ out_cst.DSM_DIRECTORY,
1700
+ "weights.tif",
1701
+ )
1702
+ if self.save_output_dsm
1703
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_WEIGHTS]
1704
+ else None
1705
+ )
1706
+
1707
+ color_file_name = (
1708
+ os.path.join(
1709
+ self.out_dir,
1710
+ out_cst.DSM_DIRECTORY,
1711
+ "image.tif",
1712
+ )
1713
+ if self.save_output_dsm
1714
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
1715
+ else None
1716
+ )
1717
+
1718
+ performance_map_file_name = (
1719
+ os.path.join(
1720
+ self.out_dir,
1721
+ out_cst.DSM_DIRECTORY,
1722
+ "performance_map.tif",
1723
+ )
1724
+ if self.save_output_dsm
1725
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
1726
+ out_cst.AUX_PERFORMANCE_MAP
1727
+ ]
1728
+ else None
1729
+ )
1730
+
1731
+ ambiguity_file_name = (
1732
+ os.path.join(
1733
+ self.out_dir,
1734
+ out_cst.DSM_DIRECTORY,
1735
+ "ambiguity.tif",
1736
+ )
1737
+ if self.save_output_dsm
1738
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_AMBIGUITY]
1739
+ else None
1740
+ )
1741
+
1742
+ classif_file_name = (
1743
+ os.path.join(
1744
+ self.out_dir,
1745
+ out_cst.DSM_DIRECTORY,
1746
+ "classification.tif",
1747
+ )
1748
+ if self.save_output_dsm
1749
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
1750
+ out_cst.AUX_CLASSIFICATION
1751
+ ]
1752
+ else None
1753
+ )
1754
+
1755
+ contributing_pair_file_name = (
1756
+ os.path.join(
1757
+ self.out_dir,
1758
+ out_cst.DSM_DIRECTORY,
1759
+ "contributing_pair.tif",
1760
+ )
1761
+ if self.save_output_dsm
1762
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
1763
+ out_cst.AUX_CONTRIBUTING_PAIR
1764
+ ]
1765
+ else None
1766
+ )
1767
+
1768
+ filling_file_name = (
1769
+ os.path.join(
1770
+ self.out_dir,
1771
+ out_cst.DSM_DIRECTORY,
1772
+ "filling.tif",
1773
+ )
1774
+ if self.save_output_dsm
1775
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
1776
+ else None
1777
+ )
1778
+
1779
+ # rasterize point cloud
1780
+ _ = self.rasterization_application.run(
1781
+ self.point_cloud_to_rasterize,
1782
+ self.epsg,
1783
+ self.vertical_crs,
1784
+ resolution=self.resolution,
1785
+ orchestrator=self.cars_orchestrator,
1786
+ dsm_file_name=dsm_file_name,
1787
+ weights_file_name=weights_file_name,
1788
+ color_file_name=color_file_name,
1789
+ classif_file_name=classif_file_name,
1790
+ performance_map_file_name=performance_map_file_name,
1791
+ ambiguity_file_name=ambiguity_file_name,
1792
+ contributing_pair_file_name=contributing_pair_file_name,
1793
+ filling_file_name=filling_file_name,
1794
+ color_dtype=self.color_type,
1795
+ dump_dir=self.rasterization_dump_dir,
1796
+ performance_map_classes=self.used_conf[OUTPUT][AUXILIARY][
1797
+ out_cst.AUX_PERFORMANCE_MAP
1798
+ ],
1799
+ phasing=self.phasing,
1800
+ )
1801
+
1802
+ # Cleaning: don't keep terrain bbox if save_intermediate_data
1803
+ # is not activated
1804
+ if not self.used_conf[PIPELINE][ADVANCED][
1805
+ adv_cst.SAVE_INTERMEDIATE_DATA
1806
+ ]:
1807
+ self.cars_orchestrator.add_to_clean(
1808
+ os.path.join(self.dump_dir, "terrain_bbox")
1809
+ )
1810
+
1811
+ if self.quit_on_app("point_cloud_rasterization"):
1812
+ return True
1813
+
1814
+ # dsm needs to be saved before filling
1815
+ self.cars_orchestrator.breakpoint()
1816
+
1817
+ # saved used configuration
1818
+ self.save_configurations()
1819
+
1820
+ if (
1821
+ classif_file_name is not None
1822
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
1823
+ out_cst.AUX_CLASSIFICATION
1824
+ ]
1825
+ ):
1826
+ self.merge_classif_bands(
1827
+ classif_file_name,
1828
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][
1829
+ out_cst.AUX_CLASSIFICATION
1830
+ ],
1831
+ dsm_file_name,
1832
+ )
1833
+
1834
+ if (
1835
+ filling_file_name is not None
1836
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
1837
+ ):
1838
+ self.merge_filling_bands(
1839
+ filling_file_name,
1840
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING],
1841
+ dsm_file_name,
1842
+ )
1843
+
1844
+ return False
1845
+
1846
+ @cars_profile(name="merge filling bands", interval=0.5)
1847
+ def merge_filling_bands(self, filling_path, aux_filling, dsm_file):
1848
+ """
1849
+ Merge filling bands to get mono band in output
1850
+ """
1851
+
1852
+ with rasterio.open(dsm_file) as in_dsm:
1853
+ dsm_msk = in_dsm.read_masks(1)
1854
+
1855
+ with rasterio.open(filling_path) as src:
1856
+ nb_bands = src.count
1857
+
1858
+ if nb_bands == 1:
1859
+ return False
1860
+
1861
+ filling_multi_bands = src.read()
1862
+ filling_mono_bands = np.zeros(filling_multi_bands.shape[1:3])
1863
+ descriptions = src.descriptions
1864
+ dict_temp = {name: i for i, name in enumerate(descriptions)}
1865
+ profile = src.profile
1866
+
1867
+ with warnings.catch_warnings():
1868
+ warnings.simplefilter("ignore", NodataShadowWarning)
1869
+ filling_mask = src.read_masks(1)
1870
+
1871
+ filling_mono_bands[filling_mask == 0] = 0
1872
+
1873
+ filling_bands_list = {
1874
+ "fill_with_geoid": ["filling_exogenous"],
1875
+ "interpolate_from_borders": [
1876
+ "bulldozer",
1877
+ "border_interpolation",
1878
+ ],
1879
+ "fill_with_endogenous_dem": [
1880
+ "filling_exogenous",
1881
+ "bulldozer",
1882
+ ],
1883
+ "fill_with_exogenous_dem": ["bulldozer"],
1884
+ }
1885
+
1886
+ # To get the right footprint
1887
+ filling_mono_bands = np.logical_or(dsm_msk, filling_mask).astype(
1888
+ np.uint8
1889
+ )
1890
+
1891
+ # to keep the previous classif convention
1892
+ filling_mono_bands[filling_mono_bands == 0] = src.nodata
1893
+ filling_mono_bands[filling_mono_bands == 1] = 0
1894
+
1895
+ no_match = False
1896
+ for key, value in aux_filling.items():
1897
+ if isinstance(value, str):
1898
+ value = [value]
1899
+
1900
+ if isinstance(value, list):
1901
+ for elem in value:
1902
+ if elem != "other":
1903
+ filling_method = filling_bands_list[elem]
1904
+
1905
+ if all(
1906
+ method in descriptions
1907
+ for method in filling_method
1908
+ ):
1909
+ indices_true = [
1910
+ dict_temp[m] for m in filling_method
1911
+ ]
1912
+
1913
+ mask_true = np.all(
1914
+ filling_multi_bands[indices_true, :, :]
1915
+ == 1,
1916
+ axis=0,
1917
+ )
1918
+
1919
+ indices_false = [
1920
+ i
1921
+ for i in range(filling_multi_bands.shape[0])
1922
+ if i not in indices_true
1923
+ ]
1924
+
1925
+ mask_false = np.all(
1926
+ filling_multi_bands[indices_false, :, :]
1927
+ == 0,
1928
+ axis=0,
1929
+ )
1930
+
1931
+ mask = mask_true & mask_false
1932
+
1933
+ filling_mono_bands[mask] = key
1934
+ else:
1935
+ no_match = True
1936
+
1937
+ if no_match:
1938
+ mask_1 = np.all(
1939
+ filling_multi_bands[1:, :, :] == 1,
1940
+ axis=0,
1941
+ )
1942
+
1943
+ mask_2 = np.all(
1944
+ filling_mono_bands == 0,
1945
+ axis=0,
1946
+ )
1947
+
1948
+ filling_mono_bands[mask_1 & mask_2] = (
1949
+ aux_filling["other"] if "other" in aux_filling else 50
1950
+ )
1951
+
1952
+ profile.update(count=1, dtype=filling_mono_bands.dtype)
1953
+ with rasterio.open(filling_path, "w", **profile) as src:
1954
+ src.write(filling_mono_bands, 1)
1955
+
1956
+ return True
1957
+
1958
+ @cars_profile(name="merge classif bands", interval=0.5)
1959
+ def merge_classif_bands(self, classif_path, aux_classif, dsm_file):
1960
+ """
1961
+ Merge classif bands to get mono band in output
1962
+ """
1963
+ with rasterio.open(dsm_file) as in_dsm:
1964
+ dsm_msk = in_dsm.read_masks(1)
1965
+
1966
+ with rasterio.open(classif_path) as src:
1967
+ nb_bands = src.count
1968
+
1969
+ if nb_bands == 1:
1970
+ return False
1971
+
1972
+ classif_multi_bands = src.read()
1973
+ classif_mono_band = np.zeros(classif_multi_bands.shape[1:3])
1974
+ descriptions = src.descriptions
1975
+ profile = src.profile
1976
+ classif_mask = src.read_masks(1)
1977
+ classif_mono_band[classif_mask == 0] = 0
1978
+
1979
+ # To get the right footprint
1980
+ classif_mono_band = np.logical_or(dsm_msk, classif_mask).astype(
1981
+ np.uint8
1982
+ )
1983
+
1984
+ # to keep the previous classif convention
1985
+ classif_mono_band[classif_mono_band == 0] = src.nodata
1986
+ classif_mono_band[classif_mono_band == 1] = 0
1987
+
1988
+ for key, value in aux_classif.items():
1989
+ if isinstance(value, int):
1990
+ num_band = descriptions.index(str(value))
1991
+ mask_1 = classif_mono_band == 0
1992
+ mask_2 = classif_multi_bands[num_band, :, :] == 1
1993
+ classif_mono_band[mask_1 & mask_2] = key
1994
+ elif isinstance(value, list):
1995
+ for elem in value:
1996
+ num_band = descriptions.index(str(elem))
1997
+ mask_1 = classif_mono_band == 0
1998
+ mask_2 = classif_multi_bands[num_band, :, :] == 1
1999
+ classif_mono_band[mask_1 & mask_2] = key
2000
+
2001
+ profile.update(count=1, dtype=classif_mono_band.dtype)
2002
+ with rasterio.open(classif_path, "w", **profile) as src:
2003
+ src.write(classif_mono_band, 1)
2004
+
2005
+ return True
2006
+
2007
+ @cars_profile(name="Preprocess depth maps", interval=0.5)
2008
+ def preprocess_depth_maps(self):
2009
+ """
2010
+ Adds multiple processing steps to the depth maps :
2011
+ Merging.
2012
+ Creates the point cloud that will be rasterized in
2013
+ the last step of the pipeline.
2014
+ """
2015
+
2016
+ self.point_cloud_to_rasterize = (
2017
+ self.list_epipolar_point_clouds,
2018
+ self.terrain_bounds,
2019
+ )
2020
+ self.color_type = self.point_cloud_to_rasterize[0][0].attributes.get(
2021
+ "color_type", None
2022
+ )
2023
+
2024
+ @cars_profile(name="Final cleanup", interval=0.5)
2025
+ def final_cleanup(self):
2026
+ """
2027
+ Clean temporary files and directory at the end of cars processing
2028
+ """
2029
+
2030
+ if not self.used_conf[PIPELINE][ADVANCED][
2031
+ adv_cst.SAVE_INTERMEDIATE_DATA
2032
+ ]:
2033
+ # delete everything in tile_processing if save_intermediate_data is
2034
+ # not activated
2035
+ self.cars_orchestrator.add_to_clean(
2036
+ os.path.join(self.dump_dir, "tile_processing")
2037
+ )
2038
+
2039
+ self.cars_orchestrator.add_to_clean(
2040
+ os.path.join(self.out_dir, "tie_points")
2041
+ )
2042
+
2043
+ # Remove dump_dir if no intermediate data should be written
2044
+ if not any(
2045
+ app.get("save_intermediate_data", False) is True
2046
+ for app in self.used_conf[PIPELINE][APPLICATIONS].values()
2047
+ if app is not None
2048
+ ):
2049
+ self.cars_orchestrator.add_to_clean(self.dump_dir)
2050
+
2051
+ @cars_profile(name="run_surface_modeling_pipeline", interval=0.5)
2052
+ def run(
2053
+ self,
2054
+ which_resolution="single",
2055
+ log_dir=None,
2056
+ ): # noqa C901
2057
+ """
2058
+ Run pipeline
2059
+
2060
+ """
2061
+ if log_dir is not None:
2062
+ self.log_dir = log_dir
2063
+ else:
2064
+ self.log_dir = os.path.join(self.out_dir, "logs")
2065
+
2066
+ self.texture_bands = self.used_conf[OUTPUT][AUXILIARY][
2067
+ out_cst.AUX_IMAGE
2068
+ ]
2069
+
2070
+ self.auxiliary = self.used_conf[OUTPUT][out_cst.AUXILIARY]
2071
+
2072
+ self.which_resolution = which_resolution
2073
+
2074
+ # saved used configuration
2075
+ self.save_configurations()
2076
+ # start cars orchestrator
2077
+ with orchestrator.Orchestrator(
2078
+ orchestrator_conf=self.used_conf[ORCHESTRATOR],
2079
+ out_dir=self.out_dir,
2080
+ log_dir=self.log_dir,
2081
+ out_yaml_path=os.path.join(
2082
+ self.out_dir,
2083
+ out_cst.INFO_FILENAME,
2084
+ ),
2085
+ ) as self.cars_orchestrator:
2086
+ # initialize out_json
2087
+ self.cars_orchestrator.update_out_info({"version": __version__})
2088
+
2089
+ if self.compute_depth_map:
2090
+ self.sensor_to_depth_maps()
2091
+
2092
+ if self.save_output_dsm or self.save_output_point_cloud:
2093
+ self.preprocess_depth_maps()
2094
+
2095
+ if self.save_output_dsm:
2096
+ self.rasterize_point_cloud()
2097
+
2098
+ self.final_cleanup()