cars 1.0.0rc3__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. cars/__init__.py +74 -0
  2. cars/applications/__init__.py +40 -0
  3. cars/applications/application.py +117 -0
  4. cars/applications/application_constants.py +29 -0
  5. cars/applications/application_template.py +146 -0
  6. cars/applications/auxiliary_filling/__init__.py +29 -0
  7. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +105 -0
  8. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
  9. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -0
  10. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
  11. cars/applications/dem_generation/__init__.py +30 -0
  12. cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
  13. cars/applications/dem_generation/bulldozer_config/base_config.yaml +46 -0
  14. cars/applications/dem_generation/bulldozer_dem_app.py +641 -0
  15. cars/applications/dem_generation/bulldozer_memory.py +55 -0
  16. cars/applications/dem_generation/dem_generation_algo.py +107 -0
  17. cars/applications/dem_generation/dem_generation_constants.py +32 -0
  18. cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
  19. cars/applications/dense_match_filling/__init__.py +30 -0
  20. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
  21. cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
  22. cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
  23. cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
  24. cars/applications/dense_match_filling/zero_padding_app.py +302 -0
  25. cars/applications/dense_matching/__init__.py +30 -0
  26. cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
  27. cars/applications/dense_matching/census_mccnn_sgm_app.py +1461 -0
  28. cars/applications/dense_matching/cpp/__init__.py +0 -0
  29. cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-313-x86_64-linux-gnu.so +0 -0
  30. cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
  31. cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
  32. cars/applications/dense_matching/cpp/meson.build +9 -0
  33. cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
  34. cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
  35. cars/applications/dense_matching/dense_matching_algo.py +401 -0
  36. cars/applications/dense_matching/dense_matching_constants.py +89 -0
  37. cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
  38. cars/applications/dense_matching/disparity_grid_algo.py +597 -0
  39. cars/applications/dense_matching/loaders/__init__.py +23 -0
  40. cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
  41. cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
  42. cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
  43. cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
  44. cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
  45. cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
  46. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  47. cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
  48. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  49. cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
  50. cars/applications/dsm_filling/__init__.py +32 -0
  51. cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
  52. cars/applications/dsm_filling/border_interpolation_app.py +278 -0
  53. cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
  54. cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
  55. cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
  56. cars/applications/dsm_merging/__init__.py +28 -0
  57. cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
  58. cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
  59. cars/applications/grid_correction/__init__.py +30 -0
  60. cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
  61. cars/applications/grid_correction/grid_correction_app.py +557 -0
  62. cars/applications/grid_generation/__init__.py +30 -0
  63. cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
  64. cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
  65. cars/applications/grid_generation/grid_generation_algo.py +388 -0
  66. cars/applications/grid_generation/grid_generation_constants.py +46 -0
  67. cars/applications/grid_generation/transform_grid.py +88 -0
  68. cars/applications/ground_truth_reprojection/__init__.py +30 -0
  69. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
  70. cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
  71. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
  72. cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
  73. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
  74. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
  75. cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
  76. cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
  77. cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
  78. cars/applications/rasterization/__init__.py +30 -0
  79. cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
  80. cars/applications/rasterization/rasterization_algo.py +534 -0
  81. cars/applications/rasterization/rasterization_constants.py +38 -0
  82. cars/applications/rasterization/rasterization_wrappers.py +639 -0
  83. cars/applications/rasterization/simple_gaussian_app.py +1152 -0
  84. cars/applications/resampling/__init__.py +28 -0
  85. cars/applications/resampling/abstract_resampling_app.py +187 -0
  86. cars/applications/resampling/bicubic_resampling_app.py +760 -0
  87. cars/applications/resampling/resampling_algo.py +590 -0
  88. cars/applications/resampling/resampling_constants.py +36 -0
  89. cars/applications/resampling/resampling_wrappers.py +309 -0
  90. cars/applications/sensors_subsampling/__init__.py +32 -0
  91. cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
  92. cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
  93. cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
  94. cars/applications/sparse_matching/__init__.py +30 -0
  95. cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
  96. cars/applications/sparse_matching/sift_app.py +724 -0
  97. cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
  98. cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
  99. cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
  100. cars/applications/triangulation/__init__.py +32 -0
  101. cars/applications/triangulation/abstract_triangulation_app.py +227 -0
  102. cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
  103. cars/applications/triangulation/pc_transform.py +552 -0
  104. cars/applications/triangulation/triangulation_algo.py +371 -0
  105. cars/applications/triangulation/triangulation_constants.py +38 -0
  106. cars/applications/triangulation/triangulation_wrappers.py +259 -0
  107. cars/bundleadjustment.py +750 -0
  108. cars/cars.py +179 -0
  109. cars/conf/__init__.py +23 -0
  110. cars/conf/geoid/egm96.grd +0 -0
  111. cars/conf/geoid/egm96.grd.hdr +15 -0
  112. cars/conf/input_parameters.py +156 -0
  113. cars/conf/mask_cst.py +35 -0
  114. cars/core/__init__.py +23 -0
  115. cars/core/cars_logging.py +402 -0
  116. cars/core/constants.py +191 -0
  117. cars/core/constants_disparity.py +50 -0
  118. cars/core/datasets.py +140 -0
  119. cars/core/geometry/__init__.py +27 -0
  120. cars/core/geometry/abstract_geometry.py +1130 -0
  121. cars/core/geometry/shareloc_geometry.py +604 -0
  122. cars/core/inputs.py +568 -0
  123. cars/core/outputs.py +176 -0
  124. cars/core/preprocessing.py +722 -0
  125. cars/core/projection.py +843 -0
  126. cars/core/roi_tools.py +215 -0
  127. cars/core/tiling.py +774 -0
  128. cars/core/utils.py +164 -0
  129. cars/data_structures/__init__.py +23 -0
  130. cars/data_structures/cars_dataset.py +1544 -0
  131. cars/data_structures/cars_dict.py +74 -0
  132. cars/data_structures/corresponding_tiles_tools.py +186 -0
  133. cars/data_structures/dataframe_converter.py +185 -0
  134. cars/data_structures/format_transformation.py +297 -0
  135. cars/devibrate.py +689 -0
  136. cars/extractroi.py +264 -0
  137. cars/orchestrator/__init__.py +23 -0
  138. cars/orchestrator/achievement_tracker.py +125 -0
  139. cars/orchestrator/cluster/__init__.py +37 -0
  140. cars/orchestrator/cluster/abstract_cluster.py +250 -0
  141. cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
  142. cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
  143. cars/orchestrator/cluster/dask_config/README.md +94 -0
  144. cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
  145. cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
  146. cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
  147. cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
  148. cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
  149. cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
  150. cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
  151. cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
  152. cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
  153. cars/orchestrator/cluster/local_dask_cluster.py +116 -0
  154. cars/orchestrator/cluster/log_wrapper.py +728 -0
  155. cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
  156. cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
  157. cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
  158. cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
  159. cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
  160. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
  161. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
  162. cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
  163. cars/orchestrator/cluster/sequential_cluster.py +139 -0
  164. cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
  165. cars/orchestrator/memory_tools.py +47 -0
  166. cars/orchestrator/orchestrator.py +755 -0
  167. cars/orchestrator/orchestrator_constants.py +29 -0
  168. cars/orchestrator/registry/__init__.py +23 -0
  169. cars/orchestrator/registry/abstract_registry.py +143 -0
  170. cars/orchestrator/registry/compute_registry.py +106 -0
  171. cars/orchestrator/registry/id_generator.py +116 -0
  172. cars/orchestrator/registry/replacer_registry.py +213 -0
  173. cars/orchestrator/registry/saver_registry.py +363 -0
  174. cars/orchestrator/registry/unseen_registry.py +118 -0
  175. cars/orchestrator/tiles_profiler.py +279 -0
  176. cars/pipelines/__init__.py +26 -0
  177. cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
  178. cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
  179. cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
  180. cars/pipelines/default/__init__.py +26 -0
  181. cars/pipelines/default/default_pipeline.py +1095 -0
  182. cars/pipelines/filling/__init__.py +26 -0
  183. cars/pipelines/filling/filling.py +981 -0
  184. cars/pipelines/formatting/__init__.py +26 -0
  185. cars/pipelines/formatting/formatting.py +190 -0
  186. cars/pipelines/merging/__init__.py +26 -0
  187. cars/pipelines/merging/merging.py +439 -0
  188. cars/pipelines/parameters/__init__.py +0 -0
  189. cars/pipelines/parameters/advanced_parameters.py +256 -0
  190. cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
  191. cars/pipelines/parameters/application_parameters.py +72 -0
  192. cars/pipelines/parameters/depth_map_inputs.py +0 -0
  193. cars/pipelines/parameters/dsm_inputs.py +349 -0
  194. cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
  195. cars/pipelines/parameters/output_constants.py +52 -0
  196. cars/pipelines/parameters/output_parameters.py +435 -0
  197. cars/pipelines/parameters/sensor_inputs.py +859 -0
  198. cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
  199. cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
  200. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
  201. cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
  202. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
  203. cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
  204. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
  205. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
  206. cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
  207. cars/pipelines/pipeline.py +119 -0
  208. cars/pipelines/pipeline_constants.py +38 -0
  209. cars/pipelines/pipeline_template.py +135 -0
  210. cars/pipelines/subsampling/__init__.py +26 -0
  211. cars/pipelines/subsampling/subsampling.py +358 -0
  212. cars/pipelines/surface_modeling/__init__.py +26 -0
  213. cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
  214. cars/pipelines/tie_points/__init__.py +26 -0
  215. cars/pipelines/tie_points/tie_points.py +536 -0
  216. cars/starter.py +167 -0
  217. cars-1.0.0rc3.dist-info/METADATA +289 -0
  218. cars-1.0.0rc3.dist-info/RECORD +220 -0
  219. cars-1.0.0rc3.dist-info/WHEEL +6 -0
  220. cars-1.0.0rc3.dist-info/entry_points.txt +8 -0
cars/core/tiling.py ADDED
@@ -0,0 +1,774 @@
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
+ Tiling module:
23
+ contains functions related to regions and tiles management
24
+ """
25
+ # pylint: disable=too-many-lines
26
+
27
+ import logging
28
+
29
+ # Standard imports
30
+ import math
31
+ from typing import Dict, List, Tuple
32
+
33
+ # Third party imports
34
+ import numpy as np
35
+ from pyproj import CRS
36
+ from scipy.ndimage import generic_filter
37
+ from scipy.spatial import Delaunay # pylint: disable=no-name-in-module
38
+ from scipy.spatial import cKDTree # pylint: disable=no-name-in-module
39
+ from scipy.spatial import tsearch # pylint: disable=no-name-in-module
40
+ from shapely.geometry import box, mapping
41
+ from shapely.geometry.multipolygon import MultiPolygon
42
+
43
+ from cars.orchestrator.cluster.log_wrapper import cars_profile
44
+
45
+
46
+ def grid( # pylint: disable=too-many-positional-arguments
47
+ xmin: float, ymin: float, xmax: float, ymax: float, xsplit: int, ysplit: int
48
+ ) -> np.ndarray:
49
+ """
50
+ Generate grid of positions by splitting [xmin, xmax]x[ymin, ymax]
51
+ in splits of xsplit x ysplit size
52
+
53
+ :param xmin : xmin of the bounding box of the region to split
54
+ :param ymin : ymin of the bounding box of the region to split
55
+ :param xmax : xmax of the bounding box of the region to split
56
+ :param ymax : ymax of the bounding box of the region to split
57
+ :param xsplit: width of splits
58
+ :param ysplit: height of splits
59
+
60
+ :return: The output ndarray grid with nb_ysplits splits in first direction
61
+ and nb_xsplits in second direction for 2 dimensions 0:x, 1:y
62
+ :rtype: numpy array
63
+ """
64
+ nb_xsplits = math.ceil((xmax - xmin) / xsplit)
65
+ nb_ysplits = math.ceil((ymax - ymin) / ysplit)
66
+
67
+ out_grid = np.ndarray(
68
+ shape=(nb_ysplits + 1, nb_xsplits + 1, 2), dtype=float
69
+ )
70
+
71
+ for i in range(0, nb_xsplits + 1):
72
+ for j in range(0, nb_ysplits + 1):
73
+ out_grid[j, i, 0] = min(xmax, xmin + i * xsplit)
74
+ out_grid[j, i, 1] = min(ymax, ymin + j * ysplit)
75
+
76
+ return out_grid
77
+
78
+
79
+ @cars_profile(name="Transform four layers to two layers grid", interval=0.5)
80
+ def transform_four_layers_to_two_layers_grid(tiling_grid, terrain=False):
81
+ """
82
+ Transform a 4 layer grid: (N, M, 4) containing
83
+ [rowmin, rowmax, colmin, colmax] when epipolar
84
+ and [xmin, xmax, ymin, ymax]
85
+ with x = col and y = row
86
+ into a 2 layer grid: (N+1, M+1, 2) containing x and y
87
+ defined like : grid[j, i, 0] = min(xmax, xmin + i * xsplit)
88
+ and grid[j, i, 1] = min(ymax, ymin + j * ysplit)
89
+
90
+ :param tiling_grid: tiling grid
91
+ :type tiling_grid: np.ndarray
92
+
93
+ :return: 2D grid
94
+ :rtype: np.ndarray
95
+ """
96
+
97
+ if terrain is False:
98
+ tiling_grid_ = tiling_grid.copy()
99
+ tiling_grid_[:, :, [0, 1, 2, 3]] = tiling_grid_[:, :, [2, 3, 0, 1]]
100
+ else:
101
+ tiling_grid_ = tiling_grid.transpose(1, 0, 2).copy()
102
+
103
+ arr = np.ndarray(
104
+ shape=(tiling_grid_.shape[0] + 1, tiling_grid_.shape[1] + 1, 2),
105
+ dtype=float,
106
+ )
107
+
108
+ # Fill x
109
+ arr[0:-1, 0:-1, 0] = tiling_grid_[:, :, 0]
110
+ arr[0:-1, -1, 0] = tiling_grid_[:, -1, 1]
111
+ arr[-1, :, 0] = arr[0, :, 0] # All rows are identical
112
+
113
+ # Fill y
114
+ arr[0:-1, 0:-1, 1] = tiling_grid_[:, :, 2]
115
+ arr[-1, 0:-1, 1] = tiling_grid_[-1, :, 3]
116
+ arr[:, -1, 1] = arr[:, 0, 1] # All cols are identical
117
+
118
+ return arr
119
+
120
+
121
+ @cars_profile(name="Transform disp range grid to two layers", interval=0.5)
122
+ def transform_disp_range_grid_to_two_layers(disp_min_grid, disp_max_grid):
123
+ """
124
+ Transform tiling disp min and max to N+1 M+1 array corresponding
125
+ to N+1, M+1 , 2 tiling grid
126
+
127
+ :param disp_min_grid: disp min tiling
128
+ :type disp_min_grid: np ndarray
129
+ :param disp_max_grid: disp max tiling
130
+ :type disp_max_grid: np ndarray
131
+
132
+ :return: disp_min_grid, disp_max_grid
133
+ :rtype: np ndarray , np ndarray
134
+ """
135
+
136
+ # Create a 2xN+1, 2xM+1 matrix to apply filter on it
137
+ nb_row = 2 * disp_min_grid.shape[0] + 1
138
+ nb_col = 2 * disp_min_grid.shape[1] + 1
139
+
140
+ disp_min = np.full((nb_row, nb_col), np.nan)
141
+ disp_max = np.full((nb_row, nb_col), np.nan)
142
+
143
+ disp_min[1::2, 1::2] = disp_min_grid
144
+ disp_max[1::2, 1::2] = disp_max_grid
145
+
146
+ # Apply filter min and max:
147
+ # as each cell represent a node of 4 tiles from a regular grid
148
+ # we want for each node to
149
+ # represent the min and max of 4 cells
150
+
151
+ disp_min = generic_filter(disp_min, np.nanmin, [3, 3])
152
+ disp_max = generic_filter(disp_max, np.nanmax, [3, 3])
153
+
154
+ # eliminate odd indexes
155
+ disp_min = disp_min[::2, ::2]
156
+ disp_max = disp_max[::2, ::2]
157
+
158
+ return disp_min, disp_max
159
+
160
+
161
+ def generate_tiling_grid( # pylint: disable=too-many-positional-arguments
162
+ row_min: float,
163
+ col_min: float,
164
+ row_max: float,
165
+ col_max: float,
166
+ row_split: int,
167
+ col_split: int,
168
+ ) -> np.ndarray:
169
+ """
170
+ Generate grid of positions by splitting [row_min, row_max] x
171
+ [col_min, col_max]
172
+ in splits of row_split x col_split size
173
+
174
+ :param row_min : row_min of the bounding box of the region to split
175
+ :param col_min : col_min of the bounding box of the region to split
176
+ :param row_max : row_max of the bounding box of the region to split
177
+ :param col_max : col_max of the bounding box of the region to split
178
+ :param row_split: height of splits
179
+ :param col_split: width of splits
180
+
181
+ :return: The output ndarray grid with nb_row_split splits in first direction
182
+ and nb_col_split in second direction for 2 dimensions 0:y, 1:x
183
+ [row, col, :] containing [row_min, row_max, col_min, col_max]
184
+ :rtype: numpy array
185
+ """
186
+
187
+ nb_col_split = math.ceil((col_max - col_min) / col_split)
188
+ nb_row_split = math.ceil((row_max - row_min) / row_split)
189
+
190
+ out_grid = np.ndarray(shape=(nb_row_split, nb_col_split, 4), dtype=float)
191
+
192
+ for row in range(0, nb_row_split):
193
+ for col in range(0, nb_col_split):
194
+ out_grid[row, col, 0] = min(row_max, row_min + row * row_split)
195
+ out_grid[row, col, 1] = min(
196
+ row_max, row_min + (row + 1) * row_split
197
+ )
198
+ out_grid[row, col, 2] = min(col_max, col_min + col * col_split)
199
+ out_grid[row, col, 3] = min(
200
+ col_max, col_min + (col + 1) * col_split
201
+ )
202
+
203
+ return out_grid
204
+
205
+
206
+ def split(
207
+ xmin, ymin, xmax, ymax, xsplit, ysplit
208
+ ): # pylint: disable=too-many-positional-arguments
209
+ """
210
+ Split a region defined by [xmin, xmax] x [ymin, ymax]
211
+ in splits of xsplit x ysplit size
212
+
213
+ :param xmin : xmin of the bounding box of the region to split
214
+ :type xmin: float
215
+ :param ymin : ymin of the bounding box of the region to split
216
+ :type ymin: float
217
+ :param xmax : xmax of the bounding box of the region to split
218
+ :type xmax: float
219
+ :param ymax : ymax of the bounding box of the region to split
220
+ :type ymax: float
221
+ :param xsplit: width of splits
222
+ :type xsplit: int
223
+ :param ysplit: height of splits
224
+ :type ysplit: int
225
+
226
+ :return: A list of splits represented
227
+ by arrays of 4 elements [xmin, ymin, xmax, ymax]
228
+ :rtype: list of 4 float
229
+ """
230
+ nb_xsplits = math.ceil((xmax - xmin) / xsplit)
231
+ nb_ysplits = math.ceil((ymax - ymin) / ysplit)
232
+
233
+ terrain_regions = []
234
+
235
+ for i in range(0, nb_xsplits):
236
+ for j in range(0, nb_ysplits):
237
+ region = [
238
+ xmin + i * xsplit,
239
+ ymin + j * ysplit,
240
+ xmin + (i + 1) * xsplit,
241
+ ymin + (j + 1) * ysplit,
242
+ ]
243
+
244
+ # Crop to largest region
245
+ region = crop(region, [xmin, ymin, xmax, ymax])
246
+
247
+ terrain_regions.append(region)
248
+
249
+ return terrain_regions
250
+
251
+
252
+ def crop(region1, region2):
253
+ """
254
+ Crop a region by another one
255
+
256
+ :param region1: The region to crop as an array [xmin, ymin, xmax, ymax]
257
+ :type region1: list of four float
258
+ :param region2: The region used for cropping
259
+ as an array [xmin, ymin, xmax, ymax]
260
+ :type region2: list of four float
261
+
262
+ :return: The cropped regiona as an array [xmin, ymin, xmax, ymax].
263
+ If region1 is outside region2, might result in inconsistent region
264
+ :rtype: list of four float
265
+ """
266
+ out = region1[:]
267
+ out[0] = min(region2[2], max(region2[0], region1[0]))
268
+ out[2] = min(region2[2], max(region2[0], region1[2]))
269
+ out[1] = min(region2[3], max(region2[1], region1[1]))
270
+ out[3] = min(region2[3], max(region2[1], region1[3]))
271
+
272
+ return out
273
+
274
+
275
+ def pad(region, margins):
276
+ """
277
+ Pad region according to a margin
278
+
279
+ :param region: The region to pad
280
+ :type region: list of four floats
281
+ :param margins: Margin to add
282
+ :type margins: list of four floats
283
+ :return: padded region
284
+ :rtype: list of four float
285
+ """
286
+ out = region[:]
287
+ out[0] -= margins[0]
288
+ out[1] -= margins[1]
289
+ out[2] += margins[2]
290
+ out[3] += margins[3]
291
+
292
+ return out
293
+
294
+
295
+ def empty(region):
296
+ """
297
+ Check if a region is empty or inconsistent
298
+
299
+ :param region: region as an array [xmin, ymin, xmax, ymax]
300
+ :type region: list of four float
301
+ :return: True if the region is considered empty (no pixels inside),
302
+ False otherwise
303
+ :rtype: bool"""
304
+ return region[0] >= region[2] or region[1] >= region[3]
305
+
306
+
307
+ def union(regions):
308
+ """
309
+ Returns union of all regions
310
+
311
+ :param regions: list of region as an array [xmin, ymin, xmax, ymax]
312
+ :type regions: list of list of four float
313
+ :return: xmin, ymin, xmax, ymax
314
+ :rtype: list of 4 float
315
+ """
316
+
317
+ xmin = min((r[0] for r in regions))
318
+ xmax = max((r[2] for r in regions))
319
+ ymin = min((r[1] for r in regions))
320
+ ymax = max((r[3] for r in regions))
321
+
322
+ return xmin, ymin, xmax, ymax
323
+
324
+
325
+ def list_tiles(region, largest_region, tile_size, margin=1):
326
+ """
327
+ Given a region, cut largest_region into tiles of size tile_size
328
+ and return tiles that intersect region within margin pixels.
329
+
330
+ :param region: The region to list intersecting tiles
331
+ :type region: list of four float
332
+ :param largest_region: The region to split
333
+ :type largest_region: list of four float
334
+ :param tile_size: Width of tiles for splitting (squared tiles)
335
+ :type tile_size: int
336
+ :param margin: Also include margin neighboring tiles
337
+ :type margin: int
338
+ :return: A list of tiles as dicts containing idx and idy
339
+ :rtype: list of dict
340
+ """
341
+ # Find tile indices covered by region
342
+ min_tile_idx_x = int(math.floor(region[0] / tile_size))
343
+ max_tile_idx_x = int(math.ceil(region[2] / tile_size))
344
+ min_tile_idx_y = int(math.floor(region[1] / tile_size))
345
+ max_tile_idx_y = int(math.ceil(region[3] / tile_size))
346
+
347
+ # Include additional tiles
348
+ min_tile_idx_x -= margin
349
+ min_tile_idx_y -= margin
350
+ max_tile_idx_x += margin
351
+ max_tile_idx_y += margin
352
+
353
+ out = []
354
+
355
+ # Loop on tile idx
356
+ for tile_idx_x in range(min_tile_idx_x, max_tile_idx_x):
357
+ for tile_idx_y in range(min_tile_idx_y, max_tile_idx_y):
358
+ # Derive tile coordinates
359
+ tile = [
360
+ tile_idx_x * tile_size,
361
+ tile_idx_y * tile_size,
362
+ (tile_idx_x + 1) * tile_size,
363
+ (tile_idx_y + 1) * tile_size,
364
+ ]
365
+
366
+ # Crop to largest region
367
+ tile = crop(tile, largest_region)
368
+
369
+ # Check if tile is empty
370
+ if not empty(tile):
371
+ out.append({"idx": tile_idx_x, "idy": tile_idx_y, "tile": tile})
372
+
373
+ return out
374
+
375
+
376
+ def roi_to_start_and_size(region, resolution):
377
+ """
378
+ Convert roi as array of [xmin, ymin, xmax, ymax]
379
+ to xmin, ymin, xsize, ysize given a resolution
380
+
381
+ Beware that a negative spacing is considered for y axis,
382
+ and thus returned ystart is in fact ymax
383
+
384
+ :param region: The region to convert
385
+ :type region: list of four float
386
+ :param resolution: The resolution to use to determine sizes
387
+ :type resolution: float
388
+ :return: xstart, ystart, xsize, ysize tuple
389
+ :rtype: list of two float + two int
390
+ """
391
+ xstart = region[0]
392
+ ystart = region[3]
393
+ xsize = int(np.round((region[2] - region[0]) / resolution))
394
+ ysize = int(np.round((region[3] - region[1]) / resolution))
395
+
396
+ return xstart, ystart, xsize, ysize
397
+
398
+
399
+ def snap_to_grid(xmin, ymin, xmax, ymax, resolution):
400
+ """
401
+ Given a roi as xmin, ymin, xmax, ymax, snap values to entire step
402
+ of resolution
403
+
404
+ :param xmin: xmin of the roi
405
+ :type xmin: float
406
+ :param ymin: ymin of the roi
407
+ :type ymin: float
408
+ :param xmax: xmax of the roi
409
+ :type xmax: float
410
+ :param ymax: ymax of the roi
411
+ :type ymax: float
412
+ :param resolution: size of cells for snapping
413
+ :type resolution: float
414
+ :return: xmin, ymin, xmax, ymax snapped tuple
415
+ :rtype: list of four float
416
+ """
417
+ xmin = math.floor(xmin / resolution) * resolution
418
+ xmax = math.ceil(xmax / resolution) * resolution
419
+ ymin = math.floor(ymin / resolution) * resolution
420
+ ymax = math.ceil(ymax / resolution) * resolution
421
+
422
+ return xmin, ymin, xmax, ymax
423
+
424
+
425
+ def filter_simplices_on_the_edges(
426
+ original_grid_shape: Tuple, tri: Delaunay, simplices: np.ndarray
427
+ ):
428
+ """
429
+ Filter simplices on the edges which allows to cut triangles out of the
430
+ concave Delaunay triangulation.
431
+
432
+ :param original_grid_shape: shape of the original grid (almost regular) used
433
+ to create delaunay triangulation
434
+ :param tri: Delaunay triangulation
435
+ :param simplices: Selected simplices to filter: set -1 if selected simplex
436
+ is on the edges
437
+ """
438
+
439
+ # Filter simplices on the edges
440
+ edges = np.zeros((4, *original_grid_shape))
441
+
442
+ # left, bottom, right, top
443
+ edges[0, :, 0] = 1
444
+ edges[1, -1, :] = 1
445
+ edges[2, :, -1] = 1
446
+ edges[3, 0, :] = 1
447
+
448
+ for idx in range(edges.shape[0]):
449
+ edges_ravel = np.ravel(edges[idx, :, :])
450
+ # simplices filtered if all points are on an edge
451
+ edges_simplices = np.sum(edges_ravel[tri.simplices], axis=1) == 3
452
+ simplices[edges_simplices[simplices]] = -1
453
+
454
+
455
+ def terrain_grid_to_epipolar(
456
+ terrain_tiling_grid,
457
+ epipolar_tiling_grid,
458
+ epipolar_grid_min,
459
+ epipolar_grid_max,
460
+ epsg,
461
+ ):
462
+ """
463
+ Transform terrain grid to epipolar region
464
+ """
465
+
466
+ terrain_regions_grid = transform_four_layers_to_two_layers_grid(
467
+ terrain_tiling_grid, terrain=True
468
+ )
469
+ epipolar_regions_grid = transform_four_layers_to_two_layers_grid(
470
+ epipolar_tiling_grid
471
+ )
472
+
473
+ epipolar_regions_grid_shape = np.shape(epipolar_regions_grid)[:2]
474
+ epipolar_regions_grid_flat = epipolar_regions_grid.reshape(
475
+ -1, epipolar_regions_grid.shape[-1]
476
+ )
477
+
478
+ # in the following code a factor is used to increase the precision
479
+ spatial_ref = CRS.from_epsg(epsg)
480
+ if spatial_ref.is_geographic:
481
+ precision_factor = 1000.0
482
+ else:
483
+ precision_factor = 1.0
484
+
485
+ # Build delaunay_triangulation
486
+ tri_min = Delaunay(epipolar_grid_min * precision_factor)
487
+ tri_max = Delaunay(epipolar_grid_max * precision_factor)
488
+
489
+ # Build kdtrees
490
+ tree_min = cKDTree(epipolar_grid_min * precision_factor)
491
+ tree_max = cKDTree(epipolar_grid_max * precision_factor)
492
+
493
+ # Look-up terrain_regions_grid with Delaunay
494
+ s_min = tsearch(tri_min, terrain_regions_grid * precision_factor)
495
+ s_max = tsearch(tri_max, terrain_regions_grid * precision_factor)
496
+
497
+ # Filter simplices on the edges
498
+ filter_simplices_on_the_edges(epipolar_regions_grid_shape, tri_min, s_min)
499
+ filter_simplices_on_the_edges(epipolar_regions_grid_shape, tri_max, s_max)
500
+
501
+ points_disp_min = epipolar_regions_grid_flat[tri_min.simplices[s_min]]
502
+
503
+ points_disp_max = epipolar_regions_grid_flat[tri_max.simplices[s_max]]
504
+
505
+ nn_disp_min = epipolar_regions_grid_flat[
506
+ tree_min.query(terrain_regions_grid * precision_factor)[1]
507
+ ]
508
+
509
+ nn_disp_max = epipolar_regions_grid_flat[
510
+ tree_max.query(terrain_regions_grid * precision_factor)[1]
511
+ ]
512
+
513
+ points_disp_min_min = np.min(points_disp_min, axis=2)
514
+ points_disp_min_max = np.max(points_disp_min, axis=2)
515
+ points_disp_max_min = np.min(points_disp_max, axis=2)
516
+ points_disp_max_max = np.max(points_disp_max, axis=2)
517
+
518
+ # Use either Delaunay search or NN search
519
+ # if delaunay search fails (point outside triangles)
520
+ points_disp_min_min = np.where(
521
+ np.stack((s_min, s_min), axis=-1) != -1,
522
+ points_disp_min_min,
523
+ nn_disp_min,
524
+ )
525
+
526
+ points_disp_min_max = np.where(
527
+ np.stack((s_min, s_min), axis=-1) != -1,
528
+ points_disp_min_max,
529
+ nn_disp_min,
530
+ )
531
+
532
+ points_disp_max_min = np.where(
533
+ np.stack((s_max, s_max), axis=-1) != -1,
534
+ points_disp_max_min,
535
+ nn_disp_max,
536
+ )
537
+
538
+ points_disp_max_max = np.where(
539
+ np.stack((s_max, s_max), axis=-1) != -1,
540
+ points_disp_max_max,
541
+ nn_disp_max,
542
+ )
543
+
544
+ points = np.stack(
545
+ (
546
+ points_disp_min_min,
547
+ points_disp_min_max,
548
+ points_disp_max_min,
549
+ points_disp_max_max,
550
+ ),
551
+ axis=0,
552
+ )
553
+
554
+ points_min = np.min(points, axis=0)
555
+ points_max = np.max(points, axis=0)
556
+
557
+ return points_min, points_max
558
+
559
+
560
+ def region_hash_string(region: Tuple):
561
+ """
562
+ This lambda will allow to derive a key
563
+ to index region in the previous dictionary
564
+
565
+ :param region: region to hash
566
+ """
567
+ return "{}_{}_{}_{}".format(region[0], region[1], region[2], region[3])
568
+
569
+
570
+ # pylint: disable=too-many-positional-arguments
571
+ def get_corresponding_tiles_row_col(
572
+ terrain_tiling_grid: np.ndarray,
573
+ row: int,
574
+ col: int,
575
+ list_point_clouds: list,
576
+ list_epipolar_points_min: list,
577
+ list_epipolar_points_max: list,
578
+ ) -> Tuple[List, List, List]:
579
+ """
580
+ This function allows to get required point cloud for each
581
+ terrain region.
582
+
583
+ :param terrain_tiling_grid: terrain grid positions
584
+ :param row: row
585
+ :param col: column
586
+ epipolar input tiles where keys are image pairs index and values are
587
+ epipolar_points_min, epipolar_points_max, largest_epipolar_region,
588
+ opt_epipolar_tile_size
589
+
590
+ :return: Terrain regions
591
+ Corresponding tiles selected from delayed_point_clouds with
592
+ associated id
593
+ Terrain regions "rank" allowing to sorting tiles for dask
594
+ processing
595
+ """
596
+
597
+ logging.debug(
598
+ "Processing tile located at {},{} in tile grid".format(row, col)
599
+ )
600
+
601
+ # Terrain grid [row, j, :] = [xmin, xmax, ymin, ymax]
602
+ # terrain region = [xmin, ymin, xmax, ymax]
603
+ terrain_region = [
604
+ terrain_tiling_grid[row, col, 0],
605
+ terrain_tiling_grid[row, col, 2],
606
+ terrain_tiling_grid[row, col, 1],
607
+ terrain_tiling_grid[row, col, 3],
608
+ ]
609
+
610
+ # reverse convention as row and col correspond to new format
611
+ # Former format is transposed
612
+ row, col = col, row
613
+
614
+ logging.debug("Corresponding terrain region: {}".format(terrain_region))
615
+
616
+ # This list will hold the required points clouds for this terrain tile
617
+ required_point_clouds = []
618
+
619
+ # This list contains indexes of tiles (debug purpose)
620
+ list_indexes = []
621
+
622
+ # For each stereo configuration
623
+ for pc_id, (
624
+ point_cloud,
625
+ epipolar_points_min,
626
+ epipolar_points_max,
627
+ ) in enumerate(
628
+ zip( # noqa: B905
629
+ list_point_clouds,
630
+ list_epipolar_points_min,
631
+ list_epipolar_points_max,
632
+ )
633
+ ):
634
+ largest_epipolar_region = point_cloud.attributes[
635
+ "largest_epipolar_region"
636
+ ]
637
+ opt_epipolar_tile_size = point_cloud.attributes[
638
+ "opt_epipolar_tile_size"
639
+ ]
640
+
641
+ tile_min = np.minimum(
642
+ np.minimum(
643
+ np.minimum(
644
+ epipolar_points_min[row, col],
645
+ epipolar_points_min[row + 1, col],
646
+ ),
647
+ np.minimum(
648
+ epipolar_points_min[row + 1, col + 1],
649
+ epipolar_points_min[row, col + 1],
650
+ ),
651
+ ),
652
+ np.minimum(
653
+ np.minimum(
654
+ epipolar_points_max[row, col],
655
+ epipolar_points_max[row + 1, col],
656
+ ),
657
+ np.minimum(
658
+ epipolar_points_max[row + 1, col + 1],
659
+ epipolar_points_max[row, col + 1],
660
+ ),
661
+ ),
662
+ )
663
+
664
+ tile_max = np.maximum(
665
+ np.maximum(
666
+ np.maximum(
667
+ epipolar_points_min[row, col],
668
+ epipolar_points_min[row + 1, col],
669
+ ),
670
+ np.maximum(
671
+ epipolar_points_min[row + 1, col + 1],
672
+ epipolar_points_min[row, col + 1],
673
+ ),
674
+ ),
675
+ np.maximum(
676
+ np.maximum(
677
+ epipolar_points_max[row, col],
678
+ epipolar_points_max[row + 1, col],
679
+ ),
680
+ np.maximum(
681
+ epipolar_points_max[row + 1, col + 1],
682
+ epipolar_points_max[row, col + 1],
683
+ ),
684
+ ),
685
+ )
686
+
687
+ # Bounding region of corresponding cell
688
+ epipolar_region_minx = tile_min[0]
689
+ epipolar_region_miny = tile_min[1]
690
+ epipolar_region_maxx = tile_max[0]
691
+ epipolar_region_maxy = tile_max[1]
692
+
693
+ epipolar_region = [
694
+ epipolar_region_minx,
695
+ epipolar_region_miny,
696
+ epipolar_region_maxx,
697
+ epipolar_region_maxy,
698
+ ]
699
+
700
+ # Crop epipolar region to largest region
701
+ epipolar_region = crop(epipolar_region, largest_epipolar_region)
702
+
703
+ logging.debug(
704
+ "Corresponding epipolar region: {}".format(epipolar_region)
705
+ )
706
+
707
+ # Check if the epipolar region contains any pixels to process
708
+ if empty(epipolar_region):
709
+ logging.debug(
710
+ "Skipping terrain region "
711
+ "because corresponding epipolar region is empty"
712
+ )
713
+ else:
714
+ # Loop on all epipolar tiles covered by epipolar region
715
+ for epipolar_tile in list_tiles(
716
+ epipolar_region,
717
+ largest_epipolar_region,
718
+ opt_epipolar_tile_size,
719
+ ):
720
+ id_x = epipolar_tile["idx"]
721
+ id_y = epipolar_tile["idy"]
722
+
723
+ epi_grid_shape = point_cloud.tiling_grid.shape
724
+
725
+ if (
726
+ 0 <= id_x < epi_grid_shape[1]
727
+ and 0 <= id_y < epi_grid_shape[0]
728
+ ):
729
+ required_point_clouds.append(
730
+ (point_cloud[id_y, id_x], pc_id)
731
+ )
732
+ list_indexes.append([id_y, id_x])
733
+
734
+ rank = col * col + row * row
735
+
736
+ return (
737
+ terrain_region,
738
+ required_point_clouds,
739
+ rank,
740
+ list_indexes,
741
+ )
742
+
743
+
744
+ def get_paired_regions_as_geodict(
745
+ terrain_regions: List, epipolar_regions: List
746
+ ) -> Tuple[Dict, Dict]:
747
+ """
748
+ Get paired regions (terrain/epipolar) as "geodictionnaries": these
749
+ objects can be dumped into geojson files to be visualized.
750
+
751
+ :param terrain_regions: terrain region respecting cars tiling
752
+ :param epipolar_regions: corresponding epipolar regions
753
+
754
+ :return: Terrain dictionary and Epipolar dictionary containing
755
+ respectively Terrain tiles in terrain projection and Epipolar tiles
756
+ in epipolar projection
757
+ """
758
+
759
+ ter_geodict = {"type": "FeatureCollection", "features": []}
760
+ epi_geodict = {"type": "FeatureCollection", "features": []}
761
+
762
+ for idx, (ter, epi_list) in enumerate(
763
+ zip(terrain_regions, epipolar_regions) # noqa: B905
764
+ ):
765
+ feature = {}
766
+ feature["type"] = "Feature"
767
+ feature["properties"] = {"id": idx, "nb_epi": len(epi_list)}
768
+ feature["geometry"] = mapping(box(*ter))
769
+ ter_geodict["features"].append(feature.copy())
770
+ feature["geometry"] = mapping(MultiPolygon(box(*x) for x in epi_list))
771
+
772
+ epi_geodict["features"].append(feature.copy())
773
+
774
+ return ter_geodict, epi_geodict