cars 1.0.0a3__cp312-cp312-win_amd64.whl → 1.0.0a4__cp312-cp312-win_amd64.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 (139) hide show
  1. cars/__init__.py +3 -3
  2. cars/applications/__init__.py +0 -3
  3. cars/applications/application_template.py +20 -0
  4. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +12 -2
  5. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +2 -2
  6. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +80 -36
  7. cars/applications/dem_generation/dem_generation_algo.py +1 -1
  8. cars/applications/dem_generation/dem_generation_wrappers.py +23 -57
  9. cars/applications/dem_generation/dichotomic_generation_app.py +3 -3
  10. cars/applications/dem_generation/rasterization_app.py +100 -41
  11. cars/applications/dense_match_filling/__init__.py +1 -1
  12. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +2 -15
  13. cars/applications/dense_match_filling/fill_disp_algo.py +32 -373
  14. cars/applications/dense_match_filling/fill_disp_wrappers.py +0 -343
  15. cars/applications/dense_match_filling/zero_padding_app.py +10 -5
  16. cars/applications/dense_matching/abstract_dense_matching_app.py +2 -1
  17. cars/applications/dense_matching/census_mccnn_sgm_app.py +38 -39
  18. cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.dll.a +0 -0
  19. cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.pyd +0 -0
  20. cars/applications/dense_matching/dense_matching_algo.py +48 -14
  21. cars/applications/dense_matching/dense_matching_wrappers.py +11 -3
  22. cars/applications/dense_matching/disparity_grid_algo.py +84 -62
  23. cars/applications/dense_matching/loaders/pandora_loader.py +91 -33
  24. cars/applications/dsm_filling/border_interpolation_app.py +1 -7
  25. cars/applications/dsm_filling/bulldozer_filling_app.py +2 -8
  26. cars/applications/dsm_filling/exogenous_filling_app.py +4 -9
  27. cars/applications/grid_generation/abstract_grid_generation_app.py +1 -1
  28. cars/applications/grid_generation/epipolar_grid_generation_app.py +4 -2
  29. cars/applications/grid_generation/grid_correction_app.py +4 -1
  30. cars/applications/grid_generation/grid_generation_algo.py +7 -2
  31. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +1 -1
  32. cars/applications/ground_truth_reprojection/direct_localization_app.py +2 -2
  33. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +2 -1
  34. cars/applications/point_cloud_fusion/abstract_pc_fusion_app.py +0 -155
  35. cars/applications/point_cloud_fusion/mapping_to_terrain_tiles_app.py +0 -658
  36. cars/applications/point_cloud_fusion/pc_fusion_algo.py +0 -1339
  37. cars/applications/point_cloud_fusion/pc_fusion_wrappers.py +0 -869
  38. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +2 -1
  39. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +9 -8
  40. cars/applications/point_cloud_outlier_removal/small_components_app.py +96 -267
  41. cars/applications/point_cloud_outlier_removal/statistical_app.py +116 -275
  42. cars/applications/rasterization/abstract_pc_rasterization_app.py +1 -1
  43. cars/applications/rasterization/rasterization_algo.py +18 -6
  44. cars/applications/rasterization/rasterization_wrappers.py +2 -1
  45. cars/applications/rasterization/simple_gaussian_app.py +60 -113
  46. cars/applications/resampling/abstract_resampling_app.py +1 -1
  47. cars/applications/resampling/bicubic_resampling_app.py +3 -1
  48. cars/applications/resampling/resampling_algo.py +16 -4
  49. cars/applications/resampling/resampling_wrappers.py +3 -1
  50. cars/applications/sparse_matching/abstract_sparse_matching_app.py +1 -1
  51. cars/applications/sparse_matching/sift_app.py +3 -3
  52. cars/applications/sparse_matching/sparse_matching_algo.py +3 -2
  53. cars/applications/sparse_matching/sparse_matching_wrappers.py +1 -1
  54. cars/applications/triangulation/abstract_triangulation_app.py +1 -1
  55. cars/applications/triangulation/line_of_sight_intersection_app.py +13 -11
  56. cars/applications/triangulation/pc_transform.py +552 -0
  57. cars/applications/triangulation/triangulation_algo.py +6 -4
  58. cars/applications/triangulation/triangulation_wrappers.py +1 -0
  59. cars/bundleadjustment.py +6 -6
  60. cars/cars.py +11 -9
  61. cars/core/cars_logging.py +80 -49
  62. cars/core/constants.py +0 -1
  63. cars/core/datasets.py +5 -2
  64. cars/core/geometry/abstract_geometry.py +256 -25
  65. cars/core/geometry/shareloc_geometry.py +110 -82
  66. cars/core/inputs.py +57 -19
  67. cars/core/outputs.py +1 -1
  68. cars/core/preprocessing.py +17 -3
  69. cars/core/projection.py +9 -6
  70. cars/core/tiling.py +10 -3
  71. cars/data_structures/cars_dataset.py +5 -5
  72. cars/data_structures/corresponding_tiles_tools.py +0 -103
  73. cars/data_structures/format_transformation.py +4 -1
  74. cars/devibrate.py +6 -3
  75. cars/extractroi.py +20 -21
  76. cars/orchestrator/cluster/abstract_cluster.py +15 -5
  77. cars/orchestrator/cluster/abstract_dask_cluster.py +6 -2
  78. cars/orchestrator/cluster/dask_jobqueue_utils.py +1 -1
  79. cars/orchestrator/cluster/log_wrapper.py +148 -21
  80. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +11 -3
  81. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +2 -2
  82. cars/orchestrator/cluster/pbs_dask_cluster.py +1 -1
  83. cars/orchestrator/cluster/sequential_cluster.py +5 -4
  84. cars/orchestrator/cluster/slurm_dask_cluster.py +1 -1
  85. cars/orchestrator/orchestrator.py +14 -3
  86. cars/orchestrator/registry/id_generator.py +1 -0
  87. cars/orchestrator/registry/saver_registry.py +2 -2
  88. cars/pipelines/conf_resolution/conf_final_resolution.json +5 -3
  89. cars/pipelines/default/default_pipeline.py +462 -1073
  90. cars/pipelines/parameters/advanced_parameters.py +74 -64
  91. cars/pipelines/parameters/advanced_parameters_constants.py +2 -5
  92. cars/pipelines/parameters/application_parameters.py +71 -0
  93. cars/pipelines/parameters/depth_map_inputs.py +0 -314
  94. cars/pipelines/parameters/dsm_inputs.py +40 -4
  95. cars/pipelines/parameters/output_parameters.py +2 -2
  96. cars/pipelines/parameters/sensor_inputs.py +30 -75
  97. cars/pipelines/parameters/sensor_inputs_constants.py +0 -2
  98. cars/pipelines/parameters/sensor_loaders/__init__.py +4 -3
  99. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +106 -0
  100. cars/pipelines/parameters/sensor_loaders/{basic_sensor_loader.py → basic_image_loader.py} +16 -22
  101. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +121 -0
  102. cars/pipelines/parameters/sensor_loaders/{pivot_sensor_loader.py → pivot_image_loader.py} +10 -21
  103. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +4 -6
  104. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +1 -3
  105. cars/pipelines/pipeline_template.py +1 -3
  106. cars/pipelines/unit/unit_pipeline.py +527 -1016
  107. cars/starter.py +4 -3
  108. cars-1.0.0a4.dist-info/DELVEWHEEL +2 -0
  109. {cars-1.0.0a3.dist-info → cars-1.0.0a4.dist-info}/METADATA +135 -53
  110. {cars-1.0.0a3.dist-info → cars-1.0.0a4.dist-info}/RECORD +115 -131
  111. cars.libs/libgcc_s_seh-1-b2494fcbd4d80cf2c98fdd5261f6d850.dll +0 -0
  112. cars.libs/libstdc++-6-e9b0d12ae0e9555bbae55e8dfd08c3f7.dll +0 -0
  113. cars.libs/libwinpthread-1-7882d1b093714ccdfaf4e0789a817792.dll +0 -0
  114. cars/applications/dense_match_filling/cpp/__init__.py +0 -0
  115. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp312-win_amd64.dll.a +0 -0
  116. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp312-win_amd64.pyd +0 -0
  117. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.py +0 -72
  118. cars/applications/dense_match_filling/cpp/includes/dense_match_filling.hpp +0 -46
  119. cars/applications/dense_match_filling/cpp/meson.build +0 -9
  120. cars/applications/dense_match_filling/cpp/src/bindings.cpp +0 -11
  121. cars/applications/dense_match_filling/cpp/src/dense_match_filling.cpp +0 -142
  122. cars/applications/dense_match_filling/plane_app.py +0 -556
  123. cars/applications/hole_detection/__init__.py +0 -30
  124. cars/applications/hole_detection/abstract_hole_detection_app.py +0 -125
  125. cars/applications/hole_detection/cloud_to_bbox_app.py +0 -346
  126. cars/applications/hole_detection/hole_detection_algo.py +0 -144
  127. cars/applications/hole_detection/hole_detection_wrappers.py +0 -53
  128. cars/applications/point_cloud_denoising/__init__.py +0 -29
  129. cars/applications/point_cloud_denoising/abstract_pc_denoising_app.py +0 -273
  130. cars/applications/point_cloud_fusion/__init__.py +0 -30
  131. cars/applications/point_cloud_fusion/cloud_fusion_constants.py +0 -39
  132. cars/applications/sparse_matching/pandora_sparse_matching_app.py +0 -0
  133. cars/pipelines/parameters/depth_map_inputs_constants.py +0 -25
  134. cars-1.0.0a3.dist-info/DELVEWHEEL +0 -2
  135. cars.libs/libgcc_s_seh-1-ca70890bbc5723b6d0ea31e9c9cded2b.dll +0 -0
  136. cars.libs/libstdc++-6-00ee19f73d5122a1277c137b1c218401.dll +0 -0
  137. cars.libs/libwinpthread-1-f5042e8e3d21edce20c1bc99445f551b.dll +0 -0
  138. {cars-1.0.0a3.dist-info → cars-1.0.0a4.dist-info}/WHEEL +0 -0
  139. {cars-1.0.0a3.dist-info → cars-1.0.0a4.dist-info}/entry_points.txt +0 -0
@@ -33,42 +33,58 @@ from __future__ import print_function
33
33
  import copy
34
34
  import json
35
35
  import logging
36
- import math
37
36
  import os
38
37
  import shutil
38
+ from datetime import datetime
39
39
 
40
40
  # CARS imports
41
- from cars import __version__
42
- from cars.applications.application import Application
43
41
  from cars.core import cars_logging
44
- from cars.core.inputs import get_descriptions_bands, rasterio_get_size
45
42
  from cars.core.utils import safe_makedirs
46
- from cars.orchestrator import orchestrator
43
+ from cars.data_structures import cars_dataset
47
44
  from cars.orchestrator.cluster import log_wrapper
48
45
  from cars.orchestrator.cluster.log_wrapper import cars_profile
49
46
  from cars.pipelines.parameters import advanced_parameters
50
47
  from cars.pipelines.parameters import advanced_parameters_constants as adv_cst
51
- from cars.pipelines.parameters import depth_map_inputs
52
- from cars.pipelines.parameters import depth_map_inputs_constants as depth_cst
53
- from cars.pipelines.parameters import dsm_inputs
54
48
  from cars.pipelines.parameters import dsm_inputs_constants as dsm_cst
55
49
  from cars.pipelines.parameters import output_constants as out_cst
56
- from cars.pipelines.parameters import output_parameters, sensor_inputs
50
+ from cars.pipelines.parameters import output_parameters
57
51
  from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
58
52
  from cars.pipelines.pipeline import Pipeline
59
53
  from cars.pipelines.pipeline_constants import (
60
54
  ADVANCED,
61
55
  APPLICATIONS,
62
56
  INPUTS,
63
- ORCHESTRATOR,
64
57
  OUTPUT,
65
58
  )
66
- from cars.pipelines.pipeline_template import (
67
- PipelineTemplate,
68
- _merge_resolution_conf_rec,
69
- )
59
+ from cars.pipelines.pipeline_template import PipelineTemplate
70
60
  from cars.pipelines.unit.unit_pipeline import UnitPipeline
71
61
 
62
+ package_path = os.path.dirname(__file__)
63
+ FIRST_RES = "first_resolution"
64
+ INTERMEDIATE_RES = "intermediate_resolution"
65
+ FINAL_RES = "final_resolution"
66
+
67
+ PIPELINE_CONFS = {
68
+ FIRST_RES: os.path.join(
69
+ package_path,
70
+ "..",
71
+ "conf_resolution",
72
+ "conf_first_resolution.json",
73
+ ),
74
+ INTERMEDIATE_RES: os.path.join(
75
+ package_path,
76
+ "..",
77
+ "conf_resolution",
78
+ "conf_intermediate_resolution.json",
79
+ ),
80
+ FINAL_RES: os.path.join(
81
+ package_path,
82
+ "..",
83
+ "conf_resolution",
84
+ "conf_final_resolution.json",
85
+ ),
86
+ }
87
+
72
88
 
73
89
  @Pipeline.register(
74
90
  "default",
@@ -84,17 +100,6 @@ class DefaultPipeline(PipelineTemplate):
84
100
  """
85
101
  Creates pipeline
86
102
 
87
- Directly creates class attributes:
88
- used_conf
89
- generate_terrain_products
90
- debug_with_roi
91
- save_output_dsm
92
- save_output_depth_map
93
- save_output_point_clouds
94
- geom_plugin_without_dem_and_geoid
95
- geom_plugin_with_dem_and_geoid
96
- dem_generation_roi
97
-
98
103
  :param pipeline_name: name of the pipeline.
99
104
  :type pipeline_name: str
100
105
  :param cfg: configuration {'matching_cost_method': value}
@@ -103,9 +108,7 @@ class DefaultPipeline(PipelineTemplate):
103
108
  :type config_dir: str
104
109
  """
105
110
 
106
- # Used conf
107
- self.used_conf = {}
108
-
111
+ self.config_dir = config_dir
109
112
  # Transform relative path to absolute path
110
113
  if config_dir is not None:
111
114
  config_dir = os.path.abspath(config_dir)
@@ -113,1161 +116,547 @@ class DefaultPipeline(PipelineTemplate):
113
116
  # Check global conf
114
117
  self.check_global_schema(conf)
115
118
 
116
- # The inputs, outputs and advanced
117
- # parameters are all the same for each resolutions
118
- # So we do the check once
119
-
120
- # Check conf inputs
121
- inputs = self.check_inputs(conf[INPUTS], config_dir=config_dir)
122
-
123
- # Check advanced parameters
124
- # TODO static method in the base class
125
- (
126
- _,
127
- advanced,
128
- self.geometry_plugin,
129
- self.geom_plugin_without_dem_and_geoid,
130
- self.geom_plugin_with_dem_and_geoid,
131
- _,
132
- self.scaling_coeff,
133
- _,
134
- _,
135
- ) = advanced_parameters.check_advanced_parameters(
136
- inputs, conf.get(ADVANCED, {}), check_epipolar_a_priori=True
137
- )
138
-
139
- # Check conf output
140
- (
141
- output,
142
- self.scaling_coeff,
143
- ) = self.check_output(conf[OUTPUT], self.scaling_coeff)
144
-
145
- resolutions = advanced["epipolar_resolutions"]
146
- if isinstance(resolutions, int):
147
- resolutions = [resolutions]
119
+ self.out_dir = conf[OUTPUT][out_cst.OUT_DIRECTORY]
148
120
 
149
- if (
150
- (depth_cst.DEPTH_MAPS in inputs) or (dsm_cst.DSMS in inputs)
151
- ) and len(resolutions) != 1:
152
- raise RuntimeError(
121
+ # Get epipolar resolutions to use
122
+ self.epipolar_resolutions = (
123
+ advanced_parameters.get_epipolar_resolutions(conf.get(ADVANCED, {}))
124
+ )
125
+ if isinstance(self.epipolar_resolutions, int):
126
+ self.epipolar_resolutions = [self.epipolar_resolutions]
127
+
128
+ # Check application
129
+ self.check_applications(conf)
130
+ # Check input
131
+ conf[INPUTS] = self.check_inputs(conf)
132
+ # check advanced
133
+ conf[ADVANCED] = self.check_advanced(conf)
134
+ # check output
135
+ conf[OUTPUT] = self.check_output(conf)
136
+
137
+ if dsm_cst.DSMS in conf[INPUTS] and len(self.epipolar_resolutions) != 1:
138
+ logging.info(
153
139
  "For the use of those pipelines, "
154
140
  "you have to give only one resolution"
155
141
  )
142
+ # overide epipolar resolutions
143
+ # TODO: delete with external dsm pipeline (refactoring)
144
+ self.epipolar_resolutions = [1]
156
145
 
157
- i = 0
158
- last_res = False
159
- for res in resolutions:
160
-
161
- if not isinstance(res, int) or res < 0:
162
- raise RuntimeError("The resolution has to be an int > 0")
163
-
164
- # Get the current key
165
- key = "resolution_" + str(res)
146
+ used_configurations = {}
147
+ self.unit_pipelines = {}
148
+ self.positions = {}
166
149
 
167
- # Choose the right default configuration regarding the resolution
168
- package_path = os.path.dirname(__file__)
169
-
170
- if i == 0:
171
- json_file = os.path.join(
172
- package_path,
173
- "..",
174
- "conf_resolution",
175
- "conf_first_resolution.json",
176
- )
177
- elif i == len(resolutions) - 1 or len(resolutions) == 1:
178
- json_file = os.path.join(
179
- package_path,
180
- "..",
181
- "conf_resolution",
182
- "conf_final_resolution.json",
183
- )
184
- else:
185
- json_file = os.path.join(
186
- package_path,
187
- "..",
188
- "conf_resolution",
189
- "conf_intermediate_resolution.json",
190
- )
191
-
192
- with open(json_file, "r", encoding="utf8") as fstream:
193
- resolution_config = json.load(fstream)
194
-
195
- self.used_conf[key] = {}
196
-
197
- # Check conf orchestrator
198
- self.used_conf[key][ORCHESTRATOR] = self.check_orchestrator(
199
- conf.get(ORCHESTRATOR, None)
200
- )
201
-
202
- # copy inputs
203
- self.used_conf[key][INPUTS] = copy.deepcopy(inputs)
204
-
205
- # Override the resolution
206
- self.used_conf[key][ADVANCED] = copy.deepcopy(advanced)
207
- self.used_conf[key][ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS] = res
208
-
209
- # Copy output
210
- self.used_conf[key][OUTPUT] = copy.deepcopy(output)
211
-
212
- # Get save intermediate data per res
213
- # If true we don't delete the resolution directory
214
- if isinstance(
215
- self.used_conf[key][ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA],
216
- dict,
217
- ):
218
-
219
- self.used_conf[key][ADVANCED][
220
- adv_cst.SAVE_INTERMEDIATE_DATA
221
- ] = self.used_conf[key][ADVANCED][
222
- adv_cst.SAVE_INTERMEDIATE_DATA
223
- ].get(
224
- key, False
225
- )
226
-
227
- self.save_intermediate_data = self.used_conf[key][ADVANCED][
228
- adv_cst.SAVE_INTERMEDIATE_DATA
229
- ]
230
-
231
- self.keep_low_res_dir = self.used_conf[key][ADVANCED][
232
- adv_cst.KEEP_LOW_RES_DIR
233
- ]
234
-
235
- if i != len(resolutions) - 1:
236
- # Change output dir for lower resolution
237
- new_dir = (
238
- self.used_conf[key][OUTPUT][out_cst.OUT_DIRECTORY]
239
- + "/intermediate_res/out_res"
240
- + str(res)
241
- )
242
- self.used_conf[key][OUTPUT][out_cst.OUT_DIRECTORY] = new_dir
243
- safe_makedirs(
244
- self.used_conf[key][OUTPUT][out_cst.OUT_DIRECTORY]
245
- )
246
-
247
- # Put dems in auxiliary output
248
- self.used_conf[key][OUTPUT][out_cst.AUXILIARY][
249
- out_cst.AUX_DEM_MAX
250
- ] = True
251
- self.used_conf[key][OUTPUT][out_cst.AUXILIARY][
252
- out_cst.AUX_DEM_MIN
253
- ] = True
254
- self.used_conf[key][OUTPUT][out_cst.AUXILIARY][
255
- out_cst.AUX_DEM_MEDIAN
256
- ] = True
257
-
258
- if not self.save_intermediate_data:
259
- # For each resolutions we need to calculate the dsm
260
- # (except the last one)
261
- self.used_conf[key][OUTPUT][out_cst.PRODUCT_LEVEL] = "dsm"
262
-
263
- else:
264
- # If save_intermediate_data is true,
265
- # we save the depth_maps also to debug
266
- self.used_conf[key][OUTPUT][out_cst.PRODUCT_LEVEL] = [
267
- "dsm",
268
- "depth_map",
269
- ]
270
- else:
271
- # For the final res we don't change anything
272
- last_res = True
150
+ self.intermediate_data_dir = os.path.join(
151
+ self.out_dir, "intermediate_data"
152
+ )
273
153
 
274
- prod_level = self.used_conf[key][OUTPUT][out_cst.PRODUCT_LEVEL]
154
+ self.keep_low_res_dir = conf[ADVANCED][adv_cst.KEEP_LOW_RES_DIR]
275
155
 
276
- self.save_output_dsm = "dsm" in prod_level
277
- self.save_output_depth_map = "depth_map" in prod_level
278
- self.save_output_point_cloud = "point_cloud" in prod_level
156
+ # Get first res outdir for sift matches
157
+ self.first_res_out_dir_with_sensors = None
279
158
 
280
- self.output_level_none = not (
281
- self.save_output_dsm
282
- or self.save_output_depth_map
283
- or self.save_output_point_cloud
284
- )
285
- self.sensors_in_inputs = (
286
- sens_cst.SENSORS in self.used_conf[key][INPUTS]
159
+ for epipolar_resolution_index, epipolar_res in enumerate(
160
+ self.epipolar_resolutions
161
+ ):
162
+ first_res = epipolar_resolution_index == 0
163
+ intermediate_res = (
164
+ epipolar_resolution_index == len(self.epipolar_resolutions) - 1
165
+ or len(self.epipolar_resolutions) == 1
287
166
  )
288
- self.depth_maps_in_inputs = (
289
- depth_cst.DEPTH_MAPS in self.used_conf[key][INPUTS]
167
+ last_res = (
168
+ epipolar_resolution_index == len(self.epipolar_resolutions) - 1
290
169
  )
291
- self.dsms_in_inputs = dsm_cst.DSMS in self.used_conf[key][INPUTS]
292
-
293
- self.merging = self.used_conf[key][ADVANCED][adv_cst.MERGING]
294
-
295
- self.phasing = self.used_conf[key][ADVANCED][adv_cst.PHASING]
296
-
297
- self.save_all_point_clouds_by_pair = self.used_conf[key][
298
- OUTPUT
299
- ].get(out_cst.SAVE_BY_PAIR, False)
300
-
301
- # Check conf application
302
- if APPLICATIONS in conf:
303
- if all(
304
- "resolution_" + str(val) not in conf[APPLICATIONS]
305
- for val in resolutions
306
- ):
307
- application_all_conf = conf.get(APPLICATIONS, {})
308
- else:
309
- self.check_application_keys_name(
310
- resolutions, conf[APPLICATIONS]
311
- )
312
- application_all_conf = conf[APPLICATIONS].get(key, {})
313
- else:
314
- application_all_conf = {}
315
170
 
316
- application_all_conf = self.merge_resolution_conf(
317
- application_all_conf, resolution_config
318
- )
171
+ # set computed bool
172
+ self.positions[epipolar_resolution_index] = {
173
+ "first_res": first_res,
174
+ "intermediate_res": intermediate_res,
175
+ "last_res": last_res,
176
+ }
319
177
 
320
- application_conf = self.check_applications(
321
- application_all_conf, key, res, last_res
178
+ current_conf = copy.deepcopy(conf)
179
+ current_conf = extract_conf_with_resolution(
180
+ current_conf,
181
+ epipolar_res,
182
+ first_res,
183
+ intermediate_res,
184
+ last_res,
185
+ self.intermediate_data_dir,
322
186
  )
323
187
 
324
- if (
325
- self.sensors_in_inputs
326
- and not self.depth_maps_in_inputs
327
- and not self.dsms_in_inputs
328
- ):
329
- # Check conf application vs inputs application
330
- application_conf = self.check_applications_with_inputs(
331
- self.used_conf[key][INPUTS],
332
- application_conf,
333
- application_all_conf,
334
- res,
335
- )
336
-
337
- self.used_conf[key][APPLICATIONS] = application_conf
338
-
339
- i += 1
340
-
341
- def quit_on_app(self, app_name):
342
- """
343
- Returns whether the pipeline should end after
344
- the application was called.
345
-
346
- Only works if the output_level is empty, so that
347
- the control is instead given to
348
- """
349
-
350
- if not self.output_level_none:
351
- # custom quit step was not set, never quit early
352
- return False
353
-
354
- return self.app_values[app_name] >= self.last_application_to_run
355
-
356
- def infer_conditions_from_applications(self, conf):
357
- """
358
- Fills the condition booleans used later in the pipeline by going
359
- through the applications and infering which application we should
360
- end the pipeline on.
361
- """
362
-
363
- self.last_application_to_run = 0
364
-
365
- sensor_to_depth_apps = {
366
- "grid_generation": 1, # and 5
367
- "resampling": 2, # and 8
368
- "hole_detection": 3,
369
- "sparse_matching.sift": 4,
370
- "ground_truth_reprojection": 6,
371
- "dense_matching": 8,
372
- "dense_match_filling.1": 9,
373
- "dense_match_filling.2": 10,
374
- "triangulation": 11,
375
- "point_cloud_outlier_removal.1": 12,
376
- "point_cloud_outlier_removal.2": 13,
377
- }
188
+ if first_res:
189
+ self.first_res_out_dir_with_sensors = current_conf[OUTPUT][
190
+ "directory"
191
+ ]
378
192
 
379
- depth_merge_apps = {
380
- "point_cloud_fusion": 14,
381
- }
193
+ if not isinstance(epipolar_res, int) or epipolar_res < 0:
194
+ raise RuntimeError("The resolution has to be an int > 0")
382
195
 
383
- depth_to_dsm_apps = {
384
- "pc_denoising": 15,
385
- "point_cloud_rasterization": 16,
386
- "dem_generation": 17,
387
- "dsm_filling.1": 18,
388
- "dsm_filling.2": 19,
389
- "dsm_filling.3": 20,
390
- "auxiliary_filling": 21,
391
- }
196
+ # Initialize unit pipeline in order to retrieve the
197
+ # used configuration
198
+ # This pipeline will not be run
392
199
 
393
- self.app_values = {}
394
- self.app_values.update(sensor_to_depth_apps)
395
- self.app_values.update(depth_merge_apps)
396
- self.app_values.update(depth_to_dsm_apps)
397
-
398
- app_conf = conf.get(APPLICATIONS, {})
399
- for key in app_conf:
400
-
401
- if adv_cst.SAVE_INTERMEDIATE_DATA not in app_conf[key]:
402
- continue
403
-
404
- if not app_conf[key][adv_cst.SAVE_INTERMEDIATE_DATA]:
405
- continue
406
-
407
- if key in sensor_to_depth_apps:
408
-
409
- if not self.sensors_in_inputs:
410
- warn_msg = (
411
- "The application {} can only be used when sensor "
412
- "images are given as an input. "
413
- "Its configuration will be ignored."
414
- ).format(key)
415
- logging.warning(warn_msg)
416
-
417
- elif (
418
- self.sensors_in_inputs
419
- and not self.depth_maps_in_inputs
420
- and not self.dsms_in_inputs
421
- ):
422
- self.compute_depth_map = True
423
- self.last_application_to_run = max(
424
- self.last_application_to_run, self.app_values[key]
425
- )
426
-
427
- elif key in depth_to_dsm_apps:
428
-
429
- if not (
430
- self.sensors_in_inputs
431
- or self.depth_maps_in_inputs
432
- or self.dsms_in_inputs
433
- ):
434
- warn_msg = (
435
- "The application {} can only be used when sensor "
436
- "images or depth maps are given as an input. "
437
- "Its configuration will be ignored."
438
- ).format(key)
439
- logging.warning(warn_msg)
440
-
441
- else:
442
- if (
443
- self.sensors_in_inputs
444
- and not self.depth_maps_in_inputs
445
- and not self.dsms_in_inputs
446
- ):
447
- self.compute_depth_map = True
448
-
449
- # enabled to start the depth map to dsm process
450
- self.save_output_dsm = True
451
-
452
- self.last_application_to_run = max(
453
- self.last_application_to_run, self.app_values[key]
454
- )
455
-
456
- elif key in depth_merge_apps:
457
-
458
- if not self.merging:
459
- warn_msg = (
460
- "The application {} can only be used when merging "
461
- "is activated (this parameter is located in the "
462
- "'advanced' config key). "
463
- "The application's configuration will be ignored."
464
- ).format(key)
465
- logging.warning(warn_msg)
466
-
467
- elif not (
468
- self.sensors_in_inputs
469
- or self.depth_maps_in_inputs
470
- or self.dsms_in_inputs
471
- ):
472
- warn_msg = (
473
- "The application {} can only be used when sensor "
474
- "images or depth maps are given as an input. "
475
- "Its configuration will be ignored."
476
- ).format(key)
477
- logging.warning(warn_msg)
478
-
479
- else:
480
- if (
481
- self.sensors_in_inputs
482
- and not self.depth_maps_in_inputs
483
- and not self.dsms_in_inputs
484
- ):
485
- self.compute_depth_map = True
486
-
487
- # enabled to start the depth map to dsm process
488
- self.save_output_point_cloud = True
489
-
490
- self.last_application_to_run = max(
491
- self.last_application_to_run, self.app_values[key]
492
- )
493
- else:
494
- warn_msg = (
495
- "The application {} was not recognized. Its configuration"
496
- "will be ignored."
497
- ).format(key)
498
- logging.warning(warn_msg)
499
-
500
- if not (self.compute_depth_map or self.save_output_dsm):
501
- log_msg = (
502
- "No product level was given. CARS has not detected any "
503
- "data you wish to save. No computation will be done."
200
+ current_unit_pipeline = UnitPipeline(
201
+ current_conf, config_dir=self.config_dir
504
202
  )
505
- logging.info(log_msg)
506
- else:
507
- log_msg = (
508
- "No product level was given. CARS has detected that you "
509
- + "wish to run up to the {} application.".format(
510
- next(
511
- k
512
- for k, v in self.app_values.items()
513
- if v == self.last_application_to_run
514
- )
515
- )
203
+ self.unit_pipelines[epipolar_resolution_index] = (
204
+ current_unit_pipeline
516
205
  )
517
- logging.warning(log_msg)
206
+ # Get used_conf
207
+ used_configurations[epipolar_res] = current_unit_pipeline.used_conf
208
+
209
+ # Generate full used_conf
210
+ full_used_conf = merge_used_conf(
211
+ used_configurations, self.epipolar_resolutions
212
+ )
213
+ # Save used_conf
214
+ cars_dataset.save_dict(
215
+ full_used_conf,
216
+ os.path.join(self.out_dir, "global_used_conf.json"),
217
+ safe_save=True,
218
+ )
518
219
 
519
- @staticmethod
520
- def check_inputs(conf, config_dir=None):
220
+ def check_inputs(self, conf, config_json_dir=None):
521
221
  """
522
222
  Check the inputs given
523
223
 
524
- :param conf: configuration of inputs
224
+ :param conf: configuration
525
225
  :type conf: dict
526
226
  :param config_dir: directory of used json, if
527
227
  user filled paths with relative paths
528
228
  :type config_dir: str
529
229
 
530
- :return: overloaded inputs
230
+ :return: overloader inputs
531
231
  :rtype: dict
532
232
  """
233
+ return UnitPipeline.check_inputs(
234
+ conf[INPUTS], config_dir=self.config_dir
235
+ )
533
236
 
534
- output_config = {}
535
- if (
536
- sens_cst.SENSORS in conf
537
- and depth_cst.DEPTH_MAPS not in conf
538
- and dsm_cst.DSMS not in conf
539
- ):
540
- output_config = sensor_inputs.sensors_check_inputs(
541
- conf, config_dir=config_dir
542
- )
543
- elif depth_cst.DEPTH_MAPS in conf:
544
- output_config = {
545
- **output_config,
546
- **depth_map_inputs.check_depth_maps_inputs(
547
- conf, config_dir=config_dir
548
- ),
549
- }
550
- else:
551
- output_config = {
552
- **output_config,
553
- **dsm_inputs.check_dsm_inputs(conf, config_dir=config_dir),
554
- }
555
- return output_config
556
-
557
- @staticmethod
558
- def check_output(conf, scaling_coeff):
237
+ def check_output(self, conf):
559
238
  """
560
239
  Check the output given
561
240
 
562
241
  :param conf: configuration of output
563
242
  :type conf: dict
564
- :param scaling_coeff: scaling factor for resolution
565
- :type scaling_coeff: float
566
243
 
567
244
  :return overloader output
568
245
  :rtype : dict
569
246
  """
570
- return output_parameters.check_output_parameters(conf, scaling_coeff)
247
+ conf_output, self.scaling_coeff = (
248
+ output_parameters.check_output_parameters(
249
+ conf[OUTPUT], self.scaling_coeff
250
+ )
251
+ )
252
+ return conf_output
571
253
 
572
- def merge_resolution_conf(self, config1, config2):
254
+ def check_advanced(self, conf):
573
255
  """
574
- Merge two configuration dict, generating a new configuration
575
-
576
- :param conf1: configuration
577
- :type conf1: dict
578
- :param conf2: configuration
579
- :type conf2: dict
256
+ Check all conf for advanced configuration
580
257
 
581
- :return: merged conf
258
+ :return: overridden advanced conf
582
259
  :rtype: dict
260
+ """
261
+ (_, advanced, _, _, _, self.scaling_coeff, _, _) = (
262
+ advanced_parameters.check_advanced_parameters(
263
+ conf[INPUTS],
264
+ conf.get(ADVANCED, {}),
265
+ check_epipolar_a_priori=True,
266
+ )
267
+ )
268
+ return advanced
583
269
 
270
+ def check_applications(self, conf):
584
271
  """
272
+ Check the given configuration for applications
585
273
 
586
- merged_dict = config1.copy()
274
+ :param conf: configuration of applications
275
+ :type conf: dict
276
+ """
277
+ applications_conf = conf.get(APPLICATIONS, {})
278
+ # check format: contains "all" of "resolutions
587
279
 
588
- _merge_resolution_conf_rec(merged_dict, config2)
280
+ int_keys = [int(epi_res) for epi_res in self.epipolar_resolutions]
281
+ string_keys = [str(key) for key in int_keys]
589
282
 
590
- return merged_dict
283
+ possible_keys = ["all"] + int_keys + string_keys
591
284
 
592
- def check_application_keys_name(self, resolutions, applications_conf):
285
+ # Check conf keys in possible keys
286
+ for app_base_key in applications_conf.keys():
287
+ if app_base_key not in possible_keys:
288
+ raise RuntimeError(
289
+ "Application key {} not in possibles keys in : 'all', {} , "
290
+ "as int or str".format(app_base_key, string_keys)
291
+ )
292
+
293
+ # Key str and int key are not defined for the same resolution
294
+ for resolution in int_keys:
295
+ if (
296
+ resolution in applications_conf
297
+ and str(resolution) in applications_conf
298
+ ):
299
+ raise RuntimeError(
300
+ "Application configuration for {} resolution "
301
+ "is defined both "
302
+ "with int and str key".format(resolution)
303
+ )
304
+
305
+ def cleanup_low_res_dir(self):
593
306
  """
594
- Check if the application name for each res match 'resolution_res'
307
+ Clean low res dir
595
308
  """
596
309
 
597
- for key_app, _ in applications_conf.items():
598
- if not key_app.startswith("resolution"):
599
- raise RuntimeError(
600
- "If you decided to define an "
601
- "application per resolution, "
602
- "all the keys have to be : "
603
- "resolution_res"
310
+ if os.path.exists(self.intermediate_data_dir) and os.path.isdir(
311
+ self.intermediate_data_dir
312
+ ):
313
+ try:
314
+ shutil.rmtree(self.intermediate_data_dir)
315
+ logging.info(
316
+ f"th directory {self.intermediate_data_dir} "
317
+ f" has been cleaned."
604
318
  )
605
- if int(key_app.split("_")[1]) not in resolutions:
606
- raise RuntimeError(
607
- "This resolution "
608
- + key_app.split("_")[1]
609
- + " is not in the resolution list"
319
+ except Exception as exception:
320
+ logging.error(
321
+ f"Error while deleting {self.intermediate_data_dir}: "
322
+ f"{exception}"
610
323
  )
324
+ else:
325
+ logging.info(
326
+ f"The directory {self.intermediate_data_dir} has not "
327
+ f"been deleted"
328
+ )
611
329
 
612
- def check_applications( # noqa: C901 : too complex
613
- self,
614
- conf,
615
- key=None,
616
- res=None,
617
- last_res=False,
618
- ):
330
+ def override_with_apriori(self, conf, previous_out_dir, first_res):
619
331
  """
620
- Check the given configuration for applications,
621
- and generates needed applications for pipeline.
332
+ Override configuration with terrain a priori
622
333
 
623
- :param conf: configuration of applications
624
- :type conf: dict
334
+ :param new_conf: configuration
335
+ :type new_conf: dict
625
336
  """
626
- scaling_coeff = self.scaling_coeff
627
-
628
- # Check if all specified applications are used
629
- # Application in terrain_application are note used in
630
- # the sensors_to_dense_depth_maps pipeline
631
- needed_applications = []
632
-
633
- if self.sensors_in_inputs:
634
- needed_applications += [
635
- "grid_generation",
636
- "resampling",
637
- "ground_truth_reprojection",
638
- "hole_detection",
639
- "dense_match_filling.1",
640
- "dense_match_filling.2",
641
- "sparse_matching.sift",
642
- "dense_matching",
643
- "triangulation",
644
- "dem_generation",
645
- "point_cloud_outlier_removal.1",
646
- "point_cloud_outlier_removal.2",
647
- ]
648
-
649
- if self.save_output_dsm or self.save_output_point_cloud:
650
-
651
- needed_applications += ["pc_denoising"]
652
-
653
- if self.save_output_dsm:
654
- needed_applications += [
655
- "point_cloud_rasterization",
656
- "dsm_filling.1",
657
- "dsm_filling.2",
658
- "dsm_filling.3",
659
- "auxiliary_filling",
660
- ]
661
337
 
662
- if self.merging: # we have to merge point clouds, add merging apps
663
- needed_applications += ["point_cloud_fusion"]
338
+ new_conf = copy.deepcopy(conf)
664
339
 
665
- for app_key in conf.keys():
666
- if app_key not in needed_applications:
667
- msg = (
668
- f"No {app_key} application used in the "
669
- + "default Cars pipeline"
670
- )
671
- logging.error(msg)
672
- raise NameError(msg)
673
-
674
- # Initialize used config
675
- used_conf = {}
676
-
677
- for app_key in [
678
- "point_cloud_outlier_removal.1",
679
- "point_cloud_outlier_removal.2",
680
- "auxiliary_filling",
681
- ]:
682
- if conf.get(app_key) is not None:
683
- config_app = conf.get(app_key)
684
- if "activated" not in config_app:
685
- conf[app_key]["activated"] = True
686
-
687
- for app_key in needed_applications:
688
- used_conf[app_key] = conf.get(app_key, {})
689
- used_conf[app_key]["save_intermediate_data"] = (
690
- self.save_intermediate_data
691
- or used_conf[app_key].get("save_intermediate_data", False)
692
- )
340
+ # Extract avanced parameters configuration
341
+ # epipolar and terrain a priori can only be used on first resolution
342
+ if not first_res:
343
+ dem_min = os.path.join(previous_out_dir, "dsm/dem_min.tif")
344
+ dem_max = os.path.join(previous_out_dir, "dsm/dem_max.tif")
345
+ dem_median = os.path.join(previous_out_dir, "dsm/dem_median.tif")
693
346
 
694
- for app_key in [
695
- "point_cloud_fusion",
696
- "pc_denoising",
697
- ]:
698
- if app_key in needed_applications:
699
- used_conf[app_key]["save_by_pair"] = used_conf[app_key].get(
700
- "save_by_pair", self.save_all_point_clouds_by_pair
347
+ new_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] = {
348
+ "dem_min": dem_min,
349
+ "dem_max": dem_max,
350
+ "dem_median": dem_median,
351
+ }
352
+ # Use initial elevation or dem median according to use wish
353
+ if new_conf[INPUTS][sens_cst.INITIAL_ELEVATION]["dem"] is None:
354
+ new_conf[INPUTS][sens_cst.INITIAL_ELEVATION] = dem_median
355
+ else:
356
+ new_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI]["dem_median"] = (
357
+ new_conf[INPUTS][sens_cst.INITIAL_ELEVATION]["dem"]
701
358
  )
359
+ if new_conf[ADVANCED][adv_cst.USE_ENDOGENOUS_DEM] and not first_res:
360
+ new_conf[INPUTS][sens_cst.INITIAL_ELEVATION] = dem_median
702
361
 
703
- self.epipolar_grid_generation_application = None
704
- self.resampling_application = None
705
- self.ground_truth_reprojection = None
706
- self.hole_detection_app = None
707
- self.dense_match_filling_1 = None
708
- self.dense_match_filling_2 = None
709
- self.sparse_mtch_sift_app = None
710
- self.dense_matching_app = None
711
- self.triangulation_application = None
712
- self.dem_generation_application = None
713
- self.pc_denoising_application = None
714
- self.pc_outlier_removal_1_app = None
715
- self.pc_outlier_removal_2_app = None
716
- self.rasterization_application = None
717
- self.pc_fusion_application = None
718
- self.dsm_filling_1_application = None
719
- self.dsm_filling_2_application = None
720
- self.dsm_filling_3_application = None
721
-
722
- if self.sensors_in_inputs:
723
- # Epipolar grid generation
724
- self.epipolar_grid_generation_application = Application(
725
- "grid_generation",
726
- cfg=used_conf.get("grid_generation", {}),
727
- scaling_coeff=scaling_coeff,
728
- )
729
- used_conf["grid_generation"] = (
730
- self.epipolar_grid_generation_application.get_conf()
731
- )
362
+ new_conf[ADVANCED][adv_cst.EPIPOLAR_A_PRIORI] = None
732
363
 
733
- # image resampling
364
+ return new_conf
734
365
 
735
- self.resampling_application = Application(
736
- "resampling",
737
- cfg=used_conf.get("resampling", {}),
738
- scaling_coeff=scaling_coeff,
739
- )
740
- used_conf["resampling"] = self.resampling_application.get_conf()
741
-
742
- # ground truth disparity map computation
743
- if self.used_conf[key][ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
744
- used_conf["ground_truth_reprojection"][
745
- "save_intermediate_data"
746
- ] = True
747
-
748
- if isinstance(
749
- self.used_conf[key][ADVANCED][adv_cst.GROUND_TRUTH_DSM], str
750
- ):
751
- self.used_conf[key][ADVANCED][adv_cst.GROUND_TRUTH_DSM] = {
752
- "dsm": self.used_conf[key][ADVANCED][
753
- adv_cst.GROUND_TRUTH_DSM
754
- ]
755
- }
756
-
757
- self.ground_truth_reprojection = Application(
758
- "ground_truth_reprojection",
759
- cfg=used_conf.get("ground_truth_reprojection", {}),
760
- scaling_coeff=scaling_coeff,
761
- )
762
- # holes detection
763
- self.hole_detection_app = Application(
764
- "hole_detection",
765
- cfg=used_conf.get("hole_detection", {}),
766
- scaling_coeff=scaling_coeff,
767
- )
768
- used_conf["hole_detection"] = self.hole_detection_app.get_conf()
769
-
770
- # disparity filling 1 plane
771
- self.dense_match_filling_1 = Application(
772
- "dense_match_filling",
773
- cfg=used_conf.get(
774
- "dense_match_filling.1",
775
- {"method": "plane"},
776
- ),
777
- scaling_coeff=scaling_coeff,
778
- )
779
- used_conf["dense_match_filling.1"] = (
780
- self.dense_match_filling_1.get_conf()
781
- )
366
+ @cars_profile(name="Run_default_pipeline", interval=0.5)
367
+ def run(self, args=None): # noqa C901
368
+ """
369
+ Run pipeline
782
370
 
783
- # disparity filling 2
784
- self.dense_match_filling_2 = Application(
785
- "dense_match_filling",
786
- cfg=used_conf.get(
787
- "dense_match_filling.2",
788
- {"method": "zero_padding"},
789
- ),
790
- scaling_coeff=scaling_coeff,
791
- )
792
- used_conf["dense_match_filling.2"] = (
793
- self.dense_match_filling_2.get_conf()
794
- )
371
+ """
795
372
 
796
- # Sparse Matching
797
- self.sparse_mtch_sift_app = Application(
798
- "sparse_matching",
799
- cfg=used_conf.get("sparse_matching.sift", {"method": "sift"}),
800
- scaling_coeff=scaling_coeff,
801
- )
802
- used_conf["sparse_matching.sift"] = (
803
- self.sparse_mtch_sift_app.get_conf()
804
- )
373
+ global_log_file = os.path.join(
374
+ self.out_dir,
375
+ "logs",
376
+ "{}_{}.log".format(
377
+ datetime.now().strftime("%y-%m-%d_%Hh%Mm"), "default_pipeline"
378
+ ),
379
+ )
805
380
 
806
- # Matching
807
- generate_performance_map = (
808
- self.used_conf[key][OUTPUT]
809
- .get(out_cst.AUXILIARY, {})
810
- .get(out_cst.AUX_PERFORMANCE_MAP, False)
811
- )
812
- generate_ambiguity = (
813
- self.used_conf[key][OUTPUT]
814
- .get(out_cst.AUXILIARY, {})
815
- .get(out_cst.AUX_AMBIGUITY, False)
816
- )
817
- dense_matching_config = used_conf.get("dense_matching", {})
818
- if generate_ambiguity is True:
819
- dense_matching_config["generate_ambiguity"] = True
381
+ previous_out_dir = None
382
+ updated_conf = {}
383
+ for resolution_index, epipolar_res in enumerate(
384
+ self.epipolar_resolutions
385
+ ):
820
386
 
821
- if (
822
- generate_performance_map is True
823
- and dense_matching_config.get("performance_map_method", None)
824
- is None
825
- ):
826
- dense_matching_config["performance_map_method"] = "risk"
827
- self.dense_matching_app = Application(
828
- "dense_matching",
829
- cfg=dense_matching_config,
830
- scaling_coeff=scaling_coeff,
387
+ # Get tested unit pipeline
388
+ used_pipeline = self.unit_pipelines[resolution_index]
389
+ current_out_dir = used_pipeline.used_conf[OUTPUT]["directory"]
390
+
391
+ # get position
392
+ first_res, _, last_res = (
393
+ self.positions[resolution_index]["first_res"],
394
+ self.positions[resolution_index]["intermediate_res"],
395
+ self.positions[resolution_index]["last_res"],
831
396
  )
832
- used_conf["dense_matching"] = self.dense_matching_app.get_conf()
833
397
 
834
- if not last_res:
835
- used_conf["dense_matching"]["performance_map_method"] = [
836
- "risk",
837
- "intervals",
838
- ]
398
+ # setup logging
399
+ loglevel = getattr(args, "loglevel", "PROGRESS").upper()
839
400
 
840
- # Triangulation
841
- self.triangulation_application = Application(
842
- "triangulation",
843
- cfg=used_conf.get("triangulation", {}),
844
- scaling_coeff=scaling_coeff,
845
- )
846
- used_conf["triangulation"] = (
847
- self.triangulation_application.get_conf()
401
+ current_log_dir = os.path.join(
402
+ self.out_dir, "logs", "res_" + str(epipolar_res)
848
403
  )
849
404
 
850
- # MNT generation
851
- self.dem_generation_application = Application(
852
- "dem_generation",
853
- cfg=used_conf.get("dem_generation", {}),
854
- scaling_coeff=scaling_coeff,
405
+ cars_logging.setup_logging(
406
+ loglevel,
407
+ out_dir=current_log_dir,
408
+ pipeline="unit_pipeline",
409
+ global_log_file=global_log_file,
855
410
  )
856
411
 
857
- height_margin = None
858
- if res >= 8 and "height_margin" not in used_conf["dem_generation"]:
859
- height_margin = [50, 250]
860
-
861
- used_conf["dem_generation"] = (
862
- self.dem_generation_application.get_conf()
412
+ cars_logging.add_progress_message(
413
+ "Starting pipeline for resolution 1/" + str(epipolar_res)
863
414
  )
864
415
 
865
- if height_margin is not None:
866
- used_conf["dem_generation"]["height_margin"] = height_margin
867
-
868
- # Points cloud small component outlier removal
869
- if "point_cloud_outlier_removal.1" in used_conf:
870
- if "method" not in used_conf["point_cloud_outlier_removal.1"]:
871
- used_conf["point_cloud_outlier_removal.1"][
872
- "method"
873
- ] = "small_components"
874
-
875
- self.pc_outlier_removal_1_app = Application(
876
- "point_cloud_outlier_removal",
877
- cfg=used_conf.get(
878
- "point_cloud_outlier_removal.1",
879
- {"method": "small_components"},
880
- ),
881
- scaling_coeff=scaling_coeff,
882
- )
416
+ # use sift a priori if not first
417
+ use_sift_a_priori = False
418
+ if not first_res:
419
+ use_sift_a_priori = True
883
420
 
884
- connection_val = None
885
- if (
886
- "connection_distance"
887
- not in used_conf["point_cloud_outlier_removal.1"]
888
- ):
889
- connection_val = (
890
- self.pc_outlier_removal_1_app.connection_distance * res
891
- )
421
+ # define wich resolution
422
+ if first_res and last_res:
423
+ which_resolution = "single"
424
+ elif first_res:
425
+ which_resolution = "first"
426
+ elif last_res:
427
+ which_resolution = "final"
428
+ else:
429
+ which_resolution = "intermediate"
892
430
 
893
- used_conf["point_cloud_outlier_removal.1"] = (
894
- self.pc_outlier_removal_1_app.get_conf()
895
- )
431
+ # Generate dem
432
+ generate_dems = True
433
+ if last_res:
434
+ generate_dems = False
896
435
 
897
- if connection_val is not None:
898
- used_conf["point_cloud_outlier_removal.1"][
899
- "connection_distance"
900
- ] = connection_val
901
-
902
- # Points cloud statistical outlier removal
903
- self.pc_outlier_removal_2_app = Application(
904
- "point_cloud_outlier_removal",
905
- cfg=used_conf.get(
906
- "point_cloud_outlier_removal.2",
907
- {"method": "statistical"},
908
- ),
909
- scaling_coeff=scaling_coeff,
436
+ # Overide with a priori
437
+ overridden_conf = self.override_with_apriori(
438
+ used_pipeline.used_conf, previous_out_dir, first_res
439
+ )
440
+ updated_pipeline = UnitPipeline(
441
+ overridden_conf, config_dir=self.config_dir
910
442
  )
911
- used_conf["point_cloud_outlier_removal.2"] = (
912
- self.pc_outlier_removal_2_app.get_conf()
443
+ updated_pipeline.run(
444
+ generate_dems=generate_dems,
445
+ which_resolution=which_resolution,
446
+ use_sift_a_priori=use_sift_a_priori,
447
+ first_res_out_dir=self.first_res_out_dir_with_sensors,
448
+ log_dir=current_log_dir,
913
449
  )
914
450
 
915
- if self.save_output_dsm or self.save_output_point_cloud:
451
+ # update previous out dir
452
+ previous_out_dir = current_out_dir
916
453
 
917
- # Point cloud denoising
918
- self.pc_denoising_application = Application(
919
- "pc_denoising",
920
- cfg=used_conf.get("pc_denoising", {"method": "none"}),
921
- scaling_coeff=scaling_coeff,
454
+ # generate summary
455
+ log_wrapper.generate_summary(
456
+ current_log_dir, updated_pipeline.used_conf
922
457
  )
923
- used_conf["pc_denoising"] = self.pc_denoising_application.get_conf()
924
458
 
925
- if self.save_output_dsm:
459
+ updated_conf[epipolar_res] = updated_pipeline.used_conf
926
460
 
927
- # Rasterization
928
- self.rasterization_application = Application(
929
- "point_cloud_rasterization",
930
- cfg=used_conf.get("point_cloud_rasterization", {}),
931
- scaling_coeff=scaling_coeff,
932
- )
933
- used_conf["point_cloud_rasterization"] = (
934
- self.rasterization_application.get_conf()
935
- )
936
- # DSM filling 1 : Exogenous filling
937
- self.dsm_filling_1_application = Application(
938
- "dsm_filling",
939
- cfg=conf.get(
940
- "dsm_filling.1",
941
- {"method": "exogenous_filling"},
942
- ),
943
- scaling_coeff=scaling_coeff,
944
- )
945
- used_conf["dsm_filling.1"] = (
946
- self.dsm_filling_1_application.get_conf()
947
- )
948
- # DSM filling 2 : Bulldozer
949
- self.dsm_filling_2_application = Application(
950
- "dsm_filling",
951
- cfg=conf.get(
952
- "dsm_filling.2",
953
- {"method": "bulldozer"},
954
- ),
955
- )
956
- used_conf["dsm_filling.2"] = (
957
- self.dsm_filling_2_application.get_conf()
958
- )
959
- # DSM filling 3 : Border interpolation
960
- self.dsm_filling_3_application = Application(
961
- "dsm_filling",
962
- cfg=conf.get(
963
- "dsm_filling.3",
964
- {"method": "border_interpolation"},
965
- ),
966
- scaling_coeff=scaling_coeff,
967
- )
968
- used_conf["dsm_filling.3"] = (
969
- self.dsm_filling_3_application.get_conf()
970
- )
971
- # Auxiliary filling
972
- self.auxiliary_filling_application = Application(
973
- "auxiliary_filling",
974
- cfg=conf.get("auxiliary_filling", {}),
975
- scaling_coeff=scaling_coeff,
976
- )
977
- used_conf["auxiliary_filling"] = (
978
- self.auxiliary_filling_application.get_conf()
979
- )
461
+ # Generate full used_conf
462
+ full_used_conf = merge_used_conf(
463
+ updated_conf, self.epipolar_resolutions
464
+ )
465
+ # Save used_conf
466
+ cars_dataset.save_dict(
467
+ full_used_conf,
468
+ os.path.join(self.out_dir, "global_used_conf.json"),
469
+ safe_save=True,
470
+ )
980
471
 
981
- if self.merging:
472
+ # Merge profiling in pdf
473
+ log_wrapper.generate_pdf_profiling(os.path.join(self.out_dir, "logs"))
982
474
 
983
- # Point cloud fusion
984
- self.pc_fusion_application = Application(
985
- "point_cloud_fusion",
986
- cfg=used_conf.get("point_cloud_fusion", {}),
987
- scaling_coeff=scaling_coeff,
988
- )
989
- used_conf["point_cloud_fusion"] = (
990
- self.pc_fusion_application.get_conf()
991
- )
475
+ # clean outdir
476
+ if not self.keep_low_res_dir:
477
+ self.cleanup_low_res_dir()
992
478
 
993
- return used_conf
994
479
 
995
- def check_applications_with_inputs( # noqa: C901 : too complex
996
- self, inputs_conf, application_conf, initial_conf_app, res
997
- ):
998
- """
999
- Check for each application the input and output configuration
1000
- consistency
480
+ def extract_applications(current_applications_conf, res, default_conf_for_res):
481
+ """
482
+ Extract applications for current resolution
483
+ """
1001
484
 
1002
- :param inputs_conf: inputs checked configuration
1003
- :type inputs_conf: dict
1004
- :param application_conf: application checked configuration
1005
- :type application_conf: dict
1006
- """
485
+ # "all" : applied to all conf
486
+ # int (1, 2, 4, 8, 16, ...) applied for specified resolution
487
+
488
+ all_conf = current_applications_conf.get("all", {})
489
+ # Overide with default_conf_for_res
490
+ all_conf = overide_pipeline_conf(all_conf, default_conf_for_res)
491
+ # Get configuration for current res
492
+ if res in current_applications_conf:
493
+ # key is int
494
+ key = res
495
+ else:
496
+ key = str(res)
497
+
498
+ res_conf = current_applications_conf.get(key, {})
499
+
500
+ new_application_conf = overide_pipeline_conf(all_conf, res_conf)
501
+ return new_application_conf
502
+
503
+
504
+ # pylint: disable=too-many-positional-arguments
505
+ def extract_conf_with_resolution(
506
+ current_conf,
507
+ res,
508
+ first_res,
509
+ intermediate_res,
510
+ last_res,
511
+ intermediate_data_dir,
512
+ ):
513
+ """
514
+ Extract the configuration for the given resolution
515
+
516
+ :param current_conf: current configuration
517
+ :type current_conf: dict
518
+ :param res: resolution to extract
519
+ :type res: int
520
+ :return: configuration for the given resolution
521
+ :rtype: dict
522
+ :param first_res: is first resolution
523
+ :type first_res: bool
524
+ :param intermediate_res: is intermediate resolution
525
+ :type intermediate_res: bool
526
+ :param last_res: is last resolution
527
+ :type last_res: bool
528
+ :param previous_out_dir: path to previous outdir
529
+ :type: previous_out_dir: str
530
+ """
1007
531
 
1008
- initial_elevation = (
1009
- inputs_conf[sens_cst.INITIAL_ELEVATION]["dem"] is not None
1010
- )
1011
- if self.sparse_mtch_sift_app.elevation_delta_lower_bound is None:
1012
- self.sparse_mtch_sift_app.used_config[
1013
- "elevation_delta_lower_bound"
1014
- ] = (-500 if initial_elevation else -1000)
1015
- self.sparse_mtch_sift_app.elevation_delta_lower_bound = (
1016
- self.sparse_mtch_sift_app.used_config[
1017
- "elevation_delta_lower_bound"
1018
- ]
1019
- )
1020
- if self.sparse_mtch_sift_app.elevation_delta_upper_bound is None:
1021
- self.sparse_mtch_sift_app.used_config[
1022
- "elevation_delta_upper_bound"
1023
- ] = (1000 if initial_elevation else 9000)
1024
- self.sparse_mtch_sift_app.elevation_delta_upper_bound = (
1025
- self.sparse_mtch_sift_app.used_config[
1026
- "elevation_delta_upper_bound"
1027
- ]
1028
- )
1029
- application_conf["sparse_matching.sift"] = (
1030
- self.sparse_mtch_sift_app.get_conf()
532
+ new_dir_out_dir = current_conf[OUTPUT][out_cst.OUT_DIRECTORY]
533
+ if not last_res:
534
+ new_dir_out_dir = os.path.join(
535
+ intermediate_data_dir, "out_res" + str(res)
1031
536
  )
537
+ safe_makedirs(new_dir_out_dir)
538
+
539
+ new_conf = copy.deepcopy(current_conf)
540
+
541
+ # Get save intermediate data
542
+ if isinstance(new_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA], dict):
543
+ # If save_intermediate_data is not a dict, we set it to False
544
+ new_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA] = new_conf[ADVANCED][
545
+ adv_cst.SAVE_INTERMEDIATE_DATA
546
+ ].get("resolution_" + str(res), False)
547
+
548
+ # Overide epipolar resolution
549
+ new_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS] = res
550
+
551
+ # Overide configuration with pipeline conf
552
+ if first_res:
553
+ # read the first resolution conf with json package
554
+ with open(PIPELINE_CONFS[FIRST_RES], "r", encoding="utf-8") as file:
555
+ overiding_conf = json.load(file)
556
+ elif intermediate_res:
557
+ with open(
558
+ PIPELINE_CONFS[INTERMEDIATE_RES], "r", encoding="utf-8"
559
+ ) as file:
560
+ overiding_conf = json.load(file)
561
+ else:
562
+ with open(PIPELINE_CONFS[FINAL_RES], "r", encoding="utf-8") as file:
563
+ overiding_conf = json.load(file)
564
+
565
+ # extract application
566
+ new_conf[APPLICATIONS] = extract_applications(
567
+ current_conf.get(APPLICATIONS, {}),
568
+ res,
569
+ overiding_conf.get(APPLICATIONS, {}),
570
+ )
571
+
572
+ # Overide output to not compute data
573
+ # Overide resolution to let unit pipeline manage it
574
+ if not last_res:
575
+ overiding_conf = {
576
+ OUTPUT: {
577
+ out_cst.OUT_DIRECTORY: new_dir_out_dir,
578
+ out_cst.RESOLUTION: None,
579
+ out_cst.SAVE_BY_PAIR: True,
580
+ out_cst.AUXILIARY: {
581
+ out_cst.AUX_DEM_MAX: True,
582
+ out_cst.AUX_DEM_MIN: True,
583
+ out_cst.AUX_DEM_MEDIAN: True,
584
+ },
585
+ },
586
+ APPLICATIONS: {
587
+ "dense_matching": {
588
+ "performance_map_method": ["risk", "intervals"]
589
+ }
590
+ },
591
+ }
592
+ new_conf = overide_pipeline_conf(new_conf, overiding_conf)
1032
593
 
1033
- if (
1034
- application_conf["dem_generation"]["method"]
1035
- == "bulldozer_on_raster"
1036
- ):
1037
- first_image_path = next(iter(inputs_conf["sensors"].values()))[
1038
- "image"
1039
- ]["main_file"]
1040
- first_image_size = rasterio_get_size(first_image_path)
1041
- first_image_nb_pixels = math.prod(first_image_size)
1042
- dem_gen_used_mem = first_image_nb_pixels / 1e8
1043
- if dem_gen_used_mem > 8:
1044
- logging.warning(
1045
- "DEM generation method is 'bulldozer_on_raster'. "
1046
- f"This method can use up to {dem_gen_used_mem} Gb "
1047
- "of memory. If you think that it is too much for "
1048
- "your computer, you can re-lauch the run using "
1049
- "'dichotomic' method for DEM generation"
1050
- )
1051
-
1052
- # check classification application parameter compare
1053
- # to each sensors inputs classification list
1054
-
1055
- for application_key in application_conf:
1056
- if "classification" in application_conf[application_key]:
1057
- for item in inputs_conf["sensors"]:
1058
- if "classification" in inputs_conf["sensors"][item].keys():
1059
- if inputs_conf["sensors"][item]["classification"]:
1060
- descriptions = get_descriptions_bands(
1061
- inputs_conf["sensors"][item]["classification"]
1062
- )
1063
- if application_conf[application_key][
1064
- "classification"
1065
- ] and not set(
1066
- application_conf[application_key][
1067
- "classification"
1068
- ]
1069
- ).issubset(
1070
- set(descriptions) | {"nodata"}
1071
- ):
1072
- raise RuntimeError(
1073
- "The {} bands description {} ".format(
1074
- inputs_conf["sensors"][item][
1075
- "classification"
1076
- ],
1077
- list(descriptions),
1078
- )
1079
- + "and the {} config are not ".format(
1080
- application_key
1081
- )
1082
- + "consistent: {}".format(
1083
- application_conf[application_key][
1084
- "classification"
1085
- ]
1086
- )
1087
- )
1088
- for key1, key2 in inputs_conf["pairing"]:
1089
- corr_cfg = self.dense_matching_app.loader.get_conf()
1090
- img_left = inputs_conf["sensors"][key1]["image"]["main_file"]
1091
- img_right = inputs_conf["sensors"][key2]["image"]["main_file"]
1092
- bands_left = list(
1093
- inputs_conf["sensors"][key1]["image"]["bands"].keys()
1094
- )
1095
- bands_right = list(
1096
- inputs_conf["sensors"][key2]["image"]["bands"].keys()
1097
- )
1098
- classif_left = None
1099
- classif_right = None
1100
- if (
1101
- "classification" in inputs_conf["sensors"][key1]
1102
- and inputs_conf["sensors"][key1]["classification"] is not None
1103
- ):
1104
- classif_left = inputs_conf["sensors"][key1]["classification"][
1105
- "main_file"
1106
- ]
1107
- if (
1108
- "classification" in inputs_conf["sensors"][key2]
1109
- and inputs_conf["sensors"][key1]["classification"] is not None
1110
- ):
1111
- classif_right = inputs_conf["sensors"][key2]["classification"][
1112
- "main_file"
1113
- ]
1114
- self.dense_matching_app.corr_config = (
1115
- self.dense_matching_app.loader.check_conf(
1116
- corr_cfg,
1117
- img_left,
1118
- img_right,
1119
- bands_left,
1120
- bands_right,
1121
- classif_left,
1122
- classif_right,
1123
- )
1124
- )
594
+ # set product level to dsm
595
+ new_conf[OUTPUT][out_cst.PRODUCT_LEVEL] = ["dsm"]
596
+ # remove resolution to let CARS compute it for current
597
+ # epipolar resolution
598
+ new_conf[OUTPUT]["resolution"] = None
1125
599
 
1126
- # Change the step regarding the resolution
1127
- # For the small resolution, the resampling perform better
1128
- # with a small step
1129
- # For the higher ones, a step at 30 should be better
1130
- first_image_path = next(iter(inputs_conf["sensors"].values()))["image"][
1131
- "main_file"
1132
- ]
1133
- first_image_size = rasterio_get_size(first_image_path)
1134
- size_low_res_img_row = first_image_size[0] // res
1135
- size_low_res_img_col = first_image_size[1] // res
1136
- if (
1137
- "grid_generation" not in initial_conf_app
1138
- or "epi_step" not in initial_conf_app["grid_generation"]
1139
- ):
1140
- if size_low_res_img_row <= 900 and size_low_res_img_col <= 900:
1141
- application_conf["grid_generation"]["epi_step"] = res * 5
1142
- else:
1143
- application_conf["grid_generation"]["epi_step"] = res * 30
600
+ if not new_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA]:
601
+ # Save the less possible things
602
+ aux_items = new_conf[OUTPUT][out_cst.AUXILIARY].items()
603
+ for aux_key, _ in aux_items:
604
+ if aux_key not in ("dem_min", "dem_max", "dem_median"):
605
+ new_conf[OUTPUT][out_cst.AUXILIARY][aux_key] = False
1144
606
 
1145
- return application_conf
607
+ return new_conf
1146
608
 
1147
- def cleanup_low_res_dir(self):
1148
- """
1149
- Clean low res dir
1150
- """
1151
609
 
1152
- items = list(self.used_conf.items())
1153
- for _, conf_res in items[:-1]:
1154
- out_dir = conf_res[OUTPUT][out_cst.OUT_DIRECTORY]
1155
- if os.path.exists(out_dir) and os.path.isdir(out_dir):
1156
- try:
1157
- shutil.rmtree(out_dir)
1158
- print(f"th directory {out_dir} has been cleaned.")
1159
- except Exception as exception:
1160
- print(f"Error while deleting {out_dir}: {exception}")
1161
- else:
1162
- print(f"The directory {out_dir} has not been deleted")
610
+ def overide_pipeline_conf(conf, overiding_conf):
611
+ """
612
+ Merge two dictionaries recursively without removing keys from the base conf.
613
+
614
+ :param conf: base configuration dictionary
615
+ :type conf: dict
616
+ :param overiding_conf: overriding configuration dictionary
617
+ :type overiding_conf: dict
618
+ :return: merged configuration
619
+ :rtype: dict
620
+ """
621
+ result = copy.deepcopy(conf)
1163
622
 
1164
- @cars_profile(name="run_dense_pipeline", interval=0.5)
1165
- def run(self, args=None): # noqa C901
623
+ def merge_recursive(base_dict, override_dict):
1166
624
  """
1167
- Run pipeline
1168
-
625
+ Main recursive function
1169
626
  """
1170
- first_res_out_dir = None
1171
- previous_out_dir = None
1172
- generate_dems = True
1173
- last_key = list(self.used_conf)[-1]
1174
- final_out_dir = self.used_conf[last_key][OUTPUT][out_cst.OUT_DIRECTORY]
1175
- use_sift_a_priori = False
1176
-
1177
- i = 0
1178
- nb_res = len(list(self.used_conf.items()))
1179
- for key, conf_res in self.used_conf.items():
1180
- out_dir = conf_res[OUTPUT][out_cst.OUT_DIRECTORY]
1181
-
1182
- if nb_res != 1 and args is not None:
1183
- # Logging configuration with args Loglevel
1184
- loglevel = getattr(args, "loglevel", "PROGRESS").upper()
1185
-
1186
- cars_logging.setup_logging(
1187
- loglevel,
1188
- out_dir=os.path.join(out_dir, "logs"),
1189
- pipeline="",
1190
- )
1191
-
1192
- if int(key.split("_")[-1]) != 1:
1193
- cars_logging.add_progress_message(
1194
- "Starting pipeline for resolution 1/" + key.split("_")[-1]
1195
- )
1196
- else:
1197
- cars_logging.add_progress_message(
1198
- "Starting pipeline for resolution 1"
1199
- )
1200
-
1201
- # Get the resolution step
1202
- if previous_out_dir is not None:
1203
- if i == len(self.used_conf) - 1:
1204
- which_resolution = "final"
1205
- generate_dems = False
1206
- else:
1207
- which_resolution = "intermediate"
627
+ for key, value in override_dict.items():
628
+ if (
629
+ key in base_dict
630
+ and isinstance(base_dict[key], dict)
631
+ and isinstance(value, dict)
632
+ ):
633
+ merge_recursive(base_dict[key], value)
1208
634
  else:
1209
- if len(self.used_conf) == 1:
1210
- which_resolution = "single"
1211
- else:
1212
- which_resolution = "first"
1213
- first_res_out_dir = out_dir
1214
-
1215
- # Build a priori
1216
- if which_resolution in ("final", "intermediate"):
1217
- dem_min = os.path.join(previous_out_dir, "dsm/dem_min.tif")
1218
- dem_max = os.path.join(previous_out_dir, "dsm/dem_max.tif")
1219
- dem_median = os.path.join(
1220
- previous_out_dir, "dsm/dem_median.tif"
1221
- )
635
+ base_dict[key] = value
1222
636
 
1223
- conf_res[ADVANCED][adv_cst.TERRAIN_A_PRIORI] = {
1224
- "dem_min": dem_min,
1225
- "dem_max": dem_max,
1226
- "dem_median": dem_median,
1227
- }
637
+ merge_recursive(result, overiding_conf)
638
+ return result
1228
639
 
1229
- if conf_res[INPUTS][sens_cst.INITIAL_ELEVATION]["dem"] is None:
1230
- conf_res[INPUTS][sens_cst.INITIAL_ELEVATION] = dem_median
1231
- else:
1232
- conf_res[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
1233
- "dem_median"
1234
- ] = conf_res[INPUTS][sens_cst.INITIAL_ELEVATION]["dem"]
1235
640
 
1236
- conf_res[ADVANCED][adv_cst.USE_EPIPOLAR_A_PRIORI] = True
1237
- use_sift_a_priori = True
641
+ def merge_used_conf(used_configurations, epipolar_resolutions):
642
+ """
643
+ Merge all used configuration
644
+ """
645
+ used_configurations = copy.deepcopy(used_configurations)
1238
646
 
1239
- # start cars orchestrator
1240
- with orchestrator.Orchestrator(
1241
- orchestrator_conf=conf_res[ORCHESTRATOR],
1242
- out_dir=out_dir,
1243
- out_json_path=os.path.join(
1244
- out_dir,
1245
- out_cst.INFO_FILENAME,
1246
- ),
1247
- ) as self.cars_orchestrator:
1248
-
1249
- # initialize out_json
1250
- self.cars_orchestrator.update_out_info({"version": __version__})
1251
-
1252
- used_pipeline = UnitPipeline(conf_res)
1253
-
1254
- used_pipeline.run(
1255
- self.cars_orchestrator,
1256
- generate_dems,
1257
- which_resolution,
1258
- use_sift_a_priori,
1259
- first_res_out_dir,
1260
- final_out_dir,
1261
- )
647
+ merged_conf = {
648
+ INPUTS: used_configurations[epipolar_resolutions[0]][INPUTS],
649
+ ADVANCED: used_configurations[epipolar_resolutions[0]][ADVANCED],
650
+ OUTPUT: used_configurations[epipolar_resolutions[0]][OUTPUT],
651
+ }
1262
652
 
1263
- if nb_res != 1 and args is not None:
1264
- # Generate summary of tasks
1265
- log_wrapper.generate_summary(
1266
- out_dir, used_pipeline.used_conf, clean_worker_logs=True
1267
- )
653
+ merged_conf[APPLICATIONS] = {}
654
+ merged_conf[APPLICATIONS]["all"] = {}
1268
655
 
1269
- previous_out_dir = out_dir
1270
- i += 1
656
+ # Merge applications
657
+ for res in epipolar_resolutions:
658
+ merged_conf[APPLICATIONS][res] = used_configurations[res][APPLICATIONS]
1271
659
 
1272
- if not self.keep_low_res_dir:
1273
- self.cleanup_low_res_dir()
660
+ # apply epipolar resolutions
661
+ merged_conf[ADVANCED]["epipolar_resolutions"] = epipolar_resolutions
662
+ return merged_conf