cars 1.0.0rc1__cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.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.

Potentially problematic release.


This version of cars might be problematic. Click here for more details.

Files changed (200) hide show
  1. cars/__init__.py +74 -0
  2. cars/applications/__init__.py +37 -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 +104 -0
  8. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
  9. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +630 -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 +42 -0
  14. cars/applications/dem_generation/bulldozer_dem_app.py +655 -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 +1460 -0
  28. cars/applications/dense_matching/cpp/__init__.py +0 -0
  29. cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-312-i386-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 +588 -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 +270 -0
  53. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  54. cars/applications/dsm_filling/bulldozer_filling_app.py +279 -0
  55. cars/applications/dsm_filling/exogenous_filling_app.py +333 -0
  56. cars/applications/grid_generation/__init__.py +30 -0
  57. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  58. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  59. cars/applications/grid_generation/grid_correction_app.py +496 -0
  60. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  61. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  62. cars/applications/grid_generation/transform_grid.py +88 -0
  63. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  64. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  65. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  66. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  67. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  68. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  69. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  70. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  71. cars/applications/point_cloud_outlier_removal/small_components_app.py +527 -0
  72. cars/applications/point_cloud_outlier_removal/statistical_app.py +531 -0
  73. cars/applications/rasterization/__init__.py +30 -0
  74. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  75. cars/applications/rasterization/rasterization_algo.py +534 -0
  76. cars/applications/rasterization/rasterization_constants.py +38 -0
  77. cars/applications/rasterization/rasterization_wrappers.py +634 -0
  78. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  79. cars/applications/resampling/__init__.py +28 -0
  80. cars/applications/resampling/abstract_resampling_app.py +187 -0
  81. cars/applications/resampling/bicubic_resampling_app.py +762 -0
  82. cars/applications/resampling/resampling_algo.py +614 -0
  83. cars/applications/resampling/resampling_constants.py +36 -0
  84. cars/applications/resampling/resampling_wrappers.py +309 -0
  85. cars/applications/sparse_matching/__init__.py +30 -0
  86. cars/applications/sparse_matching/abstract_sparse_matching_app.py +498 -0
  87. cars/applications/sparse_matching/sift_app.py +735 -0
  88. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  89. cars/applications/sparse_matching/sparse_matching_constants.py +68 -0
  90. cars/applications/sparse_matching/sparse_matching_wrappers.py +238 -0
  91. cars/applications/triangulation/__init__.py +32 -0
  92. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  93. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  94. cars/applications/triangulation/pc_transform.py +552 -0
  95. cars/applications/triangulation/triangulation_algo.py +371 -0
  96. cars/applications/triangulation/triangulation_constants.py +38 -0
  97. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  98. cars/bundleadjustment.py +757 -0
  99. cars/cars.py +177 -0
  100. cars/conf/__init__.py +23 -0
  101. cars/conf/geoid/egm96.grd +0 -0
  102. cars/conf/geoid/egm96.grd.hdr +15 -0
  103. cars/conf/input_parameters.py +156 -0
  104. cars/conf/mask_cst.py +35 -0
  105. cars/core/__init__.py +23 -0
  106. cars/core/cars_logging.py +402 -0
  107. cars/core/constants.py +191 -0
  108. cars/core/constants_disparity.py +50 -0
  109. cars/core/datasets.py +140 -0
  110. cars/core/geometry/__init__.py +27 -0
  111. cars/core/geometry/abstract_geometry.py +1119 -0
  112. cars/core/geometry/shareloc_geometry.py +598 -0
  113. cars/core/inputs.py +568 -0
  114. cars/core/outputs.py +176 -0
  115. cars/core/preprocessing.py +722 -0
  116. cars/core/projection.py +843 -0
  117. cars/core/roi_tools.py +215 -0
  118. cars/core/tiling.py +774 -0
  119. cars/core/utils.py +164 -0
  120. cars/data_structures/__init__.py +23 -0
  121. cars/data_structures/cars_dataset.py +1541 -0
  122. cars/data_structures/cars_dict.py +74 -0
  123. cars/data_structures/corresponding_tiles_tools.py +186 -0
  124. cars/data_structures/dataframe_converter.py +185 -0
  125. cars/data_structures/format_transformation.py +297 -0
  126. cars/devibrate.py +689 -0
  127. cars/extractroi.py +264 -0
  128. cars/orchestrator/__init__.py +23 -0
  129. cars/orchestrator/achievement_tracker.py +125 -0
  130. cars/orchestrator/cluster/__init__.py +37 -0
  131. cars/orchestrator/cluster/abstract_cluster.py +244 -0
  132. cars/orchestrator/cluster/abstract_dask_cluster.py +375 -0
  133. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  134. cars/orchestrator/cluster/dask_config/README.md +94 -0
  135. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  136. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  137. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  138. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  139. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  140. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  141. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  142. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  143. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  144. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  145. cars/orchestrator/cluster/log_wrapper.py +1075 -0
  146. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  147. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  148. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  149. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  150. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  151. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +873 -0
  152. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  153. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  154. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  155. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  156. cars/orchestrator/orchestrator.py +905 -0
  157. cars/orchestrator/orchestrator_constants.py +29 -0
  158. cars/orchestrator/registry/__init__.py +23 -0
  159. cars/orchestrator/registry/abstract_registry.py +143 -0
  160. cars/orchestrator/registry/compute_registry.py +106 -0
  161. cars/orchestrator/registry/id_generator.py +116 -0
  162. cars/orchestrator/registry/replacer_registry.py +213 -0
  163. cars/orchestrator/registry/saver_registry.py +363 -0
  164. cars/orchestrator/registry/unseen_registry.py +118 -0
  165. cars/orchestrator/tiles_profiler.py +279 -0
  166. cars/pipelines/__init__.py +26 -0
  167. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  168. cars/pipelines/conf_resolution/conf_first_resolution.yaml +2 -0
  169. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  170. cars/pipelines/default/__init__.py +26 -0
  171. cars/pipelines/default/default_pipeline.py +786 -0
  172. cars/pipelines/parameters/__init__.py +0 -0
  173. cars/pipelines/parameters/advanced_parameters.py +417 -0
  174. cars/pipelines/parameters/advanced_parameters_constants.py +69 -0
  175. cars/pipelines/parameters/application_parameters.py +71 -0
  176. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  177. cars/pipelines/parameters/dsm_inputs.py +918 -0
  178. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  179. cars/pipelines/parameters/output_constants.py +52 -0
  180. cars/pipelines/parameters/output_parameters.py +454 -0
  181. cars/pipelines/parameters/sensor_inputs.py +842 -0
  182. cars/pipelines/parameters/sensor_inputs_constants.py +49 -0
  183. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  184. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  185. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  186. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  187. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  188. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  189. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  190. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  191. cars/pipelines/pipeline.py +119 -0
  192. cars/pipelines/pipeline_constants.py +31 -0
  193. cars/pipelines/pipeline_template.py +139 -0
  194. cars/pipelines/unit/__init__.py +26 -0
  195. cars/pipelines/unit/unit_pipeline.py +2850 -0
  196. cars/starter.py +167 -0
  197. cars-1.0.0rc1.dist-info/METADATA +292 -0
  198. cars-1.0.0rc1.dist-info/RECORD +200 -0
  199. cars-1.0.0rc1.dist-info/WHEEL +6 -0
  200. cars-1.0.0rc1.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,2850 @@
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 default 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 pyproj import CRS
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.applications.grid_generation import grid_correction_app
55
+ from cars.applications.grid_generation.transform_grid import transform_grid_func
56
+ from cars.core import preprocessing, projection, roi_tools
57
+ from cars.core.geometry.abstract_geometry import AbstractGeometry
58
+ from cars.core.inputs import (
59
+ get_descriptions_bands,
60
+ rasterio_get_crs,
61
+ rasterio_get_epsg,
62
+ rasterio_get_size,
63
+ read_vector,
64
+ )
65
+ from cars.core.utils import safe_makedirs
66
+ from cars.data_structures import cars_dataset
67
+ from cars.orchestrator import orchestrator
68
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
69
+ from cars.pipelines.parameters import advanced_parameters
70
+ from cars.pipelines.parameters import advanced_parameters_constants as adv_cst
71
+ from cars.pipelines.parameters import application_parameters, dsm_inputs
72
+ from cars.pipelines.parameters import dsm_inputs_constants as dsm_cst
73
+ from cars.pipelines.parameters import output_constants as out_cst
74
+ from cars.pipelines.parameters import output_parameters, sensor_inputs
75
+ from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
76
+ from cars.pipelines.parameters.advanced_parameters_constants import (
77
+ USE_ENDOGENOUS_DEM,
78
+ )
79
+ from cars.pipelines.parameters.output_constants import AUXILIARY
80
+ from cars.pipelines.pipeline import Pipeline
81
+ from cars.pipelines.pipeline_constants import (
82
+ ADVANCED,
83
+ APPLICATIONS,
84
+ INPUT,
85
+ ORCHESTRATOR,
86
+ OUTPUT,
87
+ )
88
+ from cars.pipelines.pipeline_template import PipelineTemplate
89
+
90
+
91
+ @Pipeline.register(
92
+ "unit",
93
+ )
94
+ class UnitPipeline(PipelineTemplate):
95
+ """
96
+ UnitPipeline
97
+ """
98
+
99
+ # pylint: disable=too-many-instance-attributes
100
+
101
+ def __init__(self, conf, config_dir=None): # noqa: C901
102
+ """
103
+ Creates pipeline
104
+
105
+ Directly creates class attributes:
106
+ used_conf
107
+ generate_terrain_products
108
+ debug_with_roi
109
+ save_output_dsm
110
+ save_output_depth_map
111
+ save_output_point_clouds
112
+ geom_plugin_without_dem_and_geoid
113
+ geom_plugin_with_dem_and_geoid
114
+
115
+ :param pipeline_name: name of the pipeline.
116
+ :type pipeline_name: str
117
+ :param cfg: configuration {'matching_cost_method': value}
118
+ :type cfg: dictionary
119
+ :param config_dir: path to dir containing json/yaml
120
+ :type config_dir: str
121
+ """
122
+
123
+ # Used conf
124
+ self.used_conf = {}
125
+ # refined conf
126
+ self.refined_conf = {}
127
+
128
+ # Transform relative path to absolute path
129
+ if config_dir is not None:
130
+ config_dir = os.path.abspath(config_dir)
131
+
132
+ # Check global conf
133
+ self.check_global_schema(conf)
134
+
135
+ # Check conf orchestrator
136
+ self.used_conf[ORCHESTRATOR] = self.check_orchestrator(
137
+ conf.get(ORCHESTRATOR, None)
138
+ )
139
+
140
+ # Check conf inputs
141
+ inputs = self.check_inputs(conf[INPUT], config_dir=config_dir)
142
+ self.used_conf[INPUT] = inputs
143
+ self.refined_conf[INPUT] = copy.deepcopy(inputs)
144
+
145
+ # Check advanced parameters
146
+ # TODO static method in the base class
147
+ output_dem_dir = os.path.join(
148
+ conf[OUTPUT][out_cst.OUT_DIRECTORY], "dump_dir", "initial_elevation"
149
+ )
150
+ safe_makedirs(output_dem_dir)
151
+ (
152
+ inputs,
153
+ advanced,
154
+ self.geometry_plugin,
155
+ self.geom_plugin_without_dem_and_geoid,
156
+ self.geom_plugin_with_dem_and_geoid,
157
+ self.scaling_coeff,
158
+ self.land_cover_map,
159
+ self.classification_to_config_mapping,
160
+ ) = advanced_parameters.check_advanced_parameters(
161
+ inputs,
162
+ conf.get(ADVANCED, {}),
163
+ check_epipolar_a_priori=True,
164
+ output_dem_dir=output_dem_dir,
165
+ )
166
+ self.used_conf[ADVANCED] = advanced
167
+
168
+ self.refined_conf[ADVANCED] = copy.deepcopy(advanced)
169
+ # Refined conf: resolutions 1
170
+ self.refined_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS] = [1]
171
+
172
+ # Get ROI
173
+ (
174
+ self.input_roi_poly,
175
+ self.input_roi_epsg,
176
+ ) = roi_tools.generate_roi_poly_from_inputs(
177
+ self.used_conf[INPUT][sens_cst.ROI]
178
+ )
179
+
180
+ self.debug_with_roi = self.used_conf[ADVANCED][adv_cst.DEBUG_WITH_ROI]
181
+
182
+ # Check conf output
183
+ (
184
+ output,
185
+ self.scaling_coeff,
186
+ ) = self.check_output(inputs, conf[OUTPUT], self.scaling_coeff)
187
+
188
+ self.used_conf[OUTPUT] = output
189
+ self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
190
+ self.dump_dir = os.path.join(self.out_dir, "dump_dir")
191
+
192
+ self.refined_conf[OUTPUT] = copy.deepcopy(output)
193
+
194
+ prod_level = output[out_cst.PRODUCT_LEVEL]
195
+
196
+ self.save_output_dsm = "dsm" in prod_level
197
+ self.save_output_depth_map = "depth_map" in prod_level
198
+ self.save_output_point_cloud = "point_cloud" in prod_level
199
+ self.save_output_classif_for_filling = False
200
+
201
+ self.output_level_none = not (
202
+ self.save_output_dsm
203
+ or self.save_output_depth_map
204
+ or self.save_output_point_cloud
205
+ )
206
+ self.sensors_in_inputs = sens_cst.SENSORS in self.used_conf[INPUT]
207
+
208
+ # Used classification values, for filling -> will be masked
209
+ self.used_classif_values_for_filling = self.get_classif_values_filling(
210
+ self.used_conf[INPUT]
211
+ )
212
+
213
+ self.dsms_in_inputs = dsm_cst.DSMS in self.used_conf[INPUT]
214
+
215
+ self.phasing = self.used_conf[ADVANCED][adv_cst.PHASING]
216
+
217
+ self.compute_depth_map = (
218
+ self.sensors_in_inputs
219
+ and (not self.output_level_none)
220
+ and not self.dsms_in_inputs
221
+ )
222
+
223
+ if self.output_level_none:
224
+ self.infer_conditions_from_applications(conf)
225
+
226
+ self.save_all_intermediate_data = self.used_conf[ADVANCED][
227
+ adv_cst.SAVE_INTERMEDIATE_DATA
228
+ ]
229
+
230
+ if isinstance(
231
+ self.used_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS], list
232
+ ):
233
+ if len(self.used_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS]) > 1:
234
+ raise RuntimeError(
235
+ "For the unit pipeline, "
236
+ "the epipolar resolution has to "
237
+ "be a single value"
238
+ )
239
+
240
+ self.res_resamp = self.used_conf[ADVANCED][
241
+ adv_cst.EPIPOLAR_RESOLUTIONS
242
+ ][0]
243
+ else:
244
+ self.res_resamp = self.used_conf[ADVANCED][
245
+ adv_cst.EPIPOLAR_RESOLUTIONS
246
+ ]
247
+
248
+ self.save_all_point_clouds_by_pair = self.used_conf[OUTPUT].get(
249
+ out_cst.SAVE_BY_PAIR, False
250
+ )
251
+
252
+ # Check conf application
253
+ application_conf = self.check_applications(conf.get(APPLICATIONS, {}))
254
+
255
+ if self.sensors_in_inputs and not self.dsms_in_inputs:
256
+ # Check conf application vs inputs application
257
+ application_conf = self.check_applications_with_inputs(
258
+ self.used_conf[INPUT], application_conf, self.res_resamp
259
+ )
260
+
261
+ self.used_conf[APPLICATIONS] = application_conf
262
+
263
+ self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
264
+
265
+ def quit_on_app(self, app_name):
266
+ """
267
+ Returns whether the pipeline should end after
268
+ the application was called.
269
+
270
+ Only works if the output_level is empty, so that
271
+ the control is instead given to
272
+ """
273
+
274
+ if not self.output_level_none:
275
+ # custom quit step was not set, never quit early
276
+ return False
277
+
278
+ return self.app_values[app_name] >= self.last_application_to_run
279
+
280
+ def infer_conditions_from_applications(self, conf):
281
+ """
282
+ Fills the condition booleans used later in the pipeline by going
283
+ through the applications and infering which application we should
284
+ end the pipeline on.
285
+ """
286
+
287
+ self.last_application_to_run = 0
288
+
289
+ sensor_to_depth_apps = {
290
+ "grid_generation": 1, # and 5
291
+ "resampling": 2, # and 8
292
+ "sparse_matching": 4,
293
+ "ground_truth_reprojection": 6,
294
+ "dense_matching": 8,
295
+ "dense_match_filling": 9,
296
+ "triangulation": 11,
297
+ "point_cloud_outlier_removal.1": 12,
298
+ "point_cloud_outlier_removal.2": 13,
299
+ }
300
+
301
+ depth_to_dsm_apps = {
302
+ "point_cloud_rasterization": 15,
303
+ "dem_generation": 16,
304
+ "dsm_filling.1": 17,
305
+ "dsm_filling.2": 18,
306
+ "dsm_filling.3": 19,
307
+ "auxiliary_filling": 20,
308
+ }
309
+
310
+ self.app_values = {}
311
+ self.app_values.update(sensor_to_depth_apps)
312
+ self.app_values.update(depth_to_dsm_apps)
313
+
314
+ app_conf = conf.get(APPLICATIONS, {})
315
+ for key in app_conf:
316
+
317
+ if adv_cst.SAVE_INTERMEDIATE_DATA not in app_conf[key]:
318
+ continue
319
+
320
+ if not app_conf[key][adv_cst.SAVE_INTERMEDIATE_DATA]:
321
+ continue
322
+
323
+ if key in sensor_to_depth_apps:
324
+
325
+ if not self.sensors_in_inputs:
326
+ warn_msg = (
327
+ "The application {} can only be used when sensor "
328
+ "images are given as an input. "
329
+ "Its configuration will be ignored."
330
+ ).format(key)
331
+ logging.warning(warn_msg)
332
+
333
+ elif self.sensors_in_inputs and not self.dsms_in_inputs:
334
+ self.compute_depth_map = True
335
+ self.last_application_to_run = max(
336
+ self.last_application_to_run, self.app_values[key]
337
+ )
338
+
339
+ elif key in depth_to_dsm_apps:
340
+
341
+ if not (self.sensors_in_inputs or self.dsms_in_inputs):
342
+ warn_msg = (
343
+ "The application {} can only be used when sensor "
344
+ "images or depth maps are given as an input. "
345
+ "Its configuration will be ignored."
346
+ ).format(key)
347
+ logging.warning(warn_msg)
348
+
349
+ else:
350
+ if self.sensors_in_inputs and not self.dsms_in_inputs:
351
+ self.compute_depth_map = True
352
+
353
+ # enabled to start the depth map to dsm process
354
+ self.save_output_dsm = True
355
+
356
+ self.last_application_to_run = max(
357
+ self.last_application_to_run, self.app_values[key]
358
+ )
359
+
360
+ else:
361
+ warn_msg = (
362
+ "The application {} was not recognized. Its configuration"
363
+ "will be ignored."
364
+ ).format(key)
365
+ logging.warning(warn_msg)
366
+
367
+ if not (self.compute_depth_map or self.save_output_dsm):
368
+ log_msg = (
369
+ "No product level was given. CARS has not detected any "
370
+ "data you wish to save. No computation will be done."
371
+ )
372
+ logging.info(log_msg)
373
+ else:
374
+ log_msg = (
375
+ "No product level was given. CARS has detected that you "
376
+ + "wish to run up to the {} application.".format(
377
+ next(
378
+ k
379
+ for k, v in self.app_values.items()
380
+ if v == self.last_application_to_run
381
+ )
382
+ )
383
+ )
384
+ logging.warning(log_msg)
385
+
386
+ @staticmethod
387
+ def check_inputs(conf, config_dir=None):
388
+ """
389
+ Check the inputs given
390
+
391
+ :param conf: configuration of inputs
392
+ :type conf: dict
393
+ :param config_dir: directory of used json/yaml, if
394
+ user filled paths with relative paths
395
+ :type config_dir: str
396
+
397
+ :return: overloaded inputs
398
+ :rtype: dict
399
+ """
400
+
401
+ output_config = {}
402
+ if sens_cst.SENSORS in conf and dsm_cst.DSMS not in conf:
403
+ output_config = sensor_inputs.sensors_check_inputs(
404
+ conf, config_dir=config_dir
405
+ )
406
+ elif dsm_cst.DSMS in conf:
407
+ output_config = {
408
+ **output_config,
409
+ **dsm_inputs.check_dsm_inputs(conf, config_dir=config_dir),
410
+ }
411
+ else:
412
+ raise RuntimeError("No sensors or dsms in inputs")
413
+ return output_config
414
+
415
+ def save_configurations(self):
416
+ """
417
+ Save used_conf and refined_conf configurations
418
+ """
419
+
420
+ cars_dataset.save_dict(
421
+ self.used_conf,
422
+ os.path.join(self.out_dir, "current_res_used_conf.yaml"),
423
+ )
424
+ cars_dataset.save_dict(
425
+ self.refined_conf,
426
+ os.path.join(self.out_dir, "refined_conf.yaml"),
427
+ )
428
+
429
+ def check_output(self, inputs, conf, scaling_coeff):
430
+ """
431
+ Check the output given
432
+
433
+ :param conf: configuration of output
434
+ :type conf: dict
435
+ :param scaling_coeff: scaling factor for resolution
436
+ :type scaling_coeff: float
437
+ :return: overloader output
438
+ :rtype: dict
439
+ """
440
+ return output_parameters.check_output_parameters(
441
+ inputs, conf, scaling_coeff
442
+ )
443
+
444
+ def check_applications( # noqa: C901 : too complex
445
+ self,
446
+ conf,
447
+ ):
448
+ """
449
+ Check the given configuration for applications,
450
+ and generates needed applications for pipeline.
451
+
452
+ :param conf: configuration of applications
453
+ :type conf: dict
454
+ """
455
+ scaling_coeff = self.scaling_coeff
456
+
457
+ needed_applications = application_parameters.get_needed_apps(
458
+ self.sensors_in_inputs,
459
+ self.save_output_dsm,
460
+ self.save_output_point_cloud,
461
+ conf,
462
+ )
463
+
464
+ # Check if all specified applications are used
465
+ # Application in terrain_application are note used in
466
+ # the sensors_to_dense_depth_maps pipeline
467
+ for app_key in conf.keys():
468
+ if app_key not in needed_applications:
469
+ msg = (
470
+ f"No {app_key} application used in the "
471
+ + "default Cars pipeline"
472
+ )
473
+ logging.error(msg)
474
+ raise NameError(msg)
475
+
476
+ # Initialize used config
477
+ used_conf = {}
478
+
479
+ for app_key in needed_applications:
480
+ used_conf[app_key] = conf.get(app_key, {})
481
+ if used_conf[app_key] is None:
482
+ continue
483
+ used_conf[app_key]["save_intermediate_data"] = (
484
+ self.save_all_intermediate_data
485
+ or used_conf[app_key].get("save_intermediate_data", False)
486
+ )
487
+
488
+ if app_key == "auxiliary_filling":
489
+ if used_conf[app_key] is not None:
490
+ used_conf[app_key]["activated"] = used_conf[app_key].get(
491
+ "activated", True
492
+ )
493
+
494
+ if app_key in [
495
+ "point_cloud_fusion",
496
+ ]:
497
+ used_conf[app_key]["save_by_pair"] = used_conf[app_key].get(
498
+ "save_by_pair", self.save_all_point_clouds_by_pair
499
+ )
500
+
501
+ self.epipolar_grid_generation_application = None
502
+ self.resampling_application = None
503
+ self.ground_truth_reprojection = None
504
+ self.dense_match_filling = None
505
+ self.sparse_mtch_app = None
506
+ self.dense_matching_app = None
507
+ self.triangulation_application = None
508
+ self.dem_generation_application = None
509
+ self.pc_outlier_removal_apps = {}
510
+ self.rasterization_application = None
511
+ self.pc_fusion_application = None
512
+ self.dsm_filling_1_application = None
513
+ self.dsm_filling_2_application = None
514
+ self.dsm_filling_3_application = None
515
+ self.dsm_filling_apps = {}
516
+
517
+ if self.sensors_in_inputs:
518
+ # Epipolar grid generation
519
+ self.epipolar_grid_generation_application = Application(
520
+ "grid_generation",
521
+ cfg=used_conf.get("grid_generation", {}),
522
+ scaling_coeff=scaling_coeff,
523
+ )
524
+ used_conf["grid_generation"] = (
525
+ self.epipolar_grid_generation_application.get_conf()
526
+ )
527
+
528
+ # image resampling
529
+ self.resampling_application = Application(
530
+ "resampling",
531
+ cfg=used_conf.get("resampling", {}),
532
+ scaling_coeff=scaling_coeff,
533
+ )
534
+ used_conf["resampling"] = self.resampling_application.get_conf()
535
+
536
+ # ground truth disparity map computation
537
+ if self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
538
+ used_conf["ground_truth_reprojection"][
539
+ "save_intermediate_data"
540
+ ] = True
541
+
542
+ if isinstance(
543
+ self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM], str
544
+ ):
545
+ self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM] = {
546
+ "dsm": self.used_conf[ADVANCED][
547
+ adv_cst.GROUND_TRUTH_DSM
548
+ ]
549
+ }
550
+
551
+ self.ground_truth_reprojection = Application(
552
+ "ground_truth_reprojection",
553
+ cfg=used_conf.get("ground_truth_reprojection", {}),
554
+ scaling_coeff=scaling_coeff,
555
+ )
556
+
557
+ # disparity filling
558
+ self.dense_match_filling = Application(
559
+ "dense_match_filling",
560
+ cfg=used_conf.get(
561
+ "dense_match_filling",
562
+ {"method": "zero_padding"},
563
+ ),
564
+ scaling_coeff=scaling_coeff,
565
+ )
566
+ used_conf["dense_match_filling"] = (
567
+ self.dense_match_filling.get_conf()
568
+ )
569
+
570
+ # Sparse Matching
571
+ self.sparse_mtch_app = Application(
572
+ "sparse_matching",
573
+ cfg=used_conf.get("sparse_matching", {"method": "sift"}),
574
+ scaling_coeff=scaling_coeff,
575
+ )
576
+ used_conf["sparse_matching"] = self.sparse_mtch_app.get_conf()
577
+
578
+ # Matching
579
+ generate_performance_map = bool(
580
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][
581
+ out_cst.AUX_PERFORMANCE_MAP
582
+ ]
583
+ )
584
+
585
+ generate_ambiguity = (
586
+ self.used_conf[OUTPUT]
587
+ .get(out_cst.AUXILIARY, {})
588
+ .get(out_cst.AUX_AMBIGUITY, False)
589
+ )
590
+ dense_matching_config = used_conf.get("dense_matching", {})
591
+ if generate_ambiguity is True:
592
+ dense_matching_config["generate_ambiguity"] = True
593
+
594
+ if (
595
+ generate_performance_map is True
596
+ and dense_matching_config.get("performance_map_method", None)
597
+ is None
598
+ ):
599
+ dense_matching_config["performance_map_method"] = "risk"
600
+
601
+ # particular case for some epipolar resolutions
602
+ if not dense_matching_config:
603
+ used_conf["dense_matching"]["performance_map_method"] = [
604
+ "risk",
605
+ "intervals",
606
+ ]
607
+
608
+ self.dense_matching_app = Application(
609
+ "dense_matching",
610
+ cfg=dense_matching_config,
611
+ scaling_coeff=scaling_coeff,
612
+ )
613
+ used_conf["dense_matching"] = self.dense_matching_app.get_conf()
614
+
615
+ # Triangulation
616
+ self.triangulation_application = Application(
617
+ "triangulation",
618
+ cfg=used_conf.get("triangulation", {}),
619
+ scaling_coeff=scaling_coeff,
620
+ )
621
+ used_conf["triangulation"] = (
622
+ self.triangulation_application.get_conf()
623
+ )
624
+
625
+ # MNT generation
626
+ self.dem_generation_application = Application(
627
+ "dem_generation",
628
+ cfg=used_conf.get("dem_generation", {}),
629
+ scaling_coeff=scaling_coeff,
630
+ )
631
+ used_conf["dem_generation"] = (
632
+ self.dem_generation_application.get_conf()
633
+ )
634
+
635
+ for app_key, app_conf in used_conf.items():
636
+ if not app_key.startswith("point_cloud_outlier_removal"):
637
+ continue
638
+
639
+ if app_conf is None:
640
+ self.pc_outlier_removal_apps = {}
641
+ # keep over multiple runs
642
+ used_conf["point_cloud_outlier_removal"] = None
643
+ break
644
+
645
+ if app_key in self.pc_outlier_removal_apps:
646
+ msg = (
647
+ f"The key {app_key} is defined twice in the input "
648
+ "configuration."
649
+ )
650
+ logging.error(msg)
651
+ raise NameError(msg)
652
+
653
+ if app_key[27:] == ".1":
654
+ app_conf.setdefault("method", "small_components")
655
+ if app_key[27:] == ".2":
656
+ app_conf.setdefault("method", "statistical")
657
+
658
+ self.pc_outlier_removal_apps[app_key] = Application(
659
+ "point_cloud_outlier_removal",
660
+ cfg=app_conf,
661
+ scaling_coeff=scaling_coeff,
662
+ )
663
+ used_conf[app_key] = self.pc_outlier_removal_apps[
664
+ app_key
665
+ ].get_conf()
666
+
667
+ methods_str = "\n".join(
668
+ f" - {k}={a.used_method}"
669
+ for k, a in self.pc_outlier_removal_apps.items()
670
+ )
671
+ logging.info(
672
+ "{} point cloud outlier removal apps registered:\n{}".format(
673
+ len(self.pc_outlier_removal_apps), methods_str
674
+ )
675
+ )
676
+
677
+ if self.save_output_dsm or self.save_output_point_cloud:
678
+
679
+ if self.save_output_dsm:
680
+
681
+ # Rasterization
682
+ self.rasterization_application = Application(
683
+ "point_cloud_rasterization",
684
+ cfg=used_conf.get("point_cloud_rasterization", {}),
685
+ scaling_coeff=scaling_coeff,
686
+ )
687
+ used_conf["point_cloud_rasterization"] = (
688
+ self.rasterization_application.get_conf()
689
+ )
690
+
691
+ for app_key, app_conf in used_conf.items():
692
+ if not app_key.startswith("dsm_filling"):
693
+ continue
694
+
695
+ if app_conf is None:
696
+ self.dsm_filling_apps = {}
697
+ # keep over multiple runs
698
+ used_conf["dsm_filling"] = None
699
+ break
700
+
701
+ if app_key in self.dsm_filling_apps:
702
+ msg = (
703
+ f"The key {app_key} is defined twice in the input "
704
+ "configuration."
705
+ )
706
+ logging.error(msg)
707
+ raise NameError(msg)
708
+
709
+ if app_key[11:] == ".1":
710
+ app_conf.setdefault("method", "exogenous_filling")
711
+ if app_key[11:] == ".2":
712
+ app_conf.setdefault("method", "bulldozer")
713
+ if app_key[11:] == ".3":
714
+ app_conf.setdefault("method", "border_interpolation")
715
+
716
+ self.dsm_filling_apps[app_key] = Application(
717
+ "dsm_filling",
718
+ cfg=app_conf,
719
+ scaling_coeff=scaling_coeff,
720
+ )
721
+ used_conf[app_key] = self.dsm_filling_apps[
722
+ app_key
723
+ ].get_conf()
724
+
725
+ methods_str = "\n".join(
726
+ f" - {k}={a.used_method}"
727
+ for k, a in self.dsm_filling_apps.items()
728
+ )
729
+ logging.info(
730
+ "{} dsm filling apps registered:\n{}".format(
731
+ len(self.dsm_filling_apps), methods_str
732
+ )
733
+ )
734
+
735
+ # Auxiliary filling
736
+ self.auxiliary_filling_application = Application(
737
+ "auxiliary_filling",
738
+ cfg=conf.get("auxiliary_filling", {}),
739
+ scaling_coeff=scaling_coeff,
740
+ )
741
+ used_conf["auxiliary_filling"] = (
742
+ self.auxiliary_filling_application.get_conf()
743
+ )
744
+
745
+ if any(
746
+ app_obj.classification != ["nodata"]
747
+ for app_key, app_obj in self.dsm_filling_apps.items()
748
+ ):
749
+ self.save_output_classif_for_filling = True
750
+
751
+ return used_conf
752
+
753
+ def get_classif_values_filling(self, inputs):
754
+ """
755
+ Get values in classif, used for filling
756
+
757
+ :param inputs: inputs
758
+ :type inputs: dict
759
+
760
+ :return: list of values
761
+ :rtype: list
762
+ """
763
+
764
+ filling_classif_values = []
765
+
766
+ if sens_cst.FILLING not in inputs or inputs[sens_cst.FILLING] is None:
767
+ logging.info("No filling in input configuration")
768
+ return None
769
+
770
+ filling_classif_values = []
771
+ for _, classif_values in inputs[sens_cst.FILLING].items():
772
+ # Add new value to filling bands
773
+ if classif_values is not None:
774
+ if isinstance(classif_values, str):
775
+ classif_values = [classif_values]
776
+ filling_classif_values += classif_values
777
+
778
+ simplified_list = list(OrderedDict.fromkeys(filling_classif_values))
779
+ res_as_string_list = [str(value) for value in simplified_list]
780
+ return res_as_string_list
781
+
782
+ def check_applications_with_inputs( # noqa: C901 : too complex
783
+ self, inputs_conf, application_conf, epipolar_resolution
784
+ ):
785
+ """
786
+ Check for each application the input and output configuration
787
+ consistency
788
+
789
+ :param inputs_conf: inputs checked configuration
790
+ :type inputs_conf: dict
791
+ :param application_conf: application checked configuration
792
+ :type application_conf: dict
793
+ :param epipolar_resolution: epipolar resolution
794
+ :type epipolar_resolution: int
795
+ """
796
+
797
+ initial_elevation = (
798
+ inputs_conf[sens_cst.INITIAL_ELEVATION]["dem"] is not None
799
+ )
800
+ if self.sparse_mtch_app.elevation_delta_lower_bound is None:
801
+ self.sparse_mtch_app.used_config["elevation_delta_lower_bound"] = (
802
+ -500 if initial_elevation else -1000
803
+ )
804
+ self.sparse_mtch_app.elevation_delta_lower_bound = (
805
+ self.sparse_mtch_app.used_config["elevation_delta_lower_bound"]
806
+ )
807
+ if self.sparse_mtch_app.elevation_delta_upper_bound is None:
808
+ self.sparse_mtch_app.used_config["elevation_delta_upper_bound"] = (
809
+ 1000 if initial_elevation else 9000
810
+ )
811
+ self.sparse_mtch_app.elevation_delta_upper_bound = (
812
+ self.sparse_mtch_app.used_config["elevation_delta_upper_bound"]
813
+ )
814
+ application_conf["sparse_matching"] = self.sparse_mtch_app.get_conf()
815
+
816
+ # check classification application parameter compare
817
+ # to each sensors inputs classification list
818
+ for application_key in application_conf:
819
+ if application_conf[application_key] is None:
820
+ continue
821
+ if "classification" in application_conf[application_key]:
822
+ for item in inputs_conf["sensors"]:
823
+ if "classification" in inputs_conf["sensors"][item].keys():
824
+ if inputs_conf["sensors"][item]["classification"]:
825
+ descriptions = get_descriptions_bands(
826
+ inputs_conf["sensors"][item]["classification"]
827
+ )
828
+ if application_conf[application_key][
829
+ "classification"
830
+ ] and not set(
831
+ application_conf[application_key][
832
+ "classification"
833
+ ]
834
+ ).issubset(
835
+ set(descriptions) | {"nodata"}
836
+ ):
837
+ raise RuntimeError(
838
+ "The {} bands description {} ".format(
839
+ inputs_conf["sensors"][item][
840
+ "classification"
841
+ ],
842
+ list(descriptions),
843
+ )
844
+ + "and the {} config are not ".format(
845
+ application_key
846
+ )
847
+ + "consistent: {}".format(
848
+ application_conf[application_key][
849
+ "classification"
850
+ ]
851
+ )
852
+ )
853
+ for key1, key2 in inputs_conf["pairing"]:
854
+ corr_cfg = self.dense_matching_app.loader.get_conf()
855
+ nodata_left = inputs_conf["sensors"][key2]["image"]["no_data"]
856
+ nodata_right = inputs_conf["sensors"][key2]["image"]["no_data"]
857
+ bands_left = list(
858
+ inputs_conf["sensors"][key1]["image"]["bands"].keys()
859
+ )
860
+ bands_right = list(
861
+ inputs_conf["sensors"][key2]["image"]["bands"].keys()
862
+ )
863
+ values_classif_left = None
864
+ values_classif_right = None
865
+ if (
866
+ "classification" in inputs_conf["sensors"][key1]
867
+ and inputs_conf["sensors"][key1]["classification"] is not None
868
+ ):
869
+ values_classif_left = inputs_conf["sensors"][key1][
870
+ "classification"
871
+ ]["values"]
872
+ values_classif_left = list(map(str, values_classif_left))
873
+ if (
874
+ "classification" in inputs_conf["sensors"][key2]
875
+ and inputs_conf["sensors"][key2]["classification"] is not None
876
+ ):
877
+ values_classif_right = inputs_conf["sensors"][key2][
878
+ "classification"
879
+ ]["values"]
880
+ values_classif_right = list(map(str, values_classif_right))
881
+ self.dense_matching_app.corr_config = (
882
+ self.dense_matching_app.loader.check_conf(
883
+ corr_cfg,
884
+ nodata_left,
885
+ nodata_right,
886
+ bands_left,
887
+ bands_right,
888
+ values_classif_left,
889
+ values_classif_right,
890
+ )
891
+ )
892
+
893
+ # Change the step regarding the resolution
894
+ # For the small resolution, the resampling perform better
895
+ # with a small step
896
+ # For the higher ones, a step at 30 should be better
897
+ first_image_path = next(iter(inputs_conf["sensors"].values()))["image"][
898
+ "bands"
899
+ ]["b0"]["path"]
900
+ first_image_size = rasterio_get_size(first_image_path)
901
+ size_low_res_img_row = first_image_size[0] // epipolar_resolution
902
+ size_low_res_img_col = first_image_size[1] // epipolar_resolution
903
+ if epipolar_resolution > 1:
904
+ if size_low_res_img_row <= 900 and size_low_res_img_col <= 900:
905
+ application_conf["grid_generation"]["epi_step"] = (
906
+ epipolar_resolution * 5
907
+ )
908
+ else:
909
+ application_conf["grid_generation"]["epi_step"] = (
910
+ epipolar_resolution * 30
911
+ )
912
+
913
+ return application_conf
914
+
915
+ def generate_grid_correction_on_dem(self, pair_key, geo_plugin_on_dem):
916
+ """
917
+ Generate the epipolar grid correction for a given pair, using given dem
918
+ """
919
+
920
+ # Generate new grids with dem
921
+ # Generate rectification grids
922
+ (
923
+ grid_left_new_dem,
924
+ grid_right_new_dem,
925
+ ) = self.epipolar_grid_generation_application.run(
926
+ self.pairs[pair_key]["sensor_image_left"],
927
+ self.pairs[pair_key]["sensor_image_right"],
928
+ geo_plugin_on_dem,
929
+ orchestrator=self.cars_orchestrator,
930
+ pair_folder=os.path.join(
931
+ self.dump_dir,
932
+ "epipolar_grid_generation",
933
+ "new_dem",
934
+ pair_key,
935
+ ),
936
+ pair_key=pair_key,
937
+ )
938
+
939
+ if self.pairs[pair_key].get("sensor_matches_left", None) is None:
940
+ logging.error(
941
+ "No sensor matches available to compute grid correction"
942
+ )
943
+ return None
944
+
945
+ # Generate new matches with new grids
946
+ new_grid_matches_array = geo_plugin_on_dem.transform_matches_from_grids(
947
+ self.pairs[pair_key]["sensor_matches_left"],
948
+ self.pairs[pair_key]["sensor_matches_right"],
949
+ grid_left_new_dem,
950
+ grid_right_new_dem,
951
+ )
952
+
953
+ # Generate grid_correction
954
+ # Compute grid correction
955
+ (
956
+ new_grid_correction_coef,
957
+ _,
958
+ _,
959
+ _,
960
+ ) = grid_correction_app.estimate_right_grid_correction(
961
+ new_grid_matches_array,
962
+ grid_right_new_dem,
963
+ save_matches=False,
964
+ minimum_nb_matches=0,
965
+ pair_folder=os.path.join(
966
+ self.dump_dir, "grid_correction", " new_dem", pair_key
967
+ ),
968
+ pair_key=pair_key,
969
+ orchestrator=self.cars_orchestrator,
970
+ )
971
+
972
+ return new_grid_correction_coef
973
+
974
+ def sensor_to_depth_maps(self): # noqa: C901
975
+ """
976
+ Creates the depth map from the sensor images given in the input,
977
+ by following the CARS pipeline's steps.
978
+ """
979
+ # pylint:disable=too-many-return-statements
980
+ inputs = self.used_conf[INPUT]
981
+ output = self.used_conf[OUTPUT]
982
+
983
+ # Initialize epsg for terrain tiles
984
+ self.phasing = self.used_conf[ADVANCED][adv_cst.PHASING]
985
+
986
+ if self.phasing is not None:
987
+ self.epsg = self.phasing["epsg"]
988
+ else:
989
+ self.epsg = output[out_cst.EPSG]
990
+
991
+ if self.epsg is not None:
992
+ # Compute roi polygon, in output EPSG
993
+ self.roi_poly = preprocessing.compute_roi_poly(
994
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
995
+ )
996
+
997
+ self.resolution = output[out_cst.RESOLUTION]
998
+
999
+ # List of terrain roi corresponding to each epipolar pair
1000
+ # Used to generate final terrain roi
1001
+ self.list_terrain_roi = []
1002
+
1003
+ # Polygons representing the intersection of each pair of images
1004
+ # Used to fill the final DSM only inside of those Polygons
1005
+ self.list_intersection_poly = []
1006
+
1007
+ # initialize lists of points
1008
+ self.list_epipolar_point_clouds = []
1009
+ self.list_sensor_pairs = sensor_inputs.generate_inputs(
1010
+ inputs, self.geom_plugin_without_dem_and_geoid
1011
+ )
1012
+ logging.info(
1013
+ "Received {} stereo pairs configurations".format(
1014
+ len(self.list_sensor_pairs)
1015
+ )
1016
+ )
1017
+
1018
+ output_parameters.intialize_product_index(
1019
+ self.cars_orchestrator,
1020
+ output["product_level"],
1021
+ [sensor_pair[0] for sensor_pair in self.list_sensor_pairs],
1022
+ )
1023
+
1024
+ # pairs is a dict used to store the CarsDataset of
1025
+ # all pairs, easily retrievable with pair keys
1026
+ self.pairs = {}
1027
+
1028
+ # triangulated_matches_list is used to store triangulated matches
1029
+ # used in dem generation
1030
+ self.triangulated_matches_list = []
1031
+
1032
+ save_matches = self.sparse_mtch_app.get_save_matches()
1033
+
1034
+ save_corrected_grid = (
1035
+ self.epipolar_grid_generation_application.get_save_grids()
1036
+ )
1037
+
1038
+ for (
1039
+ pair_key,
1040
+ sensor_image_left,
1041
+ sensor_image_right,
1042
+ ) in self.list_sensor_pairs:
1043
+
1044
+ # initialize pairs for current pair
1045
+ self.pairs[pair_key] = {}
1046
+ self.pairs[pair_key]["sensor_image_left"] = sensor_image_left
1047
+ self.pairs[pair_key]["sensor_image_right"] = sensor_image_right
1048
+
1049
+ # Run applications
1050
+
1051
+ # Run grid generation
1052
+ # We generate grids with dem if it is provided.
1053
+ # If not provided, grid are generated without dem and a dem
1054
+ # will be generated, to use later for a new grid generation**
1055
+
1056
+ if inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None:
1057
+ geom_plugin = self.geom_plugin_without_dem_and_geoid
1058
+
1059
+ else:
1060
+ geom_plugin = self.geom_plugin_with_dem_and_geoid
1061
+
1062
+ # Generate rectification grids
1063
+ (
1064
+ self.pairs[pair_key]["grid_left"],
1065
+ self.pairs[pair_key]["grid_right"],
1066
+ ) = self.epipolar_grid_generation_application.run(
1067
+ self.pairs[pair_key]["sensor_image_left"],
1068
+ self.pairs[pair_key]["sensor_image_right"],
1069
+ geom_plugin,
1070
+ orchestrator=self.cars_orchestrator,
1071
+ pair_folder=os.path.join(
1072
+ self.dump_dir,
1073
+ "epipolar_grid_generation",
1074
+ "initial",
1075
+ pair_key,
1076
+ ),
1077
+ pair_key=pair_key,
1078
+ )
1079
+
1080
+ if self.quit_on_app("grid_generation"):
1081
+ continue # keep iterating over pairs, but don't go further
1082
+
1083
+ if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (
1084
+ None,
1085
+ {},
1086
+ ):
1087
+ # Run resampling only if needed:
1088
+ # no a priori
1089
+
1090
+ # Get required bands of first resampling
1091
+ required_bands = self.sparse_mtch_app.get_required_bands()
1092
+
1093
+ # Run first epipolar resampling
1094
+ (
1095
+ self.pairs[pair_key]["epipolar_image_left"],
1096
+ self.pairs[pair_key]["epipolar_image_right"],
1097
+ ) = self.resampling_application.run(
1098
+ self.pairs[pair_key]["sensor_image_left"],
1099
+ self.pairs[pair_key]["sensor_image_right"],
1100
+ self.pairs[pair_key]["grid_left"],
1101
+ self.pairs[pair_key]["grid_right"],
1102
+ geom_plugin,
1103
+ orchestrator=self.cars_orchestrator,
1104
+ pair_folder=os.path.join(
1105
+ self.dump_dir, "resampling", "initial", pair_key
1106
+ ),
1107
+ pair_key=pair_key,
1108
+ margins_fun=self.sparse_mtch_app.get_margins_fun(),
1109
+ tile_width=None,
1110
+ tile_height=None,
1111
+ required_bands=required_bands,
1112
+ )
1113
+
1114
+ if self.quit_on_app("resampling"):
1115
+ continue # keep iterating over pairs, but don't go further
1116
+
1117
+ if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (
1118
+ None,
1119
+ {},
1120
+ ):
1121
+ # Run epipolar sparse_matching application
1122
+ (
1123
+ self.pairs[pair_key]["epipolar_matches_left"],
1124
+ _,
1125
+ ) = self.sparse_mtch_app.run(
1126
+ self.pairs[pair_key]["epipolar_image_left"],
1127
+ self.pairs[pair_key]["epipolar_image_right"],
1128
+ self.pairs[pair_key]["grid_left"]["disp_to_alt_ratio"],
1129
+ orchestrator=self.cars_orchestrator,
1130
+ pair_folder=os.path.join(
1131
+ self.dump_dir, "sparse_matching", pair_key
1132
+ ),
1133
+ pair_key=pair_key,
1134
+ classif_bands_to_mask=self.used_classif_values_for_filling,
1135
+ )
1136
+
1137
+ # Run cluster breakpoint to compute sifts: force computation
1138
+ self.cars_orchestrator.breakpoint()
1139
+
1140
+ minimum_nb_matches = self.sparse_mtch_app.get_minimum_nb_matches()
1141
+
1142
+ # Run grid correction application
1143
+ if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
1144
+ # Estimate grid correction if no epipolar a priori
1145
+ # Filter and save matches
1146
+ self.pairs[pair_key]["matches_array"] = (
1147
+ self.sparse_mtch_app.filter_matches(
1148
+ self.pairs[pair_key]["epipolar_matches_left"],
1149
+ self.pairs[pair_key]["grid_left"],
1150
+ self.pairs[pair_key]["grid_right"],
1151
+ geom_plugin,
1152
+ orchestrator=self.cars_orchestrator,
1153
+ pair_key=pair_key,
1154
+ pair_folder=os.path.join(
1155
+ self.dump_dir, "sparse_matching", pair_key
1156
+ ),
1157
+ save_matches=(self.sparse_mtch_app.get_save_matches()),
1158
+ )
1159
+ )
1160
+
1161
+ # Compute grid correction
1162
+ (
1163
+ self.pairs[pair_key]["grid_correction_coef"],
1164
+ self.pairs[pair_key]["corrected_matches_array"],
1165
+ _,
1166
+ _,
1167
+ ) = grid_correction_app.estimate_right_grid_correction(
1168
+ self.pairs[pair_key]["matches_array"],
1169
+ self.pairs[pair_key]["grid_right"],
1170
+ save_matches=save_matches,
1171
+ minimum_nb_matches=minimum_nb_matches,
1172
+ pair_folder=os.path.join(
1173
+ self.dump_dir, "grid_correction", "initial", pair_key
1174
+ ),
1175
+ pair_key=pair_key,
1176
+ orchestrator=self.cars_orchestrator,
1177
+ )
1178
+ # Correct grid right
1179
+ self.pairs[pair_key]["corrected_grid_right"] = (
1180
+ grid_correction_app.correct_grid(
1181
+ self.pairs[pair_key]["grid_right"],
1182
+ self.pairs[pair_key]["grid_correction_coef"],
1183
+ os.path.join(
1184
+ self.dump_dir,
1185
+ "grid_correction",
1186
+ "initial",
1187
+ pair_key,
1188
+ ),
1189
+ save_corrected_grid,
1190
+ )
1191
+ )
1192
+
1193
+ self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
1194
+ pair_key
1195
+ ]["grid_left"]
1196
+
1197
+ if self.quit_on_app("sparse_matching"):
1198
+ continue
1199
+
1200
+ # Shrink disparity intervals according to SIFT disparities
1201
+ disp_to_alt_ratio = self.pairs[pair_key]["grid_left"][
1202
+ "disp_to_alt_ratio"
1203
+ ]
1204
+ disp_bounds_params = (
1205
+ self.sparse_mtch_app.disparity_bounds_estimation
1206
+ )
1207
+
1208
+ if disp_bounds_params["activated"]:
1209
+ matches = self.pairs[pair_key]["matches_array"]
1210
+ sift_disp = matches[:, 2] - matches[:, 0]
1211
+ disp_min = np.percentile(
1212
+ sift_disp, disp_bounds_params["percentile"]
1213
+ )
1214
+ disp_max = np.percentile(
1215
+ sift_disp, 100 - disp_bounds_params["percentile"]
1216
+ )
1217
+ logging.info(
1218
+ "Global disparity interval without margin : "
1219
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1220
+ )
1221
+ disp_min -= (
1222
+ disp_bounds_params["upper_margin"] / disp_to_alt_ratio
1223
+ )
1224
+ disp_max += (
1225
+ disp_bounds_params["lower_margin"] / disp_to_alt_ratio
1226
+ )
1227
+ logging.info(
1228
+ "Global disparity interval with margin : "
1229
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1230
+ )
1231
+ else:
1232
+ disp_min = (
1233
+ -self.sparse_mtch_app.elevation_delta_upper_bound
1234
+ / disp_to_alt_ratio
1235
+ )
1236
+ disp_max = (
1237
+ -self.sparse_mtch_app.elevation_delta_lower_bound
1238
+ / disp_to_alt_ratio
1239
+ )
1240
+ logging.info(
1241
+ "Global disparity interval : "
1242
+ f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
1243
+ )
1244
+
1245
+ if self.epsg is None:
1246
+ # compute epsg
1247
+ # Epsg uses global disparity min and max
1248
+ self.epsg = preprocessing.compute_epsg(
1249
+ self.pairs[pair_key]["sensor_image_left"],
1250
+ self.pairs[pair_key]["sensor_image_right"],
1251
+ self.pairs[pair_key]["corrected_grid_left"],
1252
+ self.pairs[pair_key]["corrected_grid_right"],
1253
+ self.geom_plugin_with_dem_and_geoid,
1254
+ disp_min=0,
1255
+ disp_max=0,
1256
+ )
1257
+ # Compute roi polygon, in input EPSG
1258
+ self.roi_poly = preprocessing.compute_roi_poly(
1259
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
1260
+ )
1261
+
1262
+ # Clean grids at the end of processing if required. Note that this will
1263
+ # also clean refined grids
1264
+ if not (save_corrected_grid or save_matches):
1265
+ self.cars_orchestrator.add_to_clean(
1266
+ os.path.join(self.dump_dir, "grid_correction")
1267
+ )
1268
+ # grids file are already cleaned in the application, but the tree
1269
+ # structure should also be cleaned
1270
+ if not save_corrected_grid:
1271
+
1272
+ self.cars_orchestrator.add_to_clean(
1273
+ os.path.join(self.dump_dir, "epipolar_grid_generation")
1274
+ )
1275
+
1276
+ # quit if any app in the loop over the pairs was the last one
1277
+ if (
1278
+ self.quit_on_app("grid_generation")
1279
+ or self.quit_on_app("resampling")
1280
+ or self.quit_on_app("sparse_matching")
1281
+ ):
1282
+ return True
1283
+
1284
+ if not self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
1285
+ # Use a priori
1286
+ dem_median = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
1287
+ adv_cst.DEM_MEDIAN
1288
+ ]
1289
+ dem_min = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
1290
+ adv_cst.DEM_MIN
1291
+ ]
1292
+ dem_max = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
1293
+ adv_cst.DEM_MAX
1294
+ ]
1295
+
1296
+ # Define param
1297
+ use_global_disp_range = self.dense_matching_app.use_global_disp_range
1298
+
1299
+ self.pairs_names = [
1300
+ pair_name for pair_name, _, _ in self.list_sensor_pairs
1301
+ ]
1302
+
1303
+ for _, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
1304
+ # Geometry plugin with dem will be used for the grid generation
1305
+ geom_plugin = self.geom_plugin_with_dem_and_geoid
1306
+
1307
+ if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
1308
+ save_matches = True
1309
+
1310
+ (
1311
+ self.pairs[pair_key]["sensor_matches_left"],
1312
+ self.pairs[pair_key]["sensor_matches_right"],
1313
+ ) = geom_plugin.get_sensor_matches(
1314
+ self.pairs[pair_key]["corrected_matches_array"],
1315
+ self.pairs[pair_key]["corrected_grid_left"],
1316
+ self.pairs[pair_key]["corrected_grid_right"],
1317
+ pair_folder=os.path.join(
1318
+ self.out_dir, "dsm/sensor_matches", pair_key
1319
+ ),
1320
+ save_matches=save_matches,
1321
+ )
1322
+ elif (
1323
+ not self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI]
1324
+ in (None, {})
1325
+ and not self.use_sift_a_priori
1326
+ ):
1327
+ # Use epipolar a priori
1328
+ # load the disparity range
1329
+ if use_global_disp_range:
1330
+ [dmin, dmax] = self.used_conf[ADVANCED][
1331
+ adv_cst.EPIPOLAR_A_PRIORI
1332
+ ][pair_key][adv_cst.DISPARITY_RANGE]
1333
+
1334
+ advanced_parameters.update_conf(
1335
+ self.refined_conf,
1336
+ dmin=dmin,
1337
+ dmax=dmax,
1338
+ pair_key=pair_key,
1339
+ )
1340
+ else:
1341
+
1342
+ # load the grid correction coefficient
1343
+ self.pairs[pair_key][
1344
+ "grid_correction_coef"
1345
+ ] = self.used_conf[ADVANCED][adv_cst.EPIPOLAR_A_PRIORI][
1346
+ pair_key
1347
+ ][
1348
+ adv_cst.GRID_CORRECTION
1349
+ ]
1350
+ self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
1351
+ pair_key
1352
+ ]["grid_left"]
1353
+ # no correction if the grid correction coefs are None
1354
+ if self.pairs[pair_key]["grid_correction_coef"] is None:
1355
+ self.pairs[pair_key]["corrected_grid_right"] = (
1356
+ self.pairs[pair_key]["grid_right"]
1357
+ )
1358
+ else:
1359
+ # Correct grid right with provided epipolar a priori
1360
+ self.pairs[pair_key]["corrected_grid_right"] = (
1361
+ grid_correction_app.correct_grid_from_1d(
1362
+ self.pairs[pair_key]["grid_right"],
1363
+ self.pairs[pair_key]["grid_correction_coef"],
1364
+ save_corrected_grid,
1365
+ os.path.join(
1366
+ self.dump_dir, "grid_correction", pair_key
1367
+ ),
1368
+ )
1369
+ )
1370
+ else:
1371
+ # Correct grids with former matches
1372
+ # Transform matches to new grids
1373
+
1374
+ save_matches = self.sparse_mtch_app.get_save_matches()
1375
+
1376
+ self.sensor_matches_left = os.path.join(
1377
+ self.first_res_out_dir,
1378
+ "dsm/sensor_matches",
1379
+ pair_key,
1380
+ "sensor_matches_left.npy",
1381
+ )
1382
+ self.sensor_matches_right = os.path.join(
1383
+ self.first_res_out_dir,
1384
+ "dsm/sensor_matches",
1385
+ pair_key,
1386
+ "sensor_matches_right.npy",
1387
+ )
1388
+
1389
+ self.pairs[pair_key]["sensor_matches_left"] = np.load(
1390
+ self.sensor_matches_left
1391
+ )
1392
+ self.pairs[pair_key]["sensor_matches_right"] = np.load(
1393
+ self.sensor_matches_right
1394
+ )
1395
+
1396
+ new_grid_matches_array = (
1397
+ geom_plugin.transform_matches_from_grids(
1398
+ self.pairs[pair_key]["sensor_matches_left"],
1399
+ self.pairs[pair_key]["sensor_matches_right"],
1400
+ self.pairs[pair_key]["grid_left"],
1401
+ self.pairs[pair_key]["grid_right"],
1402
+ )
1403
+ )
1404
+
1405
+ # Estimate grid_correction
1406
+ (
1407
+ self.pairs[pair_key]["grid_correction_coef"],
1408
+ self.pairs[pair_key]["corrected_matches_array"],
1409
+ _,
1410
+ _,
1411
+ ) = grid_correction_app.estimate_right_grid_correction(
1412
+ new_grid_matches_array,
1413
+ self.pairs[pair_key]["grid_right"],
1414
+ save_matches=save_matches,
1415
+ minimum_nb_matches=minimum_nb_matches,
1416
+ pair_folder=os.path.join(
1417
+ self.dump_dir, "grid_correction", "new", pair_key
1418
+ ),
1419
+ pair_key=pair_key,
1420
+ orchestrator=self.cars_orchestrator,
1421
+ )
1422
+
1423
+ # Correct grid right
1424
+
1425
+ self.pairs[pair_key]["corrected_grid_right"] = (
1426
+ grid_correction_app.correct_grid(
1427
+ self.pairs[pair_key]["grid_right"],
1428
+ self.pairs[pair_key]["grid_correction_coef"],
1429
+ os.path.join(
1430
+ self.dump_dir,
1431
+ "grid_correction",
1432
+ "new",
1433
+ pair_key,
1434
+ ),
1435
+ save_corrected_grid,
1436
+ )
1437
+ )
1438
+
1439
+ # Use the new grid as uncorrected grid
1440
+ self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
1441
+ pair_key
1442
+ ]["grid_left"]
1443
+
1444
+ # Run epipolar resampling
1445
+ self.pairs[pair_key]["corrected_grid_left"] = transform_grid_func(
1446
+ self.pairs[pair_key]["corrected_grid_left"],
1447
+ self.res_resamp,
1448
+ )
1449
+
1450
+ self.pairs[pair_key]["corrected_grid_right"] = transform_grid_func(
1451
+ self.pairs[pair_key]["corrected_grid_right"],
1452
+ self.res_resamp,
1453
+ right=True,
1454
+ )
1455
+
1456
+ # Update refined_conf configuration with epipolar a priori
1457
+ advanced_parameters.update_conf(
1458
+ self.refined_conf,
1459
+ grid_correction_coef=self.pairs[pair_key][
1460
+ "grid_correction_coef"
1461
+ ],
1462
+ pair_key=pair_key,
1463
+ reference_dem=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
1464
+ sens_cst.DEM_PATH
1465
+ ],
1466
+ )
1467
+ # saved used configuration
1468
+ self.save_configurations()
1469
+
1470
+ # Generate min and max disp grids
1471
+ # Global disparity min and max will be computed from
1472
+ # these grids
1473
+ dense_matching_pair_folder = os.path.join(
1474
+ self.dump_dir, "dense_matching", pair_key
1475
+ )
1476
+
1477
+ if self.which_resolution in ("first", "single") and self.used_conf[
1478
+ ADVANCED
1479
+ ][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
1480
+ dmin = disp_min / self.res_resamp
1481
+ dmax = disp_max / self.res_resamp
1482
+ # generate_disparity_grids runs orchestrator.breakpoint()
1483
+ self.pairs[pair_key]["disp_range_grid"] = (
1484
+ self.dense_matching_app.generate_disparity_grids(
1485
+ self.pairs[pair_key]["sensor_image_right"],
1486
+ self.pairs[pair_key]["corrected_grid_right"],
1487
+ self.geom_plugin_with_dem_and_geoid,
1488
+ dmin=dmin,
1489
+ dmax=dmax,
1490
+ pair_folder=dense_matching_pair_folder,
1491
+ orchestrator=self.cars_orchestrator,
1492
+ )
1493
+ )
1494
+
1495
+ dsp_marg = self.sparse_mtch_app.get_disparity_margin()
1496
+ updating_infos = {
1497
+ application_constants.APPLICATION_TAG: {
1498
+ sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
1499
+ pair_key: {
1500
+ sm_cst.DISPARITY_MARGIN_PARAM_TAG: dsp_marg,
1501
+ sm_cst.MINIMUM_DISPARITY_TAG: dmin,
1502
+ sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+ self.cars_orchestrator.update_out_info(updating_infos)
1508
+
1509
+ advanced_parameters.update_conf(
1510
+ self.refined_conf,
1511
+ dmin=dmin,
1512
+ dmax=dmax,
1513
+ pair_key=pair_key,
1514
+ )
1515
+ else:
1516
+ # Generate min and max disp grids from dems
1517
+ # generate_disparity_grids runs orchestrator.breakpoint()
1518
+ self.pairs[pair_key]["disp_range_grid"] = (
1519
+ self.dense_matching_app.generate_disparity_grids(
1520
+ self.pairs[pair_key]["sensor_image_right"],
1521
+ self.pairs[pair_key]["corrected_grid_right"],
1522
+ self.geom_plugin_with_dem_and_geoid,
1523
+ dem_min=dem_min,
1524
+ dem_max=dem_max,
1525
+ dem_median=dem_median,
1526
+ pair_folder=dense_matching_pair_folder,
1527
+ orchestrator=self.cars_orchestrator,
1528
+ )
1529
+ )
1530
+
1531
+ if use_global_disp_range:
1532
+ # Generate min and max disp grids from constants
1533
+ # sensor image is not used here
1534
+ # TODO remove when only local diparity range will be used
1535
+
1536
+ if self.use_sift_a_priori:
1537
+ dmin = self.pairs[pair_key]["disp_range_grid"][
1538
+ "global_min"
1539
+ ]
1540
+ dmax = self.pairs[pair_key]["disp_range_grid"][
1541
+ "global_max"
1542
+ ]
1543
+
1544
+ # update orchestrator_out_json
1545
+ marg = self.sparse_mtch_app.get_disparity_margin()
1546
+ updating_infos = {
1547
+ application_constants.APPLICATION_TAG: {
1548
+ sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
1549
+ pair_key: {
1550
+ sm_cst.DISPARITY_MARGIN_PARAM_TAG: marg,
1551
+ sm_cst.MINIMUM_DISPARITY_TAG: dmin,
1552
+ sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
1553
+ }
1554
+ }
1555
+ }
1556
+ }
1557
+ self.cars_orchestrator.update_out_info(updating_infos)
1558
+
1559
+ advanced_parameters.update_conf(
1560
+ self.refined_conf,
1561
+ dmin=dmin,
1562
+ dmax=dmax,
1563
+ pair_key=pair_key,
1564
+ )
1565
+
1566
+ # generate_disparity_grids runs orchestrator.breakpoint()
1567
+ self.pairs[pair_key]["disp_range_grid"] = (
1568
+ self.dense_matching_app.generate_disparity_grids(
1569
+ self.pairs[pair_key]["sensor_image_right"],
1570
+ self.pairs[pair_key]["corrected_grid_right"],
1571
+ self.geom_plugin_with_dem_and_geoid,
1572
+ dmin=dmin,
1573
+ dmax=dmax,
1574
+ pair_folder=dense_matching_pair_folder,
1575
+ orchestrator=self.cars_orchestrator,
1576
+ )
1577
+ )
1578
+
1579
+ # TODO add in metadata.json max diff max - min
1580
+ # Update used_conf configuration with epipolar a priori
1581
+ # Add global min and max computed with grids
1582
+ advanced_parameters.update_conf(
1583
+ self.refined_conf,
1584
+ dmin=self.pairs[pair_key]["disp_range_grid"]["global_min"],
1585
+ dmax=self.pairs[pair_key]["disp_range_grid"]["global_max"],
1586
+ pair_key=pair_key,
1587
+ )
1588
+ advanced_parameters.update_conf(
1589
+ self.refined_conf,
1590
+ dmin=self.pairs[pair_key]["disp_range_grid"]["global_min"],
1591
+ dmax=self.pairs[pair_key]["disp_range_grid"]["global_max"],
1592
+ pair_key=pair_key,
1593
+ )
1594
+
1595
+ # saved used configuration
1596
+ self.save_configurations()
1597
+
1598
+ # end of for loop, to finish computing disparity range grids
1599
+
1600
+ for cloud_id, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
1601
+
1602
+ # Generate roi
1603
+ epipolar_roi = preprocessing.compute_epipolar_roi(
1604
+ self.input_roi_poly,
1605
+ self.input_roi_epsg,
1606
+ self.geom_plugin_with_dem_and_geoid,
1607
+ self.pairs[pair_key]["sensor_image_left"],
1608
+ self.pairs[pair_key]["sensor_image_right"],
1609
+ self.pairs[pair_key]["corrected_grid_left"],
1610
+ self.pairs[pair_key]["corrected_grid_right"],
1611
+ os.path.join(self.dump_dir, "compute_epipolar_roi", pair_key),
1612
+ disp_min=self.pairs[pair_key]["disp_range_grid"]["global_min"],
1613
+ disp_max=self.pairs[pair_key]["disp_range_grid"]["global_max"],
1614
+ )
1615
+
1616
+ # Generate new epipolar images
1617
+ # Generated with corrected grids
1618
+ # Optimal size is computed for the worst case scenario
1619
+ # found with epipolar disparity range grids
1620
+
1621
+ (
1622
+ optimum_tile_size,
1623
+ local_tile_optimal_size_fun,
1624
+ ) = self.dense_matching_app.get_optimal_tile_size(
1625
+ self.pairs[pair_key]["disp_range_grid"],
1626
+ self.cars_orchestrator.cluster.checked_conf_cluster[
1627
+ "max_ram_per_worker"
1628
+ ],
1629
+ )
1630
+
1631
+ # Get required bands of third resampling
1632
+ required_bands = self.dense_matching_app.get_required_bands()
1633
+
1634
+ # Add left required bands for texture
1635
+ required_bands["left"] = sorted(
1636
+ set(required_bands["left"]).union(set(self.texture_bands))
1637
+ )
1638
+
1639
+ # Find index of texture band in left_dataset
1640
+ texture_bands_indices = [
1641
+ required_bands["left"].index(band)
1642
+ for band in self.texture_bands
1643
+ ]
1644
+
1645
+ # Get margins used in dense matching,
1646
+ dense_matching_margins_fun = (
1647
+ self.dense_matching_app.get_margins_fun(
1648
+ self.pairs[pair_key]["corrected_grid_left"],
1649
+ self.pairs[pair_key]["disp_range_grid"],
1650
+ )
1651
+ )
1652
+
1653
+ # Quick fix to reduce memory usage
1654
+ if self.res_resamp >= 16:
1655
+ optimum_tile_size = 200
1656
+
1657
+ # Run third epipolar resampling
1658
+ (
1659
+ new_epipolar_image_left,
1660
+ new_epipolar_image_right,
1661
+ ) = self.resampling_application.run(
1662
+ self.pairs[pair_key]["sensor_image_left"],
1663
+ self.pairs[pair_key]["sensor_image_right"],
1664
+ self.pairs[pair_key]["corrected_grid_left"],
1665
+ self.pairs[pair_key]["corrected_grid_right"],
1666
+ geom_plugin,
1667
+ orchestrator=self.cars_orchestrator,
1668
+ pair_folder=os.path.join(
1669
+ self.dump_dir, "resampling", "corrected_grid", pair_key
1670
+ ),
1671
+ pair_key=pair_key,
1672
+ margins_fun=dense_matching_margins_fun,
1673
+ tile_width=optimum_tile_size,
1674
+ tile_height=optimum_tile_size,
1675
+ add_classif=True,
1676
+ epipolar_roi=epipolar_roi,
1677
+ resolution=self.res_resamp,
1678
+ required_bands=required_bands,
1679
+ texture_bands=self.texture_bands,
1680
+ )
1681
+
1682
+ # Run ground truth dsm computation
1683
+ if self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
1684
+ self.used_conf["applications"]["ground_truth_reprojection"][
1685
+ "save_intermediate_data"
1686
+ ] = True
1687
+ new_geomplugin_dsm = AbstractGeometry( # pylint: disable=E0110
1688
+ self.geometry_plugin,
1689
+ dem=self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
1690
+ adv_cst.INPUT_GROUND_TRUTH_DSM
1691
+ ],
1692
+ geoid=self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
1693
+ adv_cst.INPUT_GEOID
1694
+ ],
1695
+ scaling_coeff=self.scaling_coeff,
1696
+ )
1697
+ self.ground_truth_reprojection.run(
1698
+ self.pairs[pair_key]["sensor_image_left"],
1699
+ self.pairs[pair_key]["sensor_image_right"],
1700
+ self.pairs[pair_key]["corrected_grid_left"],
1701
+ self.pairs[pair_key]["corrected_grid_right"],
1702
+ new_geomplugin_dsm,
1703
+ self.geom_plugin_with_dem_and_geoid,
1704
+ self.pairs[pair_key]["corrected_grid_left"][
1705
+ "disp_to_alt_ratio"
1706
+ ],
1707
+ self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
1708
+ adv_cst.INPUT_AUX_PATH
1709
+ ],
1710
+ self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
1711
+ adv_cst.INPUT_AUX_INTERP
1712
+ ],
1713
+ orchestrator=self.cars_orchestrator,
1714
+ pair_folder=os.path.join(
1715
+ self.dump_dir, "ground_truth_reprojection", pair_key
1716
+ ),
1717
+ )
1718
+
1719
+ if self.epsg is None:
1720
+ # compute epsg
1721
+ # Epsg uses global disparity min and max
1722
+ self.epsg = preprocessing.compute_epsg(
1723
+ self.pairs[pair_key]["sensor_image_left"],
1724
+ self.pairs[pair_key]["sensor_image_right"],
1725
+ self.pairs[pair_key]["corrected_grid_left"],
1726
+ self.pairs[pair_key]["corrected_grid_right"],
1727
+ self.geom_plugin_with_dem_and_geoid,
1728
+ disp_min=self.pairs[pair_key]["disp_range_grid"][
1729
+ "global_min"
1730
+ ],
1731
+ disp_max=self.pairs[pair_key]["disp_range_grid"][
1732
+ "global_max"
1733
+ ],
1734
+ )
1735
+ # Compute roi polygon, in input EPSG
1736
+ self.roi_poly = preprocessing.compute_roi_poly(
1737
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
1738
+ )
1739
+
1740
+ self.vertical_crs = projection.get_output_crs(self.epsg, output)
1741
+
1742
+ if (
1743
+ self.save_output_dsm
1744
+ or self.save_output_point_cloud
1745
+ or self.dense_matching_app.get_method() == "auto"
1746
+ ):
1747
+ # Compute terrain bounding box /roi related to
1748
+ # current images
1749
+ (current_terrain_roi_bbox, intersection_poly) = (
1750
+ preprocessing.compute_terrain_bbox(
1751
+ self.pairs[pair_key]["sensor_image_left"],
1752
+ self.pairs[pair_key]["sensor_image_right"],
1753
+ new_epipolar_image_left,
1754
+ self.pairs[pair_key]["corrected_grid_left"],
1755
+ self.pairs[pair_key]["corrected_grid_right"],
1756
+ self.epsg,
1757
+ self.geom_plugin_with_dem_and_geoid,
1758
+ resolution=self.resolution,
1759
+ disp_min=self.pairs[pair_key]["disp_range_grid"][
1760
+ "global_min"
1761
+ ],
1762
+ disp_max=self.pairs[pair_key]["disp_range_grid"][
1763
+ "global_max"
1764
+ ],
1765
+ roi_poly=(
1766
+ None if self.debug_with_roi else self.roi_poly
1767
+ ),
1768
+ orchestrator=self.cars_orchestrator,
1769
+ pair_key=pair_key,
1770
+ pair_folder=os.path.join(
1771
+ self.dump_dir, "terrain_bbox", pair_key
1772
+ ),
1773
+ check_inputs=False,
1774
+ )
1775
+ )
1776
+ self.list_terrain_roi.append(current_terrain_roi_bbox)
1777
+ self.list_intersection_poly.append(intersection_poly)
1778
+
1779
+ # compute terrain bounds for later use
1780
+ (
1781
+ self.terrain_bounds,
1782
+ self.optimal_terrain_tile_width,
1783
+ ) = preprocessing.compute_terrain_bounds(
1784
+ self.list_terrain_roi,
1785
+ roi_poly=(None if self.debug_with_roi else self.roi_poly),
1786
+ resolution=self.resolution,
1787
+ )
1788
+
1789
+ if self.which_resolution not in ("final", "single"):
1790
+ self.terrain_bounds = dem_wrappers.modify_terrain_bounds(
1791
+ self.terrain_bounds,
1792
+ self.dem_generation_application.margin[0],
1793
+ self.dem_generation_application.margin[1],
1794
+ )
1795
+
1796
+ if self.dense_matching_app.get_method() == "auto":
1797
+ # Copy the initial corr_config in order to keep
1798
+ # the inputs that have already been checked
1799
+ corr_cfg = self.dense_matching_app.corr_config.copy()
1800
+
1801
+ # Find the conf that correspond to the land cover map
1802
+ conf = self.dense_matching_app.loader.find_auto_conf(
1803
+ intersection_poly,
1804
+ self.land_cover_map,
1805
+ self.classification_to_config_mapping,
1806
+ self.epsg,
1807
+ )
1808
+
1809
+ # Update the used_conf if order to reinitialize
1810
+ # the dense matching app
1811
+ # Because we kept the information regarding the ambiguity,
1812
+ # performance_map calculus..
1813
+ self.used_conf["applications"]["dense_matching"][
1814
+ "loader_conf"
1815
+ ] = conf
1816
+ self.used_conf["applications"]["dense_matching"][
1817
+ "method"
1818
+ ] = "custom"
1819
+
1820
+ # Re initialization of the dense matching application
1821
+ self.dense_matching_app = Application(
1822
+ "dense_matching",
1823
+ cfg=self.used_conf["applications"]["dense_matching"],
1824
+ )
1825
+
1826
+ # Update the corr_config with the inputs that have
1827
+ # already been checked
1828
+ self.dense_matching_app.corr_config["input"] = corr_cfg["input"]
1829
+
1830
+ # Run epipolar matching application
1831
+ epipolar_disparity_map = self.dense_matching_app.run(
1832
+ new_epipolar_image_left,
1833
+ new_epipolar_image_right,
1834
+ local_tile_optimal_size_fun,
1835
+ orchestrator=self.cars_orchestrator,
1836
+ pair_folder=os.path.join(
1837
+ self.dump_dir, "dense_matching", pair_key
1838
+ ),
1839
+ pair_key=pair_key,
1840
+ disp_range_grid=self.pairs[pair_key]["disp_range_grid"],
1841
+ compute_disparity_masks=False,
1842
+ margins_to_keep=sum(
1843
+ app.get_epipolar_margin()
1844
+ for _, app in self.pc_outlier_removal_apps.items()
1845
+ ),
1846
+ texture_bands=texture_bands_indices,
1847
+ classif_bands_to_mask=self.used_classif_values_for_filling,
1848
+ )
1849
+
1850
+ if self.quit_on_app("dense_matching"):
1851
+ continue # keep iterating over pairs, but don't go further
1852
+
1853
+ # Fill with zeros
1854
+ (filled_epipolar_disparity_map) = self.dense_match_filling.run(
1855
+ epipolar_disparity_map,
1856
+ orchestrator=self.cars_orchestrator,
1857
+ pair_folder=os.path.join(
1858
+ self.dump_dir, "dense_match_filling", pair_key
1859
+ ),
1860
+ pair_key=pair_key,
1861
+ )
1862
+
1863
+ if self.quit_on_app("dense_match_filling"):
1864
+ continue # keep iterating over pairs, but don't go further
1865
+
1866
+ if isinstance(output[sens_cst.GEOID], str):
1867
+ output_geoid_path = output[sens_cst.GEOID]
1868
+ elif (
1869
+ isinstance(output[sens_cst.GEOID], bool)
1870
+ and output[sens_cst.GEOID]
1871
+ ):
1872
+ package_path = os.path.dirname(__file__)
1873
+ output_geoid_path = os.path.join(
1874
+ package_path,
1875
+ "..",
1876
+ "..",
1877
+ "conf",
1878
+ sensor_inputs.CARS_GEOID_PATH,
1879
+ )
1880
+ else:
1881
+ # default case : stay on the ellipsoid
1882
+ output_geoid_path = None
1883
+
1884
+ depth_map_dir = None
1885
+ if self.save_output_depth_map:
1886
+ depth_map_dir = os.path.join(
1887
+ self.out_dir, "depth_map", pair_key
1888
+ )
1889
+ safe_makedirs(depth_map_dir)
1890
+
1891
+ point_cloud_dir = None
1892
+ if self.save_output_point_cloud:
1893
+ point_cloud_dir = os.path.join(
1894
+ self.out_dir, "point_cloud", pair_key
1895
+ )
1896
+ safe_makedirs(point_cloud_dir)
1897
+
1898
+ triangulation_point_cloud_dir = (
1899
+ point_cloud_dir
1900
+ if (point_cloud_dir and len(self.pc_outlier_removal_apps) == 0)
1901
+ else None
1902
+ )
1903
+
1904
+ # Run epipolar triangulation application
1905
+ epipolar_point_cloud = self.triangulation_application.run(
1906
+ self.pairs[pair_key]["sensor_image_left"],
1907
+ self.pairs[pair_key]["sensor_image_right"],
1908
+ self.pairs[pair_key]["corrected_grid_left"],
1909
+ self.pairs[pair_key]["corrected_grid_right"],
1910
+ filled_epipolar_disparity_map,
1911
+ self.geom_plugin_without_dem_and_geoid,
1912
+ new_epipolar_image_left,
1913
+ epsg=self.epsg,
1914
+ denoising_overload_fun=None,
1915
+ source_pc_names=self.pairs_names,
1916
+ orchestrator=self.cars_orchestrator,
1917
+ pair_dump_dir=os.path.join(
1918
+ self.dump_dir, "triangulation", pair_key
1919
+ ),
1920
+ pair_key=pair_key,
1921
+ uncorrected_grid_right=self.pairs[pair_key]["grid_right"],
1922
+ geoid_path=output_geoid_path,
1923
+ cloud_id=cloud_id,
1924
+ performance_maps_param=(
1925
+ self.dense_matching_app.get_performance_map_parameters()
1926
+ ),
1927
+ depth_map_dir=depth_map_dir,
1928
+ point_cloud_dir=triangulation_point_cloud_dir,
1929
+ save_output_coordinates=(len(self.pc_outlier_removal_apps) == 0)
1930
+ and (
1931
+ self.save_output_depth_map or self.save_output_point_cloud
1932
+ ),
1933
+ save_output_color=bool(depth_map_dir)
1934
+ and self.auxiliary[out_cst.AUX_IMAGE],
1935
+ save_output_classification=bool(depth_map_dir)
1936
+ and self.auxiliary[out_cst.AUX_CLASSIFICATION],
1937
+ save_output_filling=bool(depth_map_dir)
1938
+ and self.auxiliary[out_cst.AUX_FILLING],
1939
+ save_output_performance_map=bool(depth_map_dir)
1940
+ and self.auxiliary[out_cst.AUX_PERFORMANCE_MAP],
1941
+ save_output_ambiguity=bool(depth_map_dir)
1942
+ and self.auxiliary[out_cst.AUX_AMBIGUITY],
1943
+ )
1944
+
1945
+ if self.quit_on_app("triangulation"):
1946
+ continue # keep iterating over pairs, but don't go further
1947
+
1948
+ filtered_epipolar_point_cloud = epipolar_point_cloud
1949
+ for app_key, app in self.pc_outlier_removal_apps.items():
1950
+
1951
+ app_key_is_last = (
1952
+ app_key == list(self.pc_outlier_removal_apps)[-1]
1953
+ )
1954
+ filtering_depth_map_dir = (
1955
+ depth_map_dir if app_key_is_last else None
1956
+ )
1957
+ filtering_point_cloud_dir = (
1958
+ point_cloud_dir if app_key_is_last else None
1959
+ )
1960
+
1961
+ filtered_epipolar_point_cloud = app.run(
1962
+ filtered_epipolar_point_cloud,
1963
+ depth_map_dir=filtering_depth_map_dir,
1964
+ point_cloud_dir=filtering_point_cloud_dir,
1965
+ dump_dir=os.path.join(
1966
+ self.dump_dir,
1967
+ ( # pylint: disable=inconsistent-quotes
1968
+ f"pc_outlier_removal"
1969
+ f"{str(app_key[27:]).replace('.', '_')}"
1970
+ ),
1971
+ pair_key,
1972
+ ),
1973
+ epsg=self.epsg,
1974
+ orchestrator=self.cars_orchestrator,
1975
+ )
1976
+ if self.quit_on_app("point_cloud_outlier_removal"):
1977
+ continue # keep iterating over pairs, but don't go further
1978
+
1979
+ self.list_epipolar_point_clouds.append(
1980
+ filtered_epipolar_point_cloud
1981
+ )
1982
+
1983
+ # quit if any app in the loop over the pairs was the last one
1984
+ # pylint:disable=too-many-boolean-expressions
1985
+ if (
1986
+ self.quit_on_app("dense_matching")
1987
+ or self.quit_on_app("dense_match_filling")
1988
+ or self.quit_on_app("triangulation")
1989
+ or self.quit_on_app("point_cloud_outlier_removal.1")
1990
+ or self.quit_on_app("point_cloud_outlier_removal.2")
1991
+ ):
1992
+ return True
1993
+
1994
+ return False
1995
+
1996
+ def rasterize_point_cloud(self):
1997
+ """
1998
+ Final step of the pipeline: rasterize the point
1999
+ cloud created in the prior steps.
2000
+ """
2001
+
2002
+ self.rasterization_dump_dir = os.path.join(
2003
+ self.dump_dir, "rasterization"
2004
+ )
2005
+
2006
+ dsm_file_name = (
2007
+ os.path.join(
2008
+ self.out_dir,
2009
+ out_cst.DSM_DIRECTORY,
2010
+ "dsm.tif",
2011
+ )
2012
+ if self.save_output_dsm
2013
+ else None
2014
+ )
2015
+
2016
+ weights_file_name = (
2017
+ os.path.join(
2018
+ self.out_dir,
2019
+ out_cst.DSM_DIRECTORY,
2020
+ "weights.tif",
2021
+ )
2022
+ if self.save_output_dsm
2023
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_WEIGHTS]
2024
+ else None
2025
+ )
2026
+
2027
+ color_file_name = (
2028
+ os.path.join(
2029
+ self.out_dir,
2030
+ out_cst.DSM_DIRECTORY,
2031
+ "image.tif",
2032
+ )
2033
+ if self.save_output_dsm
2034
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
2035
+ else None
2036
+ )
2037
+
2038
+ performance_map_file_name = (
2039
+ os.path.join(
2040
+ self.out_dir,
2041
+ out_cst.DSM_DIRECTORY,
2042
+ "performance_map.tif",
2043
+ )
2044
+ if self.save_output_dsm
2045
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2046
+ out_cst.AUX_PERFORMANCE_MAP
2047
+ ]
2048
+ else None
2049
+ )
2050
+
2051
+ ambiguity_file_name = (
2052
+ os.path.join(
2053
+ self.out_dir,
2054
+ out_cst.DSM_DIRECTORY,
2055
+ "ambiguity.tif",
2056
+ )
2057
+ if self.save_output_dsm
2058
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_AMBIGUITY]
2059
+ else None
2060
+ )
2061
+
2062
+ classif_file_name = (
2063
+ os.path.join(
2064
+ self.out_dir,
2065
+ out_cst.DSM_DIRECTORY,
2066
+ "classification.tif",
2067
+ )
2068
+ if self.save_output_dsm
2069
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2070
+ out_cst.AUX_CLASSIFICATION
2071
+ ]
2072
+ else None
2073
+ )
2074
+
2075
+ if classif_file_name is None and self.save_output_classif_for_filling:
2076
+ classif_file_name = os.path.join(
2077
+ self.rasterization_dump_dir,
2078
+ "classification_for_filling.tif",
2079
+ )
2080
+
2081
+ contributing_pair_file_name = (
2082
+ os.path.join(
2083
+ self.out_dir,
2084
+ out_cst.DSM_DIRECTORY,
2085
+ "contributing_pair.tif",
2086
+ )
2087
+ if self.save_output_dsm
2088
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2089
+ out_cst.AUX_CONTRIBUTING_PAIR
2090
+ ]
2091
+ else None
2092
+ )
2093
+
2094
+ filling_file_name = (
2095
+ os.path.join(
2096
+ self.out_dir,
2097
+ out_cst.DSM_DIRECTORY,
2098
+ "filling.tif",
2099
+ )
2100
+ if self.save_output_dsm
2101
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
2102
+ else None
2103
+ )
2104
+
2105
+ # rasterize point cloud
2106
+ _ = self.rasterization_application.run(
2107
+ self.point_cloud_to_rasterize,
2108
+ self.epsg,
2109
+ self.vertical_crs,
2110
+ resolution=self.resolution,
2111
+ orchestrator=self.cars_orchestrator,
2112
+ dsm_file_name=dsm_file_name,
2113
+ weights_file_name=weights_file_name,
2114
+ color_file_name=color_file_name,
2115
+ classif_file_name=classif_file_name,
2116
+ performance_map_file_name=performance_map_file_name,
2117
+ ambiguity_file_name=ambiguity_file_name,
2118
+ contributing_pair_file_name=contributing_pair_file_name,
2119
+ filling_file_name=filling_file_name,
2120
+ color_dtype=self.color_type,
2121
+ dump_dir=self.rasterization_dump_dir,
2122
+ performance_map_classes=self.used_conf[OUTPUT][AUXILIARY][
2123
+ out_cst.AUX_PERFORMANCE_MAP
2124
+ ],
2125
+ phasing=self.phasing,
2126
+ )
2127
+
2128
+ # Cleaning: don't keep terrain bbox if save_intermediate_data
2129
+ # is not activated
2130
+ if not self.used_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA]:
2131
+ self.cars_orchestrator.add_to_clean(
2132
+ os.path.join(self.dump_dir, "terrain_bbox")
2133
+ )
2134
+
2135
+ if self.quit_on_app("point_cloud_rasterization"):
2136
+ return True
2137
+
2138
+ # dsm needs to be saved before filling
2139
+ self.cars_orchestrator.breakpoint()
2140
+
2141
+ if self.generate_dems:
2142
+ dsm_file_name = (
2143
+ os.path.join(
2144
+ self.out_dir,
2145
+ out_cst.DSM_DIRECTORY,
2146
+ "dsm.tif",
2147
+ )
2148
+ if self.save_output_dsm
2149
+ else None
2150
+ )
2151
+
2152
+ dem_min_file_name = (
2153
+ os.path.join(
2154
+ self.out_dir,
2155
+ out_cst.DSM_DIRECTORY,
2156
+ "dem_min.tif",
2157
+ )
2158
+ if self.save_output_dsm
2159
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2160
+ out_cst.AUX_DEM_MIN
2161
+ ]
2162
+ else None
2163
+ )
2164
+
2165
+ dem_max_file_name = (
2166
+ os.path.join(
2167
+ self.out_dir,
2168
+ out_cst.DSM_DIRECTORY,
2169
+ "dem_max.tif",
2170
+ )
2171
+ if self.save_output_dsm
2172
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2173
+ out_cst.AUX_DEM_MAX
2174
+ ]
2175
+ else None
2176
+ )
2177
+
2178
+ dem_median_file_name = (
2179
+ os.path.join(
2180
+ self.out_dir,
2181
+ out_cst.DSM_DIRECTORY,
2182
+ "dem_median.tif",
2183
+ )
2184
+ if self.save_output_dsm
2185
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2186
+ out_cst.AUX_DEM_MEDIAN
2187
+ ]
2188
+ else None
2189
+ )
2190
+
2191
+ dem_generation_output_dir = os.path.join(
2192
+ self.dump_dir, "dem_generation"
2193
+ )
2194
+ safe_makedirs(dem_generation_output_dir)
2195
+ if not self.dem_generation_application.used_config[
2196
+ "save_intermediate_data"
2197
+ ]:
2198
+ self.cars_orchestrator.add_to_clean(dem_generation_output_dir)
2199
+
2200
+ # Use initial elevation if provided, and generate dems
2201
+ _, paths, _ = self.dem_generation_application.run(
2202
+ dsm_file_name,
2203
+ dem_generation_output_dir,
2204
+ dem_min_file_name,
2205
+ dem_max_file_name,
2206
+ dem_median_file_name,
2207
+ input_geoid=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
2208
+ sens_cst.GEOID
2209
+ ],
2210
+ output_geoid=self.used_conf[OUTPUT][out_cst.OUT_GEOID],
2211
+ initial_elevation=(
2212
+ self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
2213
+ sens_cst.DEM_PATH
2214
+ ]
2215
+ ),
2216
+ default_alt=self.geom_plugin_with_dem_and_geoid.default_alt,
2217
+ cars_orchestrator=self.cars_orchestrator,
2218
+ )
2219
+
2220
+ # Update refined conf configuration with dem paths
2221
+ dem_median = paths["dem_median"]
2222
+ dem_min = paths["dem_min"]
2223
+ dem_max = paths["dem_max"]
2224
+
2225
+ advanced_parameters.update_conf(
2226
+ self.refined_conf,
2227
+ dem_median=dem_median,
2228
+ dem_min=dem_min,
2229
+ dem_max=dem_max,
2230
+ )
2231
+
2232
+ if self.used_conf[ADVANCED][USE_ENDOGENOUS_DEM]:
2233
+ # Generate new geom plugin with dem
2234
+ output_dem_dir = os.path.join(
2235
+ self.dump_dir, "initial_elevation"
2236
+ )
2237
+ new_geom_plugin = (
2238
+ sensor_inputs.generate_geometry_plugin_with_dem(
2239
+ self.geometry_plugin,
2240
+ self.used_conf[INPUT],
2241
+ dem=dem_median,
2242
+ output_dem_dir=output_dem_dir,
2243
+ )
2244
+ )
2245
+
2246
+ for (
2247
+ pair_key,
2248
+ _,
2249
+ _,
2250
+ ) in self.list_sensor_pairs:
2251
+ new_grid_correction_coef = (
2252
+ self.generate_grid_correction_on_dem(
2253
+ pair_key,
2254
+ new_geom_plugin,
2255
+ )
2256
+ )
2257
+ if new_grid_correction_coef is not None:
2258
+ # Update refined_conf configuration with epipolar
2259
+ # a priori
2260
+ advanced_parameters.update_conf(
2261
+ self.refined_conf,
2262
+ grid_correction_coef=new_grid_correction_coef,
2263
+ pair_key=pair_key,
2264
+ reference_dem=dem_median,
2265
+ )
2266
+
2267
+ # saved used configuration
2268
+ self.save_configurations()
2269
+
2270
+ return False
2271
+
2272
+ def filling(self): # noqa: C901 : too complex
2273
+ """
2274
+ Fill the dsm
2275
+ """
2276
+
2277
+ dsm_file_name = (
2278
+ os.path.join(
2279
+ self.out_dir,
2280
+ out_cst.DSM_DIRECTORY,
2281
+ "dsm.tif",
2282
+ )
2283
+ if self.save_output_dsm
2284
+ else None
2285
+ )
2286
+
2287
+ if self.dsms_in_inputs:
2288
+ dsms_merging_dump_dir = os.path.join(self.dump_dir, "dsms_merging")
2289
+
2290
+ dsm_dict = self.used_conf[INPUT][dsm_cst.DSMS]
2291
+ dict_path = {}
2292
+ for key in dsm_dict.keys():
2293
+ for path_name in dsm_dict[key].keys():
2294
+ if dsm_dict[key][path_name] is not None:
2295
+ if isinstance(dsm_dict[key][path_name], str):
2296
+ if path_name not in dict_path:
2297
+ dict_path[path_name] = [
2298
+ dsm_dict[key][path_name]
2299
+ ]
2300
+ else:
2301
+ dict_path[path_name].append(
2302
+ dsm_dict[key][path_name]
2303
+ )
2304
+
2305
+ color_file_name = (
2306
+ os.path.join(
2307
+ self.out_dir,
2308
+ out_cst.DSM_DIRECTORY,
2309
+ "image.tif",
2310
+ )
2311
+ if "texture" in dict_path
2312
+ or self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
2313
+ else None
2314
+ )
2315
+
2316
+ performance_map_file_name = (
2317
+ os.path.join(
2318
+ self.out_dir,
2319
+ out_cst.DSM_DIRECTORY,
2320
+ "performance_map.tif",
2321
+ )
2322
+ if "performance_map" in dict_path
2323
+ else None
2324
+ )
2325
+
2326
+ ambiguity_bool = any("ambiguity" in key for key in dict_path)
2327
+ ambiguity_file_name = (
2328
+ os.path.join(
2329
+ self.out_dir,
2330
+ out_cst.DSM_DIRECTORY,
2331
+ )
2332
+ if ambiguity_bool
2333
+ else None
2334
+ )
2335
+
2336
+ classif_file_name = (
2337
+ os.path.join(
2338
+ self.out_dir,
2339
+ out_cst.DSM_DIRECTORY,
2340
+ "classification.tif",
2341
+ )
2342
+ if "classification" in dict_path
2343
+ or self.used_conf[OUTPUT][out_cst.AUXILIARY][
2344
+ out_cst.AUX_CLASSIFICATION
2345
+ ]
2346
+ else None
2347
+ )
2348
+
2349
+ contributing_all_pair_file_name = (
2350
+ os.path.join(
2351
+ self.out_dir,
2352
+ out_cst.DSM_DIRECTORY,
2353
+ "contributing_pair.tif",
2354
+ )
2355
+ if "contributing_pair" in dict_path
2356
+ else None
2357
+ )
2358
+
2359
+ filling_file_name = (
2360
+ os.path.join(
2361
+ self.out_dir,
2362
+ out_cst.DSM_DIRECTORY,
2363
+ "filling.tif",
2364
+ )
2365
+ if "filling" in dict_path
2366
+ else None
2367
+ )
2368
+
2369
+ self.epsg = rasterio_get_epsg(dict_path["dsm"][0])
2370
+ self.vertical_crs = rasterio_get_crs(dict_path["dsm"][0])
2371
+
2372
+ # Compute roi polygon, in input EPSG
2373
+ self.roi_poly = preprocessing.compute_roi_poly(
2374
+ self.input_roi_poly, self.input_roi_epsg, self.epsg
2375
+ )
2376
+
2377
+ _ = dsm_inputs.merge_dsm_infos(
2378
+ dict_path,
2379
+ self.cars_orchestrator,
2380
+ self.roi_poly,
2381
+ self.used_conf[ADVANCED][adv_cst.DSM_MERGING_TILE_SIZE],
2382
+ dsms_merging_dump_dir,
2383
+ dsm_file_name,
2384
+ color_file_name,
2385
+ classif_file_name,
2386
+ filling_file_name,
2387
+ performance_map_file_name,
2388
+ ambiguity_file_name,
2389
+ contributing_all_pair_file_name,
2390
+ )
2391
+
2392
+ # dsm needs to be saved before filling
2393
+ self.cars_orchestrator.breakpoint()
2394
+ else:
2395
+ color_file_name = (
2396
+ os.path.join(
2397
+ self.out_dir,
2398
+ out_cst.DSM_DIRECTORY,
2399
+ "image.tif",
2400
+ )
2401
+ if self.save_output_dsm
2402
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
2403
+ else None
2404
+ )
2405
+
2406
+ classif_file_name = (
2407
+ os.path.join(
2408
+ self.out_dir,
2409
+ out_cst.DSM_DIRECTORY,
2410
+ "classification.tif",
2411
+ )
2412
+ if self.save_output_dsm
2413
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2414
+ out_cst.AUX_CLASSIFICATION
2415
+ ]
2416
+ else None
2417
+ )
2418
+
2419
+ if (
2420
+ classif_file_name is None
2421
+ and self.save_output_classif_for_filling
2422
+ ):
2423
+ classif_file_name = os.path.join(
2424
+ self.rasterization_dump_dir,
2425
+ "classification_for_filling.tif",
2426
+ )
2427
+
2428
+ filling_file_name = (
2429
+ os.path.join(
2430
+ self.out_dir,
2431
+ out_cst.DSM_DIRECTORY,
2432
+ "filling.tif",
2433
+ )
2434
+ if self.save_output_dsm
2435
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2436
+ out_cst.AUX_FILLING
2437
+ ]
2438
+ else None
2439
+ )
2440
+
2441
+ if not hasattr(self, "list_intersection_poly"):
2442
+ if (
2443
+ self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
2444
+ sens_cst.DEM_PATH
2445
+ ]
2446
+ is not None
2447
+ and self.sensors_in_inputs
2448
+ ):
2449
+ self.list_sensor_pairs = sensor_inputs.generate_inputs(
2450
+ self.used_conf[INPUT],
2451
+ self.geom_plugin_without_dem_and_geoid,
2452
+ )
2453
+
2454
+ self.list_intersection_poly = []
2455
+ for _, (
2456
+ pair_key,
2457
+ sensor_image_left,
2458
+ sensor_image_right,
2459
+ ) in enumerate(self.list_sensor_pairs):
2460
+ pair_folder = os.path.join(
2461
+ self.dump_dir, "terrain_bbox", pair_key
2462
+ )
2463
+ safe_makedirs(pair_folder)
2464
+ geojson1 = os.path.join(
2465
+ pair_folder, "left_envelope.geojson"
2466
+ )
2467
+ geojson2 = os.path.join(
2468
+ pair_folder, "right_envelope.geojson"
2469
+ )
2470
+ out_envelopes_intersection = os.path.join(
2471
+ pair_folder, "envelopes_intersection.geojson"
2472
+ )
2473
+
2474
+ inter_poly, _ = projection.ground_intersection_envelopes(
2475
+ sensor_image_left[sens_cst.INPUT_IMG]["bands"]["b0"][
2476
+ "path"
2477
+ ],
2478
+ sensor_image_right[sens_cst.INPUT_IMG]["bands"]["b0"][
2479
+ "path"
2480
+ ],
2481
+ sensor_image_left[sens_cst.INPUT_GEO_MODEL],
2482
+ sensor_image_right[sens_cst.INPUT_GEO_MODEL],
2483
+ self.geom_plugin_with_dem_and_geoid,
2484
+ geojson1,
2485
+ geojson2,
2486
+ out_envelopes_intersection,
2487
+ envelope_file_driver="GeoJSON",
2488
+ intersect_file_driver="GeoJSON",
2489
+ )
2490
+
2491
+ # Retrieve bounding box of the grd inters of the envelopes
2492
+ inter_poly, inter_epsg = read_vector(
2493
+ out_envelopes_intersection
2494
+ )
2495
+
2496
+ # Project polygon if epsg is different
2497
+ if self.vertical_crs != CRS(inter_epsg):
2498
+ inter_poly = projection.polygon_projection_crs(
2499
+ inter_poly, CRS(inter_epsg), self.vertical_crs
2500
+ )
2501
+
2502
+ self.list_intersection_poly.append(inter_poly)
2503
+ else:
2504
+ self.list_intersection_poly = None
2505
+
2506
+ dtm_file_name = None
2507
+ for app_key, app in self.dsm_filling_apps.items():
2508
+
2509
+ app_dump_dir = os.path.join(
2510
+ self.dump_dir, app_key.replace(".", "_")
2511
+ )
2512
+
2513
+ if app.get_conf()["method"] == "exogenous_filling":
2514
+ _ = app.run(
2515
+ dsm_file=dsm_file_name,
2516
+ classif_file=classif_file_name,
2517
+ filling_file=filling_file_name,
2518
+ dump_dir=app_dump_dir,
2519
+ roi_polys=self.list_intersection_poly,
2520
+ roi_epsg=self.epsg,
2521
+ output_geoid=self.used_conf[OUTPUT][sens_cst.GEOID],
2522
+ geom_plugin=self.geom_plugin_with_dem_and_geoid,
2523
+ )
2524
+ elif app.get_conf()["method"] == "bulldozer":
2525
+ dtm_file_name = app.run(
2526
+ dsm_file=dsm_file_name,
2527
+ classif_file=classif_file_name,
2528
+ filling_file=filling_file_name,
2529
+ dump_dir=app_dump_dir,
2530
+ roi_polys=self.list_intersection_poly,
2531
+ roi_epsg=self.epsg,
2532
+ orchestrator=self.cars_orchestrator,
2533
+ )
2534
+ elif app.get_conf()["method"] == "border_interpolation":
2535
+ _ = app.run(
2536
+ dsm_file=dsm_file_name,
2537
+ classif_file=classif_file_name,
2538
+ filling_file=filling_file_name,
2539
+ dtm_file=dtm_file_name,
2540
+ dump_dir=app_dump_dir,
2541
+ roi_polys=self.list_intersection_poly,
2542
+ roi_epsg=self.epsg,
2543
+ )
2544
+
2545
+ if not app.save_intermediate_data:
2546
+ self.cars_orchestrator.add_to_clean(app_dump_dir)
2547
+
2548
+ _ = self.auxiliary_filling_application.run(
2549
+ dsm_file=dsm_file_name,
2550
+ color_file=color_file_name,
2551
+ classif_file=classif_file_name,
2552
+ dump_dir=self.dump_dir,
2553
+ sensor_inputs=self.used_conf[INPUT].get("sensors"),
2554
+ pairing=self.used_conf[INPUT].get("pairing"),
2555
+ geom_plugin=self.geom_plugin_with_dem_and_geoid,
2556
+ texture_bands=self.texture_bands,
2557
+ output_geoid=self.used_conf[OUTPUT][sens_cst.GEOID],
2558
+ orchestrator=self.cars_orchestrator,
2559
+ )
2560
+ self.cars_orchestrator.breakpoint()
2561
+
2562
+ if (
2563
+ classif_file_name is not None
2564
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][
2565
+ out_cst.AUX_CLASSIFICATION
2566
+ ]
2567
+ ):
2568
+ self.merge_classif_bands(
2569
+ classif_file_name,
2570
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][
2571
+ out_cst.AUX_CLASSIFICATION
2572
+ ],
2573
+ dsm_file_name,
2574
+ )
2575
+ if (
2576
+ filling_file_name is not None
2577
+ and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
2578
+ ):
2579
+ self.merge_filling_bands(
2580
+ filling_file_name,
2581
+ self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING],
2582
+ dsm_file_name,
2583
+ )
2584
+
2585
+ return self.quit_on_app("auxiliary_filling")
2586
+
2587
+ @cars_profile(name="merge filling bands", interval=0.5)
2588
+ def merge_filling_bands(self, filling_path, aux_filling, dsm_file):
2589
+ """
2590
+ Merge filling bands to get mono band in output
2591
+ """
2592
+
2593
+ with rasterio.open(dsm_file) as in_dsm:
2594
+ dsm_msk = in_dsm.read_masks(1)
2595
+
2596
+ with rasterio.open(filling_path) as src:
2597
+ nb_bands = src.count
2598
+
2599
+ if nb_bands == 1:
2600
+ return False
2601
+
2602
+ filling_multi_bands = src.read()
2603
+ filling_mono_bands = np.zeros(filling_multi_bands.shape[1:3])
2604
+ descriptions = src.descriptions
2605
+ dict_temp = {name: i for i, name in enumerate(descriptions)}
2606
+ profile = src.profile
2607
+
2608
+ with warnings.catch_warnings():
2609
+ warnings.simplefilter("ignore", NodataShadowWarning)
2610
+ filling_mask = src.read_masks(1)
2611
+
2612
+ filling_mono_bands[filling_mask == 0] = 0
2613
+
2614
+ filling_bands_list = {
2615
+ "fill_with_geoid": ["zeros_padding", "filling_exogenous"],
2616
+ "interpolate_from_borders": [
2617
+ "zeros_padding",
2618
+ "bulldozer",
2619
+ "border_interpolation",
2620
+ ],
2621
+ "fill_with_endogenous_dem": [
2622
+ "zeros_padding",
2623
+ "filling_exogenous",
2624
+ "bulldozer",
2625
+ ],
2626
+ "fill_with_exogenous_dem": ["zeros_padding", "bulldozer"],
2627
+ }
2628
+
2629
+ # To get the right footprint
2630
+ filling_mono_bands = np.logical_or(dsm_msk, filling_mask).astype(
2631
+ np.uint8
2632
+ )
2633
+
2634
+ # to keep the previous classif convention
2635
+ filling_mono_bands[filling_mono_bands == 0] = src.nodata
2636
+ filling_mono_bands[filling_mono_bands == 1] = 0
2637
+
2638
+ no_match = False
2639
+ for key, value in aux_filling.items():
2640
+ if isinstance(value, str):
2641
+ value = [value]
2642
+
2643
+ if isinstance(value, list):
2644
+ for elem in value:
2645
+ if elem != "other":
2646
+ filling_method = filling_bands_list[elem]
2647
+
2648
+ if all(
2649
+ method in descriptions
2650
+ for method in filling_method
2651
+ ):
2652
+ indices_true = [
2653
+ dict_temp[m] for m in filling_method
2654
+ ]
2655
+
2656
+ mask_true = np.all(
2657
+ filling_multi_bands[indices_true, :, :]
2658
+ == 1,
2659
+ axis=0,
2660
+ )
2661
+
2662
+ indices_false = [
2663
+ i
2664
+ for i in range(filling_multi_bands.shape[0])
2665
+ if i not in indices_true
2666
+ ]
2667
+
2668
+ mask_false = np.all(
2669
+ filling_multi_bands[indices_false, :, :]
2670
+ == 0,
2671
+ axis=0,
2672
+ )
2673
+
2674
+ mask = mask_true & mask_false
2675
+
2676
+ filling_mono_bands[mask] = key
2677
+ else:
2678
+ no_match = True
2679
+
2680
+ if no_match:
2681
+ mask_1 = np.all(
2682
+ filling_multi_bands[1:, :, :] == 1,
2683
+ axis=0,
2684
+ )
2685
+
2686
+ mask_2 = np.all(
2687
+ filling_mono_bands == 0,
2688
+ axis=0,
2689
+ )
2690
+
2691
+ filling_mono_bands[mask_1 & mask_2] = (
2692
+ aux_filling["other"] if "other" in aux_filling else 50
2693
+ )
2694
+
2695
+ profile.update(count=1, dtype=filling_mono_bands.dtype)
2696
+ with rasterio.open(filling_path, "w", **profile) as src:
2697
+ src.write(filling_mono_bands, 1)
2698
+
2699
+ return True
2700
+
2701
+ @cars_profile(name="merge classif bands", interval=0.5)
2702
+ def merge_classif_bands(self, classif_path, aux_classif, dsm_file):
2703
+ """
2704
+ Merge classif bands to get mono band in output
2705
+ """
2706
+ with rasterio.open(dsm_file) as in_dsm:
2707
+ dsm_msk = in_dsm.read_masks(1)
2708
+
2709
+ with rasterio.open(classif_path) as src:
2710
+ nb_bands = src.count
2711
+
2712
+ if nb_bands == 1:
2713
+ return False
2714
+
2715
+ classif_multi_bands = src.read()
2716
+ classif_mono_band = np.zeros(classif_multi_bands.shape[1:3])
2717
+ descriptions = src.descriptions
2718
+ profile = src.profile
2719
+ classif_mask = src.read_masks(1)
2720
+ classif_mono_band[classif_mask == 0] = 0
2721
+
2722
+ # To get the right footprint
2723
+ classif_mono_band = np.logical_or(dsm_msk, classif_mask).astype(
2724
+ np.uint8
2725
+ )
2726
+
2727
+ # to keep the previous classif convention
2728
+ classif_mono_band[classif_mono_band == 0] = src.nodata
2729
+ classif_mono_band[classif_mono_band == 1] = 0
2730
+
2731
+ for key, value in aux_classif.items():
2732
+ if isinstance(value, int):
2733
+ num_band = descriptions.index(str(value))
2734
+ mask_1 = classif_mono_band == 0
2735
+ mask_2 = classif_multi_bands[num_band, :, :] == 1
2736
+ classif_mono_band[mask_1 & mask_2] = key
2737
+ elif isinstance(value, list):
2738
+ for elem in value:
2739
+ num_band = descriptions.index(str(elem))
2740
+ mask_1 = classif_mono_band == 0
2741
+ mask_2 = classif_multi_bands[num_band, :, :] == 1
2742
+ classif_mono_band[mask_1 & mask_2] = key
2743
+
2744
+ profile.update(count=1, dtype=classif_mono_band.dtype)
2745
+ with rasterio.open(classif_path, "w", **profile) as src:
2746
+ src.write(classif_mono_band, 1)
2747
+
2748
+ return True
2749
+
2750
+ @cars_profile(name="Preprocess depth maps", interval=0.5)
2751
+ def preprocess_depth_maps(self):
2752
+ """
2753
+ Adds multiple processing steps to the depth maps :
2754
+ Merging.
2755
+ Creates the point cloud that will be rasterized in
2756
+ the last step of the pipeline.
2757
+ """
2758
+
2759
+ self.point_cloud_to_rasterize = (
2760
+ self.list_epipolar_point_clouds,
2761
+ self.terrain_bounds,
2762
+ )
2763
+ self.color_type = self.point_cloud_to_rasterize[0][0].attributes.get(
2764
+ "color_type", None
2765
+ )
2766
+
2767
+ @cars_profile(name="Final cleanup", interval=0.5)
2768
+ def final_cleanup(self):
2769
+ """
2770
+ Clean temporary files and directory at the end of cars processing
2771
+ """
2772
+
2773
+ if not self.used_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA]:
2774
+ # delete everything in tile_processing if save_intermediate_data is
2775
+ # not activated
2776
+ self.cars_orchestrator.add_to_clean(
2777
+ os.path.join(self.dump_dir, "tile_processing")
2778
+ )
2779
+
2780
+ # Remove dump_dir if no intermediate data should be written
2781
+ if (
2782
+ not any(
2783
+ app.get("save_intermediate_data", False) is True
2784
+ for app in self.used_conf[APPLICATIONS].values()
2785
+ if app is not None
2786
+ )
2787
+ and not self.dsms_in_inputs
2788
+ ):
2789
+ self.cars_orchestrator.add_to_clean(self.dump_dir)
2790
+
2791
+ @cars_profile(name="run_unit_pipeline", interval=0.5)
2792
+ def run( # pylint: disable=too-many-positional-arguments
2793
+ self,
2794
+ generate_dems=False,
2795
+ which_resolution="single",
2796
+ use_sift_a_priori=False,
2797
+ first_res_out_dir=None,
2798
+ log_dir=None,
2799
+ ): # noqa C901
2800
+ """
2801
+ Run pipeline
2802
+
2803
+ """
2804
+ if log_dir is not None:
2805
+ self.log_dir = log_dir
2806
+ else:
2807
+ self.log_dir = os.path.join(self.out_dir, "logs")
2808
+
2809
+ self.first_res_out_dir = first_res_out_dir
2810
+ self.texture_bands = self.used_conf[OUTPUT][AUXILIARY][
2811
+ out_cst.AUX_IMAGE
2812
+ ]
2813
+
2814
+ self.auxiliary = self.used_conf[OUTPUT][out_cst.AUXILIARY]
2815
+
2816
+ self.use_sift_a_priori = use_sift_a_priori
2817
+
2818
+ self.generate_dems = generate_dems
2819
+
2820
+ self.which_resolution = which_resolution
2821
+
2822
+ # saved used configuration
2823
+ self.save_configurations()
2824
+ # start cars orchestrator
2825
+ with orchestrator.Orchestrator(
2826
+ orchestrator_conf=self.used_conf[ORCHESTRATOR],
2827
+ out_dir=self.out_dir,
2828
+ log_dir=self.log_dir,
2829
+ out_yaml_path=os.path.join(
2830
+ self.out_dir,
2831
+ out_cst.INFO_FILENAME,
2832
+ ),
2833
+ ) as self.cars_orchestrator:
2834
+ # initialize out_json
2835
+ self.cars_orchestrator.update_out_info({"version": __version__})
2836
+
2837
+ if not self.dsms_in_inputs:
2838
+ if self.compute_depth_map:
2839
+ self.sensor_to_depth_maps()
2840
+
2841
+ if self.save_output_dsm or self.save_output_point_cloud:
2842
+ self.preprocess_depth_maps()
2843
+
2844
+ if self.save_output_dsm:
2845
+ self.rasterize_point_cloud()
2846
+ self.filling()
2847
+ else:
2848
+ self.filling()
2849
+
2850
+ self.final_cleanup()