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,842 @@
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
+ # pylint: disable=C0302
21
+ """
22
+ CARS containing inputs checking for sensor input data
23
+ Used for full_res and low_res pipelines
24
+ """
25
+
26
+ import logging
27
+ import math
28
+ import os
29
+
30
+ import numpy as np
31
+ import rasterio as rio
32
+ from json_checker import Checker, Or
33
+
34
+ # CARS imports
35
+ from cars.core import inputs, projection
36
+ from cars.core.geometry.abstract_geometry import AbstractGeometry
37
+ from cars.core.utils import make_relative_path_absolute
38
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
39
+ from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
40
+ from cars.pipelines.parameters.sensor_loaders.sensor_loader import SensorLoader
41
+
42
+ CARS_GEOID_PATH = "geoid/egm96.grd" # Path in cars package (pkg)
43
+
44
+
45
+ def sensors_check_inputs(conf, config_dir=None): # noqa: C901
46
+ """
47
+ Check the inputs given
48
+
49
+ :param conf: configuration of inputs
50
+ :type conf: dict
51
+ :param config_dir: path to dir containing json
52
+ :type config_dir: str
53
+ """
54
+
55
+ overloaded_conf = conf.copy()
56
+
57
+ overloaded_conf[sens_cst.ROI] = conf.get(sens_cst.ROI, None)
58
+
59
+ overloaded_conf[sens_cst.PAIRING] = conf.get(sens_cst.PAIRING, None)
60
+
61
+ overloaded_conf[sens_cst.INITIAL_ELEVATION] = get_initial_elevation(
62
+ conf.get(sens_cst.INITIAL_ELEVATION, None)
63
+ )
64
+
65
+ overloaded_conf[sens_cst.LOADERS] = check_loaders(
66
+ conf.get(sens_cst.LOADERS, {})
67
+ )
68
+
69
+ classif_loader = overloaded_conf[sens_cst.LOADERS][
70
+ sens_cst.INPUT_CLASSIFICATION
71
+ ]
72
+
73
+ overloaded_conf[sens_cst.FILLING] = check_filling(
74
+ conf.get(sens_cst.FILLING, {}), classif_loader
75
+ )
76
+
77
+ # Validate inputs
78
+ inputs_schema = {
79
+ sens_cst.SENSORS: dict,
80
+ sens_cst.PAIRING: Or([[str]], None),
81
+ sens_cst.INITIAL_ELEVATION: Or(str, dict, None),
82
+ sens_cst.ROI: Or(str, dict, None),
83
+ sens_cst.LOADERS: dict,
84
+ sens_cst.FILLING: dict,
85
+ }
86
+
87
+ checker_inputs = Checker(inputs_schema)
88
+ checker_inputs.validate(overloaded_conf)
89
+
90
+ check_sensors(conf, overloaded_conf, config_dir)
91
+
92
+ # Check srtm dir
93
+ check_srtm(overloaded_conf[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH])
94
+
95
+ return overloaded_conf
96
+
97
+
98
+ def check_sensors(conf, overloaded_conf, config_dir=None): # noqa: C901
99
+ """
100
+ Check sensors
101
+ """
102
+ # Validate each sensor image
103
+ sensor_schema = {
104
+ sens_cst.INPUT_IMG: Or(str, dict),
105
+ sens_cst.INPUT_GEO_MODEL: Or(str, dict),
106
+ sens_cst.INPUT_MSK: Or(str, None),
107
+ sens_cst.INPUT_CLASSIFICATION: Or(str, dict, None),
108
+ }
109
+
110
+ checker_sensor = Checker(sensor_schema)
111
+
112
+ for sensor_image_key in conf[sens_cst.SENSORS]:
113
+ # Case where the sensor is defined as a string refering to the input
114
+ # image instead of a dict
115
+ if isinstance(conf[sens_cst.SENSORS][sensor_image_key], str):
116
+ # initialize sensor dictionary
117
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key] = {
118
+ sens_cst.INPUT_IMG: conf[sens_cst.SENSORS][sensor_image_key]
119
+ }
120
+
121
+ # Overload parameters
122
+ image = overloaded_conf[sens_cst.SENSORS][sensor_image_key].get(
123
+ sens_cst.INPUT_IMG, None
124
+ )
125
+ loader_name = (
126
+ overloaded_conf[sens_cst.LOADERS][sens_cst.INPUT_IMG]
127
+ + "_"
128
+ + sens_cst.INPUT_IMG
129
+ )
130
+ image_loader = SensorLoader(loader_name, image, config_dir)
131
+ image_as_pivot_format = (
132
+ image_loader.get_pivot_format() # pylint: disable=E1101
133
+ )
134
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key][
135
+ sens_cst.INPUT_IMG
136
+ ] = image_as_pivot_format
137
+ image_path = image_as_pivot_format["bands"]["b0"]["path"]
138
+
139
+ geomodel = overloaded_conf[sens_cst.SENSORS][sensor_image_key].get(
140
+ "geomodel",
141
+ image_path,
142
+ )
143
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key][
144
+ "geomodel"
145
+ ] = geomodel
146
+
147
+ mask = overloaded_conf[sens_cst.SENSORS][sensor_image_key].get(
148
+ sens_cst.INPUT_MSK, None
149
+ )
150
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key][
151
+ sens_cst.INPUT_MSK
152
+ ] = mask
153
+
154
+ classif = overloaded_conf[sens_cst.SENSORS][sensor_image_key].get(
155
+ sens_cst.INPUT_CLASSIFICATION, None
156
+ )
157
+ if classif is not None:
158
+ loader_name = (
159
+ overloaded_conf[sens_cst.LOADERS][sens_cst.INPUT_CLASSIFICATION]
160
+ + "_"
161
+ + sens_cst.INPUT_CLASSIFICATION
162
+ )
163
+ classif_loader = SensorLoader(loader_name, classif, config_dir)
164
+ classif_as_pivot_format = (
165
+ classif_loader.get_pivot_format() # pylint: disable=E1101
166
+ )
167
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key][
168
+ sens_cst.INPUT_CLASSIFICATION
169
+ ] = classif_as_pivot_format
170
+ else:
171
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key][
172
+ sens_cst.INPUT_CLASSIFICATION
173
+ ] = None
174
+
175
+ # Validate
176
+ checker_sensor.validate(
177
+ overloaded_conf[sens_cst.SENSORS][sensor_image_key]
178
+ )
179
+
180
+ # Image are now in pivot format
181
+ overloaded_conf[sens_cst.LOADERS][sens_cst.INPUT_IMG] = "pivot"
182
+ overloaded_conf[sens_cst.LOADERS][sens_cst.INPUT_CLASSIFICATION] = "pivot"
183
+
184
+ # Modify to absolute path
185
+ if config_dir is not None:
186
+ modify_to_absolute_path(config_dir, overloaded_conf)
187
+
188
+ # Check image, msk and color size compatibility
189
+ for sensor_image_key in overloaded_conf[sens_cst.SENSORS]:
190
+ sensor_image = overloaded_conf[sens_cst.SENSORS][sensor_image_key]
191
+ check_input_size(
192
+ sensor_image[sens_cst.INPUT_IMG],
193
+ sensor_image[sens_cst.INPUT_MSK],
194
+ sensor_image[sens_cst.INPUT_CLASSIFICATION],
195
+ )
196
+ # check band nbits of msk
197
+ check_nbits(
198
+ sensor_image[sens_cst.INPUT_MSK],
199
+ )
200
+
201
+ # Validate pairs
202
+ # If there is two inputs with no associated pairing, consider that the first
203
+ # image is left and the second image is right
204
+ if (
205
+ overloaded_conf[sens_cst.PAIRING] is None
206
+ and len(overloaded_conf[sens_cst.SENSORS]) == 2
207
+ ):
208
+ sensor_keys = list(overloaded_conf[sens_cst.SENSORS].keys())
209
+ overloaded_conf[sens_cst.PAIRING] = [[sensor_keys[0], sensor_keys[1]]]
210
+ logging.info(
211
+ (
212
+ "Pairing is not defined, '{}' will be used as left sensor and "
213
+ + "'{}' will be used as right sensor"
214
+ ).format(sensor_keys[0], sensor_keys[1])
215
+ )
216
+
217
+ if overloaded_conf[sens_cst.PAIRING] is None:
218
+ raise RuntimeError(
219
+ "Pairing is not defined and cannot be determined "
220
+ + "because there are more than two inputs products"
221
+ )
222
+
223
+ for key1, key2 in overloaded_conf[sens_cst.PAIRING]:
224
+ if key1 not in overloaded_conf[sens_cst.SENSORS]:
225
+ logging.error("{} not in sensors images".format(key1))
226
+ raise RuntimeError("{} not in sensors images".format(key1))
227
+ if key2 not in overloaded_conf["sensors"]:
228
+ logging.error("{} not in sensors images".format(key2))
229
+ raise RuntimeError("{} not in sensors images".format(key2))
230
+
231
+ # Modify to absolute path
232
+ if config_dir is not None:
233
+ modify_to_absolute_path(config_dir, overloaded_conf)
234
+ else:
235
+ logging.debug(
236
+ "path of config file was not given,"
237
+ "relative path are not transformed to absolute paths"
238
+ )
239
+
240
+ # Check consistency of pairs images
241
+ for key1, key2 in overloaded_conf[sens_cst.PAIRING]:
242
+ compare_image_type(
243
+ overloaded_conf[sens_cst.SENSORS], sens_cst.INPUT_IMG, key1, key2
244
+ )
245
+ compare_classification_values(
246
+ overloaded_conf[sens_cst.SENSORS],
247
+ sens_cst.INPUT_CLASSIFICATION,
248
+ key1,
249
+ key2,
250
+ overloaded_conf[sens_cst.FILLING],
251
+ )
252
+
253
+ return overloaded_conf
254
+
255
+
256
+ def check_loaders(conf):
257
+ """
258
+ Check loaders section
259
+ :param conf: loaders section of input conf
260
+ :type conf: dict
261
+ """
262
+ overloaded_conf = conf.copy()
263
+ overloaded_conf[sens_cst.INPUT_IMG] = conf.get(sens_cst.INPUT_IMG, "basic")
264
+ overloaded_conf[sens_cst.INPUT_CLASSIFICATION] = conf.get(
265
+ sens_cst.INPUT_CLASSIFICATION, "basic"
266
+ )
267
+
268
+ # Validate loaders
269
+ loaders_schema = {
270
+ sens_cst.INPUT_IMG: str,
271
+ sens_cst.INPUT_CLASSIFICATION: str,
272
+ }
273
+
274
+ checker_loaders = Checker(loaders_schema)
275
+ checker_loaders.validate(overloaded_conf)
276
+
277
+ return overloaded_conf
278
+
279
+
280
+ def check_filling(conf, classif_loader):
281
+ """
282
+ Check filling section
283
+ :param conf: filling section of input conf
284
+ :type conf: dict
285
+ """
286
+ basic_filling = {
287
+ "fill_with_geoid": None,
288
+ "interpolate_from_borders": None,
289
+ "fill_with_endogenous_dem": None,
290
+ "fill_with_exogenous_dem": None,
291
+ }
292
+ slurp_filling = {
293
+ "fill_with_geoid": [8],
294
+ "interpolate_from_borders": [9],
295
+ "fill_with_endogenous_dem": [10],
296
+ "fill_with_exogenous_dem": [6],
297
+ }
298
+ filling_from_loader = {}
299
+ filling_from_loader["basic"] = basic_filling
300
+ filling_from_loader["slurp"] = slurp_filling
301
+ filling_from_loader["pivot"] = basic_filling
302
+ default_filling = filling_from_loader[classif_loader]
303
+ overloaded_conf = conf.copy()
304
+ for filling_method in basic_filling:
305
+ overloaded_conf[filling_method] = conf.get(
306
+ filling_method, default_filling[filling_method]
307
+ )
308
+ if isinstance(overloaded_conf[filling_method], int):
309
+ overloaded_conf[filling_method] = [overloaded_conf[filling_method]]
310
+
311
+ # Validate loaders
312
+ loaders_schema = {
313
+ "fill_with_geoid": Or(None, [int]),
314
+ "interpolate_from_borders": Or(None, [int]),
315
+ "fill_with_endogenous_dem": Or(None, [int]),
316
+ "fill_with_exogenous_dem": Or(None, [int]),
317
+ }
318
+
319
+ checker_loaders = Checker(loaders_schema)
320
+ checker_loaders.validate(overloaded_conf)
321
+
322
+ return overloaded_conf
323
+
324
+
325
+ def get_sensor_resolution(
326
+ geom_plugin, sensor_path, geomodel, target_epsg=32631
327
+ ):
328
+ """
329
+ Estimate the sensor image resolution in meters per pixel
330
+ using geolocation of 3 corners of the image.
331
+
332
+ :param geom_plugin: geometry plugin instance
333
+ :param sensor_path: path to the sensor image
334
+ :type sensor_path: dict
335
+ :param geomodel: geometric model for the sensor image
336
+ :param target_epsg: target EPSG code for projection
337
+ :type target_epsg: int
338
+ :return: average resolution in meters/pixel along x and y
339
+ :rtype: float
340
+ """
341
+ width, height = inputs.rasterio_get_size(sensor_path["bands"]["b0"]["path"])
342
+
343
+ upper_left = (0.5, 0.5)
344
+ upper_right = (width - 0.5, 0.5)
345
+ bottom_left = (0.5, height - 0.5)
346
+
347
+ # get geodetic coordinates
348
+ lat_ul, lon_ul, _ = geom_plugin.direct_loc(
349
+ sensor_path["bands"]["b0"]["path"],
350
+ geomodel,
351
+ np.array([upper_left[0]]),
352
+ np.array([upper_left[1]]),
353
+ )
354
+ lat_ur, lon_ur, _ = geom_plugin.direct_loc(
355
+ sensor_path["bands"]["b0"]["path"],
356
+ geomodel,
357
+ np.array([upper_right[0]]),
358
+ np.array([upper_right[1]]),
359
+ )
360
+ lat_bl, lon_bl, _ = geom_plugin.direct_loc(
361
+ sensor_path["bands"]["b0"]["path"],
362
+ geomodel,
363
+ np.array([bottom_left[0]]),
364
+ np.array([bottom_left[1]]),
365
+ )
366
+
367
+ coords_ll = np.array(
368
+ [[lon_ul, lat_ul, 0], [lon_ur, lat_ur, 0], [lon_bl, lat_bl, 0]]
369
+ )
370
+
371
+ # Convert to target CRS
372
+ coords_xy = projection.point_cloud_conversion(coords_ll, 4326, target_epsg)
373
+
374
+ diff_x = np.linalg.norm(coords_xy[1] - coords_xy[0]) # UL to UR (width)
375
+ diff_y = np.linalg.norm(coords_xy[2] - coords_xy[0]) # UL to BL (height)
376
+
377
+ # resolution in meters per pixel
378
+ res_x = diff_x / (width - 1)
379
+ res_y = diff_y / (height - 1)
380
+
381
+ return (res_x + res_y) / 2
382
+
383
+
384
+ def check_geometry_plugin(
385
+ conf_inputs, conf_geom_plugin, epipolar_resolution, output_dem_dir
386
+ ):
387
+ """
388
+ Check the geometry plugin with inputs
389
+
390
+ :param conf_inputs: checked configuration of inputs
391
+ :type conf_inputs: type
392
+ :param conf_advanced: checked configuration of advanced
393
+ :type conf_advanced: type
394
+ :param conf_geom_plugin: name of geometry plugin
395
+ :type conf_geom_plugin: str
396
+ :param epipolar_resolution: epipolar resolution
397
+ :type epipolar_resolution: int
398
+ :return: overload inputs conf
399
+ overloaded geometry plugin conf
400
+ geometry plugin without dem
401
+ geometry plugin with dem
402
+ """
403
+ if conf_geom_plugin is None:
404
+ conf_geom_plugin = "SharelocGeometry"
405
+
406
+ # Initialize a temporary plugin, to get the product's resolution
407
+ temp_geom_plugin = (
408
+ AbstractGeometry( # pylint: disable=abstract-class-instantiated
409
+ conf_geom_plugin,
410
+ default_alt=sens_cst.CARS_DEFAULT_ALT,
411
+ )
412
+ )
413
+ average_sensor_resolution = 0
414
+ for _, sensor_image in conf_inputs[sens_cst.SENSORS].items():
415
+ sensor = sensor_image[sens_cst.INPUT_IMG]
416
+ geomodel = sensor_image[sens_cst.INPUT_GEO_MODEL]
417
+ (
418
+ sensor,
419
+ geomodel,
420
+ ) = temp_geom_plugin.check_product_consistency(sensor, geomodel)
421
+ average_sensor_resolution += (
422
+ get_sensor_resolution(temp_geom_plugin, sensor, geomodel)
423
+ * epipolar_resolution
424
+ )
425
+ average_sensor_resolution /= len(conf_inputs[sens_cst.SENSORS])
426
+ # approximate resolution to the highest digit:
427
+ # 0.47 -> 0.5
428
+ # 7.52 -> 8
429
+ # 12.9 -> 10
430
+ nb_digits = int(math.floor(math.log10(abs(average_sensor_resolution))))
431
+ scaling_coeff = round(average_sensor_resolution, -nb_digits)
432
+ # make it so 0.5 (CO3D) is the baseline for parameters
433
+ scaling_coeff *= 2
434
+
435
+ # Initialize the desired geometry plugin without elevation information
436
+ geom_plugin_without_dem_and_geoid = (
437
+ AbstractGeometry( # pylint: disable=abstract-class-instantiated
438
+ conf_geom_plugin,
439
+ default_alt=sens_cst.CARS_DEFAULT_ALT,
440
+ scaling_coeff=scaling_coeff,
441
+ )
442
+ )
443
+
444
+ # Check products consistency with this plugin
445
+ overloaded_conf_inputs = conf_inputs.copy()
446
+ for sensor_key, sensor_image in conf_inputs[sens_cst.SENSORS].items():
447
+ sensor = sensor_image[sens_cst.INPUT_IMG]
448
+ geomodel = sensor_image[sens_cst.INPUT_GEO_MODEL]
449
+ (
450
+ sensor,
451
+ geomodel,
452
+ ) = geom_plugin_without_dem_and_geoid.check_product_consistency(
453
+ sensor, geomodel
454
+ )
455
+ overloaded_conf_inputs[sens_cst.SENSORS][sensor_key][
456
+ sens_cst.INPUT_IMG
457
+ ] = sensor
458
+ overloaded_conf_inputs[sens_cst.SENSORS][sensor_key][
459
+ sens_cst.INPUT_GEO_MODEL
460
+ ] = geomodel
461
+
462
+ geom_plugin_with_dem_and_geoid = generate_geometry_plugin_with_dem(
463
+ conf_geom_plugin,
464
+ conf_inputs,
465
+ scaling_coeff=scaling_coeff,
466
+ output_dem_dir=output_dem_dir,
467
+ )
468
+
469
+ return (
470
+ overloaded_conf_inputs,
471
+ conf_geom_plugin,
472
+ geom_plugin_without_dem_and_geoid,
473
+ geom_plugin_with_dem_and_geoid,
474
+ scaling_coeff,
475
+ )
476
+
477
+
478
+ # pylint: disable=too-many-positional-arguments
479
+ def generate_geometry_plugin_with_dem(
480
+ conf_geom_plugin,
481
+ conf_inputs,
482
+ dem=None,
483
+ crop_dem=True,
484
+ output_dem_dir=None,
485
+ scaling_coeff=1,
486
+ ):
487
+ """
488
+ Generate geometry plugin with dem and geoid
489
+
490
+ :param conf_geom_plugin: plugin configuration
491
+ :param conf_inputs: inputs configuration
492
+ :param dem: dem to overide the one in inputs
493
+ :param scaling_coeff: scaling factor for resolution
494
+ :type scaling_coeff: float
495
+
496
+ :return: geometry plugin object, with a dem
497
+ """
498
+
499
+ dem_path = (
500
+ dem
501
+ if dem is not None
502
+ else conf_inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH]
503
+ )
504
+
505
+ if crop_dem:
506
+ # Get image pairs for DEM intersection with ROI
507
+ pairs_for_roi = []
508
+ for key1, key2 in conf_inputs[sens_cst.PAIRING]:
509
+ sensor1 = conf_inputs[sens_cst.SENSORS][key1]
510
+ sensor2 = conf_inputs[sens_cst.SENSORS][key2]
511
+ image1 = sensor1[sens_cst.INPUT_IMG]
512
+ image2 = sensor2[sens_cst.INPUT_IMG]
513
+ geomodel1 = sensor1[sens_cst.INPUT_GEO_MODEL]
514
+ geomodel2 = sensor2[sens_cst.INPUT_GEO_MODEL]
515
+ pairs_for_roi.append((image1, geomodel1, image2, geomodel2))
516
+ else:
517
+ pairs_for_roi = None
518
+
519
+ # Initialize a second geometry plugin with elevation information
520
+
521
+ geom_plugin_with_dem_and_geoid = (
522
+ AbstractGeometry( # pylint: disable=abstract-class-instantiated
523
+ conf_geom_plugin,
524
+ dem=dem_path,
525
+ geoid=conf_inputs[sens_cst.INITIAL_ELEVATION][sens_cst.GEOID],
526
+ default_alt=sens_cst.CARS_DEFAULT_ALT,
527
+ pairs_for_roi=pairs_for_roi,
528
+ scaling_coeff=scaling_coeff,
529
+ output_dem_dir=output_dem_dir,
530
+ )
531
+ )
532
+
533
+ return geom_plugin_with_dem_and_geoid
534
+
535
+
536
+ def modify_to_absolute_path(config_dir, overloaded_conf):
537
+ """
538
+ Modify input file path to absolute path
539
+
540
+ :param config_dir: directory of the json configuration
541
+ :type config_dir: str
542
+ :param overloaded_conf: overloaded configuration json
543
+ :dict overloaded_conf: dict
544
+ """
545
+
546
+ for sensor_image_key in overloaded_conf[sens_cst.SENSORS]:
547
+ sensor_image = overloaded_conf[sens_cst.SENSORS][sensor_image_key]
548
+ for tag in [
549
+ sens_cst.INPUT_MSK,
550
+ sens_cst.INPUT_GEO_MODEL,
551
+ ]:
552
+ if isinstance(sensor_image[tag], dict):
553
+ sensor_image[tag][sens_cst.INPUT_PATH] = (
554
+ make_relative_path_absolute(
555
+ sensor_image[tag][sens_cst.INPUT_PATH], config_dir
556
+ )
557
+ )
558
+ elif sensor_image[tag] is not None:
559
+ sensor_image[tag] = make_relative_path_absolute(
560
+ sensor_image[tag], config_dir
561
+ )
562
+
563
+ if overloaded_conf[sens_cst.ROI] is not None:
564
+ if isinstance(overloaded_conf[sens_cst.ROI], str):
565
+ overloaded_conf[sens_cst.ROI] = make_relative_path_absolute(
566
+ overloaded_conf[sens_cst.ROI], config_dir
567
+ )
568
+
569
+ for tag in [sens_cst.DEM_PATH, sens_cst.GEOID]:
570
+ if overloaded_conf[sens_cst.INITIAL_ELEVATION][tag] is not None:
571
+ if isinstance(
572
+ overloaded_conf[sens_cst.INITIAL_ELEVATION][tag], str
573
+ ):
574
+ overloaded_conf[sens_cst.INITIAL_ELEVATION][tag] = (
575
+ make_relative_path_absolute(
576
+ overloaded_conf[sens_cst.INITIAL_ELEVATION][tag],
577
+ config_dir,
578
+ )
579
+ )
580
+
581
+
582
+ def check_srtm(srtm_dir):
583
+ """
584
+ Check srtm data
585
+
586
+ :param srtm_dir: directory of srtm
587
+ :type srtm_dir: str
588
+ """
589
+
590
+ if srtm_dir is not None:
591
+ if os.path.isdir(srtm_dir):
592
+ srtm_tiles = os.listdir(srtm_dir)
593
+ if len(srtm_tiles) == 0:
594
+ logging.warning(
595
+ "SRTM directory is empty, "
596
+ "the default altitude will be used as reference altitude."
597
+ )
598
+ else:
599
+ logging.info(
600
+ "Indicated SRTM tiles valid regions "
601
+ "will be used as reference altitudes "
602
+ "(the default altitude is used "
603
+ "for undefined regions of the SRTM)"
604
+ )
605
+ else:
606
+ # TODO add check for single file
607
+ pass
608
+ else:
609
+ logging.info("The default altitude will be used as reference altitude.")
610
+
611
+
612
+ def check_input_data(image, color):
613
+ """
614
+ Check data of the image and color
615
+
616
+ :param image: image path
617
+ :type image: str
618
+ :param color: color path
619
+ :type color: str
620
+ """
621
+
622
+ with rio.open(image) as img_reader:
623
+ trans = img_reader.transform
624
+ if trans.e < 0:
625
+ logging.warning(
626
+ "{} seems to have an incoherent pixel size. "
627
+ "Input images has to be in sensor geometry.".format(image)
628
+ )
629
+
630
+ with rio.open(color) as img_reader:
631
+ trans = img_reader.transform
632
+ if trans.e < 0:
633
+ logging.warning(
634
+ "{} seems to have an incoherent pixel size. "
635
+ "Input images has to be in sensor geometry.".format(image)
636
+ )
637
+
638
+
639
+ def get_initial_elevation(config):
640
+ """
641
+ Return initial elevation parameters (dem and geoid paths)
642
+ from input configuration.
643
+
644
+ :param config: input initial elevation
645
+ :type config: str, dict or None
646
+ """
647
+
648
+ # Case 1 config is already a dict
649
+ if isinstance(config, dict):
650
+ updated_config = config
651
+ else:
652
+ updated_config = {}
653
+ updated_config[sens_cst.DEM_PATH] = (
654
+ config if isinstance(config, str) else None
655
+ )
656
+
657
+ # Add geoid path to the initial_elevation dict
658
+ if sens_cst.GEOID not in updated_config:
659
+ # use cars geoid
660
+ logging.info("CARS will use its own internal file as geoid reference")
661
+ # Get root package directory
662
+ package_path = os.path.dirname(__file__)
663
+ geoid_path = os.path.join(
664
+ package_path, "..", "..", "conf", CARS_GEOID_PATH
665
+ )
666
+ updated_config[sens_cst.GEOID] = geoid_path
667
+
668
+ return updated_config
669
+
670
+
671
+ def check_input_size(image, mask, classif):
672
+ """
673
+ Check image, mask, classif and color given
674
+
675
+ Images must have same size
676
+
677
+ :param image: image path
678
+ :type image: str
679
+ :param mask: mask path
680
+ :type mask: str
681
+ :param color: color path
682
+ :type color: str
683
+ :param classif: classif path
684
+ :type classif: str
685
+ """
686
+ image = image["bands"]["b0"]["path"]
687
+ if classif is not None:
688
+ classif = classif[sens_cst.INPUT_PATH]
689
+
690
+ if mask is not None:
691
+ if inputs.rasterio_get_size(image) != inputs.rasterio_get_size(mask):
692
+ raise RuntimeError(
693
+ "The image {} and the mask {} "
694
+ "do not have the same size".format(image, mask)
695
+ )
696
+
697
+ if classif is not None:
698
+ if inputs.rasterio_get_size(image) != inputs.rasterio_get_size(classif):
699
+ raise RuntimeError(
700
+ "The classification bands {} and {} "
701
+ "do not have the same size".format(image, classif)
702
+ )
703
+
704
+
705
+ def check_nbits(mask):
706
+ """
707
+ Check the bits number of the mask, classif
708
+ mask and classification are limited to 1 bits per band
709
+
710
+ :param mask: mask path
711
+ :type mask: str
712
+ :param classif: classif path
713
+ :type classif: str
714
+ """
715
+ if mask is not None:
716
+ nbits = inputs.rasterio_get_nbits(mask)
717
+ if not check_all_nbits_equal_one(nbits):
718
+ raise RuntimeError(
719
+ "The mask {} have {} nbits per band. ".format(mask, nbits)
720
+ + "Only the mask with nbits=1 is supported! "
721
+ )
722
+
723
+
724
+ def compare_image_type(sensors, sensor_type, key1, key2):
725
+ """
726
+ Compare the data type between a pair of images
727
+
728
+ :param sensors: list of sensor paths
729
+ :type sensors: str
730
+ :param sensor_type: type of cardataset image (IMG, MASK, CLASSIF...)
731
+ :type sensor_type: int
732
+ :param key1: key of the images pair
733
+ :type key1: str
734
+ :param key2: other key of the images pair
735
+ :type key2: str
736
+ """
737
+ dtype1 = inputs.rasterio_get_image_type(
738
+ sensors[key1][sensor_type]["bands"]["b0"]["path"]
739
+ )
740
+ dtype2 = inputs.rasterio_get_image_type(
741
+ sensors[key2][sensor_type]["bands"]["b0"]["path"]
742
+ )
743
+
744
+ if dtype1 != dtype2:
745
+ raise RuntimeError(
746
+ "The pair images haven't the same data type."
747
+ + "\nSensor[{}]: {}".format(key1, dtype1)
748
+ + "; Sensor[{}]: {}".format(key2, dtype2)
749
+ )
750
+
751
+
752
+ def compare_classification_values(sensors, sensor_type, key1, key2, filling):
753
+ """
754
+ Compare the classification values between a pair of images
755
+
756
+ :param imgs: list of image paths
757
+ :type imgs: str
758
+ :param classif_type: type of cardataset image (IMG, MASK, CLASSIF...)
759
+ :type classif_type: int
760
+ :param key1: key of the images pair
761
+ :type key1: str
762
+ :param key2: other key of the images pair
763
+ :type key2: str
764
+ """
765
+ classif1 = sensors[key1][sensor_type]
766
+ classif2 = sensors[key2][sensor_type]
767
+ if classif1 is not None and classif2 is not None:
768
+ values1 = classif1[sens_cst.INPUT_VALUES]
769
+ values2 = classif2[sens_cst.INPUT_VALUES]
770
+ all_values = list(set(values1) | set(values2))
771
+ classif1[sens_cst.INPUT_VALUES] = all_values
772
+ classif2[sens_cst.INPUT_VALUES] = all_values
773
+ for filling_method in filling:
774
+ filling_values = filling[filling_method]
775
+ if filling_values is not None and not all(
776
+ isinstance(val, int) for val in filling_values
777
+ ):
778
+ raise TypeError(
779
+ "Not all values defined for "
780
+ "filling {} are int : {}".format(
781
+ filling_method,
782
+ filling_values,
783
+ )
784
+ )
785
+ if filling_values is not None and not set(filling_values) <= set(
786
+ all_values
787
+ ):
788
+ logging.warning(
789
+ "One of the values {} on which filling {} must be applied "
790
+ "does not exist on classifications {} and {}".format(
791
+ filling_values,
792
+ filling_method,
793
+ classif1[sens_cst.INPUT_PATH],
794
+ classif2[sens_cst.INPUT_PATH],
795
+ )
796
+ )
797
+
798
+
799
+ def check_all_nbits_equal_one(nbits):
800
+ """
801
+ Check if all the nbits = 1
802
+ :param nbits: list of the nbits
803
+ :return: True if all the nbits = 1
804
+ """
805
+ if len(nbits) > 0 and nbits[0] == 1 and all(x == nbits[0] for x in nbits):
806
+ return True
807
+ return False
808
+
809
+
810
+ @cars_profile(name="Generate inputs")
811
+ def generate_inputs(conf, geometry_plugin):
812
+ """
813
+ Generate sensors inputs form inputs conf :
814
+
815
+ a list of (sensor_left, sensor_right)
816
+
817
+ :param conf: input conf
818
+ :type conf: dict
819
+
820
+ :return: list of sensors pairs
821
+ :rtype: list(tuple(dict, dict))
822
+
823
+ """
824
+ # Load geomodels directly on conf object
825
+ sensors = conf[sens_cst.SENSORS]
826
+ for key in sensors:
827
+ geomodel = sensors[key][sens_cst.INPUT_GEO_MODEL]
828
+ loaded_geomodel = geometry_plugin.load_geomodel(geomodel)
829
+ sensors[key][sens_cst.INPUT_GEO_MODEL] = loaded_geomodel
830
+
831
+ # Get needed pairs
832
+ pairs = conf[sens_cst.PAIRING]
833
+
834
+ # Generate list of pairs
835
+ list_sensor_pairs = []
836
+ for key1, key2 in pairs:
837
+ merged_key = key1 + "_" + key2
838
+ sensor1 = conf[sens_cst.SENSORS][key1]
839
+ sensor2 = conf[sens_cst.SENSORS][key2]
840
+ list_sensor_pairs.append((merged_key, sensor1, sensor2))
841
+
842
+ return list_sensor_pairs