cars 1.0.0rc2__cp312-cp312-win_amd64.whl

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

Potentially problematic release.


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

Files changed (225) hide show
  1. cars/__init__.py +86 -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 +42 -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.cp312-win_amd64.dll.a +0 -0
  30. cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.pyd +0 -0
  31. cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
  32. cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
  33. cars/applications/dense_matching/cpp/meson.build +9 -0
  34. cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
  35. cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
  36. cars/applications/dense_matching/dense_matching_algo.py +401 -0
  37. cars/applications/dense_matching/dense_matching_constants.py +89 -0
  38. cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
  39. cars/applications/dense_matching/disparity_grid_algo.py +597 -0
  40. cars/applications/dense_matching/loaders/__init__.py +23 -0
  41. cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
  42. cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
  43. cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
  44. cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
  45. cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
  46. cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
  47. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  48. cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
  49. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  50. cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
  51. cars/applications/dsm_filling/__init__.py +32 -0
  52. cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
  53. cars/applications/dsm_filling/border_interpolation_app.py +278 -0
  54. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  55. cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
  56. cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
  57. cars/applications/dsm_merging/__init__.py +28 -0
  58. cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
  59. cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
  60. cars/applications/grid_correction/__init__.py +30 -0
  61. cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
  62. cars/applications/grid_correction/grid_correction_app.py +557 -0
  63. cars/applications/grid_generation/__init__.py +30 -0
  64. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  65. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  66. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  67. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  68. cars/applications/grid_generation/transform_grid.py +88 -0
  69. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  70. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  71. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  72. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  73. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  74. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  75. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  76. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  77. cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
  78. cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
  79. cars/applications/rasterization/__init__.py +30 -0
  80. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  81. cars/applications/rasterization/rasterization_algo.py +534 -0
  82. cars/applications/rasterization/rasterization_constants.py +38 -0
  83. cars/applications/rasterization/rasterization_wrappers.py +639 -0
  84. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  85. cars/applications/resampling/__init__.py +28 -0
  86. cars/applications/resampling/abstract_resampling_app.py +187 -0
  87. cars/applications/resampling/bicubic_resampling_app.py +760 -0
  88. cars/applications/resampling/resampling_algo.py +590 -0
  89. cars/applications/resampling/resampling_constants.py +36 -0
  90. cars/applications/resampling/resampling_wrappers.py +309 -0
  91. cars/applications/sensors_subsampling/__init__.py +32 -0
  92. cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
  93. cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
  94. cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
  95. cars/applications/sparse_matching/__init__.py +30 -0
  96. cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
  97. cars/applications/sparse_matching/sift_app.py +724 -0
  98. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  99. cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
  100. cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
  101. cars/applications/triangulation/__init__.py +32 -0
  102. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  103. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  104. cars/applications/triangulation/pc_transform.py +552 -0
  105. cars/applications/triangulation/triangulation_algo.py +371 -0
  106. cars/applications/triangulation/triangulation_constants.py +38 -0
  107. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  108. cars/bundleadjustment.py +750 -0
  109. cars/cars.py +179 -0
  110. cars/conf/__init__.py +23 -0
  111. cars/conf/geoid/egm96.grd +0 -0
  112. cars/conf/geoid/egm96.grd.hdr +15 -0
  113. cars/conf/input_parameters.py +156 -0
  114. cars/conf/mask_cst.py +35 -0
  115. cars/core/__init__.py +23 -0
  116. cars/core/cars_logging.py +402 -0
  117. cars/core/constants.py +191 -0
  118. cars/core/constants_disparity.py +50 -0
  119. cars/core/datasets.py +140 -0
  120. cars/core/geometry/__init__.py +27 -0
  121. cars/core/geometry/abstract_geometry.py +1119 -0
  122. cars/core/geometry/shareloc_geometry.py +598 -0
  123. cars/core/inputs.py +568 -0
  124. cars/core/outputs.py +176 -0
  125. cars/core/preprocessing.py +722 -0
  126. cars/core/projection.py +843 -0
  127. cars/core/roi_tools.py +215 -0
  128. cars/core/tiling.py +774 -0
  129. cars/core/utils.py +164 -0
  130. cars/data_structures/__init__.py +23 -0
  131. cars/data_structures/cars_dataset.py +1544 -0
  132. cars/data_structures/cars_dict.py +74 -0
  133. cars/data_structures/corresponding_tiles_tools.py +186 -0
  134. cars/data_structures/dataframe_converter.py +185 -0
  135. cars/data_structures/format_transformation.py +297 -0
  136. cars/devibrate.py +689 -0
  137. cars/extractroi.py +264 -0
  138. cars/orchestrator/__init__.py +23 -0
  139. cars/orchestrator/achievement_tracker.py +125 -0
  140. cars/orchestrator/cluster/__init__.py +37 -0
  141. cars/orchestrator/cluster/abstract_cluster.py +250 -0
  142. cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
  143. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  144. cars/orchestrator/cluster/dask_config/README.md +94 -0
  145. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  146. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  147. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  148. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  149. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  150. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  151. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  152. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  153. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  154. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  155. cars/orchestrator/cluster/log_wrapper.py +728 -0
  156. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  157. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  158. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  159. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  160. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  161. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
  162. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  163. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  164. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  165. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  166. cars/orchestrator/memory_tools.py +47 -0
  167. cars/orchestrator/orchestrator.py +755 -0
  168. cars/orchestrator/orchestrator_constants.py +29 -0
  169. cars/orchestrator/registry/__init__.py +23 -0
  170. cars/orchestrator/registry/abstract_registry.py +143 -0
  171. cars/orchestrator/registry/compute_registry.py +106 -0
  172. cars/orchestrator/registry/id_generator.py +116 -0
  173. cars/orchestrator/registry/replacer_registry.py +213 -0
  174. cars/orchestrator/registry/saver_registry.py +363 -0
  175. cars/orchestrator/registry/unseen_registry.py +118 -0
  176. cars/orchestrator/tiles_profiler.py +279 -0
  177. cars/pipelines/__init__.py +26 -0
  178. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  179. cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
  180. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  181. cars/pipelines/default/__init__.py +26 -0
  182. cars/pipelines/default/default_pipeline.py +1088 -0
  183. cars/pipelines/filling/__init__.py +26 -0
  184. cars/pipelines/filling/filling.py +981 -0
  185. cars/pipelines/formatting/__init__.py +26 -0
  186. cars/pipelines/formatting/formatting.py +186 -0
  187. cars/pipelines/merging/__init__.py +26 -0
  188. cars/pipelines/merging/merging.py +439 -0
  189. cars/pipelines/parameters/__init__.py +0 -0
  190. cars/pipelines/parameters/advanced_parameters.py +256 -0
  191. cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
  192. cars/pipelines/parameters/application_parameters.py +72 -0
  193. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  194. cars/pipelines/parameters/dsm_inputs.py +349 -0
  195. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  196. cars/pipelines/parameters/output_constants.py +52 -0
  197. cars/pipelines/parameters/output_parameters.py +438 -0
  198. cars/pipelines/parameters/sensor_inputs.py +859 -0
  199. cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
  200. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  201. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  202. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  203. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  204. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  205. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  206. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  207. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  208. cars/pipelines/pipeline.py +119 -0
  209. cars/pipelines/pipeline_constants.py +38 -0
  210. cars/pipelines/pipeline_template.py +135 -0
  211. cars/pipelines/subsampling/__init__.py +26 -0
  212. cars/pipelines/subsampling/subsampling.py +358 -0
  213. cars/pipelines/surface_modeling/__init__.py +26 -0
  214. cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
  215. cars/pipelines/tie_points/__init__.py +26 -0
  216. cars/pipelines/tie_points/tie_points.py +536 -0
  217. cars/starter.py +167 -0
  218. cars-1.0.0rc2.dist-info/DELVEWHEEL +2 -0
  219. cars-1.0.0rc2.dist-info/METADATA +289 -0
  220. cars-1.0.0rc2.dist-info/RECORD +225 -0
  221. cars-1.0.0rc2.dist-info/WHEEL +4 -0
  222. cars-1.0.0rc2.dist-info/entry_points.txt +8 -0
  223. cars.libs/libgcc_s_seh-1-b2494fcbd4d80cf2c98fdd5261f6d850.dll +0 -0
  224. cars.libs/libstdc++-6-e9b0d12ae0e9555bbae55e8dfd08c3f7.dll +0 -0
  225. cars.libs/libwinpthread-1-7882d1b093714ccdfaf4e0789a817792.dll +0 -0
cars/devibrate.py ADDED
@@ -0,0 +1,689 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf8
3
+ #
4
+ # Copyright (c) 2024 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-devibrate: devibrate a high resolution DSM using a low resolution DSM
23
+ """
24
+
25
+ import argparse
26
+ import json
27
+ import logging
28
+ import math
29
+
30
+ # Standard imports
31
+ import os
32
+ import pickle
33
+ from typing import List, Union
34
+
35
+ # Third party imports
36
+ import numpy as np
37
+ import pandas as pd
38
+ import pyproj
39
+ import rasterio as rio
40
+ import xarray as xr
41
+ from rasterio.windows import bounds as to_bounds
42
+ from rasterio.windows import from_bounds
43
+ from scipy import interpolate
44
+ from scipy.signal import butter, filtfilt, lfilter, lfilter_zi
45
+
46
+ # CARS / SHARELOC imports
47
+ from shareloc.dtm_reader import interpolate_geoid_height
48
+ from shareloc.geofunctions import triangulation
49
+
50
+ from cars.applications.rasterization import rasterization_algo as rasterization
51
+ from cars.core.geometry.abstract_geometry import AbstractGeometry
52
+ from cars.core.geometry.shareloc_geometry import SharelocGeometry
53
+
54
+ # Get full path geoid
55
+ package_path = os.path.dirname(__file__)
56
+ GEOID_DEFAULT = os.path.join(package_path, "conf", "geoid/egm96.grd")
57
+
58
+
59
+ def acquisition_direction(
60
+ sensor1, geomodel1, sensor2, geomodel2, geometry_plugin
61
+ ):
62
+ """
63
+ Computes the mean acquisition of the input images pair
64
+
65
+ :param sensor1: sensor image name of the first product
66
+ :param geomodel1: geomodel name of the first product
67
+ :param sensor2: sensor image name of the second product
68
+ :param geomodel2: geomodel name of the second product
69
+ :return: a tuple composed of :
70
+ - the mean acquisition direction as a numpy array
71
+ - the acquisition direction of the first product as a numpy array
72
+ - the acquisition direction of the second product as a numpy array
73
+ """
74
+ vec1 = get_time_ground_direction(sensor1, geomodel1, geometry_plugin)
75
+ vec2 = get_time_ground_direction(sensor2, geomodel2, geometry_plugin)
76
+ time_direction_vector = (vec1 + vec2) / 2
77
+
78
+ def display_angle(vec):
79
+ """
80
+ Display angle in degree from a vector x
81
+ :param vec: vector to display
82
+ :return: angle in degree
83
+ """
84
+ return 180 * math.atan2(vec[1], vec[0]) / math.pi
85
+
86
+ logging.info(
87
+ "Time direction average azimuth: "
88
+ "{}deg (img1: {}deg, img2: {}deg)".format(
89
+ display_angle(time_direction_vector),
90
+ display_angle(vec1),
91
+ display_angle(vec2),
92
+ )
93
+ )
94
+
95
+ return time_direction_vector, vec1, vec2
96
+
97
+
98
+ def get_time_ground_direction( # pylint: disable=too-many-positional-arguments
99
+ sensor,
100
+ geomodel,
101
+ geometry_plugin,
102
+ x_loc: float = None,
103
+ y_loc: float = None,
104
+ y_offset: float = None,
105
+ ) -> np.ndarray:
106
+ """
107
+ For a given image, compute the direction of increasing acquisition
108
+ time on ground.
109
+ Done by two localizations at "y" and "y+y_offset" values.
110
+
111
+ :param sensor: sensor image name
112
+ :param geomodel: geomodel name
113
+ :param x_loc: x location in image for estimation (default=center)
114
+ :param y_loc: y location in image for estimation (default=1/4)
115
+ :param y_offset: y location in image for estimation (default=1/2)
116
+ :param dem: DEM for direct localisation function
117
+ :param geoid: path to geoid file
118
+ :return: normalized direction vector as a numpy array
119
+ """
120
+ # Define x: image center, y: 1/4 of image,
121
+ # y_offset: 3/4 of image if not defined
122
+ with rio.open(sensor) as src:
123
+ img_size_x, img_size_y = src.width, src.height
124
+ if x_loc is None:
125
+ x_loc = img_size_x / 2
126
+ if y_loc is None:
127
+ y_loc = img_size_y / 4
128
+ if y_offset is None:
129
+ y_offset = img_size_y / 2
130
+
131
+ # Check x, y, y_offset to be in image
132
+ assert x_loc >= 0
133
+ assert x_loc <= img_size_x
134
+ assert y_loc >= 0
135
+ assert y_loc <= img_size_y
136
+ assert y_offset > 0
137
+ assert y_loc + y_offset <= img_size_y
138
+
139
+ # Get coordinates of time direction vectors
140
+ lat1, lon1, __ = geometry_plugin.direct_loc(sensor, geomodel, x_loc, y_loc)
141
+ lat2, lon2, __ = geometry_plugin.direct_loc(
142
+ sensor, geomodel, x_loc, y_loc + y_offset
143
+ )
144
+
145
+ # Create and normalize the time direction vector
146
+ vec = np.array([lon1 - lon2, lat1 - lat2])
147
+ vec = vec / np.linalg.norm(vec)
148
+
149
+ return vec
150
+
151
+
152
+ def project_coordinates_on_line(
153
+ x_coord: Union[float, np.ndarray],
154
+ y_coord: Union[float, np.ndarray],
155
+ origin: Union[List[float], np.ndarray],
156
+ vec: Union[List[float], np.ndarray],
157
+ ) -> Union[float, np.ndarray]:
158
+ """
159
+ Project coordinates (x, y) on a line starting from origin with a
160
+ direction vector vec, and return the euclidean distances between
161
+ projected points and origin.
162
+
163
+ :param x_coord: scalar or vector of coordinates x
164
+ :param y_coord: scalar or vector of coordinates y
165
+ :param origin: coordinates of origin point for line
166
+ :param vec: direction vector of line
167
+ :return: vector of distances of projected points to origin
168
+ """
169
+ assert len(x_coord) == len(y_coord)
170
+ assert len(origin) == 2
171
+ assert len(vec) == 2
172
+
173
+ vec_angle = math.atan2(vec[1], vec[0])
174
+ point_angle = np.arctan2(y_coord - origin[1], x_coord - origin[0])
175
+ proj_angle = point_angle - vec_angle
176
+ dist_to_origin = np.sqrt(
177
+ np.square(x_coord - origin[0]) + np.square(y_coord - origin[1])
178
+ )
179
+
180
+ return dist_to_origin * np.cos(proj_angle)
181
+
182
+
183
+ # pylint: disable=too-many-positional-arguments
184
+ def lowres_initial_dem_splines_fit(
185
+ lowres_dsm_from_matches: xr.Dataset,
186
+ lowres_initial_dem: xr.Dataset,
187
+ origin: np.ndarray,
188
+ time_direction_vector: np.ndarray,
189
+ ext: int = 3,
190
+ order: int = 3,
191
+ min_pts_per_time: int = 100,
192
+ min_pts_along_time_direction: int = 100,
193
+ butterworth_filter_order: int = 3,
194
+ butterworth_critical_frequency: float = 0.05,
195
+ ):
196
+ """
197
+ This function takes 2 datasets containing DSM and models the
198
+ difference between the two as an UnivariateSpline along the
199
+ direction given by origin and time_direction_vector. Internally,
200
+ it looks for the highest smoothing factor that satisfies the rmse threshold.
201
+
202
+ :param lowres_dsm_from_matches: Dataset containing the low resolution DSM
203
+ obtained from matches, as returned by the
204
+ rasterization.simple_rasterization_dataset function.
205
+ :param lowres_initial_dem: Dataset containing the low resolution DEM,
206
+ on the same grid as lowres_dsm_from_matches
207
+ :param origin: coordinates of origin point for line
208
+ :type origin: list(float) or np.array(float) of size 2
209
+ :param time_direction_vector: direction vector of line
210
+ :type time_direction_vector: list(float) or np.array(float) of size 2
211
+ :param ext: behavior outside of interpolation domain
212
+ :param order: spline order
213
+ :param min_pts_per_time: minimum number of points for
214
+ each measurement
215
+ :param min_pts_along_time_direction: minimum number of points for
216
+ time direction
217
+ :param butterworth_filter_order: Order of the filter.
218
+ See scipy.signal.butter
219
+ :param butterworth_critical_frequency: The filter critical frequency
220
+ or frequencies. See scipy.signal.butter
221
+ """
222
+ # Initial DSM difference
223
+ dsm_diff = lowres_initial_dem.hgt - lowres_dsm_from_matches.hgt
224
+
225
+ # Form arrays of coordinates
226
+ x_values_2d, y_values_2d = np.meshgrid(dsm_diff.x, dsm_diff.y)
227
+
228
+ # Project coordinates on the line
229
+ # of increasing acquisition time to form 1D coordinates
230
+ # If there are sensor oscillations, they will occur in this direction
231
+ linear_coords = project_coordinates_on_line(
232
+ x_values_2d, y_values_2d, origin, time_direction_vector
233
+ )
234
+
235
+ # Form a 1D array with diff values indexed with linear coords
236
+ linear_diff_array = xr.DataArray(
237
+ dsm_diff.values.ravel(), coords={"l": linear_coords.ravel()}, dims=("l")
238
+ )
239
+ linear_diff_array = linear_diff_array.dropna(dim="l")
240
+ linear_diff_array = linear_diff_array.sortby("l")
241
+
242
+ # Apply median filtering within cell matching input dsm resolution
243
+ min_l = np.min(linear_diff_array.l)
244
+ max_l = np.max(linear_diff_array.l)
245
+ nbins = int(
246
+ math.ceil((max_l - min_l) / (lowres_dsm_from_matches.resolution))
247
+ )
248
+ median_linear_diff_array = linear_diff_array.groupby_bins(
249
+ "l", nbins
250
+ ).median()
251
+ median_linear_diff_array = median_linear_diff_array.rename({"l_bins": "l"})
252
+ median_linear_diff_array = median_linear_diff_array.assign_coords(
253
+ {"l": np.array([d.mid for d in median_linear_diff_array.l.data])}
254
+ )
255
+
256
+ count_linear_diff_array = linear_diff_array.groupby_bins("l", nbins).count()
257
+ count_linear_diff_array = count_linear_diff_array.rename({"l_bins": "l"})
258
+ count_linear_diff_array = count_linear_diff_array.assign_coords(
259
+ {"l": np.array([d.mid for d in count_linear_diff_array.l.data])}
260
+ )
261
+
262
+ # Filter measurements with insufficient amount of points
263
+ median_linear_diff_array = median_linear_diff_array.where(
264
+ count_linear_diff_array > min_pts_per_time
265
+ ).dropna(dim="l")
266
+
267
+ if len(median_linear_diff_array) < min_pts_along_time_direction:
268
+ raise RuntimeError(
269
+ "Insufficient amount of points ({} < {}) along time direction "
270
+ "after measurements filtering to estimate correction "
271
+ "to fit initial DEM".format(
272
+ len(median_linear_diff_array), min_pts_along_time_direction
273
+ )
274
+ )
275
+
276
+ # Apply butterworth lowpass filter to retrieve only the low frequency
277
+ # (from example of doc: https://docs.scipy.org/doc/scipy/reference/
278
+ # generated/scipy.signal.butter.html#scipy.signal.butter )
279
+ b, a = butter(butterworth_filter_order, butterworth_critical_frequency)
280
+ zi_filter = lfilter_zi(b, a)
281
+ z_filter, _ = lfilter(
282
+ b,
283
+ a,
284
+ median_linear_diff_array.values,
285
+ zi=zi_filter * median_linear_diff_array.values[0],
286
+ )
287
+ lfilter(b, a, z_filter, zi=zi_filter * z_filter[0])
288
+ filtered_median_linear_diff_array = xr.DataArray(
289
+ filtfilt(b, a, median_linear_diff_array.values),
290
+ coords=median_linear_diff_array.coords,
291
+ )
292
+
293
+ # Estimate performances of spline s = 100 * length of diff array
294
+ smoothing_factor = 100 * len(filtered_median_linear_diff_array.l)
295
+ splines = interpolate.UnivariateSpline(
296
+ filtered_median_linear_diff_array.l,
297
+ filtered_median_linear_diff_array.values,
298
+ ext=ext,
299
+ k=order,
300
+ s=smoothing_factor,
301
+ )
302
+ estimated_correction = xr.DataArray(
303
+ splines(filtered_median_linear_diff_array.l),
304
+ coords=filtered_median_linear_diff_array.coords,
305
+ )
306
+ rmse = (filtered_median_linear_diff_array - estimated_correction).std(
307
+ dim="l"
308
+ )
309
+
310
+ target_rmse = 0.3
311
+
312
+ # If RMSE is too high, try to decrease smoothness until it fits
313
+ while rmse > target_rmse and smoothing_factor > 0.001:
314
+ # divide s by 2
315
+ smoothing_factor /= 2
316
+
317
+ # Estimate splines
318
+ splines = interpolate.UnivariateSpline(
319
+ filtered_median_linear_diff_array.l,
320
+ filtered_median_linear_diff_array.values,
321
+ ext=ext,
322
+ k=order,
323
+ s=smoothing_factor,
324
+ )
325
+
326
+ # Compute RMSE
327
+ estimated_correction = xr.DataArray(
328
+ splines(filtered_median_linear_diff_array.l),
329
+ coords=filtered_median_linear_diff_array.coords,
330
+ )
331
+
332
+ rmse = (filtered_median_linear_diff_array - estimated_correction).std(
333
+ dim="l"
334
+ )
335
+
336
+ logging.debug(
337
+ "Best smoothing factor for splines regression: "
338
+ "{} (rmse={})".format(smoothing_factor, rmse)
339
+ )
340
+
341
+ # Return estimated spline
342
+ return splines
343
+
344
+
345
+ def read_lowres_dsm(srtm_path, startx, starty, endx, endy):
346
+ """
347
+ Read an extract of the low resolution input DSM and return it as an Array
348
+ """
349
+
350
+ with rio.open(srtm_path) as src:
351
+ window = from_bounds(startx, starty, endx, endy, src.transform)
352
+ window = window.round_offsets()
353
+ window = window.round_lengths()
354
+ bounds = to_bounds(window, src.transform)
355
+ resolution = src.res[0]
356
+ dem_as_array = src.read(1, window=window)
357
+
358
+ newstartx, newstarty = bounds[0], bounds[-1]
359
+ sizex, sizey = window.width, window.height
360
+
361
+ x_values_1d = np.linspace(
362
+ newstartx + 0.5 * resolution,
363
+ newstartx + resolution * (sizex + 0.5),
364
+ sizex,
365
+ endpoint=False,
366
+ )
367
+
368
+ y_values_1d = np.linspace(
369
+ newstarty - 0.5 * resolution,
370
+ newstarty - resolution * (sizey + 0.5),
371
+ sizey,
372
+ endpoint=False,
373
+ )
374
+
375
+ dims = ["y", "x"]
376
+ coords = {"x": x_values_1d, "y": y_values_1d}
377
+ dsm_as_ds = xr.Dataset({"hgt": (dims, dem_as_array)}, coords=coords)
378
+ dsm_as_ds["epsg"] = 4326
379
+ dsm_as_ds["resolution"] = resolution
380
+ return dsm_as_ds, newstartx, newstarty, sizex, sizey, resolution
381
+
382
+
383
+ def compute_splines( # pylint: disable=too-many-positional-arguments
384
+ sensor1,
385
+ geomodel1,
386
+ sensor2,
387
+ geomodel2,
388
+ matches,
389
+ srtm_path,
390
+ geoid_path,
391
+ out_dir,
392
+ min_pts_per_time: int = 100,
393
+ min_pts_along_time_direction: int = 100,
394
+ butterworth_filter_order: int = 3,
395
+ butterworth_critical_frequency: float = 0.05,
396
+ ):
397
+ """
398
+ Compute a spline dict containing estimated splines, origin
399
+ and time_direction_vector
400
+ """
401
+ geometry_plugin = AbstractGeometry( # pylint: disable=E0110
402
+ "SharelocGeometry"
403
+ )
404
+
405
+ # retrieve time direction from models
406
+ time_direction_vector, _, _ = acquisition_direction(
407
+ sensor1, geomodel1, sensor2, geomodel2, geometry_plugin
408
+ )
409
+
410
+ # load matches and triangulate them
411
+ corrected_matches = np.load(matches)
412
+ disp = corrected_matches[:, 0] - corrected_matches[:, 2]
413
+ mini = np.percentile(disp, 5.0)
414
+ maxi = np.percentile(disp, 95)
415
+ corrected_matches = corrected_matches[
416
+ np.logical_and(disp >= mini, disp <= maxi), :
417
+ ]
418
+
419
+ shareloc_model1 = SharelocGeometry.load_geom_model(geomodel1)
420
+ shareloc_model2 = SharelocGeometry.load_geom_model(geomodel2)
421
+
422
+ matches_sensor = corrected_matches[:, 4:8]
423
+ [__, pc_from_matches, __] = triangulation.sensor_triangulation(
424
+ matches_sensor, shareloc_model1, shareloc_model2
425
+ )
426
+
427
+ positions = np.array(pc_from_matches[:, 0:2])
428
+ geoid_height = interpolate_geoid_height(geoid_path, positions)
429
+ pc_from_matches[:, 2] = pc_from_matches[:, 2] - geoid_height
430
+
431
+ # deduce area from sift
432
+ pc_xx = pc_from_matches[:, 0]
433
+ pc_yy = pc_from_matches[:, 1]
434
+ startx, endx = pc_xx.min(), pc_xx.max()
435
+ starty, endy = pc_yy.min(), pc_yy.max()
436
+
437
+ # read corresponding dem
438
+ lowres_initial_dem, startx, starty, sizex, sizey, resolution = (
439
+ read_lowres_dsm(srtm_path, startx, starty, endx, endy)
440
+ )
441
+
442
+ points_cloud_from_matches = pd.DataFrame(
443
+ data=pc_from_matches, columns=["x", "y", "z"]
444
+ )
445
+
446
+ # rasterize point cloud (superimposable image with lowres initial dem)
447
+ points_cloud_from_matches.attrs["attributes"] = {"number_of_pc": 0}
448
+ lowres_dsm = rasterization.simple_rasterization_dataset_wrapper(
449
+ points_cloud_from_matches,
450
+ resolution,
451
+ 4326,
452
+ xstart=startx,
453
+ ystart=starty,
454
+ xsize=sizex,
455
+ ysize=sizey,
456
+ )
457
+
458
+ lowres_dsm_diff = lowres_initial_dem - lowres_dsm
459
+ origin = [
460
+ float(lowres_dsm_diff["x"][0].values),
461
+ float(lowres_dsm_diff["y"][0].values),
462
+ ]
463
+
464
+ # fit initial dem and low res dsm from sift and deduce splines correction
465
+ splines = lowres_initial_dem_splines_fit(
466
+ lowres_dsm,
467
+ lowres_initial_dem,
468
+ origin,
469
+ time_direction_vector,
470
+ min_pts_per_time=min_pts_per_time,
471
+ min_pts_along_time_direction=min_pts_along_time_direction,
472
+ butterworth_filter_order=butterworth_filter_order,
473
+ butterworth_critical_frequency=butterworth_critical_frequency,
474
+ )
475
+
476
+ # save intermediate data
477
+ lowres_initial_dem_file = os.path.join(out_dir, "lowres_initial_dem.nc")
478
+ lowres_initial_dem.to_netcdf(lowres_initial_dem_file)
479
+ lowres_dsm_file = os.path.join(out_dir, "lowres_dsm.nc")
480
+ lowres_dsm.to_netcdf(lowres_dsm_file)
481
+ lowres_dsm_diff_file = os.path.join(out_dir, "lowres_dsm_diff.nc")
482
+ lowres_dsm_diff.to_netcdf(lowres_dsm_diff_file)
483
+
484
+ # use splines for correction introduced in rasterization
485
+ lowres_dsm_as_df = lowres_dsm.to_dataframe().reset_index()
486
+
487
+ distance_vector = project_coordinates_on_line(
488
+ lowres_dsm_as_df["x"],
489
+ lowres_dsm_as_df["y"],
490
+ origin,
491
+ time_direction_vector,
492
+ )
493
+ correction = splines(distance_vector)
494
+
495
+ lowres_dsm["hgt"] = lowres_dsm["hgt"] + correction.reshape(
496
+ lowres_dsm["hgt"].shape
497
+ )
498
+ new_lowres_dsm_file = os.path.join(out_dir, "new_lowres_dsm.nc")
499
+ lowres_dsm.to_netcdf(new_lowres_dsm_file)
500
+ new_lowres_dsm_diff = lowres_initial_dem - lowres_dsm
501
+ new_lowres_dsm_diff_file = os.path.join(out_dir, "new_lowres_dsm_diff.nc")
502
+ new_lowres_dsm_diff.to_netcdf(new_lowres_dsm_diff_file)
503
+
504
+ return {
505
+ "splines": splines,
506
+ "origin": origin,
507
+ "time_direction_vector": time_direction_vector,
508
+ }
509
+
510
+
511
+ def cars_devibrate( # pylint: disable=too-many-positional-arguments
512
+ used_conf,
513
+ srtm_path,
514
+ geoid_path,
515
+ min_pts_per_time: int = 100,
516
+ min_pts_along_time_direction: int = 100,
517
+ butterworth_filter_order: int = 3,
518
+ butterworth_critical_frequency: float = 0.05,
519
+ ):
520
+ """
521
+ Main fonction. Expects a dictionary from the CLI
522
+ or directly the input parameters.
523
+ """
524
+ out_dir = os.path.dirname(used_conf)
525
+
526
+ with open(used_conf, "r", encoding="utf8") as jsonfile:
527
+ data = json.load(jsonfile)
528
+
529
+ pairing = data["inputs"]["pairing"]
530
+ assert len(pairing) == 1
531
+
532
+ sensor1 = data["inputs"]["sensors"][pairing[0][0]]["image"]
533
+ geomodel1 = data["inputs"]["sensors"][pairing[0][0]]["geomodel"]
534
+ sensor2 = data["inputs"]["sensors"][pairing[0][1]]["image"]
535
+ geomodel2 = data["inputs"]["sensors"][pairing[0][1]]["geomodel"]
536
+
537
+ matches = os.path.join(
538
+ out_dir,
539
+ "dump_dir",
540
+ "sparse_matching.sift",
541
+ "_".join(pairing[0]),
542
+ "filtered_matches.npy",
543
+ )
544
+ if not os.path.isfile(matches):
545
+ raise RuntimeError(
546
+ "Matches must be saved: Set applications.sparse_matching."
547
+ "sift.save_intermediate_data to true in CARS configuration file."
548
+ )
549
+
550
+ dsm_path = os.path.join(out_dir, "dsm", "dsm.tif")
551
+ if not os.path.isfile(dsm_path):
552
+ raise RuntimeError("DSM must be generated: set product level to `dsm`")
553
+ splines_path = os.path.join(out_dir, "splines.pck")
554
+
555
+ if os.path.exists(splines_path) is False:
556
+ splines_dict = compute_splines(
557
+ sensor1,
558
+ geomodel1,
559
+ sensor2,
560
+ geomodel2,
561
+ matches,
562
+ srtm_path,
563
+ geoid_path,
564
+ out_dir,
565
+ min_pts_per_time=min_pts_per_time,
566
+ min_pts_along_time_direction=min_pts_along_time_direction,
567
+ butterworth_filter_order=butterworth_filter_order,
568
+ butterworth_critical_frequency=butterworth_critical_frequency,
569
+ )
570
+
571
+ with open(splines_path, "wb") as writer:
572
+ pickle.dump(splines_dict, writer)
573
+ else:
574
+ with open(splines_path, "rb") as reader:
575
+ splines_dict = pickle.load(reader)
576
+
577
+ with rio.open(dsm_path) as src:
578
+ bounds = src.bounds
579
+ startx, starty = bounds[0], bounds[-1]
580
+ sizex, sizey = src.width, src.height
581
+ resolution = src.res[0]
582
+
583
+ x_values_1d = np.linspace(
584
+ startx + 0.5 * resolution,
585
+ startx + resolution * (sizex + 0.5),
586
+ sizex,
587
+ endpoint=False,
588
+ )
589
+
590
+ y_values_1d = np.linspace(
591
+ starty - 0.5 * resolution,
592
+ starty - resolution * (sizey + 0.5),
593
+ sizey,
594
+ endpoint=False,
595
+ )
596
+
597
+ x_values_2d, y_values_2d = np.meshgrid(x_values_1d, y_values_1d)
598
+
599
+ positions = None
600
+ if src.crs != "EPSG:4326":
601
+ transformer = pyproj.Transformer.from_crs(
602
+ src.crs, "EPSG:4326", always_xy=True
603
+ )
604
+ positions = np.array(
605
+ transformer.transform(x_values_2d, y_values_2d)
606
+ )
607
+
608
+ distance_vector = project_coordinates_on_line(
609
+ positions[0],
610
+ positions[1],
611
+ splines_dict["origin"],
612
+ splines_dict["time_direction_vector"],
613
+ )
614
+
615
+ bloclen = int(distance_vector.shape[0] / 2)
616
+ correction_1 = splines_dict["splines"](distance_vector[:bloclen, :])
617
+ correction_2 = splines_dict["splines"](distance_vector[bloclen:, :])
618
+ correction = np.vstack((correction_1, correction_2))
619
+
620
+ profile = src.profile
621
+ # User can apply correction with:
622
+ # otbcli_BandMath -il dsm.tif correction.tif \
623
+ # -exp "im1b1==-32768?-32768:im1b1+im2b1" -out corrected_dsm.tif
624
+ with rio.open(
625
+ os.path.join(out_dir, "correction.tif"), "w", **profile
626
+ ) as dst:
627
+ dst.write(correction, 1)
628
+
629
+
630
+ def cli():
631
+ """
632
+ Main cars-devibrate entrypoint (Command Line Interface)
633
+ """
634
+ parser = argparse.ArgumentParser(
635
+ "cars-devibrate",
636
+ description="Devibrate a DSM produced from stereo images",
637
+ )
638
+
639
+ parser.add_argument(
640
+ "used_conf", type=str, help="CARS Used Configuration File"
641
+ )
642
+
643
+ parser.add_argument(
644
+ "srtm_path",
645
+ type=str,
646
+ help="SRTM path",
647
+ )
648
+
649
+ parser.add_argument(
650
+ "--geoid_path",
651
+ type=str,
652
+ help="Geoid path",
653
+ default=GEOID_DEFAULT,
654
+ )
655
+
656
+ parser.add_argument(
657
+ "--min_pts_per_time",
658
+ type=int,
659
+ help="minimum number of points for" "each measurement",
660
+ default=100,
661
+ )
662
+
663
+ parser.add_argument(
664
+ "--min_pts_along_time_direction",
665
+ type=int,
666
+ help="minimum number of points for" "time direction",
667
+ default=100,
668
+ )
669
+
670
+ parser.add_argument(
671
+ "--butterworth_filter_order",
672
+ type=int,
673
+ help="Order of the butterworth filter",
674
+ default=3,
675
+ )
676
+
677
+ parser.add_argument(
678
+ "--butterworth_critical_frequency",
679
+ type=float,
680
+ help=" The butterworth filter critical frequency",
681
+ default=0.05,
682
+ )
683
+
684
+ args = parser.parse_args()
685
+ cars_devibrate(**vars(args))
686
+
687
+
688
+ if __name__ == "__main__":
689
+ cli()