cars 1.0.0rc1__cp313-cp313-musllinux_1_2_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 (202) 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-313-i386-linux-musl.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 +202 -0
  199. cars-1.0.0rc1.dist-info/WHEEL +5 -0
  200. cars-1.0.0rc1.dist-info/entry_points.txt +8 -0
  201. cars.libs/libgcc_s-1257a076.so.1 +0 -0
  202. cars.libs/libstdc++-0530927c.so.6.0.32 +0 -0
@@ -0,0 +1,101 @@
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 abstract dsm filling application class.
23
+ """
24
+ import logging
25
+ from abc import ABCMeta, abstractmethod
26
+ from typing import Dict
27
+
28
+ from cars.applications.application import Application
29
+ from cars.applications.application_template import ApplicationTemplate
30
+
31
+
32
+ @Application.register("dsm_filling")
33
+ class DsmFilling(ApplicationTemplate, metaclass=ABCMeta):
34
+ """
35
+ DsmFilling
36
+ """
37
+
38
+ available_applications: Dict = {}
39
+ default_application = "bulldozer"
40
+
41
+ def __new__(cls, conf=None): # pylint: disable=W0613
42
+ """
43
+ Return the required application
44
+ :raises:
45
+ - KeyError when the required application is not registered
46
+
47
+ :param orchestrator: orchestrator used
48
+ :param conf: configuration for filling
49
+ :return: an application_to_use object
50
+ """
51
+
52
+ dsm_filling_method = cls.default_application
53
+ if bool(conf) is False:
54
+ logging.info(
55
+ "dsm_filling method not specified, default"
56
+ " {} is used".format(dsm_filling_method)
57
+ )
58
+ else:
59
+ dsm_filling_method = conf["method"]
60
+
61
+ if dsm_filling_method not in cls.available_applications:
62
+ logging.error(
63
+ "No dsm_filling application named {} registered".format(
64
+ dsm_filling_method
65
+ )
66
+ )
67
+ raise KeyError(
68
+ "No dsm_filling application named {} registered".format(
69
+ dsm_filling_method
70
+ )
71
+ )
72
+
73
+ logging.info(
74
+ "The DsmFilling {} application will be used".format(
75
+ dsm_filling_method
76
+ )
77
+ )
78
+
79
+ return super(DsmFilling, cls).__new__(
80
+ cls.available_applications[dsm_filling_method]
81
+ )
82
+
83
+ def __init_subclass__(cls, short_name, **kwargs): # pylint: disable=E0302
84
+ super().__init_subclass__(**kwargs)
85
+ cls.available_applications[short_name] = cls
86
+
87
+ def __init__(self, conf=None):
88
+ """
89
+ Init function of DSM Filling
90
+
91
+ :param conf: configuration
92
+ :return: an application_to_use object
93
+ """
94
+
95
+ super().__init__(conf=conf)
96
+
97
+ @abstractmethod
98
+ def run(self):
99
+ """
100
+ Run dsm filling using initial elevation and the current dsm
101
+ """
@@ -0,0 +1,270 @@
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 border interpolation 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
+ import scipy
33
+ import skimage
34
+ from json_checker import Checker, Or
35
+ from pyproj import CRS
36
+ from rasterio.errors import NodataShadowWarning
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 BorderInterpolation(DsmFilling, short_name="border_interpolation"):
46
+ """
47
+ Border interpolation
48
+ """
49
+
50
+ def __init__(self, conf=None):
51
+ """
52
+ Init function of BorderInterpolation
53
+
54
+ :param conf: configuration for BulldozerFilling
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.component_min_size = self.used_config["component_min_size"]
63
+ self.border_size = self.used_config["border_size"]
64
+ self.percentile = self.used_config["percentile"]
65
+ self.save_intermediate_data = self.used_config["save_intermediate_data"]
66
+
67
+ def check_conf(self, conf):
68
+
69
+ # init conf
70
+ if conf is not None:
71
+ overloaded_conf = conf.copy()
72
+ else:
73
+ conf = {}
74
+ overloaded_conf = {}
75
+
76
+ # Overload conf
77
+ overloaded_conf["method"] = conf.get("method", "border_interpolation")
78
+ overloaded_conf["classification"] = conf.get("classification", "nodata")
79
+ if isinstance(overloaded_conf["classification"], str):
80
+ overloaded_conf["classification"] = [
81
+ overloaded_conf["classification"]
82
+ ]
83
+ overloaded_conf["component_min_size"] = conf.get(
84
+ "component_min_size", 5
85
+ )
86
+ overloaded_conf["border_size"] = conf.get("border_size", 10)
87
+ overloaded_conf["percentile"] = conf.get("percentile", 10)
88
+ overloaded_conf["save_intermediate_data"] = conf.get(
89
+ "save_intermediate_data", False
90
+ )
91
+
92
+ rectification_schema = {
93
+ "method": str,
94
+ "classification": Or(None, [str]),
95
+ "component_min_size": int,
96
+ "border_size": int,
97
+ "percentile": Or(int, float),
98
+ "save_intermediate_data": bool,
99
+ }
100
+
101
+ # Check conf
102
+ checker = Checker(rectification_schema)
103
+ checker.validate(overloaded_conf)
104
+
105
+ return overloaded_conf
106
+
107
+ @cars_profile(name="Border interpolation filling")
108
+ def run( # pylint: disable=too-many-positional-arguments # noqa C901
109
+ self,
110
+ dsm_file,
111
+ classif_file,
112
+ filling_file,
113
+ dtm_file,
114
+ dump_dir,
115
+ roi_polys,
116
+ roi_epsg,
117
+ ):
118
+ """
119
+ Run dsm filling using initial elevation and the current dsm
120
+ Replaces dsm.tif by the filled dsm. Adds a new band
121
+ to filling.tif if it exists.
122
+ The old dsm is saved in dump_dir.
123
+
124
+ roi_poly can any of these objects :
125
+ - a list of Shapely Polygons
126
+ - a Shapely Polygon
127
+ """
128
+
129
+ if self.classification is None:
130
+ self.classification = ["nodata"]
131
+ logging.error(
132
+ "Filling method 'border_interpolation' needs a classification"
133
+ )
134
+
135
+ if not os.path.exists(dump_dir):
136
+ os.makedirs(dump_dir)
137
+
138
+ old_dsm_path = os.path.join(dump_dir, "dsm_not_filled.tif")
139
+ new_dsm_path = os.path.join(dump_dir, "dsm_filled.tif")
140
+
141
+ # get dsm to be filled and its metadata
142
+ with rio.open(dsm_file) as in_dsm:
143
+ dsm = in_dsm.read(1)
144
+ dsm_tr = in_dsm.transform
145
+ dsm_crs = in_dsm.crs
146
+ dsm_meta = in_dsm.meta
147
+ dsm_nodata = in_dsm.nodata
148
+
149
+ roi_raster = np.ones(dsm.shape)
150
+
151
+ if isinstance(roi_polys, list):
152
+ roi_polys_outepsg = []
153
+ for poly in roi_polys:
154
+ if isinstance(poly, Polygon):
155
+ roi_poly_outepsg = projection.polygon_projection_crs(
156
+ poly, CRS(roi_epsg), dsm_crs
157
+ )
158
+ roi_polys_outepsg.append(roi_poly_outepsg)
159
+
160
+ roi_raster = rio.features.rasterize(
161
+ roi_polys_outepsg, out_shape=roi_raster.shape, transform=dsm_tr
162
+ )
163
+ elif isinstance(roi_polys, Polygon):
164
+ roi_poly_outepsg = projection.polygon_projection_crs(
165
+ roi_polys, CRS(roi_epsg), dsm_crs
166
+ )
167
+ roi_raster = rio.features.rasterize(
168
+ [roi_poly_outepsg], out_shape=roi_raster.shape, transform=dsm_tr
169
+ )
170
+
171
+ # get dtm to fill the dsm
172
+ if dtm_file is not None:
173
+ logging.info(
174
+ "Use DTM file {} for border interpolation".format(dtm_file)
175
+ )
176
+ with rio.open(dtm_file) as in_dtm:
177
+ dtm = in_dtm.read(1)
178
+ dtm_nodata = in_dtm.nodata
179
+ else:
180
+ logging.info(
181
+ "No DTM provided : DSM {} will be used for "
182
+ "border interpolation".format(dsm_file)
183
+ )
184
+ dtm = dsm.copy()
185
+ dtm_nodata = dsm_nodata
186
+ dtm[dtm == dtm_nodata] = np.nan
187
+
188
+ if self.save_intermediate_data:
189
+ with rio.open(old_dsm_path, "w", **dsm_meta) as out_dsm:
190
+ out_dsm.write(dsm, 1)
191
+
192
+ if classif_file is not None:
193
+ classif_descriptions = inputs.get_descriptions_bands(classif_file)
194
+ else:
195
+ classif_descriptions = []
196
+ combined_mask = np.zeros_like(dsm).astype(np.uint8)
197
+ for label in self.classification:
198
+ if label in classif_descriptions:
199
+ index_classif = classif_descriptions.index(label) + 1
200
+ with rio.open(classif_file) as in_classif:
201
+ classif = in_classif.read(index_classif)
202
+
203
+ with warnings.catch_warnings():
204
+ warnings.simplefilter("ignore", NodataShadowWarning)
205
+ classif_msk = in_classif.read_masks(1)
206
+
207
+ classif[classif_msk == 0] = 0
208
+ filling_mask = np.logical_and(classif, roi_raster > 0)
209
+ else:
210
+ logging.error(
211
+ "Label {} not found in classification "
212
+ "descriptions {}".format(label, classif_descriptions)
213
+ )
214
+ continue
215
+ logging.info(
216
+ "Filling of {} with Bulldozer DTM using "
217
+ "border interpolation".format(label)
218
+ )
219
+ filling_mask[classif_msk == 0] = 0
220
+ filling_mask = skimage.morphology.binary_opening(
221
+ filling_mask,
222
+ footprint=[
223
+ (np.ones((self.component_min_size, 1)), 1),
224
+ (np.ones((1, self.component_min_size)), 1),
225
+ ],
226
+ )
227
+ features, num_features = scipy.ndimage.label(filling_mask)
228
+ logging.info("Filling of {} features".format(num_features))
229
+ features_boundaries = skimage.morphology.dilation(
230
+ features,
231
+ footprint=[
232
+ (np.ones((self.border_size, 1)), 1),
233
+ (np.ones((1, self.border_size)), 1),
234
+ ],
235
+ )
236
+ features_boundaries[filling_mask] = 0
237
+ borders_file_path = os.path.join(
238
+ dump_dir, "borders_of_{}.tif".format(label)
239
+ )
240
+ if self.save_intermediate_data:
241
+ with rio.open(
242
+ borders_file_path, "w", **dsm_meta
243
+ ) as out_borders:
244
+ out_borders.write(features_boundaries, 1)
245
+ for feature_id in range(1, num_features + 1):
246
+ altitude = np.nanpercentile(
247
+ dtm[features_boundaries == feature_id], self.percentile
248
+ )
249
+ if altitude is not None:
250
+ dsm[features == feature_id] = altitude
251
+ combined_mask = np.logical_or(combined_mask, filling_mask)
252
+
253
+ with rio.open(dsm_file, "w", **dsm_meta) as out_dsm:
254
+ out_dsm.write(dsm, 1)
255
+ if self.save_intermediate_data:
256
+ shutil.copy2(dsm_file, new_dsm_path)
257
+
258
+ if filling_file is not None:
259
+ with rio.open(filling_file, "r") as src:
260
+ fill_meta = src.meta
261
+ bands = [src.read(i + 1) for i in range(src.count)]
262
+ bands_desc = [src.descriptions[i] for i in range(src.count)]
263
+ fill_meta["count"] += 1
264
+ bands.append(combined_mask.astype(np.uint8))
265
+ bands_desc.append("border_interpolation")
266
+
267
+ with rio.open(filling_file, "w", **fill_meta) as out:
268
+ for i, band in enumerate(bands):
269
+ out.write(band, i + 1)
270
+ out.set_band_description(i + 1, bands_desc[i])
@@ -0,0 +1,44 @@
1
+ # both paths will be overwritten in the filling application
2
+ dsm_path: "path/to/dsm"
3
+ output_dir: "path/to/output"
4
+
5
+ # everything else will be used directly as-is
6
+
7
+ #-------------------------#
8
+ # Options #
9
+ #-------------------------#
10
+
11
+
12
+ max_object_size : 16
13
+ # If True, keep the intermediate results
14
+ developer_mode : False
15
+
16
+
17
+ #-------------------------#
18
+ # Advanced settings #
19
+ #-------------------------#
20
+ # [Optional] - Altimetric height accuracy of the input DSM (m). If null, use the default value: 2*planimetric resolution
21
+ dsm_z_accuracy: null
22
+ # [Optional] - Maximum slope of the observed landscape terrain (%)
23
+ # former slope_threshold
24
+ max_ground_slope: 2.0
25
+ # [Optional] - If True, activate the ground pre-detection
26
+ activate_ground_anchors : False
27
+ # [Optional] - If True, keep the intermediate results
28
+ developer_mode : False
29
+
30
+
31
+ #-------------------------#
32
+ # Bulldozer core settings #
33
+ #-------------------------#
34
+ # /!\ Modify those data at your own risk (it is suggested to keep the default values) /!\
35
+
36
+ # [Optional] - DtmExtraction parameters
37
+ # former uniform_filter_size
38
+ cloth_tension_force : 3
39
+ prevent_unhook_iter : 10
40
+ num_outer_iter : 50
41
+ num_inner_iter : 10
42
+
43
+
44
+
@@ -0,0 +1,279 @@
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
+ ):
119
+ """
120
+ Run dsm filling using initial elevation and the current dsm
121
+ Replaces dsm.tif by the filled dsm. Adds a new band
122
+ to filling.tif if it exists.
123
+ The old dsm is saved in dump_dir.
124
+
125
+ roi_poly can any of these objects :
126
+ - a list of Shapely Polygons
127
+ - a Shapely Polygon
128
+ """
129
+
130
+ if self.classification is None:
131
+ self.classification = ["nodata"]
132
+
133
+ if not os.path.exists(dump_dir):
134
+ os.makedirs(dump_dir)
135
+
136
+ old_dsm_path = os.path.join(dump_dir, "dsm_not_filled.tif")
137
+ new_dsm_path = os.path.join(dump_dir, "dsm_filled.tif")
138
+
139
+ # create the config for the bulldozer execution
140
+ bull_conf_path = os.path.join(
141
+ os.path.dirname(__file__), "bulldozer_config/base_config.yaml"
142
+ )
143
+ with open(bull_conf_path, "r", encoding="utf8") as bull_conf_file:
144
+ bull_conf = yaml.safe_load(bull_conf_file)
145
+
146
+ bull_conf["dsm_path"] = dsm_file
147
+ bull_conf["output_dir"] = os.path.join(dump_dir, "bulldozer")
148
+
149
+ if orchestrator is not None:
150
+ if (
151
+ orchestrator.get_conf()["mode"] == "multiprocessing"
152
+ or orchestrator.get_conf()["mode"] == "local_dask"
153
+ ):
154
+ bull_conf["nb_max_workers"] = orchestrator.get_conf()[
155
+ "nb_workers"
156
+ ]
157
+
158
+ bull_conf_path = os.path.join(dump_dir, "bulldozer_config.yaml")
159
+ with open(bull_conf_path, "w", encoding="utf8") as bull_conf_file:
160
+ yaml.dump(bull_conf, bull_conf_file)
161
+
162
+ dtm_path = os.path.join(bull_conf["output_dir"], "dtm.tif")
163
+
164
+ # get dsm to be filled and its metadata
165
+ with rio.open(dsm_file) as in_dsm:
166
+ dsm = in_dsm.read(1)
167
+ dsm_tr = in_dsm.transform
168
+ dsm_crs = in_dsm.crs
169
+ dsm_meta = in_dsm.meta
170
+
171
+ roi_raster = np.ones(dsm.shape)
172
+
173
+ if isinstance(roi_polys, list):
174
+ roi_polys_outepsg = []
175
+ for poly in roi_polys:
176
+ if isinstance(poly, Polygon):
177
+ roi_poly_outepsg = projection.polygon_projection_crs(
178
+ poly, CRS(roi_epsg), dsm_crs
179
+ )
180
+ roi_polys_outepsg.append(roi_poly_outepsg)
181
+
182
+ roi_raster = rio.features.rasterize(
183
+ roi_polys_outepsg, out_shape=roi_raster.shape, transform=dsm_tr
184
+ )
185
+ elif isinstance(roi_polys, Polygon):
186
+ roi_poly_outepsg = projection.polygon_projection_crs(
187
+ roi_polys, CRS(roi_epsg), dsm_crs
188
+ )
189
+ roi_raster = rio.features.rasterize(
190
+ [roi_poly_outepsg], out_shape=roi_raster.shape, transform=dsm_tr
191
+ )
192
+
193
+ try:
194
+ try:
195
+ # suppress prints in bulldozer by redirecting stdout&stderr
196
+ with open(os.devnull, "w", encoding="utf8") as devnull:
197
+ with (
198
+ contextlib.redirect_stdout(devnull),
199
+ contextlib.redirect_stderr(devnull),
200
+ ):
201
+ dsm_to_dtm(bull_conf_path)
202
+ except Exception:
203
+ logging.info(
204
+ "Bulldozer failed on its first execution. Retrying"
205
+ )
206
+ # suppress prints in bulldozer by redirecting stdout&stderr
207
+ with open(os.devnull, "w", encoding="utf8") as devnull:
208
+ with (
209
+ contextlib.redirect_stdout(devnull),
210
+ contextlib.redirect_stderr(devnull),
211
+ ):
212
+ dsm_to_dtm(bull_conf_path)
213
+ except Exception:
214
+ logging.warning(
215
+ "Bulldozer failed on its second execution."
216
+ + " The DSM could not be filled."
217
+ )
218
+ return None
219
+ with rio.open(dtm_path) as in_dtm:
220
+ dtm = in_dtm.read(1)
221
+
222
+ if self.save_intermediate_data:
223
+ with rio.open(old_dsm_path, "w", **dsm_meta) as out_dsm:
224
+ out_dsm.write(dsm, 1)
225
+
226
+ if classif_file is not None and os.path.exists(classif_file):
227
+ classif_descriptions = inputs.get_descriptions_bands(classif_file)
228
+ else:
229
+ classif_descriptions = []
230
+ combined_mask = np.zeros_like(dsm).astype(np.uint8)
231
+ for label in self.classification:
232
+ if label in classif_descriptions:
233
+ index_classif = classif_descriptions.index(label) + 1
234
+ with rio.open(classif_file) as in_classif:
235
+ classif = in_classif.read(index_classif)
236
+ with warnings.catch_warnings():
237
+ warnings.simplefilter("ignore", NodataShadowWarning)
238
+ classif_msk = in_classif.read_masks(1)
239
+ classif[classif_msk == 0] = 0
240
+ filling_mask = np.logical_and(classif, roi_raster > 0)
241
+ elif label == "nodata":
242
+ if classif_file is not None and os.path.exists(classif_file):
243
+ with rio.open(classif_file) as in_classif:
244
+ classif_msk = in_classif.read_masks(1)
245
+ classif = ~classif_msk
246
+ else:
247
+ with rio.open(dsm_file) as in_dsm:
248
+ dsm_msk = in_dsm.read_masks(1)
249
+ classif = ~dsm_msk
250
+ filling_mask = np.logical_and(classif, roi_raster > 0)
251
+ else:
252
+ logging.error(
253
+ "Label {} not found in classification "
254
+ "descriptions {}".format(label, classif_descriptions)
255
+ )
256
+ continue
257
+ logging.info("Filling of {} with Bulldozer DTM".format(label))
258
+ dsm[filling_mask] = dtm[filling_mask]
259
+ combined_mask = np.logical_or(combined_mask, filling_mask)
260
+
261
+ with rio.open(dsm_file, "w", **dsm_meta) as out_dsm:
262
+ out_dsm.write(dsm, 1)
263
+ if self.save_intermediate_data:
264
+ shutil.copy2(dsm_file, new_dsm_path)
265
+
266
+ if filling_file is not None:
267
+ with rio.open(filling_file, "r") as src:
268
+ fill_meta = src.meta
269
+ bands = [src.read(i + 1) for i in range(src.count)]
270
+ bands_desc = [src.descriptions[i] for i in range(src.count)]
271
+ fill_meta["count"] += 1
272
+ bands.append(combined_mask.astype(np.uint8))
273
+ bands_desc.append("bulldozer")
274
+
275
+ with rio.open(filling_file, "w", **fill_meta) as out:
276
+ for i, band in enumerate(bands):
277
+ out.write(band, i + 1)
278
+ out.set_band_description(i + 1, bands_desc[i])
279
+ return dtm_path