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