cars 1.0.0rc3__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. cars/__init__.py +74 -0
  2. cars/applications/__init__.py +40 -0
  3. cars/applications/application.py +117 -0
  4. cars/applications/application_constants.py +29 -0
  5. cars/applications/application_template.py +146 -0
  6. cars/applications/auxiliary_filling/__init__.py +29 -0
  7. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +105 -0
  8. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
  9. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -0
  10. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
  11. cars/applications/dem_generation/__init__.py +30 -0
  12. cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
  13. cars/applications/dem_generation/bulldozer_config/base_config.yaml +46 -0
  14. cars/applications/dem_generation/bulldozer_dem_app.py +641 -0
  15. cars/applications/dem_generation/bulldozer_memory.py +55 -0
  16. cars/applications/dem_generation/dem_generation_algo.py +107 -0
  17. cars/applications/dem_generation/dem_generation_constants.py +32 -0
  18. cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
  19. cars/applications/dense_match_filling/__init__.py +30 -0
  20. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
  21. cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
  22. cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
  23. cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
  24. cars/applications/dense_match_filling/zero_padding_app.py +302 -0
  25. cars/applications/dense_matching/__init__.py +30 -0
  26. cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
  27. cars/applications/dense_matching/census_mccnn_sgm_app.py +1461 -0
  28. cars/applications/dense_matching/cpp/__init__.py +0 -0
  29. cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-313-x86_64-linux-gnu.so +0 -0
  30. cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
  31. cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
  32. cars/applications/dense_matching/cpp/meson.build +9 -0
  33. cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
  34. cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
  35. cars/applications/dense_matching/dense_matching_algo.py +401 -0
  36. cars/applications/dense_matching/dense_matching_constants.py +89 -0
  37. cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
  38. cars/applications/dense_matching/disparity_grid_algo.py +597 -0
  39. cars/applications/dense_matching/loaders/__init__.py +23 -0
  40. cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
  41. cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
  42. cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
  43. cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
  44. cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
  45. cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
  46. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  47. cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
  48. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  49. cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
  50. cars/applications/dsm_filling/__init__.py +32 -0
  51. cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
  52. cars/applications/dsm_filling/border_interpolation_app.py +278 -0
  53. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  54. cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
  55. cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
  56. cars/applications/dsm_merging/__init__.py +28 -0
  57. cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
  58. cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
  59. cars/applications/grid_correction/__init__.py +30 -0
  60. cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
  61. cars/applications/grid_correction/grid_correction_app.py +557 -0
  62. cars/applications/grid_generation/__init__.py +30 -0
  63. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  64. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  65. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  66. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  67. cars/applications/grid_generation/transform_grid.py +88 -0
  68. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  69. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  70. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  71. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  72. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  73. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  74. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  75. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  76. cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
  77. cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
  78. cars/applications/rasterization/__init__.py +30 -0
  79. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  80. cars/applications/rasterization/rasterization_algo.py +534 -0
  81. cars/applications/rasterization/rasterization_constants.py +38 -0
  82. cars/applications/rasterization/rasterization_wrappers.py +639 -0
  83. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  84. cars/applications/resampling/__init__.py +28 -0
  85. cars/applications/resampling/abstract_resampling_app.py +187 -0
  86. cars/applications/resampling/bicubic_resampling_app.py +760 -0
  87. cars/applications/resampling/resampling_algo.py +590 -0
  88. cars/applications/resampling/resampling_constants.py +36 -0
  89. cars/applications/resampling/resampling_wrappers.py +309 -0
  90. cars/applications/sensors_subsampling/__init__.py +32 -0
  91. cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
  92. cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
  93. cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
  94. cars/applications/sparse_matching/__init__.py +30 -0
  95. cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
  96. cars/applications/sparse_matching/sift_app.py +724 -0
  97. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  98. cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
  99. cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
  100. cars/applications/triangulation/__init__.py +32 -0
  101. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  102. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  103. cars/applications/triangulation/pc_transform.py +552 -0
  104. cars/applications/triangulation/triangulation_algo.py +371 -0
  105. cars/applications/triangulation/triangulation_constants.py +38 -0
  106. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  107. cars/bundleadjustment.py +750 -0
  108. cars/cars.py +179 -0
  109. cars/conf/__init__.py +23 -0
  110. cars/conf/geoid/egm96.grd +0 -0
  111. cars/conf/geoid/egm96.grd.hdr +15 -0
  112. cars/conf/input_parameters.py +156 -0
  113. cars/conf/mask_cst.py +35 -0
  114. cars/core/__init__.py +23 -0
  115. cars/core/cars_logging.py +402 -0
  116. cars/core/constants.py +191 -0
  117. cars/core/constants_disparity.py +50 -0
  118. cars/core/datasets.py +140 -0
  119. cars/core/geometry/__init__.py +27 -0
  120. cars/core/geometry/abstract_geometry.py +1130 -0
  121. cars/core/geometry/shareloc_geometry.py +604 -0
  122. cars/core/inputs.py +568 -0
  123. cars/core/outputs.py +176 -0
  124. cars/core/preprocessing.py +722 -0
  125. cars/core/projection.py +843 -0
  126. cars/core/roi_tools.py +215 -0
  127. cars/core/tiling.py +774 -0
  128. cars/core/utils.py +164 -0
  129. cars/data_structures/__init__.py +23 -0
  130. cars/data_structures/cars_dataset.py +1544 -0
  131. cars/data_structures/cars_dict.py +74 -0
  132. cars/data_structures/corresponding_tiles_tools.py +186 -0
  133. cars/data_structures/dataframe_converter.py +185 -0
  134. cars/data_structures/format_transformation.py +297 -0
  135. cars/devibrate.py +689 -0
  136. cars/extractroi.py +264 -0
  137. cars/orchestrator/__init__.py +23 -0
  138. cars/orchestrator/achievement_tracker.py +125 -0
  139. cars/orchestrator/cluster/__init__.py +37 -0
  140. cars/orchestrator/cluster/abstract_cluster.py +250 -0
  141. cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
  142. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  143. cars/orchestrator/cluster/dask_config/README.md +94 -0
  144. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  145. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  146. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  147. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  148. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  149. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  150. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  151. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  152. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  153. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  154. cars/orchestrator/cluster/log_wrapper.py +728 -0
  155. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  156. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  157. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  158. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  159. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  160. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
  161. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  162. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  163. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  164. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  165. cars/orchestrator/memory_tools.py +47 -0
  166. cars/orchestrator/orchestrator.py +755 -0
  167. cars/orchestrator/orchestrator_constants.py +29 -0
  168. cars/orchestrator/registry/__init__.py +23 -0
  169. cars/orchestrator/registry/abstract_registry.py +143 -0
  170. cars/orchestrator/registry/compute_registry.py +106 -0
  171. cars/orchestrator/registry/id_generator.py +116 -0
  172. cars/orchestrator/registry/replacer_registry.py +213 -0
  173. cars/orchestrator/registry/saver_registry.py +363 -0
  174. cars/orchestrator/registry/unseen_registry.py +118 -0
  175. cars/orchestrator/tiles_profiler.py +279 -0
  176. cars/pipelines/__init__.py +26 -0
  177. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  178. cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
  179. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  180. cars/pipelines/default/__init__.py +26 -0
  181. cars/pipelines/default/default_pipeline.py +1095 -0
  182. cars/pipelines/filling/__init__.py +26 -0
  183. cars/pipelines/filling/filling.py +981 -0
  184. cars/pipelines/formatting/__init__.py +26 -0
  185. cars/pipelines/formatting/formatting.py +190 -0
  186. cars/pipelines/merging/__init__.py +26 -0
  187. cars/pipelines/merging/merging.py +439 -0
  188. cars/pipelines/parameters/__init__.py +0 -0
  189. cars/pipelines/parameters/advanced_parameters.py +256 -0
  190. cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
  191. cars/pipelines/parameters/application_parameters.py +72 -0
  192. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  193. cars/pipelines/parameters/dsm_inputs.py +349 -0
  194. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  195. cars/pipelines/parameters/output_constants.py +52 -0
  196. cars/pipelines/parameters/output_parameters.py +435 -0
  197. cars/pipelines/parameters/sensor_inputs.py +859 -0
  198. cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
  199. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  200. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  201. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  202. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  203. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  204. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  205. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  206. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  207. cars/pipelines/pipeline.py +119 -0
  208. cars/pipelines/pipeline_constants.py +38 -0
  209. cars/pipelines/pipeline_template.py +135 -0
  210. cars/pipelines/subsampling/__init__.py +26 -0
  211. cars/pipelines/subsampling/subsampling.py +358 -0
  212. cars/pipelines/surface_modeling/__init__.py +26 -0
  213. cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
  214. cars/pipelines/tie_points/__init__.py +26 -0
  215. cars/pipelines/tie_points/tie_points.py +536 -0
  216. cars/starter.py +167 -0
  217. cars-1.0.0rc3.dist-info/METADATA +289 -0
  218. cars-1.0.0rc3.dist-info/RECORD +220 -0
  219. cars-1.0.0rc3.dist-info/WHEEL +6 -0
  220. cars-1.0.0rc3.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,288 @@
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 bulldozer dsm filling application class.
23
+ """
24
+
25
+ import contextlib
26
+ import logging
27
+ import os
28
+ import shutil
29
+ import warnings
30
+
31
+ import numpy as np
32
+ import rasterio as rio
33
+ import yaml
34
+ from bulldozer.pipeline.bulldozer_pipeline import dsm_to_dtm
35
+ from json_checker import Checker, Or
36
+ from pyproj import CRS
37
+ from rasterio.errors import NodataShadowWarning
38
+ from shapely import Polygon
39
+
40
+ from cars.applications.dem_generation.bulldozer_memory import (
41
+ can_allocate_shared_memory,
42
+ )
43
+ from cars.core import inputs, projection
44
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
45
+
46
+ from .abstract_dsm_filling_app import DsmFilling
47
+
48
+
49
+ class BulldozerFilling(DsmFilling, short_name="bulldozer"):
50
+ """
51
+ Bulldozer filling
52
+ """
53
+
54
+ def __init__(self, conf=None):
55
+ """
56
+ Init function of BulldozerFilling
57
+
58
+ :param conf: configuration for BulldozerFilling
59
+ :return: an application_to_use object
60
+ """
61
+ super().__init__(conf=conf)
62
+
63
+ # check conf
64
+ self.used_method = self.used_config["method"]
65
+ self.classification = self.used_config["classification"]
66
+ self.save_intermediate_data = self.used_config["save_intermediate_data"]
67
+
68
+ def check_conf(self, conf):
69
+
70
+ # init conf
71
+ if conf is not None:
72
+ overloaded_conf = conf.copy()
73
+ else:
74
+ conf = {}
75
+ overloaded_conf = {}
76
+
77
+ # Check if can use bulldozer
78
+ can_allocate_sm, log_message = can_allocate_shared_memory()
79
+ if not can_allocate_sm:
80
+ logging.error(log_message)
81
+ logging.error("DSM filling with bulldozer might crash")
82
+
83
+ # Overload conf
84
+ overloaded_conf["method"] = conf.get("method", "bulldozer")
85
+ overloaded_conf["classification"] = conf.get("classification", "nodata")
86
+
87
+ if isinstance(overloaded_conf["classification"], str):
88
+ overloaded_conf["classification"] = [
89
+ overloaded_conf["classification"]
90
+ ]
91
+
92
+ overloaded_conf["save_intermediate_data"] = conf.get(
93
+ "save_intermediate_data", False
94
+ )
95
+
96
+ rectification_schema = {
97
+ "method": str,
98
+ "classification": Or(None, [str]),
99
+ "save_intermediate_data": bool,
100
+ }
101
+
102
+ # Check conf
103
+ checker = Checker(rectification_schema)
104
+ checker.validate(overloaded_conf)
105
+
106
+ return overloaded_conf
107
+
108
+ @cars_profile(name="Bulldozer filling")
109
+ def run( # pylint: disable=too-many-positional-arguments # noqa C901
110
+ self,
111
+ dsm_file,
112
+ classif_file,
113
+ filling_file,
114
+ dump_dir,
115
+ roi_polys,
116
+ roi_epsg,
117
+ orchestrator,
118
+ dsm_dir=None,
119
+ ):
120
+ """
121
+ Run dsm filling using initial elevation and the current dsm
122
+ Replaces dsm.tif by the filled dsm. Adds a new band
123
+ to filling.tif if it exists.
124
+ The old dsm is saved in dump_dir.
125
+
126
+ roi_poly can any of these objects :
127
+ - a list of Shapely Polygons
128
+ - a Shapely Polygon
129
+ """
130
+
131
+ if self.classification is None:
132
+ self.classification = ["nodata"]
133
+
134
+ if not os.path.exists(dump_dir):
135
+ os.makedirs(dump_dir)
136
+
137
+ old_dsm_path = os.path.join(dump_dir, "dsm_not_filled.tif")
138
+ new_dsm_path = os.path.join(dump_dir, "dsm_filled.tif")
139
+
140
+ if dsm_dir is not None:
141
+ dsm_path_out = os.path.join(dsm_dir, "dsm.tif")
142
+ filling_path_out = os.path.join(dsm_dir, "filling.tif")
143
+ else:
144
+ dsm_path_out = dsm_file
145
+ filling_path_out = filling_file
146
+
147
+ # create the config for the bulldozer execution
148
+ bull_conf_path = os.path.join(
149
+ os.path.dirname(__file__), "bulldozer_config/base_config.yaml"
150
+ )
151
+ with open(bull_conf_path, "r", encoding="utf8") as bull_conf_file:
152
+ bull_conf = yaml.safe_load(bull_conf_file)
153
+
154
+ bull_conf["dsm_path"] = dsm_file
155
+ bull_conf["output_dir"] = os.path.join(dump_dir, "bulldozer")
156
+
157
+ if orchestrator is not None:
158
+ if (
159
+ orchestrator.get_conf()["mode"] == "multiprocessing"
160
+ or orchestrator.get_conf()["mode"] == "local_dask"
161
+ ):
162
+ bull_conf["nb_max_workers"] = orchestrator.get_conf()[
163
+ "nb_workers"
164
+ ]
165
+
166
+ bull_conf_path = os.path.join(dump_dir, "bulldozer_config.yaml")
167
+ with open(bull_conf_path, "w", encoding="utf8") as bull_conf_file:
168
+ yaml.dump(bull_conf, bull_conf_file)
169
+
170
+ dtm_path = os.path.join(bull_conf["output_dir"], "dtm.tif")
171
+
172
+ # get dsm to be filled and its metadata
173
+ with rio.open(dsm_file) as in_dsm:
174
+ dsm = in_dsm.read(1)
175
+ dsm_tr = in_dsm.transform
176
+ dsm_crs = in_dsm.crs
177
+ dsm_meta = in_dsm.meta
178
+
179
+ roi_raster = np.ones(dsm.shape)
180
+
181
+ if isinstance(roi_polys, list):
182
+ roi_polys_outepsg = []
183
+ for poly in roi_polys:
184
+ if isinstance(poly, Polygon):
185
+ roi_poly_outepsg = projection.polygon_projection_crs(
186
+ poly, CRS(roi_epsg), dsm_crs
187
+ )
188
+ roi_polys_outepsg.append(roi_poly_outepsg)
189
+
190
+ roi_raster = rio.features.rasterize(
191
+ roi_polys_outepsg, out_shape=roi_raster.shape, transform=dsm_tr
192
+ )
193
+ elif isinstance(roi_polys, Polygon):
194
+ roi_poly_outepsg = projection.polygon_projection_crs(
195
+ roi_polys, CRS(roi_epsg), dsm_crs
196
+ )
197
+ roi_raster = rio.features.rasterize(
198
+ [roi_poly_outepsg], out_shape=roi_raster.shape, transform=dsm_tr
199
+ )
200
+ try:
201
+ try:
202
+ # suppress prints in bulldozer by redirecting stdout&stderr
203
+ with open(os.devnull, "w", encoding="utf8") as devnull:
204
+ with (
205
+ contextlib.redirect_stdout(devnull),
206
+ contextlib.redirect_stderr(devnull),
207
+ ):
208
+ dsm_to_dtm(bull_conf_path)
209
+ except Exception:
210
+ logging.info(
211
+ "Bulldozer failed on its first execution. Retrying"
212
+ )
213
+ # suppress prints in bulldozer by redirecting stdout&stderr
214
+ with open(os.devnull, "w", encoding="utf8") as devnull:
215
+ with (
216
+ contextlib.redirect_stdout(devnull),
217
+ contextlib.redirect_stderr(devnull),
218
+ ):
219
+ dsm_to_dtm(bull_conf_path)
220
+ except Exception:
221
+ logging.warning(
222
+ "Bulldozer failed on its second execution."
223
+ + " The DSM could not be filled."
224
+ )
225
+ return None
226
+ with rio.open(dtm_path) as in_dtm:
227
+ dtm = in_dtm.read(1)
228
+
229
+ if self.save_intermediate_data:
230
+ with rio.open(old_dsm_path, "w", **dsm_meta) as out_dsm:
231
+ out_dsm.write(dsm, 1)
232
+
233
+ if classif_file is not None and os.path.exists(classif_file):
234
+ classif_descriptions = inputs.get_descriptions_bands(classif_file)
235
+ else:
236
+ classif_descriptions = []
237
+ combined_mask = np.zeros_like(dsm).astype(np.uint8)
238
+ for label in self.classification:
239
+ if label in classif_descriptions:
240
+ index_classif = classif_descriptions.index(label) + 1
241
+ with rio.open(classif_file) as in_classif:
242
+ classif = in_classif.read(index_classif)
243
+
244
+ with warnings.catch_warnings():
245
+ warnings.simplefilter("ignore", NodataShadowWarning)
246
+ classif_msk = in_classif.read_masks(1)
247
+ classif[classif_msk == 0] = 0
248
+
249
+ filling_mask = np.logical_and(classif, roi_raster > 0)
250
+ elif label == "nodata":
251
+ if classif_file is not None and os.path.exists(classif_file):
252
+ with rio.open(classif_file) as in_classif:
253
+ classif_msk = in_classif.read_masks(1)
254
+ classif = ~classif_msk
255
+ else:
256
+ with rio.open(dsm_file) as in_dsm:
257
+ dsm_msk = in_dsm.read_masks(1)
258
+ classif = ~dsm_msk
259
+ filling_mask = np.logical_and(classif, roi_raster > 0)
260
+ else:
261
+ logging.error(
262
+ "Label {} not found in classification "
263
+ "descriptions {}".format(label, classif_descriptions)
264
+ )
265
+ continue
266
+ logging.info("Filling of {} with Bulldozer DTM".format(label))
267
+ dsm[filling_mask] = dtm[filling_mask]
268
+ combined_mask = np.logical_or(combined_mask, filling_mask)
269
+
270
+ with rio.open(dsm_path_out, "w", **dsm_meta) as out_dsm:
271
+ out_dsm.write(dsm, 1)
272
+ if self.save_intermediate_data:
273
+ shutil.copy2(dsm_path_out, new_dsm_path)
274
+
275
+ if filling_file is not None:
276
+ with rio.open(filling_file, "r") as src:
277
+ fill_meta = src.meta
278
+ bands = [src.read(i + 1) for i in range(src.count)]
279
+ bands_desc = [src.descriptions[i] for i in range(src.count)]
280
+ fill_meta["count"] += 1
281
+ bands.append(combined_mask.astype(np.uint8))
282
+ bands_desc.append("bulldozer")
283
+
284
+ with rio.open(filling_path_out, "w", **fill_meta) as out:
285
+ for i, band in enumerate(bands):
286
+ out.write(band, i + 1)
287
+ out.set_band_description(i + 1, bands_desc[i])
288
+ return dtm_path
@@ -0,0 +1,341 @@
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 exogenous dsm filling application class.
23
+ """
24
+
25
+ import logging
26
+ import os
27
+ import shutil
28
+ import warnings
29
+
30
+ import numpy as np
31
+ import rasterio as rio
32
+ from json_checker import Checker, Or
33
+ from pyproj import CRS
34
+ from rasterio.enums import Resampling
35
+ from rasterio.errors import NodataShadowWarning
36
+ from rasterio.warp import reproject
37
+ from shapely import Polygon
38
+
39
+ from cars.core import inputs, projection
40
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
41
+
42
+ from .abstract_dsm_filling_app import DsmFilling
43
+
44
+
45
+ class ExogenousFilling(DsmFilling, short_name="exogenous_filling"):
46
+ """
47
+ Exogenous filling
48
+ """
49
+
50
+ def __init__(self, conf=None):
51
+ """
52
+ Init function of ExogenousFilling
53
+
54
+ :param conf: configuration for ExogenousFilling
55
+ :return: an application_to_use object
56
+ """
57
+ super().__init__(conf=conf)
58
+
59
+ # check conf
60
+ self.used_method = self.used_config["method"]
61
+ self.classification = self.used_config["classification"]
62
+ self.fill_with_geoid = self.used_config["fill_with_geoid"]
63
+ self.interpolation_method = self.used_config["interpolation_method"]
64
+ self.save_intermediate_data = self.used_config["save_intermediate_data"]
65
+
66
+ def check_conf(self, conf):
67
+
68
+ # init conf
69
+ if conf is not None:
70
+ overloaded_conf = conf.copy()
71
+ else:
72
+ conf = {}
73
+ overloaded_conf = {}
74
+
75
+ # Overload conf
76
+ overloaded_conf["method"] = conf.get("method", "exogenous_filling")
77
+ overloaded_conf["classification"] = conf.get("classification", "nodata")
78
+ if isinstance(overloaded_conf["classification"], str):
79
+ overloaded_conf["classification"] = [
80
+ overloaded_conf["classification"]
81
+ ]
82
+ overloaded_conf["fill_with_geoid"] = conf.get("fill_with_geoid", None)
83
+ overloaded_conf["interpolation_method"] = conf.get(
84
+ "interpolation_method", "bilinear"
85
+ )
86
+
87
+ if overloaded_conf["interpolation_method"] not in ["bilinear", "cubic"]:
88
+ # pylint: disable=inconsistent-quotes
89
+ raise RuntimeError(
90
+ f"Invalid interpolation method"
91
+ f"{overloaded_conf['interpolation_method']}, "
92
+ f"supported modes are bilinear and cubic."
93
+ )
94
+
95
+ overloaded_conf["save_intermediate_data"] = conf.get(
96
+ "save_intermediate_data", False
97
+ )
98
+
99
+ rectification_schema = {
100
+ "method": str,
101
+ "classification": Or(None, [str]),
102
+ "fill_with_geoid": Or(None, [str]),
103
+ "interpolation_method": str,
104
+ "save_intermediate_data": bool,
105
+ }
106
+
107
+ # Check conf
108
+ checker = Checker(rectification_schema)
109
+ checker.validate(overloaded_conf)
110
+
111
+ return overloaded_conf
112
+
113
+ @cars_profile(name="Exogeneous filling")
114
+ def run( # pylint: disable=too-many-positional-arguments # noqa C901
115
+ self,
116
+ dsm_file,
117
+ classif_file,
118
+ filling_file,
119
+ dump_dir,
120
+ roi_polys,
121
+ roi_epsg,
122
+ output_geoid,
123
+ geom_plugin,
124
+ dsm_dir=None,
125
+ ):
126
+ """
127
+ Run dsm filling using initial elevation and the current dsm
128
+ Replaces dsm.tif by the filled dsm. Adds a new band
129
+ to filling.tif if it exists.
130
+ The old dsm is saved in dump_dir.
131
+
132
+ roi_poly can any of these objects :
133
+ - a list of Shapely Polygons
134
+ - a Shapely Polygon
135
+ """
136
+
137
+ if dsm_dir is not None:
138
+ dsm_path_out = os.path.join(dsm_dir, "dsm.tif")
139
+ filling_path_out = os.path.join(dsm_dir, "filling.tif")
140
+ else:
141
+ dsm_path_out = dsm_file
142
+ filling_path_out = filling_file
143
+
144
+ if self.classification is None:
145
+ self.classification = ["nodata"]
146
+
147
+ if self.fill_with_geoid is None:
148
+ self.fill_with_geoid = []
149
+
150
+ interpolation_methods_dict = {
151
+ "bilinear": Resampling.bilinear,
152
+ "cubic": Resampling.cubic,
153
+ }
154
+ interpolation_method = interpolation_methods_dict.get(
155
+ self.interpolation_method, Resampling.bilinear
156
+ )
157
+
158
+ if geom_plugin is None:
159
+ logging.error(
160
+ "No DEM was provided, exogenous_filling will not run."
161
+ )
162
+ return
163
+
164
+ if not os.path.exists(dump_dir):
165
+ os.makedirs(dump_dir)
166
+
167
+ old_dsm_path = os.path.join(dump_dir, "dsm_not_filled.tif")
168
+ new_dsm_path = os.path.join(dump_dir, "dsm_filled.tif")
169
+
170
+ # get dsm to be filled and its metadata
171
+ with rio.open(dsm_file) as in_dsm:
172
+ dsm = in_dsm.read(1)
173
+ dsm_tr = in_dsm.transform
174
+ dsm_crs = in_dsm.crs
175
+ dsm_meta = in_dsm.meta
176
+
177
+ roi_raster = np.ones(dsm.shape)
178
+
179
+ if isinstance(roi_polys, list):
180
+ roi_polys_outepsg = []
181
+ for poly in roi_polys:
182
+ if isinstance(poly, Polygon):
183
+ roi_poly_outepsg = projection.polygon_projection_crs(
184
+ poly, CRS(roi_epsg), dsm_crs
185
+ )
186
+ roi_polys_outepsg.append(roi_poly_outepsg)
187
+
188
+ roi_raster = rio.features.rasterize(
189
+ roi_polys_outepsg, out_shape=roi_raster.shape, transform=dsm_tr
190
+ )
191
+ elif isinstance(roi_polys, Polygon):
192
+ roi_poly_outepsg = projection.polygon_projection_crs(
193
+ roi_polys, CRS(roi_epsg), dsm_crs
194
+ )
195
+ roi_raster = rio.features.rasterize(
196
+ [roi_poly_outepsg], out_shape=roi_raster.shape, transform=dsm_tr
197
+ )
198
+
199
+ # Get the initial elevation
200
+ with rio.open(geom_plugin.dem) as in_elev:
201
+ # Reproject the elevation data to match the DSM
202
+ elev_data = np.empty(dsm.shape, dtype=in_elev.dtypes[0])
203
+
204
+ reproject(
205
+ source=rio.band(in_elev, 1),
206
+ destination=elev_data,
207
+ src_transform=in_elev.transform,
208
+ src_crs=in_elev.crs,
209
+ dst_transform=dsm_tr,
210
+ dst_crs=dsm_crs,
211
+ resampling=interpolation_method,
212
+ )
213
+
214
+ if self.save_intermediate_data:
215
+ reprojected_dem_path = os.path.join(dump_dir, "reprojected_dem.tif")
216
+ with rio.open(reprojected_dem_path, "w", **dsm_meta) as out_elev:
217
+ out_elev.write(elev_data, 1)
218
+
219
+ with rio.open(geom_plugin.geoid) as in_geoid:
220
+ # Reproject the geoid data to match the DSM
221
+ input_geoid_data = np.empty(dsm.shape, dtype=in_geoid.dtypes[0])
222
+
223
+ reproject(
224
+ source=rio.band(in_geoid, 1),
225
+ destination=input_geoid_data,
226
+ src_transform=in_geoid.transform,
227
+ src_crs=in_geoid.crs,
228
+ dst_transform=dsm_tr,
229
+ dst_crs=dsm_crs,
230
+ resampling=interpolation_method,
231
+ )
232
+
233
+ if self.save_intermediate_data:
234
+ reprojected_geoid_path = os.path.join(
235
+ dump_dir, "reprojected_input_geoid.tif"
236
+ )
237
+ with rio.open(reprojected_geoid_path, "w", **dsm_meta) as out_geoid:
238
+ out_geoid.write(input_geoid_data, 1)
239
+
240
+ if isinstance(output_geoid, str):
241
+ with rio.open(output_geoid) as in_geoid:
242
+ # Reproject the geoid data to match the DSM
243
+ output_geoid_data = np.empty(
244
+ dsm.shape, dtype=in_geoid.dtypes[0]
245
+ )
246
+
247
+ reproject(
248
+ source=rio.band(in_geoid, 1),
249
+ destination=output_geoid_data,
250
+ src_transform=in_geoid.transform,
251
+ src_crs=in_geoid.crs,
252
+ dst_transform=dsm_tr,
253
+ dst_crs=dsm_crs,
254
+ resampling=interpolation_method,
255
+ )
256
+
257
+ if self.save_intermediate_data:
258
+ reprojected_geoid_path = os.path.join(
259
+ dump_dir, "reprojected_output_geoid.tif"
260
+ )
261
+ with rio.open(
262
+ reprojected_geoid_path, "w", **dsm_meta
263
+ ) as out_geoid:
264
+ out_geoid.write(input_geoid_data, 1)
265
+
266
+ # Save old dsm
267
+ if self.save_intermediate_data:
268
+ with rio.open(old_dsm_path, "w", **dsm_meta) as out_dsm:
269
+ out_dsm.write(dsm, 1)
270
+
271
+ # Fill DSM for every label
272
+ combined_mask = np.zeros_like(dsm).astype(np.uint8)
273
+ if classif_file is not None:
274
+ classif_descriptions = inputs.get_descriptions_bands(classif_file)
275
+ else:
276
+ classif_descriptions = []
277
+ for label in self.classification:
278
+ if label in classif_descriptions:
279
+ index_classif = classif_descriptions.index(label) + 1
280
+ with rio.open(classif_file) as in_classif:
281
+ classif = in_classif.read(index_classif)
282
+ classif_msk = in_classif.read_masks(1)
283
+ classif[classif_msk == 0] = 0
284
+ filling_mask = np.logical_and(classif, roi_raster > 0)
285
+ elif label == "nodata":
286
+ if classif_file is not None:
287
+ with rio.open(classif_file) as in_classif:
288
+ with warnings.catch_warnings():
289
+ warnings.simplefilter("ignore", NodataShadowWarning)
290
+ classif_msk = in_classif.read_masks(1)
291
+ classif = ~classif_msk
292
+ else:
293
+ with rio.open(dsm_file) as in_dsm:
294
+ dsm_msk = in_dsm.read_masks(1)
295
+ classif = ~dsm_msk
296
+ filling_mask = np.logical_and(classif, roi_raster > 0)
297
+ else:
298
+ logging.error(
299
+ "Label {} not found in classification "
300
+ "descriptions {}".format(label, classif_descriptions)
301
+ )
302
+ continue
303
+
304
+ if label in self.fill_with_geoid:
305
+ logging.info("Filling of {} with geoid".format(label))
306
+ dsm[filling_mask] = 0
307
+ else:
308
+ logging.info("Filling of {} with DEM and geoid".format(label))
309
+ dsm[filling_mask] = elev_data[filling_mask]
310
+
311
+ # apply offset to project on geoid if needed
312
+ if output_geoid is not True:
313
+ if isinstance(output_geoid, bool) and output_geoid is False:
314
+ # out geoid is ellipsoid: add geoid-ellipsoid distance
315
+ dsm[filling_mask] += input_geoid_data[filling_mask]
316
+ elif isinstance(output_geoid, str):
317
+ # out geoid is a new geoid whose path is in output_geoid:
318
+ # add carsgeoid-ellipsoid then add ellipsoid-outgeoid
319
+ dsm[filling_mask] += input_geoid_data[filling_mask]
320
+ dsm[filling_mask] -= output_geoid_data[filling_mask]
321
+
322
+ combined_mask = np.logical_or(combined_mask, filling_mask)
323
+
324
+ with rio.open(dsm_path_out, "w", **dsm_meta) as out_dsm:
325
+ out_dsm.write(dsm, 1)
326
+ if self.save_intermediate_data:
327
+ shutil.copy2(dsm_path_out, new_dsm_path)
328
+
329
+ if filling_file is not None:
330
+ with rio.open(filling_file, "r") as src:
331
+ fill_meta = src.meta
332
+ bands = [src.read(i + 1) for i in range(src.count)]
333
+ bands_desc = [src.descriptions[i] for i in range(src.count)]
334
+ fill_meta["count"] += 1
335
+ bands.append(combined_mask)
336
+ bands_desc.append("filling_exogenous")
337
+
338
+ with rio.open(filling_path_out, "w", **fill_meta) as out:
339
+ for i, band in enumerate(bands):
340
+ out.write(band, i + 1)
341
+ out.set_band_description(i + 1, bands_desc[i])
@@ -0,0 +1,28 @@
1
+ # !/usr/bin/env python
2
+ # coding: utf8
3
+ #
4
+ # Copyright (c) 2025 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
+ CARS dsm mergingmodule init file
23
+ """
24
+ # flake8: noqa: F401
25
+
26
+ from cars.applications.dsm_merging.abstract_dsm_merging_app import DsmMerging
27
+
28
+ from . import weighted_fusion_app