cars 1.0.0rc1__cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (200) hide show
  1. cars/__init__.py +74 -0
  2. cars/applications/__init__.py +37 -0
  3. cars/applications/application.py +117 -0
  4. cars/applications/application_constants.py +29 -0
  5. cars/applications/application_template.py +146 -0
  6. cars/applications/auxiliary_filling/__init__.py +29 -0
  7. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +104 -0
  8. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
  9. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +630 -0
  10. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
  11. cars/applications/dem_generation/__init__.py +30 -0
  12. cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
  13. cars/applications/dem_generation/bulldozer_config/base_config.yaml +42 -0
  14. cars/applications/dem_generation/bulldozer_dem_app.py +655 -0
  15. cars/applications/dem_generation/bulldozer_memory.py +55 -0
  16. cars/applications/dem_generation/dem_generation_algo.py +107 -0
  17. cars/applications/dem_generation/dem_generation_constants.py +32 -0
  18. cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
  19. cars/applications/dense_match_filling/__init__.py +30 -0
  20. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
  21. cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
  22. cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
  23. cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
  24. cars/applications/dense_match_filling/zero_padding_app.py +302 -0
  25. cars/applications/dense_matching/__init__.py +30 -0
  26. cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
  27. cars/applications/dense_matching/census_mccnn_sgm_app.py +1460 -0
  28. cars/applications/dense_matching/cpp/__init__.py +0 -0
  29. cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-312-i386-linux-gnu.so +0 -0
  30. cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
  31. cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
  32. cars/applications/dense_matching/cpp/meson.build +9 -0
  33. cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
  34. cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
  35. cars/applications/dense_matching/dense_matching_algo.py +401 -0
  36. cars/applications/dense_matching/dense_matching_constants.py +89 -0
  37. cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
  38. cars/applications/dense_matching/disparity_grid_algo.py +588 -0
  39. cars/applications/dense_matching/loaders/__init__.py +23 -0
  40. cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
  41. cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
  42. cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
  43. cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
  44. cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
  45. cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
  46. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  47. cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
  48. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  49. cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
  50. cars/applications/dsm_filling/__init__.py +32 -0
  51. cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
  52. cars/applications/dsm_filling/border_interpolation_app.py +270 -0
  53. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  54. cars/applications/dsm_filling/bulldozer_filling_app.py +279 -0
  55. cars/applications/dsm_filling/exogenous_filling_app.py +333 -0
  56. cars/applications/grid_generation/__init__.py +30 -0
  57. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  58. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  59. cars/applications/grid_generation/grid_correction_app.py +496 -0
  60. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  61. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  62. cars/applications/grid_generation/transform_grid.py +88 -0
  63. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  64. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  65. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  66. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  67. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  68. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  69. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  70. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  71. cars/applications/point_cloud_outlier_removal/small_components_app.py +527 -0
  72. cars/applications/point_cloud_outlier_removal/statistical_app.py +531 -0
  73. cars/applications/rasterization/__init__.py +30 -0
  74. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  75. cars/applications/rasterization/rasterization_algo.py +534 -0
  76. cars/applications/rasterization/rasterization_constants.py +38 -0
  77. cars/applications/rasterization/rasterization_wrappers.py +634 -0
  78. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  79. cars/applications/resampling/__init__.py +28 -0
  80. cars/applications/resampling/abstract_resampling_app.py +187 -0
  81. cars/applications/resampling/bicubic_resampling_app.py +762 -0
  82. cars/applications/resampling/resampling_algo.py +614 -0
  83. cars/applications/resampling/resampling_constants.py +36 -0
  84. cars/applications/resampling/resampling_wrappers.py +309 -0
  85. cars/applications/sparse_matching/__init__.py +30 -0
  86. cars/applications/sparse_matching/abstract_sparse_matching_app.py +498 -0
  87. cars/applications/sparse_matching/sift_app.py +735 -0
  88. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  89. cars/applications/sparse_matching/sparse_matching_constants.py +68 -0
  90. cars/applications/sparse_matching/sparse_matching_wrappers.py +238 -0
  91. cars/applications/triangulation/__init__.py +32 -0
  92. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  93. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  94. cars/applications/triangulation/pc_transform.py +552 -0
  95. cars/applications/triangulation/triangulation_algo.py +371 -0
  96. cars/applications/triangulation/triangulation_constants.py +38 -0
  97. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  98. cars/bundleadjustment.py +757 -0
  99. cars/cars.py +177 -0
  100. cars/conf/__init__.py +23 -0
  101. cars/conf/geoid/egm96.grd +0 -0
  102. cars/conf/geoid/egm96.grd.hdr +15 -0
  103. cars/conf/input_parameters.py +156 -0
  104. cars/conf/mask_cst.py +35 -0
  105. cars/core/__init__.py +23 -0
  106. cars/core/cars_logging.py +402 -0
  107. cars/core/constants.py +191 -0
  108. cars/core/constants_disparity.py +50 -0
  109. cars/core/datasets.py +140 -0
  110. cars/core/geometry/__init__.py +27 -0
  111. cars/core/geometry/abstract_geometry.py +1119 -0
  112. cars/core/geometry/shareloc_geometry.py +598 -0
  113. cars/core/inputs.py +568 -0
  114. cars/core/outputs.py +176 -0
  115. cars/core/preprocessing.py +722 -0
  116. cars/core/projection.py +843 -0
  117. cars/core/roi_tools.py +215 -0
  118. cars/core/tiling.py +774 -0
  119. cars/core/utils.py +164 -0
  120. cars/data_structures/__init__.py +23 -0
  121. cars/data_structures/cars_dataset.py +1541 -0
  122. cars/data_structures/cars_dict.py +74 -0
  123. cars/data_structures/corresponding_tiles_tools.py +186 -0
  124. cars/data_structures/dataframe_converter.py +185 -0
  125. cars/data_structures/format_transformation.py +297 -0
  126. cars/devibrate.py +689 -0
  127. cars/extractroi.py +264 -0
  128. cars/orchestrator/__init__.py +23 -0
  129. cars/orchestrator/achievement_tracker.py +125 -0
  130. cars/orchestrator/cluster/__init__.py +37 -0
  131. cars/orchestrator/cluster/abstract_cluster.py +244 -0
  132. cars/orchestrator/cluster/abstract_dask_cluster.py +375 -0
  133. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  134. cars/orchestrator/cluster/dask_config/README.md +94 -0
  135. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  136. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  137. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  138. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  139. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  140. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  141. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  142. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  143. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  144. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  145. cars/orchestrator/cluster/log_wrapper.py +1075 -0
  146. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  147. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  148. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  149. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  150. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  151. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +873 -0
  152. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  153. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  154. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  155. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  156. cars/orchestrator/orchestrator.py +905 -0
  157. cars/orchestrator/orchestrator_constants.py +29 -0
  158. cars/orchestrator/registry/__init__.py +23 -0
  159. cars/orchestrator/registry/abstract_registry.py +143 -0
  160. cars/orchestrator/registry/compute_registry.py +106 -0
  161. cars/orchestrator/registry/id_generator.py +116 -0
  162. cars/orchestrator/registry/replacer_registry.py +213 -0
  163. cars/orchestrator/registry/saver_registry.py +363 -0
  164. cars/orchestrator/registry/unseen_registry.py +118 -0
  165. cars/orchestrator/tiles_profiler.py +279 -0
  166. cars/pipelines/__init__.py +26 -0
  167. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  168. cars/pipelines/conf_resolution/conf_first_resolution.yaml +2 -0
  169. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  170. cars/pipelines/default/__init__.py +26 -0
  171. cars/pipelines/default/default_pipeline.py +786 -0
  172. cars/pipelines/parameters/__init__.py +0 -0
  173. cars/pipelines/parameters/advanced_parameters.py +417 -0
  174. cars/pipelines/parameters/advanced_parameters_constants.py +69 -0
  175. cars/pipelines/parameters/application_parameters.py +71 -0
  176. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  177. cars/pipelines/parameters/dsm_inputs.py +918 -0
  178. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  179. cars/pipelines/parameters/output_constants.py +52 -0
  180. cars/pipelines/parameters/output_parameters.py +454 -0
  181. cars/pipelines/parameters/sensor_inputs.py +842 -0
  182. cars/pipelines/parameters/sensor_inputs_constants.py +49 -0
  183. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  184. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  185. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  186. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  187. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  188. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  189. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  190. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  191. cars/pipelines/pipeline.py +119 -0
  192. cars/pipelines/pipeline_constants.py +31 -0
  193. cars/pipelines/pipeline_template.py +139 -0
  194. cars/pipelines/unit/__init__.py +26 -0
  195. cars/pipelines/unit/unit_pipeline.py +2850 -0
  196. cars/starter.py +167 -0
  197. cars-1.0.0rc1.dist-info/METADATA +292 -0
  198. cars-1.0.0rc1.dist-info/RECORD +200 -0
  199. cars-1.0.0rc1.dist-info/WHEEL +6 -0
  200. cars-1.0.0rc1.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,1460 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf8
3
+ #
4
+ # Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
5
+ #
6
+ # This file is part of CARS
7
+ # (see https://github.com/CNES/cars).
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ """
22
+ this module contains the dense_matching application class.
23
+ """
24
+ # pylint: disable=too-many-lines
25
+ import collections
26
+
27
+ # Standard imports
28
+ import copy
29
+ import logging
30
+ import math
31
+ import os
32
+ from typing import Dict, Tuple
33
+
34
+ # Third party imports
35
+ import numpy as np
36
+ import pandora
37
+ import xarray as xr
38
+ from json_checker import And, Checker, Or
39
+ from pandora.check_configuration import check_pipeline_section
40
+ from pandora.img_tools import add_global_disparity
41
+ from pandora.state_machine import PandoraMachine
42
+ from rasterio.profiles import DefaultGTiffProfile
43
+ from scipy.ndimage import maximum_filter, minimum_filter
44
+
45
+ import cars.applications.dense_matching.dense_matching_constants as dm_cst
46
+ import cars.orchestrator.orchestrator as ocht
47
+ from cars.applications import application_constants
48
+ from cars.applications.dense_matching import dense_matching_algo as dm_algo
49
+ from cars.applications.dense_matching import (
50
+ dense_matching_wrappers as dm_wrappers,
51
+ )
52
+ from cars.applications.dense_matching.abstract_dense_matching_app import (
53
+ DenseMatching,
54
+ )
55
+ from cars.applications.dense_matching.disparity_grid_algo import (
56
+ generate_disp_range_const_tile_wrapper,
57
+ generate_disp_range_from_dem_wrapper,
58
+ )
59
+ from cars.applications.dense_matching.loaders.pandora_loader import (
60
+ PandoraLoader,
61
+ )
62
+
63
+ # CARS imports
64
+ from cars.core import constants as cst
65
+ from cars.core import constants_disparity as cst_disp
66
+ from cars.core import inputs, tiling
67
+ from cars.core.utils import safe_makedirs
68
+ from cars.data_structures import cars_dataset, format_transformation
69
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
70
+
71
+
72
+ class CensusMccnnSgm(
73
+ DenseMatching,
74
+ short_name=[
75
+ "census_sgm_default",
76
+ "census_sgm_urban",
77
+ "census_sgm_shadow",
78
+ "census_sgm_mountain_and_vegetation",
79
+ "census_sgm_homogeneous",
80
+ "mccnn_sgm",
81
+ "auto",
82
+ "custom",
83
+ ],
84
+ ): # pylint: disable=R0903,disable=R0902,disable=C0302
85
+ """
86
+ Census SGM & MCCNN SGM matching class
87
+ """
88
+
89
+ def __init__(self, conf=None):
90
+ """
91
+ Init function of DenseMatching
92
+
93
+ :param conf: configuration for matching
94
+ :return: an application_to_use object
95
+ """
96
+
97
+ super().__init__(conf=conf)
98
+
99
+ # check conf
100
+ self.used_method = self.used_config["method"]
101
+ self.min_epi_tile_size = self.used_config["min_epi_tile_size"]
102
+ self.max_epi_tile_size = self.used_config["max_epi_tile_size"]
103
+ self.epipolar_tile_margin_in_percent = self.used_config[
104
+ "epipolar_tile_margin_in_percent"
105
+ ]
106
+ self.min_elevation_offset = self.used_config["min_elevation_offset"]
107
+ self.max_elevation_offset = self.used_config["max_elevation_offset"]
108
+
109
+ # Disparity threshold
110
+ self.disp_min_threshold = self.used_config["disp_min_threshold"]
111
+ self.disp_max_threshold = self.used_config["disp_max_threshold"]
112
+
113
+ # Ambiguity
114
+ self.generate_ambiguity = self.used_config["generate_ambiguity"]
115
+
116
+ self.classification_fusion_margin = self.used_config[
117
+ "classification_fusion_margin"
118
+ ]
119
+
120
+ # Margins computation parameters
121
+ # Use local disp
122
+ self.use_global_disp_range = self.used_config["use_global_disp_range"]
123
+ self.local_disp_grid_step = self.used_config["local_disp_grid_step"]
124
+ self.disp_range_propagation_filter_size = self.used_config[
125
+ "disp_range_propagation_filter_size"
126
+ ]
127
+ self.epi_disp_grid_tile_size = self.used_config[
128
+ "epi_disp_grid_tile_size"
129
+ ]
130
+ self.use_cross_validation = self.used_config["use_cross_validation"]
131
+ self.denoise_disparity_map = self.used_config["denoise_disparity_map"]
132
+ self.required_bands = self.used_config["required_bands"]
133
+ self.used_band = self.used_config["used_band"]
134
+
135
+ # Saving files
136
+ self.save_intermediate_data = self.used_config["save_intermediate_data"]
137
+
138
+ self.confidence_filtering = self.used_config["confidence_filtering"]
139
+ self.threshold_disp_range_to_borders = self.used_config[
140
+ "threshold_disp_range_to_borders"
141
+ ]
142
+ self.filter_incomplete_disparity_range = self.used_config[
143
+ "filter_incomplete_disparity_range"
144
+ ]
145
+
146
+ # init orchestrator
147
+ self.orchestrator = None
148
+
149
+ def get_performance_map_parameters(self):
150
+ """
151
+ Get parameter linked to performance, that will be used in triangulation
152
+
153
+ :return: parameters to use
154
+ :type: dict
155
+ """
156
+
157
+ return {
158
+ "performance_map_method": self.used_config[
159
+ "performance_map_method"
160
+ ],
161
+ "perf_ambiguity_threshold": self.used_config[
162
+ "perf_ambiguity_threshold"
163
+ ],
164
+ }
165
+
166
+ def check_conf(self, conf):
167
+ """
168
+ Check configuration
169
+
170
+ :param conf: configuration to check
171
+ :type conf: dict
172
+ :return: overloaded configuration
173
+ :rtype: dict
174
+
175
+ """
176
+ # init conf
177
+ if conf is not None:
178
+ overloaded_conf = conf.copy()
179
+ else:
180
+ conf = {}
181
+ overloaded_conf = {}
182
+
183
+ # Overload conf
184
+ # check loader
185
+ loader_conf = conf.get("loader_conf", None)
186
+ default_method = "auto"
187
+ if loader_conf is not None:
188
+ default_method = "custom"
189
+ loader = conf.get("loader", "pandora")
190
+ overloaded_conf["method"] = conf.get(
191
+ "method", default_method
192
+ ) # change it if census_sgm is not default
193
+ if overloaded_conf["method"] == "auto" and loader_conf is not None:
194
+ raise RuntimeError(
195
+ "It's not possible to use auto method with custom "
196
+ "loader configuration"
197
+ )
198
+
199
+ # method called in abstract_dense_matching_app.py
200
+ overloaded_conf["min_epi_tile_size"] = conf.get(
201
+ "min_epi_tile_size", 300
202
+ )
203
+ overloaded_conf["max_epi_tile_size"] = conf.get(
204
+ "max_epi_tile_size", 1500
205
+ )
206
+ overloaded_conf["epipolar_tile_margin_in_percent"] = conf.get(
207
+ "epipolar_tile_margin_in_percent", 60
208
+ )
209
+
210
+ overloaded_conf["classification_fusion_margin"] = conf.get(
211
+ "classification_fusion_margin", -1
212
+ )
213
+ overloaded_conf["min_elevation_offset"] = conf.get(
214
+ "min_elevation_offset", None
215
+ )
216
+ overloaded_conf["max_elevation_offset"] = conf.get(
217
+ "max_elevation_offset", None
218
+ )
219
+ overloaded_conf["denoise_disparity_map"] = conf.get(
220
+ "denoise_disparity_map", False
221
+ )
222
+
223
+ # confidence filtering parameters
224
+ overloaded_conf["confidence_filtering"] = conf.get(
225
+ "confidence_filtering", {}
226
+ )
227
+
228
+ # Disparity threshold
229
+ overloaded_conf["disp_min_threshold"] = conf.get(
230
+ "disp_min_threshold", None
231
+ )
232
+ overloaded_conf["disp_max_threshold"] = conf.get(
233
+ "disp_max_threshold", None
234
+ )
235
+
236
+ overloaded_conf["perf_eta_max_ambiguity"] = conf.get(
237
+ "perf_eta_max_ambiguity", 0.99
238
+ )
239
+ overloaded_conf["perf_eta_max_risk"] = conf.get(
240
+ "perf_eta_max_risk", 0.25
241
+ )
242
+ overloaded_conf["perf_eta_step"] = conf.get("perf_eta_step", 0.04)
243
+ overloaded_conf["perf_ambiguity_threshold"] = conf.get(
244
+ "perf_ambiguity_threshold", 0.6
245
+ )
246
+ overloaded_conf["use_cross_validation"] = conf.get(
247
+ "use_cross_validation", True
248
+ )
249
+ # Margins computation parameters
250
+ overloaded_conf["use_global_disp_range"] = conf.get(
251
+ "use_global_disp_range", False
252
+ )
253
+ overloaded_conf["local_disp_grid_step"] = conf.get(
254
+ "local_disp_grid_step", 10
255
+ )
256
+ overloaded_conf["disp_range_propagation_filter_size"] = conf.get(
257
+ "disp_range_propagation_filter_size", 50
258
+ )
259
+ overloaded_conf["epi_disp_grid_tile_size"] = conf.get(
260
+ "epi_disp_grid_tile_size", 800
261
+ )
262
+ overloaded_conf["required_bands"] = conf.get("required_bands", ["b0"])
263
+ overloaded_conf["used_band"] = conf.get("used_band", "b0")
264
+ overloaded_conf["threshold_disp_range_to_borders"] = conf.get(
265
+ "threshold_disp_range_to_borders", False
266
+ )
267
+ overloaded_conf["filter_incomplete_disparity_range"] = conf.get(
268
+ "filter_incomplete_disparity_range", True
269
+ )
270
+
271
+ # Saving files
272
+ overloaded_conf["save_intermediate_data"] = conf.get(
273
+ "save_intermediate_data", False
274
+ )
275
+
276
+ # Permormance map parameters
277
+ overloaded_conf["generate_ambiguity"] = conf.get(
278
+ "generate_ambiguity",
279
+ overloaded_conf["save_intermediate_data"],
280
+ )
281
+
282
+ # Permormance map parameters
283
+ default_perf_map_method = None
284
+ if overloaded_conf["save_intermediate_data"]:
285
+ default_perf_map_method = "risk"
286
+ overloaded_conf["performance_map_method"] = conf.get(
287
+ "performance_map_method",
288
+ default_perf_map_method,
289
+ )
290
+
291
+ # Get performance map method
292
+ perf_map_method = overloaded_conf["performance_map_method"]
293
+ if isinstance(overloaded_conf["performance_map_method"], str):
294
+ perf_map_method = [perf_map_method]
295
+ elif perf_map_method is None:
296
+ perf_map_method = []
297
+
298
+ overloaded_conf["performance_map_method"] = perf_map_method
299
+
300
+ if overloaded_conf["use_cross_validation"] is True:
301
+ overloaded_conf["use_cross_validation"] = "fast"
302
+
303
+ # TODO modify, use loader directly
304
+ logger = logging.getLogger("transitions.core")
305
+ logger.addFilter(
306
+ lambda record: "to model due to model override policy"
307
+ not in record.getMessage()
308
+ )
309
+ pandora_loader = PandoraLoader(
310
+ conf=loader_conf,
311
+ method_name=overloaded_conf["method"],
312
+ generate_performance_map_from_risk="risk" in perf_map_method,
313
+ generate_performance_map_from_intervals="intervals"
314
+ in perf_map_method,
315
+ generate_ambiguity=overloaded_conf["generate_ambiguity"],
316
+ perf_eta_max_ambiguity=overloaded_conf["perf_eta_max_ambiguity"],
317
+ perf_eta_max_risk=overloaded_conf["perf_eta_max_risk"],
318
+ perf_eta_step=overloaded_conf["perf_eta_step"],
319
+ use_cross_validation=overloaded_conf["use_cross_validation"],
320
+ denoise_disparity_map=overloaded_conf["denoise_disparity_map"],
321
+ used_band=overloaded_conf["used_band"],
322
+ )
323
+
324
+ overloaded_conf["loader"] = loader
325
+
326
+ # Get params from loader
327
+ self.loader = pandora_loader
328
+ self.corr_config = collections.OrderedDict(pandora_loader.get_conf())
329
+ # Instantiate margins from pandora check conf
330
+ # create the dataset
331
+ classif_bands = pandora_loader.get_classif_bands()
332
+ fake_dataset = xr.Dataset(
333
+ data_vars={
334
+ "image": (["row", "col"], np.zeros((10, 10))),
335
+ "classif": (
336
+ ["row", "col", "band_classif"],
337
+ np.zeros((10, 10, len(classif_bands)), dtype=np.int32),
338
+ ),
339
+ },
340
+ coords={
341
+ "band_im": [overloaded_conf["used_band"]],
342
+ "band_classif": classif_bands,
343
+ "row": np.arange(10),
344
+ "col": np.arange(10),
345
+ },
346
+ attrs={"disparity_source": [-1, 1]},
347
+ )
348
+ # Import plugins before checking configuration
349
+ pandora.import_plugin()
350
+ pandora_machine = PandoraMachine()
351
+ corr_config_pipeline = {"pipeline": dict(self.corr_config["pipeline"])}
352
+ saved_schema = copy.deepcopy(
353
+ pandora.matching_cost.matching_cost.AbstractMatchingCost.schema
354
+ )
355
+ _ = check_pipeline_section(
356
+ corr_config_pipeline, fake_dataset, fake_dataset, pandora_machine
357
+ )
358
+ # quick fix to remove when the problem is solved in pandora
359
+ pandora.matching_cost.matching_cost.AbstractMatchingCost.schema = (
360
+ saved_schema
361
+ )
362
+ self.margins = pandora_machine.margins.global_margins
363
+
364
+ if overloaded_conf["method"] != "auto":
365
+ # not final configuration
366
+ overloaded_conf["loader_conf"] = self.corr_config
367
+ else:
368
+ overloaded_conf["loader_conf"] = None
369
+
370
+ application_schema = {
371
+ "method": str,
372
+ "min_epi_tile_size": And(int, lambda x: x > 0),
373
+ "max_epi_tile_size": And(int, lambda x: x > 0),
374
+ "epipolar_tile_margin_in_percent": int,
375
+ "min_elevation_offset": Or(None, int),
376
+ "max_elevation_offset": Or(None, int),
377
+ "disp_min_threshold": Or(None, int),
378
+ "disp_max_threshold": Or(None, int),
379
+ "save_intermediate_data": bool,
380
+ "generate_ambiguity": bool,
381
+ "performance_map_method": And(
382
+ list,
383
+ lambda x: all(y in ["risk", "intervals"] for y in x),
384
+ ),
385
+ "perf_eta_max_ambiguity": float,
386
+ "perf_eta_max_risk": float,
387
+ "perf_eta_step": float,
388
+ "classification_fusion_margin": int,
389
+ "perf_ambiguity_threshold": float,
390
+ "use_cross_validation": Or(bool, str),
391
+ "denoise_disparity_map": bool,
392
+ "use_global_disp_range": bool,
393
+ "local_disp_grid_step": int,
394
+ "disp_range_propagation_filter_size": And(
395
+ Or(int, float), lambda x: x >= 0
396
+ ),
397
+ "epi_disp_grid_tile_size": int,
398
+ "required_bands": [str],
399
+ "used_band": str,
400
+ "loader_conf": Or(dict, collections.OrderedDict, str, None),
401
+ "loader": str,
402
+ "confidence_filtering": dict,
403
+ "threshold_disp_range_to_borders": bool,
404
+ "filter_incomplete_disparity_range": bool,
405
+ }
406
+
407
+ # Check conf
408
+ checker = Checker(application_schema)
409
+ checker.validate(overloaded_conf)
410
+
411
+ self.check_conf_confidence_filtering(overloaded_conf)
412
+
413
+ # Check consistency between bounds for optimal tile size search
414
+ min_epi_tile_size = overloaded_conf["min_epi_tile_size"]
415
+ max_epi_tile_size = overloaded_conf["max_epi_tile_size"]
416
+ if min_epi_tile_size > max_epi_tile_size:
417
+ raise ValueError(
418
+ "Maximal tile size should be bigger than "
419
+ "minimal tile size for optimal tile size search"
420
+ )
421
+
422
+ # Check consistency between bounds for elevation offset
423
+ min_elevation_offset = overloaded_conf["min_elevation_offset"]
424
+ max_elevation_offset = overloaded_conf["max_elevation_offset"]
425
+ if (
426
+ min_elevation_offset is not None
427
+ and max_elevation_offset is not None
428
+ and min_elevation_offset > max_elevation_offset
429
+ ):
430
+ raise ValueError(
431
+ "Maximal elevation should be bigger than "
432
+ "minimal elevation for dense matching"
433
+ )
434
+
435
+ disp_min_threshold = overloaded_conf["disp_min_threshold"]
436
+ disp_max_threshold = overloaded_conf["disp_max_threshold"]
437
+ if (
438
+ disp_min_threshold is not None
439
+ and disp_max_threshold is not None
440
+ and disp_min_threshold > disp_max_threshold
441
+ ):
442
+ raise ValueError(
443
+ "Maximal disparity should be bigger than "
444
+ "minimal disparity for dense matching"
445
+ )
446
+ return overloaded_conf
447
+
448
+ def check_conf_confidence_filtering(self, overloaded_conf):
449
+ """
450
+ Check the confidence filtering conf
451
+ """
452
+ overloaded_conf["confidence_filtering"]["activated"] = overloaded_conf[
453
+ "confidence_filtering"
454
+ ].get("activated", True)
455
+ overloaded_conf["confidence_filtering"]["bounds_ratio_threshold"] = (
456
+ overloaded_conf["confidence_filtering"].get(
457
+ "bounds_ratio_threshold", 0.2
458
+ )
459
+ )
460
+ overloaded_conf["confidence_filtering"]["risk_ratio_threshold"] = (
461
+ overloaded_conf["confidence_filtering"].get(
462
+ "risk_ratio_threshold", 0.75
463
+ )
464
+ )
465
+ overloaded_conf["confidence_filtering"]["bounds_range_threshold"] = (
466
+ overloaded_conf["confidence_filtering"].get(
467
+ "bounds_range_threshold", 3
468
+ )
469
+ )
470
+ overloaded_conf["confidence_filtering"]["risk_range_threshold"] = (
471
+ overloaded_conf["confidence_filtering"].get(
472
+ "risk_range_threshold", 9
473
+ )
474
+ )
475
+ overloaded_conf["confidence_filtering"]["nan_threshold"] = (
476
+ overloaded_conf["confidence_filtering"].get("nan_threshold", 0.2)
477
+ )
478
+ overloaded_conf["confidence_filtering"]["win_nanratio"] = (
479
+ overloaded_conf["confidence_filtering"].get("win_nanratio", 20)
480
+ )
481
+
482
+ confidence_filtering_schema = {
483
+ "activated": bool,
484
+ "bounds_ratio_threshold": float,
485
+ "risk_ratio_threshold": float,
486
+ "bounds_range_threshold": int,
487
+ "risk_range_threshold": int,
488
+ "nan_threshold": float,
489
+ "win_nanratio": int,
490
+ }
491
+
492
+ checker_confidence_filtering_schema = Checker(
493
+ confidence_filtering_schema
494
+ )
495
+ checker_confidence_filtering_schema.validate(
496
+ overloaded_conf["confidence_filtering"]
497
+ )
498
+
499
+ @cars_profile(name="Get margin fun")
500
+ def get_margins_fun(self, grid_left, disp_range_grid):
501
+ """
502
+ Get Margins function that generates margins needed by
503
+ matching method, to use during resampling
504
+
505
+ :param grid_left: left epipolar grid
506
+ :type grid_left: dict
507
+ :param disp_range_grid: minimum and maximum disparity grid
508
+ :return: function that generates margin for given roi
509
+
510
+ """
511
+
512
+ disp_min_grid_arr, _ = inputs.rasterio_read_as_array(
513
+ disp_range_grid["grid_min_path"]
514
+ )
515
+ disp_max_grid_arr, _ = inputs.rasterio_read_as_array(
516
+ disp_range_grid["grid_max_path"]
517
+ )
518
+ step_row = disp_range_grid["step_row"]
519
+ step_col = disp_range_grid["step_col"]
520
+ row_range = disp_range_grid["row_range"]
521
+ col_range = disp_range_grid["col_range"]
522
+
523
+ # get disp_to_alt_ratio
524
+ disp_to_alt_ratio = grid_left["disp_to_alt_ratio"]
525
+
526
+ # Check if we need to override disp_min
527
+ if self.min_elevation_offset is not None:
528
+ user_disp_min = self.min_elevation_offset / disp_to_alt_ratio
529
+ if np.any(disp_min_grid_arr < user_disp_min):
530
+ logging.warning(
531
+ (
532
+ "Overridden disparity minimum "
533
+ "= {:.3f} pix. (= {:.3f} m.) "
534
+ "is greater than disparity minimum estimated "
535
+ "in prepare step "
536
+ "for current pair"
537
+ ).format(
538
+ user_disp_min,
539
+ self.min_elevation_offset,
540
+ )
541
+ )
542
+ disp_min_grid_arr[:, :] = user_disp_min
543
+
544
+ # Check if we need to override disp_max
545
+ if self.max_elevation_offset is not None:
546
+ user_disp_max = self.max_elevation_offset / disp_to_alt_ratio
547
+ if np.any(disp_max_grid_arr > user_disp_max):
548
+ logging.warning(
549
+ (
550
+ "Overridden disparity maximum "
551
+ "= {:.3f} pix. (or {:.3f} m.) "
552
+ "is lower than disparity maximum estimated "
553
+ "in prepare step "
554
+ "for current pair"
555
+ ).format(
556
+ user_disp_max,
557
+ self.max_elevation_offset,
558
+ )
559
+ )
560
+ disp_max_grid_arr[:, :] = user_disp_max
561
+
562
+ # Compute global range of logging
563
+ disp_min_global = np.min(disp_min_grid_arr)
564
+ disp_max_global = np.max(disp_max_grid_arr)
565
+
566
+ logging.info(
567
+ "Global Disparity range for current pair: "
568
+ "[{:.3f} pix., {:.3f} pix.] "
569
+ "(or [{:.3f} m., {:.3f} m.])".format(
570
+ disp_min_global,
571
+ disp_max_global,
572
+ disp_min_global * disp_to_alt_ratio,
573
+ disp_max_global * disp_to_alt_ratio,
574
+ )
575
+ )
576
+
577
+ def margins_wrapper(row_min, row_max, col_min, col_max):
578
+ """
579
+ Generates margins Dataset used in resampling
580
+
581
+ :param row_min: row min
582
+ :param row_max: row max
583
+ :param col_min: col min
584
+ :param col_max: col max
585
+
586
+ :return: margins
587
+ :rtype: xr.Dataset
588
+ """
589
+
590
+ assert row_min < row_max
591
+ assert col_min < col_max
592
+
593
+ # Get region in grid
594
+
595
+ grid_row_min = max(0, int(np.floor((row_min - 1) / step_row)) - 1)
596
+ grid_row_max = min(
597
+ len(row_range), int(np.ceil((row_max + 1) / step_row) + 1)
598
+ )
599
+ grid_col_min = max(0, int(np.floor((col_min - 1) / step_col)) - 1)
600
+ grid_col_max = min(
601
+ len(col_range), int(np.ceil((col_max + 1) / step_col)) + 1
602
+ )
603
+
604
+ # Compute disp min and max in row
605
+ disp_min = np.min(
606
+ disp_min_grid_arr[
607
+ grid_row_min:grid_row_max, grid_col_min:grid_col_max
608
+ ]
609
+ )
610
+ disp_max = np.max(
611
+ disp_max_grid_arr[
612
+ grid_row_min:grid_row_max, grid_col_min:grid_col_max
613
+ ]
614
+ )
615
+ # round disp min and max
616
+ disp_min = int(math.floor(disp_min))
617
+ disp_max = int(math.ceil(disp_max))
618
+
619
+ # Compute margins for the correlator
620
+ margins = dm_wrappers.get_margins(self.margins, disp_min, disp_max)
621
+
622
+ return margins
623
+
624
+ return margins_wrapper
625
+
626
+ def get_method(self):
627
+ """
628
+ return the method that will be used in dense_matching
629
+ """
630
+
631
+ return self.used_method
632
+
633
+ @cars_profile(name="Optimal size estimation")
634
+ def get_optimal_tile_size(self, disp_range_grid, max_ram_per_worker):
635
+ """
636
+ Get the optimal tile size to use during dense matching.
637
+
638
+ :param disp_range_grid: minimum and maximum disparity grid
639
+ :param max_ram_per_worker: maximum ram per worker
640
+ :return: optimal tile size
641
+
642
+ """
643
+
644
+ disp_min_grids, _ = inputs.rasterio_read_as_array(
645
+ disp_range_grid["grid_min_path"]
646
+ )
647
+ disp_max_grids, _ = inputs.rasterio_read_as_array(
648
+ disp_range_grid["grid_max_path"]
649
+ )
650
+
651
+ # use max tile size as overlap for min and max:
652
+ # max Point to point diff is less than diff of tile
653
+
654
+ # use filter of size max_epi_tile_size
655
+ overlap = 3 * int(self.max_epi_tile_size / self.local_disp_grid_step)
656
+
657
+ disp_min_grids = minimum_filter(
658
+ disp_min_grids, size=[overlap, overlap], mode="nearest"
659
+ )
660
+ disp_max_grids = maximum_filter(
661
+ disp_max_grids, size=[overlap, overlap], mode="nearest"
662
+ )
663
+
664
+ # Worst cases scenario:
665
+ # 1: [global max - max diff, global max]
666
+ # 2: [global min, global min max diff]
667
+
668
+ max_diff = np.round(np.nanmax(disp_max_grids - disp_min_grids)) + 1
669
+ global_min = np.floor(np.nanmin(disp_min_grids))
670
+ global_max = np.ceil(np.nanmax(disp_max_grids))
671
+
672
+ # Get tiling param
673
+ opt_epipolar_tile_size_1 = (
674
+ dm_wrappers.optimal_tile_size_pandora_plugin_libsgm(
675
+ global_min,
676
+ global_min + max_diff,
677
+ self.min_epi_tile_size,
678
+ self.max_epi_tile_size,
679
+ max_ram_per_worker,
680
+ margin=self.epipolar_tile_margin_in_percent,
681
+ )
682
+ )
683
+ opt_epipolar_tile_size_2 = (
684
+ dm_wrappers.optimal_tile_size_pandora_plugin_libsgm(
685
+ global_max - max_diff,
686
+ global_max,
687
+ self.min_epi_tile_size,
688
+ self.max_epi_tile_size,
689
+ max_ram_per_worker,
690
+ margin=self.epipolar_tile_margin_in_percent,
691
+ )
692
+ )
693
+
694
+ # return worst case
695
+ opt_epipolar_tile_size = min(
696
+ opt_epipolar_tile_size_1, opt_epipolar_tile_size_2
697
+ )
698
+
699
+ # Define function to compute local optimal size for each tile
700
+ def local_tile_optimal_size_fun(local_disp_min, local_disp_max):
701
+ """
702
+ Compute optimal tile size for tile
703
+
704
+ :return: local tile size, global optimal tile sizes
705
+
706
+ """
707
+ local_opt_tile_size = (
708
+ dm_wrappers.optimal_tile_size_pandora_plugin_libsgm(
709
+ local_disp_min,
710
+ local_disp_max,
711
+ 0,
712
+ 20000, # arbitrary
713
+ max_ram_per_worker,
714
+ margin=self.epipolar_tile_margin_in_percent,
715
+ )
716
+ )
717
+
718
+ # Get max range to use with current optimal size
719
+ max_range = dm_wrappers.get_max_disp_from_opt_tile_size(
720
+ opt_epipolar_tile_size,
721
+ max_ram_per_worker,
722
+ margin=self.epipolar_tile_margin_in_percent,
723
+ used_disparity_range=(local_disp_max - local_disp_min),
724
+ )
725
+
726
+ return local_opt_tile_size, opt_epipolar_tile_size, max_range
727
+
728
+ return opt_epipolar_tile_size, local_tile_optimal_size_fun
729
+
730
+ def get_required_bands(self):
731
+ """
732
+ Get bands required by this application
733
+
734
+ :return: required bands for left and right image
735
+ :rtype: dict
736
+ """
737
+ required_bands = {}
738
+ required_bands["left"] = self.required_bands
739
+ required_bands["right"] = self.required_bands
740
+ return required_bands
741
+
742
+ # pylint: disable=too-many-positional-arguments
743
+ @cars_profile(name="Disp Grid Generation")
744
+ def generate_disparity_grids( # noqa: C901
745
+ self,
746
+ sensor_image_right,
747
+ grid_right,
748
+ geom_plugin_with_dem_and_geoid,
749
+ dmin=None,
750
+ dmax=None,
751
+ dem_median=None,
752
+ dem_min=None,
753
+ dem_max=None,
754
+ pair_folder=None,
755
+ orchestrator=None,
756
+ ):
757
+ """
758
+ Generate disparity grids min and max, with given step
759
+
760
+ global mode: uses dmin and dmax
761
+ local mode: uses dems
762
+
763
+
764
+ :param sensor_image_right: sensor image right
765
+ :type sensor_image_right: dict
766
+ :param grid_right: right epipolar grid
767
+ :type grid_right: dict
768
+ :param geom_plugin_with_dem_and_geoid: geometry plugin with dem mean
769
+ used to generate epipolar grids
770
+ :type geom_plugin_with_dem_and_geoid: GeometryPlugin
771
+ :param dmin: minimum disparity
772
+ :type dmin: float
773
+ :param dmax: maximum disparity
774
+ :type dmax: float
775
+ :param dem_median: path to median dem
776
+ :type dem_median: str
777
+ :param dem_min: path to minimum dem
778
+ :type dem_min: str
779
+ :param dem_max: path to maximum dem
780
+ :type dem_max: str
781
+ :param pair_folder: folder used for current pair
782
+ :type pair_folder: str
783
+ :param orchestrator: orchestrator
784
+ :type orchestrator: Orchestrator
785
+
786
+
787
+ :return disparity grid range, containing grid min and max
788
+ :rtype: CarsDataset
789
+ """
790
+
791
+ # Default orchestrator
792
+ if orchestrator is None:
793
+ # Create default sequential orchestrator for current application
794
+ # be aware, no out_json will be shared between orchestrators
795
+ # No files saved
796
+ self.orchestrator = ocht.Orchestrator(
797
+ orchestrator_conf={"mode": "sequential"}
798
+ )
799
+ else:
800
+ self.orchestrator = orchestrator
801
+
802
+ epi_size_row = grid_right["epipolar_size_y"]
803
+ epi_size_col = grid_right["epipolar_size_x"]
804
+ disp_to_alt_ratio = grid_right["disp_to_alt_ratio"]
805
+
806
+ # Generate grid array
807
+ nb_rows = int(epi_size_row / self.local_disp_grid_step) + 1
808
+ nb_cols = int(epi_size_col / self.local_disp_grid_step) + 1
809
+ row_range, step_row = np.linspace(
810
+ 0, epi_size_row, nb_rows, retstep=True
811
+ )
812
+ col_range, step_col = np.linspace(
813
+ 0, epi_size_col, nb_cols, retstep=True
814
+ )
815
+
816
+ # Create CarsDataset
817
+ grid_disp_range = cars_dataset.CarsDataset(
818
+ "arrays", name="grid_disp_range_unknown_pair"
819
+ )
820
+ global_infos_cars_ds = cars_dataset.CarsDataset("dict")
821
+
822
+ # Generate profile
823
+ raster_profile = DefaultGTiffProfile(count=1)
824
+
825
+ # saving infos
826
+ # disp grids
827
+
828
+ if self.save_intermediate_data:
829
+ grid_min_path = os.path.join(pair_folder, "disp_min_grid.tif")
830
+ grid_max_path = os.path.join(pair_folder, "disp_max_grid.tif")
831
+ safe_makedirs(pair_folder)
832
+ else:
833
+ if pair_folder is None:
834
+ tmp_folder = os.path.join(self.orchestrator.out_dir, "tmp")
835
+ else:
836
+ tmp_folder = os.path.join(pair_folder, "tmp")
837
+ safe_makedirs(tmp_folder)
838
+ self.orchestrator.add_to_clean(tmp_folder)
839
+ grid_min_path = os.path.join(tmp_folder, "disp_min_grid.tif")
840
+ grid_max_path = os.path.join(tmp_folder, "disp_max_grid.tif")
841
+
842
+ if None not in (dmin, dmax):
843
+ # use global disparity range
844
+ if None not in (dem_min, dem_max):
845
+ raise RuntimeError("Mix between local and global mode")
846
+
847
+ # Only one tile
848
+ grid_disp_range.tiling_grid = np.array([[[0, nb_rows, 0, nb_cols]]])
849
+
850
+ elif None not in (dem_min, dem_max, dem_median):
851
+
852
+ # Generate multiple tiles
853
+ grid_tile_size = self.epi_disp_grid_tile_size
854
+ grid_disp_range.tiling_grid = tiling.generate_tiling_grid(
855
+ 0,
856
+ 0,
857
+ nb_rows,
858
+ nb_cols,
859
+ grid_tile_size,
860
+ grid_tile_size,
861
+ )
862
+
863
+ # add tiling of global_infos_cars_ds
864
+ global_infos_cars_ds.tiling_grid = grid_disp_range.tiling_grid
865
+ self.orchestrator.add_to_replace_lists(
866
+ global_infos_cars_ds,
867
+ cars_ds_name="global infos",
868
+ )
869
+
870
+ self.orchestrator.add_to_save_lists(
871
+ grid_min_path,
872
+ dm_cst.DISP_MIN_GRID,
873
+ grid_disp_range,
874
+ dtype=np.float32,
875
+ nodata=0,
876
+ cars_ds_name="disp_min_grid",
877
+ )
878
+
879
+ self.orchestrator.add_to_save_lists(
880
+ grid_max_path,
881
+ dm_cst.DISP_MAX_GRID,
882
+ grid_disp_range,
883
+ dtype=np.float32,
884
+ nodata=0,
885
+ cars_ds_name="disp_max_grid",
886
+ )
887
+ [saving_info] = ( # pylint: disable=unbalanced-tuple-unpacking
888
+ self.orchestrator.get_saving_infos([grid_disp_range])
889
+ )
890
+ [saving_info_global_infos] = self.orchestrator.get_saving_infos(
891
+ [global_infos_cars_ds]
892
+ )
893
+
894
+ # Generate grids on dict format
895
+ grid_disp_range_dict = {
896
+ "grid_min_path": grid_min_path,
897
+ "grid_max_path": grid_max_path,
898
+ "global_min": None,
899
+ "global_max": None,
900
+ "step_row": step_row,
901
+ "step_col": step_col,
902
+ "row_range": row_range,
903
+ "col_range": col_range,
904
+ }
905
+
906
+ if None not in (dmin, dmax):
907
+ # use global disparity range
908
+ if None not in (dem_min, dem_max):
909
+ raise RuntimeError("Mix between local and global mode")
910
+
911
+ saving_info_global_infos_full = ocht.update_saving_infos(
912
+ saving_info_global_infos, row=0, col=0
913
+ )
914
+ saving_info_full = ocht.update_saving_infos(
915
+ saving_info, row=0, col=0
916
+ )
917
+
918
+ (
919
+ grid_disp_range[0, 0],
920
+ global_infos_cars_ds[0, 0],
921
+ ) = self.orchestrator.cluster.create_task(
922
+ generate_disp_range_const_tile_wrapper, nout=2
923
+ )(
924
+ row_range,
925
+ col_range,
926
+ dmin,
927
+ dmax,
928
+ raster_profile,
929
+ saving_info_full,
930
+ saving_info_global_infos_full,
931
+ )
932
+
933
+ elif None not in (dem_min, dem_max, dem_median):
934
+
935
+ # use filter to propagate min and max
936
+ filter_overlap = (
937
+ 2
938
+ * int(
939
+ self.disp_range_propagation_filter_size
940
+ / self.local_disp_grid_step
941
+ )
942
+ + 1
943
+ )
944
+
945
+ for col in range(grid_disp_range.shape[1]):
946
+ for row in range(grid_disp_range.shape[0]):
947
+ # update saving infos for potential replacement
948
+ full_saving_info = ocht.update_saving_infos(
949
+ saving_info, row=row, col=col
950
+ )
951
+ saving_info_global_infos_full = ocht.update_saving_infos(
952
+ saving_info_global_infos, row=row, col=col
953
+ )
954
+ array_window = grid_disp_range.get_window_as_dict(row, col)
955
+ (
956
+ grid_disp_range[row, col],
957
+ global_infos_cars_ds[row, col],
958
+ ) = self.orchestrator.cluster.create_task(
959
+ generate_disp_range_from_dem_wrapper, nout=2
960
+ )(
961
+ array_window,
962
+ row_range,
963
+ col_range,
964
+ sensor_image_right,
965
+ grid_right,
966
+ geom_plugin_with_dem_and_geoid,
967
+ dem_median,
968
+ dem_min,
969
+ dem_max,
970
+ raster_profile,
971
+ full_saving_info,
972
+ saving_info_global_infos_full,
973
+ filter_overlap,
974
+ disp_to_alt_ratio,
975
+ disp_min_threshold=self.disp_min_threshold,
976
+ disp_max_threshold=self.disp_max_threshold,
977
+ )
978
+
979
+ # Compute grid
980
+ self.orchestrator.breakpoint()
981
+
982
+ # Fill global infos
983
+ mins, maxs = [], []
984
+ for row in range(global_infos_cars_ds.shape[0]):
985
+ for col in range(global_infos_cars_ds.shape[1]):
986
+ try:
987
+ dict_data = global_infos_cars_ds[row, col].data
988
+ mins.append(dict_data["global_min"])
989
+ maxs.append(dict_data["global_max"])
990
+ except Exception:
991
+ logging.info(
992
+ "Tile {} {} not computed in epi disp range"
993
+ " generation".format(row, col)
994
+ )
995
+ grid_disp_range_dict["global_min"] = np.floor(np.nanmin(mins))
996
+ grid_disp_range_dict["global_max"] = np.ceil(np.nanmax(maxs))
997
+
998
+ return grid_disp_range_dict
999
+
1000
+ def run( # pylint: disable=too-many-positional-arguments
1001
+ self,
1002
+ epipolar_images_left,
1003
+ epipolar_images_right,
1004
+ local_tile_optimal_size_fun,
1005
+ orchestrator=None,
1006
+ pair_folder=None,
1007
+ pair_key="PAIR_0",
1008
+ disp_range_grid=None,
1009
+ compute_disparity_masks=False,
1010
+ margins_to_keep=0,
1011
+ texture_bands=None,
1012
+ classif_bands_to_mask=None,
1013
+ ):
1014
+ """
1015
+ Run Matching application.
1016
+
1017
+ Create CarsDataset filled with xarray.Dataset, corresponding
1018
+ to epipolar disparities, on the same geometry than
1019
+ epipolar_images_left.
1020
+
1021
+ :param epipolar_images_left: tiled left epipolar CarsDataset contains:
1022
+
1023
+ - N x M Delayed tiles. \
1024
+ Each tile will be a future xarray Dataset containing:
1025
+
1026
+ - data with keys : "im", "msk", "texture"
1027
+ - attrs with keys: "margins" with "disp_min" and "disp_max"\
1028
+ "transform", "crs", "valid_pixels", "no_data_mask",\
1029
+ "no_data_img"
1030
+ - attributes containing:
1031
+ "largest_epipolar_region","opt_epipolar_tile_size"
1032
+ :type epipolar_images_left: CarsDataset
1033
+ :param epipolar_images_right: tiled right epipolar CarsDataset contains:
1034
+
1035
+ - N x M Delayed tiles. \
1036
+ Each tile will be a future xarray Dataset containing:
1037
+
1038
+ - data with keys : "im", "msk", "texture"
1039
+ - attrs with keys: "margins" with "disp_min" and "disp_max"
1040
+ "transform", "crs", "valid_pixels", "no_data_mask",
1041
+ "no_data_img"
1042
+ - attributes containing:
1043
+ "largest_epipolar_region","opt_epipolar_tile_size"
1044
+ :type epipolar_images_right: CarsDataset
1045
+ :param local_tile_optimal_size_fun: function to compute local
1046
+ optimal tile size
1047
+ :type local_tile_optimal_size_fun: func
1048
+ :param orchestrator: orchestrator used
1049
+ :param pair_folder: folder used for current pair
1050
+ :type pair_folder: str
1051
+ :param pair_key: pair id
1052
+ :type pair_key: str
1053
+ :param disp_range_grid: minimum and maximum disparity grid
1054
+ :type disp_range_grid: CarsDataset
1055
+ :param margins_to_keep: margin to keep after dense matching
1056
+ :type margins_to_keep: int
1057
+ :param texture_bands: indices of bands from epipolar_images_left
1058
+ used for output texture
1059
+ :type texture_bands: list
1060
+ :param classif_bands_to_mask: bands from classif to mask
1061
+ :type classif_bands_to_mask: list of str / int
1062
+
1063
+ :return: disparity map: \
1064
+ The CarsDataset contains:
1065
+
1066
+ - N x M Delayed tiles.\
1067
+ Each tile will be a future xarray Dataset containing:
1068
+ - data with keys : "disp", "disp_msk"
1069
+ - attrs with keys: profile, window, overlaps
1070
+ - attributes containing:
1071
+ "largest_epipolar_region","opt_epipolar_tile_size",
1072
+ "disp_min_tiling", "disp_max_tiling"
1073
+
1074
+ :rtype: CarsDataset
1075
+ """
1076
+ # Default orchestrator
1077
+ if orchestrator is None:
1078
+ # Create default sequential orchestrator for current application
1079
+ # be awere, no out_json will be shared between orchestrators
1080
+ # No files saved
1081
+ self.orchestrator = ocht.Orchestrator(
1082
+ orchestrator_conf={"mode": "sequential"}
1083
+ )
1084
+ else:
1085
+ self.orchestrator = orchestrator
1086
+
1087
+ if pair_folder is None:
1088
+ pair_folder = os.path.join(self.orchestrator.out_dir, "tmp")
1089
+
1090
+ if epipolar_images_left.dataset_type == "arrays":
1091
+ # Create CarsDataset
1092
+ # Epipolar_disparity
1093
+ epipolar_disparity_map = cars_dataset.CarsDataset(
1094
+ "arrays", name="dense_matching_" + pair_key
1095
+ )
1096
+ epipolar_disparity_map.create_empty_copy(epipolar_images_left)
1097
+ # Modify overlaps
1098
+ epipolar_disparity_map.overlaps = (
1099
+ format_transformation.reduce_overlap(
1100
+ epipolar_disparity_map.overlaps, margins_to_keep
1101
+ )
1102
+ )
1103
+
1104
+ # Update attributes to get epipolar info
1105
+ epipolar_disparity_map.attributes.update(
1106
+ epipolar_images_left.attributes
1107
+ )
1108
+
1109
+ # Save disparity maps
1110
+ if self.save_intermediate_data:
1111
+ self.orchestrator.add_to_save_lists(
1112
+ os.path.join(pair_folder, "epi_disp.tif"),
1113
+ cst_disp.MAP,
1114
+ epipolar_disparity_map,
1115
+ cars_ds_name="epi_disp",
1116
+ nodata=-9999,
1117
+ )
1118
+
1119
+ self.orchestrator.add_to_save_lists(
1120
+ os.path.join(pair_folder, "epi_disp_image.tif"),
1121
+ cst.EPI_TEXTURE,
1122
+ epipolar_disparity_map,
1123
+ cars_ds_name="epi_disp_color",
1124
+ )
1125
+
1126
+ self.orchestrator.add_to_save_lists(
1127
+ os.path.join(pair_folder, "epi_disp_mask.tif"),
1128
+ cst_disp.VALID,
1129
+ epipolar_disparity_map,
1130
+ dtype=np.uint8,
1131
+ cars_ds_name="epi_disp_mask",
1132
+ optional_data=True,
1133
+ )
1134
+
1135
+ self.orchestrator.add_to_save_lists(
1136
+ os.path.join(pair_folder, "epi_disp_classif.tif"),
1137
+ cst.EPI_CLASSIFICATION,
1138
+ epipolar_disparity_map,
1139
+ dtype=np.uint8,
1140
+ cars_ds_name="epi_disp_classif",
1141
+ optional_data=True,
1142
+ )
1143
+
1144
+ self.orchestrator.add_to_save_lists(
1145
+ os.path.join(
1146
+ pair_folder,
1147
+ "epi_confidence.tif",
1148
+ ),
1149
+ cst_disp.CONFIDENCE,
1150
+ epipolar_disparity_map,
1151
+ cars_ds_name="confidence",
1152
+ optional_data=True,
1153
+ )
1154
+
1155
+ # disparity grids
1156
+ self.orchestrator.add_to_save_lists(
1157
+ os.path.join(
1158
+ pair_folder,
1159
+ "epi_disp_min.tif",
1160
+ ),
1161
+ cst_disp.EPI_DISP_MIN_GRID,
1162
+ epipolar_disparity_map,
1163
+ cars_ds_name="disp_min",
1164
+ )
1165
+ self.orchestrator.add_to_save_lists(
1166
+ os.path.join(
1167
+ pair_folder,
1168
+ "epi_disp_max.tif",
1169
+ ),
1170
+ cst_disp.EPI_DISP_MAX_GRID,
1171
+ epipolar_disparity_map,
1172
+ cars_ds_name="disp_max",
1173
+ )
1174
+ self.orchestrator.add_to_save_lists(
1175
+ os.path.join(
1176
+ pair_folder,
1177
+ "epi_disp_filling.tif",
1178
+ ),
1179
+ cst_disp.FILLING,
1180
+ epipolar_disparity_map,
1181
+ dtype=np.uint8,
1182
+ cars_ds_name="epi_disp_filling",
1183
+ nodata=255,
1184
+ )
1185
+
1186
+ # Get saving infos in order to save tiles when they are computed
1187
+ [saving_info] = self.orchestrator.get_saving_infos(
1188
+ [epipolar_disparity_map]
1189
+ )
1190
+
1191
+ # Add infos to orchestrator.out_json
1192
+ updating_dict = {
1193
+ application_constants.APPLICATION_TAG: {
1194
+ dm_cst.DENSE_MATCHING_RUN_TAG: {
1195
+ pair_key: {
1196
+ "global_disp_min": disp_range_grid["global_min"],
1197
+ "global_disp_max": disp_range_grid["global_max"],
1198
+ },
1199
+ },
1200
+ }
1201
+ }
1202
+ self.orchestrator.update_out_info(updating_dict)
1203
+ logging.info(
1204
+ "Compute disparity: number tiles: {}".format(
1205
+ epipolar_disparity_map.shape[1]
1206
+ * epipolar_disparity_map.shape[0]
1207
+ )
1208
+ )
1209
+
1210
+ nb_total_tiles_roi = 0
1211
+
1212
+ # broadcast grids
1213
+ broadcasted_disp_range_grid = self.orchestrator.cluster.scatter(
1214
+ disp_range_grid
1215
+ )
1216
+
1217
+ # Generate disparity maps
1218
+ for col in range(epipolar_disparity_map.shape[1]):
1219
+ for row in range(epipolar_disparity_map.shape[0]):
1220
+ use_tile = False
1221
+ crop_with_range = None
1222
+ if type(None) not in (
1223
+ type(epipolar_images_left[row, col]),
1224
+ type(epipolar_images_right[row, col]),
1225
+ ):
1226
+ use_tile = True
1227
+ nb_total_tiles_roi += 1
1228
+
1229
+ # Compute optimal tile size for tile
1230
+ (
1231
+ _,
1232
+ _,
1233
+ crop_with_range,
1234
+ ) = local_tile_optimal_size_fun(
1235
+ np.array(
1236
+ epipolar_images_left.attributes[
1237
+ "disp_min_tiling"
1238
+ ]
1239
+ )[row, col],
1240
+ np.array(
1241
+ epipolar_images_left.attributes[
1242
+ "disp_max_tiling"
1243
+ ]
1244
+ )[row, col],
1245
+ )
1246
+
1247
+ if use_tile:
1248
+ # update saving infos for potential replacement
1249
+ full_saving_info = ocht.update_saving_infos(
1250
+ saving_info, row=row, col=col
1251
+ )
1252
+ # Compute disparity
1253
+ (
1254
+ epipolar_disparity_map[row, col]
1255
+ ) = self.orchestrator.cluster.create_task(
1256
+ compute_disparity_wrapper
1257
+ )(
1258
+ epipolar_images_left[row, col],
1259
+ epipolar_images_right[row, col],
1260
+ self.corr_config,
1261
+ self.used_band,
1262
+ broadcasted_disp_range_grid,
1263
+ saving_info=full_saving_info,
1264
+ compute_disparity_masks=compute_disparity_masks,
1265
+ crop_with_range=crop_with_range,
1266
+ left_overlaps=cars_dataset.overlap_array_to_dict(
1267
+ epipolar_disparity_map.overlaps[row, col]
1268
+ ),
1269
+ margins_to_keep=margins_to_keep,
1270
+ classification_fusion_margin=(
1271
+ self.classification_fusion_margin
1272
+ ),
1273
+ texture_bands=texture_bands,
1274
+ conf_filtering=self.confidence_filtering,
1275
+ threshold_disp_range_to_borders=(
1276
+ self.threshold_disp_range_to_borders
1277
+ ),
1278
+ filter_incomplete_disparity_range=(
1279
+ self.filter_incomplete_disparity_range
1280
+ ),
1281
+ classif_bands_to_mask=classif_bands_to_mask,
1282
+ )
1283
+
1284
+ else:
1285
+ logging.error(
1286
+ "DenseMatching application doesn't "
1287
+ "support this input data format"
1288
+ )
1289
+ return epipolar_disparity_map
1290
+
1291
+
1292
+ def compute_disparity_wrapper( # pylint: disable=too-many-positional-arguments
1293
+ left_image_object: xr.Dataset,
1294
+ right_image_object: xr.Dataset,
1295
+ corr_cfg: dict,
1296
+ used_band: str,
1297
+ disp_range_grid,
1298
+ saving_info=None,
1299
+ compute_disparity_masks=False,
1300
+ crop_with_range=None,
1301
+ left_overlaps=None,
1302
+ margins_to_keep=0,
1303
+ classification_fusion_margin=-1,
1304
+ texture_bands=None,
1305
+ conf_filtering=None,
1306
+ threshold_disp_range_to_borders=False,
1307
+ filter_incomplete_disparity_range=True,
1308
+ classif_bands_to_mask=None,
1309
+ ) -> Dict[str, Tuple[xr.Dataset, xr.Dataset]]:
1310
+ """
1311
+ Compute disparity maps from image objects.
1312
+ This function will be run as a delayed task.
1313
+
1314
+ User must provide saving infos to save properly created datasets
1315
+
1316
+ :param left_image_object: tiled Left image
1317
+ - dataset with :
1318
+
1319
+ - cst.EPI_IMAGE
1320
+ - cst.EPI_MSK (if given)
1321
+ - cst.EPI_TEXTURE (for left, if given)
1322
+ :type left_image_object: xr.Dataset
1323
+ - dataset with :
1324
+
1325
+ - cst.EPI_IMAGE
1326
+ - cst.EPI_MSK (if given)
1327
+ - cst.EPI_TEXTURE (for left, if given)
1328
+ :param right_image_object: tiled Right image
1329
+ :type right_image_object: xr.Dataset
1330
+ :param corr_cfg: Correlator configuration
1331
+ :type corr_cfg: dict
1332
+ :param used_band: name of band used for correlation
1333
+ :type used_band: str
1334
+ :param disp_range_grid: minimum and maximum disparity grid
1335
+ :type disp_range_grid: np.ndarray
1336
+ :param compute_disparity_masks: Compute all the disparity \
1337
+ pandora masks(disable by default)
1338
+ :type compute_disparity_masks: bool
1339
+ :param generate_performance_map: True if generate performance map
1340
+ :type generate_performance_map: bool
1341
+ :param perf_ambiguity_threshold: ambiguity threshold used for
1342
+ performance map
1343
+ :type perf_ambiguity_threshold: float
1344
+ :param disp_to_alt_ratio: disp to alti ratio used for performance map
1345
+ :type disp_to_alt_ratio: float
1346
+ :param crop_with_range: range length to crop disparity range with
1347
+ :type crop_with_range: float
1348
+ :param left_overlaps: left overlap
1349
+ :type: left_overlaps: dict
1350
+ :param margins_to_keep: margin to keep after dense matching
1351
+ :type margins_to_keep: int
1352
+ :param classification_fusion_margin: the margin to add for the fusion
1353
+ :type classification_fusion_margin: int
1354
+ :param classif_bands_to_mask: bands from classif to mask
1355
+ :type classif_bands_to_mask: list of str / int
1356
+
1357
+
1358
+ :return: Left to right disparity dataset
1359
+ Returned dataset is composed of :
1360
+
1361
+ - cst_disp.MAP
1362
+ - cst_disp.VALID
1363
+ - cst.EPI_TEXTURE
1364
+
1365
+ """
1366
+ # Generate disparity grids
1367
+ (
1368
+ disp_min_grid,
1369
+ disp_max_grid,
1370
+ ) = dm_algo.compute_disparity_grid(
1371
+ disp_range_grid,
1372
+ left_image_object,
1373
+ right_image_object,
1374
+ used_band,
1375
+ threshold_disp_range_to_borders,
1376
+ )
1377
+
1378
+ global_disp_min = disp_range_grid["global_min"]
1379
+ global_disp_max = disp_range_grid["global_max"]
1380
+
1381
+ # add global disparity in case of ambiguity normalization
1382
+ left_image_object = add_global_disparity(
1383
+ left_image_object, global_disp_min, global_disp_max
1384
+ )
1385
+
1386
+ # Crop interval if needed
1387
+ mask_crop = np.zeros(disp_min_grid.shape, dtype=int)
1388
+ is_cropped = False
1389
+ if crop_with_range is not None:
1390
+ current_min = np.min(disp_min_grid)
1391
+ current_max = np.max(disp_max_grid)
1392
+ if (current_max - current_min) > crop_with_range:
1393
+ is_cropped = True
1394
+ logging.warning("disparity range for current tile is cropped")
1395
+ # crop
1396
+ new_min = (
1397
+ current_min * crop_with_range / (current_max - current_min)
1398
+ )
1399
+ new_max = (
1400
+ current_max * crop_with_range / (current_max - current_min)
1401
+ )
1402
+
1403
+ mask_crop = np.logical_or(
1404
+ disp_min_grid < new_min, disp_max_grid > new_max
1405
+ )
1406
+ mask_crop = mask_crop.astype(bool)
1407
+ disp_min_grid[mask_crop] = new_min
1408
+ disp_max_grid[mask_crop] = new_max
1409
+
1410
+ # Compute disparity
1411
+ # TODO : remove overwriting of EPI_MSK
1412
+ disp_dataset = dm_algo.compute_disparity(
1413
+ left_image_object,
1414
+ right_image_object,
1415
+ corr_cfg,
1416
+ used_band,
1417
+ disp_min_grid=disp_min_grid,
1418
+ disp_max_grid=disp_max_grid,
1419
+ compute_disparity_masks=compute_disparity_masks,
1420
+ cropped_range=mask_crop,
1421
+ margins_to_keep=margins_to_keep,
1422
+ classification_fusion_margin=classification_fusion_margin,
1423
+ texture_bands=texture_bands,
1424
+ filter_incomplete_disparity_range=filter_incomplete_disparity_range,
1425
+ classif_bands_to_mask=classif_bands_to_mask,
1426
+ )
1427
+
1428
+ mask = disp_dataset["disp_msk"].values
1429
+ disp_map = disp_dataset["disp"].values
1430
+ disp_map[mask == 0] = np.nan
1431
+
1432
+ # Filtering by using the confidence
1433
+ requested_confidence = [
1434
+ "confidence_from_risk_min.cars_2",
1435
+ "confidence_from_risk_max.cars_2",
1436
+ "confidence_from_interval_bounds_inf.cars_3",
1437
+ "confidence_from_interval_bounds_sup.cars_3",
1438
+ ]
1439
+
1440
+ if (
1441
+ all(key in disp_dataset for key in requested_confidence)
1442
+ and conf_filtering["activated"] is True
1443
+ ):
1444
+ dm_wrappers.confidence_filtering(
1445
+ disp_dataset,
1446
+ requested_confidence,
1447
+ conf_filtering,
1448
+ )
1449
+
1450
+ # Fill with attributes
1451
+ cars_dataset.fill_dataset(
1452
+ disp_dataset,
1453
+ saving_info=saving_info,
1454
+ window=cars_dataset.get_window_dataset(left_image_object),
1455
+ profile=cars_dataset.get_profile_rasterio(left_image_object),
1456
+ attributes={cst.CROPPED_DISPARITY_RANGE: is_cropped},
1457
+ overlaps=left_overlaps,
1458
+ )
1459
+
1460
+ return disp_dataset