cars 1.0.0a2__cp310-cp310-win_amd64.whl → 1.0.0a4__cp310-cp310-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 (144) hide show
  1. cars/__init__.py +3 -3
  2. cars/applications/__init__.py +0 -3
  3. cars/applications/application.py +14 -6
  4. cars/applications/application_template.py +42 -0
  5. cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +12 -2
  6. cars/applications/auxiliary_filling/auxiliary_filling_algo.py +2 -2
  7. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +95 -46
  8. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +7 -6
  9. cars/applications/dem_generation/abstract_dem_generation_app.py +9 -5
  10. cars/applications/dem_generation/dem_generation_algo.py +1 -1
  11. cars/applications/dem_generation/dem_generation_wrappers.py +44 -59
  12. cars/applications/dem_generation/dichotomic_generation_app.py +9 -6
  13. cars/applications/dem_generation/rasterization_app.py +112 -43
  14. cars/applications/dense_match_filling/__init__.py +1 -1
  15. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +2 -15
  16. cars/applications/dense_match_filling/fill_disp_algo.py +32 -373
  17. cars/applications/dense_match_filling/fill_disp_wrappers.py +0 -343
  18. cars/applications/dense_match_filling/zero_padding_app.py +10 -5
  19. cars/applications/dense_matching/abstract_dense_matching_app.py +2 -1
  20. cars/applications/dense_matching/census_mccnn_sgm_app.py +48 -60
  21. cars/applications/dense_matching/cpp/dense_matching_cpp.cp310-win_amd64.dll.a +0 -0
  22. cars/applications/dense_matching/cpp/dense_matching_cpp.cp310-win_amd64.pyd +0 -0
  23. cars/applications/dense_matching/dense_matching_algo.py +48 -14
  24. cars/applications/dense_matching/dense_matching_wrappers.py +11 -3
  25. cars/applications/dense_matching/disparity_grid_algo.py +95 -79
  26. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  27. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  28. cars/applications/dense_matching/loaders/pandora_loader.py +169 -34
  29. cars/applications/dsm_filling/border_interpolation_app.py +11 -12
  30. cars/applications/dsm_filling/bulldozer_filling_app.py +16 -15
  31. cars/applications/dsm_filling/exogenous_filling_app.py +14 -14
  32. cars/applications/grid_generation/abstract_grid_generation_app.py +1 -1
  33. cars/applications/grid_generation/epipolar_grid_generation_app.py +4 -2
  34. cars/applications/grid_generation/grid_correction_app.py +4 -1
  35. cars/applications/grid_generation/grid_generation_algo.py +7 -2
  36. cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +1 -1
  37. cars/applications/ground_truth_reprojection/direct_localization_app.py +2 -2
  38. cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +2 -1
  39. cars/applications/point_cloud_fusion/abstract_pc_fusion_app.py +0 -155
  40. cars/applications/point_cloud_fusion/mapping_to_terrain_tiles_app.py +0 -658
  41. cars/applications/point_cloud_fusion/pc_fusion_algo.py +0 -1339
  42. cars/applications/point_cloud_fusion/pc_fusion_wrappers.py +0 -869
  43. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +11 -6
  44. cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +9 -8
  45. cars/applications/point_cloud_outlier_removal/small_components_app.py +101 -270
  46. cars/applications/point_cloud_outlier_removal/statistical_app.py +120 -277
  47. cars/applications/rasterization/abstract_pc_rasterization_app.py +2 -1
  48. cars/applications/rasterization/rasterization_algo.py +18 -6
  49. cars/applications/rasterization/rasterization_wrappers.py +2 -1
  50. cars/applications/rasterization/simple_gaussian_app.py +88 -116
  51. cars/applications/resampling/abstract_resampling_app.py +1 -1
  52. cars/applications/resampling/bicubic_resampling_app.py +3 -1
  53. cars/applications/resampling/resampling_algo.py +60 -53
  54. cars/applications/resampling/resampling_wrappers.py +3 -1
  55. cars/applications/sparse_matching/abstract_sparse_matching_app.py +1 -1
  56. cars/applications/sparse_matching/sift_app.py +5 -25
  57. cars/applications/sparse_matching/sparse_matching_algo.py +3 -2
  58. cars/applications/sparse_matching/sparse_matching_wrappers.py +1 -1
  59. cars/applications/triangulation/abstract_triangulation_app.py +1 -1
  60. cars/applications/triangulation/line_of_sight_intersection_app.py +13 -11
  61. cars/applications/triangulation/pc_transform.py +552 -0
  62. cars/applications/triangulation/triangulation_algo.py +6 -4
  63. cars/applications/triangulation/triangulation_wrappers.py +1 -0
  64. cars/bundleadjustment.py +6 -6
  65. cars/cars.py +11 -9
  66. cars/core/cars_logging.py +80 -49
  67. cars/core/constants.py +0 -1
  68. cars/core/datasets.py +5 -2
  69. cars/core/geometry/abstract_geometry.py +364 -22
  70. cars/core/geometry/shareloc_geometry.py +112 -82
  71. cars/core/inputs.py +72 -19
  72. cars/core/outputs.py +1 -1
  73. cars/core/preprocessing.py +17 -3
  74. cars/core/projection.py +126 -6
  75. cars/core/tiling.py +10 -3
  76. cars/data_structures/cars_dataset.py +12 -10
  77. cars/data_structures/corresponding_tiles_tools.py +0 -103
  78. cars/data_structures/format_transformation.py +4 -1
  79. cars/devibrate.py +6 -3
  80. cars/extractroi.py +20 -21
  81. cars/orchestrator/cluster/abstract_cluster.py +15 -5
  82. cars/orchestrator/cluster/abstract_dask_cluster.py +6 -2
  83. cars/orchestrator/cluster/dask_jobqueue_utils.py +1 -1
  84. cars/orchestrator/cluster/log_wrapper.py +149 -22
  85. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +12 -4
  86. cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +2 -2
  87. cars/orchestrator/cluster/pbs_dask_cluster.py +1 -1
  88. cars/orchestrator/cluster/sequential_cluster.py +5 -4
  89. cars/orchestrator/cluster/slurm_dask_cluster.py +1 -1
  90. cars/orchestrator/orchestrator.py +15 -4
  91. cars/orchestrator/registry/id_generator.py +1 -0
  92. cars/orchestrator/registry/saver_registry.py +2 -2
  93. cars/pipelines/conf_resolution/conf_final_resolution.json +5 -3
  94. cars/pipelines/default/default_pipeline.py +461 -1052
  95. cars/pipelines/parameters/advanced_parameters.py +91 -64
  96. cars/pipelines/parameters/advanced_parameters_constants.py +6 -5
  97. cars/pipelines/parameters/application_parameters.py +71 -0
  98. cars/pipelines/parameters/depth_map_inputs.py +0 -314
  99. cars/pipelines/parameters/dsm_inputs.py +40 -4
  100. cars/pipelines/parameters/output_parameters.py +44 -8
  101. cars/pipelines/parameters/sensor_inputs.py +122 -73
  102. cars/pipelines/parameters/sensor_inputs_constants.py +0 -2
  103. cars/pipelines/parameters/sensor_loaders/__init__.py +4 -3
  104. cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +106 -0
  105. cars/pipelines/parameters/sensor_loaders/{basic_sensor_loader.py → basic_image_loader.py} +16 -22
  106. cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +121 -0
  107. cars/pipelines/parameters/sensor_loaders/{pivot_sensor_loader.py → pivot_image_loader.py} +10 -21
  108. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +4 -6
  109. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +1 -3
  110. cars/pipelines/pipeline_template.py +1 -3
  111. cars/pipelines/unit/unit_pipeline.py +676 -1070
  112. cars/starter.py +4 -3
  113. cars-1.0.0a4.dist-info/DELVEWHEEL +2 -0
  114. {cars-1.0.0a2.dist-info → cars-1.0.0a4.dist-info}/METADATA +135 -53
  115. {cars-1.0.0a2.dist-info → cars-1.0.0a4.dist-info}/RECORD +120 -134
  116. cars.libs/libgcc_s_seh-1-b2494fcbd4d80cf2c98fdd5261f6d850.dll +0 -0
  117. cars.libs/libstdc++-6-e9b0d12ae0e9555bbae55e8dfd08c3f7.dll +0 -0
  118. cars.libs/libwinpthread-1-7882d1b093714ccdfaf4e0789a817792.dll +0 -0
  119. cars/applications/dense_match_filling/cpp/__init__.py +0 -0
  120. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp310-win_amd64.dll.a +0 -0
  121. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp310-win_amd64.pyd +0 -0
  122. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.py +0 -72
  123. cars/applications/dense_match_filling/cpp/includes/dense_match_filling.hpp +0 -46
  124. cars/applications/dense_match_filling/cpp/meson.build +0 -9
  125. cars/applications/dense_match_filling/cpp/src/bindings.cpp +0 -11
  126. cars/applications/dense_match_filling/cpp/src/dense_match_filling.cpp +0 -142
  127. cars/applications/dense_match_filling/plane_app.py +0 -556
  128. cars/applications/hole_detection/__init__.py +0 -30
  129. cars/applications/hole_detection/abstract_hole_detection_app.py +0 -125
  130. cars/applications/hole_detection/cloud_to_bbox_app.py +0 -346
  131. cars/applications/hole_detection/hole_detection_algo.py +0 -144
  132. cars/applications/hole_detection/hole_detection_wrappers.py +0 -53
  133. cars/applications/point_cloud_denoising/__init__.py +0 -29
  134. cars/applications/point_cloud_denoising/abstract_pc_denoising_app.py +0 -273
  135. cars/applications/point_cloud_fusion/__init__.py +0 -30
  136. cars/applications/point_cloud_fusion/cloud_fusion_constants.py +0 -39
  137. cars/applications/sparse_matching/pandora_sparse_matching_app.py +0 -0
  138. cars/pipelines/parameters/depth_map_inputs_constants.py +0 -25
  139. cars-1.0.0a2.dist-info/DELVEWHEEL +0 -2
  140. cars.libs/libgcc_s_seh-1-f2b6825d483bdf14050493af93b5997d.dll +0 -0
  141. cars.libs/libstdc++-6-6b0059df6bc601df5a0f18a5805eea05.dll +0 -0
  142. cars.libs/libwinpthread-1-e01b8e85fd67c2b861f64d4ccc7df607.dll +0 -0
  143. {cars-1.0.0a2.dist-info → cars-1.0.0a4.dist-info}/WHEEL +0 -0
  144. {cars-1.0.0a2.dist-info → cars-1.0.0a4.dist-info}/entry_points.txt +0 -0
@@ -1,869 +0,0 @@
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
- # pylint: disable=too-many-lines
22
- """
23
- This module is responsible for the transition between triangulation and
24
- rasterization steps
25
- """
26
- # pylint: disable=C0302
27
-
28
- # Standard imports
29
- import logging
30
- from typing import List, Tuple, Union
31
-
32
- # Third party imports
33
- import numpy as np
34
- import pandas as pd
35
- import rasterio as rio
36
- import xarray as xr
37
- from shapely import geometry, length
38
-
39
- # CARS imports
40
- from cars.core import constants as cst
41
- from cars.core import inputs, preprocessing, projection, tiling
42
-
43
-
44
- def get_epsg(cloud_list):
45
- """
46
- Extract epsg from cloud list and check if all the same
47
-
48
- :param cloud_list: list of the point clouds
49
- """
50
- epsg = None
51
- for point_cloud in cloud_list:
52
- if epsg is None:
53
- epsg = int(point_cloud.attrs[cst.EPSG])
54
- elif int(point_cloud.attrs[cst.EPSG]) != epsg:
55
- logging.error("All point clouds do not have the same epsg code")
56
-
57
- return epsg
58
-
59
-
60
- def filter_cloud_with_mask(nb_points, crop_cloud, crop_terrain_tile_data_msk):
61
- """
62
- Delete masked points with terrain tile mask
63
-
64
- :param nb_points: total number of point cloud
65
- (increase at each point cloud)
66
- :param crop_cloud: the point cloud
67
- :param crop_terrain_tile_data_msk: terrain tile mask
68
- """
69
- crop_terrain_tile_data_msk = np.ravel(crop_terrain_tile_data_msk)
70
-
71
- crop_terrain_tile_data_msk_pos = np.nonzero(~crop_terrain_tile_data_msk)
72
-
73
- # compute nb points before apply the mask
74
- nb_points += crop_cloud.shape[1]
75
-
76
- crop_cloud = np.delete(crop_cloud, crop_terrain_tile_data_msk_pos[0], 0)
77
-
78
- return crop_cloud
79
-
80
-
81
- def compute_terrain_msk(
82
- dsm_epsg,
83
- xmin,
84
- xmax,
85
- ymin,
86
- ymax,
87
- margin,
88
- epsg,
89
- point_cloud,
90
- full_x,
91
- full_y,
92
- ):
93
- """
94
- Compute terrain tile msk bounds
95
-
96
- If the point clouds are not in the same referential as the roi,
97
- it is converted using the dsm_epsg
98
-
99
- :param dsm_epsg: epsg code for the CRS of the final output raster
100
- :param xmin: xmin of the rasterization grid
101
- (if None, the whole clouds are combined)
102
- :param xmax: xmax of the rasterization grid
103
- (if None, the whole clouds are combined)
104
- :param ymin: ymin of the rasterization grid
105
- (if None, the whole clouds are combined)
106
- :param ymax: ymax of the rasterization grid
107
- (if None, the whole clouds are combined)
108
- :param margin: Margin added for each tile, in meter or degree.
109
- (default value: 0)
110
- :param epsg: epsg code of the input cloud
111
- :param point_cloud: the point cloud
112
- :param full_x: point_cloud[X]
113
- :param full_y: point_cloud[Y]
114
- """
115
- if epsg != dsm_epsg:
116
- (
117
- full_x,
118
- full_y,
119
- ) = projection.get_converted_xy_np_arrays_from_dataset(
120
- point_cloud, dsm_epsg
121
- )
122
- msk_xmin = np.where(full_x > xmin - margin, True, False)
123
- msk_xmax = np.where(full_x < xmax + margin, True, False)
124
- msk_ymin = np.where(full_y > ymin - margin, True, False)
125
- msk_ymax = np.where(full_y < ymax + margin, True, False)
126
- terrain_tile_data_msk = np.logical_and(
127
- msk_xmin,
128
- np.logical_and(msk_xmax, np.logical_and(msk_ymin, msk_ymax)),
129
- )
130
- terrain_tile_data_msk_pos = terrain_tile_data_msk.astype(np.int8).nonzero()
131
-
132
- return terrain_tile_data_msk, terrain_tile_data_msk_pos
133
-
134
-
135
- def create_point_cloud_index(cloud_sample):
136
- """
137
- Create point cloud index from cloud list keys and color inputs
138
- """
139
- cloud_indexes_with_types = {
140
- cst.POINT_CLOUD_GLOBAL_ID: "uint16",
141
- cst.X: "float64",
142
- cst.Y: "float64",
143
- cst.Z: "float64",
144
- }
145
-
146
- # Add Z_inf Z_sup, and performance maps if computed
147
- for key in list(cloud_sample.keys()):
148
- if (
149
- cst.POINT_CLOUD_LAYER_INF in key
150
- or cst.POINT_CLOUD_LAYER_SUP in key
151
- or cst.POINT_CLOUD_PERFORMANCE_MAP_ROOT in key
152
- ):
153
- cloud_indexes_with_types[key] = "float32"
154
-
155
- # Add mask index
156
- if cst.EPI_MSK in cloud_sample:
157
- cloud_indexes_with_types[cst.POINT_CLOUD_MSK] = "uint8"
158
-
159
- # Add color indexes
160
- if cst.EPI_TEXTURE in cloud_sample:
161
- band_color = list(cloud_sample.coords[cst.BAND_IM].to_numpy())
162
- color_type = "float32"
163
- if "color_type" in cloud_sample.attrs:
164
- color_type = cloud_sample.attrs["color_type"]
165
- for band in band_color:
166
- band_index = "{}_{}".format(cst.POINT_CLOUD_CLR_KEY_ROOT, band)
167
- cloud_indexes_with_types[band_index] = color_type
168
-
169
- # Add classif indexes
170
- if cst.EPI_CLASSIFICATION in cloud_sample:
171
- band_classif = list(cloud_sample.coords[cst.BAND_CLASSIF].to_numpy())
172
- for band in band_classif:
173
- band_index = "{}_{}".format(cst.POINT_CLOUD_CLASSIF_KEY_ROOT, band)
174
- cloud_indexes_with_types[band_index] = "boolean"
175
-
176
- # Add filling information indexes
177
- if cst.EPI_FILLING in cloud_sample:
178
- band_filling = list(cloud_sample.coords[cst.BAND_FILLING].to_numpy())
179
- for band in band_filling:
180
- band_index = "{}_{}".format(cst.POINT_CLOUD_FILLING_KEY_ROOT, band)
181
- cloud_indexes_with_types[band_index] = "uint8"
182
-
183
- # Add ambiguity information index
184
- if cst.EPI_AMBIGUITY in cloud_sample:
185
- cloud_indexes_with_types[cst.EPI_AMBIGUITY] = "float32"
186
-
187
- return cloud_indexes_with_types
188
-
189
-
190
- def add_information_to_cloud(
191
- input_cloud, cloud_indexes, bbox, target_cloud, input_array, output_column
192
- ):
193
- """
194
- Add color information for a current cloud_list item
195
-
196
- :param cloud: source point cloud dataset
197
- :type cloud: xr.Dataset
198
- :param cloud_indexes: list of band data to extract
199
- :type cloud_indexes: list[str]
200
- :param bbox: bbox of interest
201
- :type bbox: list[int]
202
- :param crop_cloud: target flatten point cloud
203
- :type crop_cloud: np.array[columns, points]
204
- :param input_array: index of input to extract from cloud
205
- :type input_array: str
206
- :param output_column: index of crop_cloud to fill
207
- :type input_array: str
208
- """
209
- if input_array in input_cloud:
210
- full_array = input_cloud[input_array]
211
- if len(full_array.shape) == 3:
212
- # Array with multiple bands
213
- array = full_array[:, bbox[0] : bbox[2] + 1, bbox[1] : bbox[3] + 1]
214
- for column_name in cloud_indexes:
215
- if output_column in column_name:
216
- band_name = column_name.replace(output_column + "_", "")
217
- band = array.loc[band_name]
218
- index = cloud_indexes.index(column_name)
219
- target_cloud[index, :] = np.ravel(band.values)
220
- elif len(full_array.shape) == 2:
221
- # Array with single band
222
- array = full_array[bbox[0] : bbox[2] + 1, bbox[1] : bbox[3] + 1]
223
- index = cloud_indexes.index(output_column)
224
- target_cloud[index, :] = np.ravel(array.values)
225
-
226
-
227
- def get_color_type(clouds):
228
- """
229
- Get color type of the tiles and if the same type.
230
-
231
- :param cloud_list: list of clouds
232
- :type cloud_list: xarray Dataset
233
-
234
- :return: color type of the tiles list
235
- :rtype: str
236
-
237
- """
238
- color_types = []
239
- for cloud_id, cloud_item in enumerate(clouds):
240
- if cst.EPI_TEXTURE in clouds[cloud_id]:
241
- if "color_type" in cloud_item.attrs:
242
- color_types.append(cloud_item.attrs["color_type"])
243
- if color_types:
244
- color_type_set = set(color_types)
245
- if len(color_type_set) > 1:
246
- logging.warning("The tiles colors don't have the same type.")
247
- return color_types[0]
248
-
249
- return None
250
-
251
-
252
- def get_number_bands(cloud_list):
253
- """
254
- Get max number of bands of clouds
255
-
256
- :param cloud_list: list of clouds
257
- :type cloud_list: xarray Dataset
258
-
259
- :return: max number of band
260
- :rtype: int
261
-
262
- """
263
-
264
- nb_band_clr = 0
265
- for current_cloud in cloud_list:
266
- current_cloud_nb_bands = 0
267
- if cst.EPI_TEXTURE in current_cloud:
268
- clr_im = current_cloud[cst.EPI_TEXTURE].values
269
- if len(clr_im.shape) == 2:
270
- current_cloud_nb_bands = 1
271
- else:
272
- current_cloud_nb_bands = clr_im.shape[0]
273
-
274
- nb_band_clr = max(nb_band_clr, current_cloud_nb_bands)
275
-
276
- return nb_band_clr
277
-
278
-
279
- def filter_cloud(
280
- cloud: pd.DataFrame,
281
- index_elt_to_remove: List[int],
282
- filtered_elt_pos: bool = False,
283
- ) -> Tuple[pd.DataFrame, Union[None, pd.DataFrame]]:
284
- """
285
- Filter all points of the cloud DataFrame
286
- which index is in the index_elt_to_remove list.
287
-
288
- If filtered_elt_pos is set to True, the information of the removed elements
289
- positions in their original epipolar images are returned.
290
-
291
- To do so the cloud DataFrame has to be build
292
- with the 'with_coords' option activated.
293
-
294
- :param cloud: combined cloud
295
- as returned by the create_combined_cloud function
296
- :param index_elt_to_remove: indexes of lines
297
- to filter in the cloud DataFrame
298
- :param filtered_elt_pos: if filtered_elt_pos is set to True,
299
- the removed points positions in their original epipolar images are
300
- returned, otherwise it is set to None
301
- :return: Tuple composed of the filtered cloud DataFrame and
302
- the filtered elements epipolar position information
303
- (or None for the latter if filtered_elt_pos is set to False
304
- or if the cloud Dataframe has not been build with with_coords option)
305
- """
306
- if filtered_elt_pos and not (
307
- cst.POINT_CLOUD_COORD_EPI_GEOM_I in cloud.columns
308
- and cst.POINT_CLOUD_COORD_EPI_GEOM_J in cloud.columns
309
- and cst.POINT_CLOUD_ID_IM_EPI in cloud.columns
310
- ):
311
- logging.warning(
312
- "In filter_cloud: the filtered_elt_pos has been activated but "
313
- "the cloud Datafram has not been build with option with_coords. "
314
- "The positions cannot be retrieved."
315
- )
316
- filtered_elt_pos = False
317
-
318
- # retrieve removed points position in their original epipolar images
319
- if filtered_elt_pos:
320
- labels = [
321
- cst.POINT_CLOUD_COORD_EPI_GEOM_I,
322
- cst.POINT_CLOUD_COORD_EPI_GEOM_J,
323
- cst.POINT_CLOUD_ID_IM_EPI,
324
- ]
325
-
326
- removed_elt_pos_infos = cloud.loc[
327
- cloud.index.values[index_elt_to_remove], labels
328
- ].values
329
-
330
- removed_elt_pos_infos = pd.DataFrame(
331
- removed_elt_pos_infos, columns=labels
332
- )
333
- else:
334
- removed_elt_pos_infos = None
335
-
336
- # remove points from the cloud
337
- cloud = cloud.drop(index=cloud.index.values[index_elt_to_remove])
338
-
339
- return cloud, removed_elt_pos_infos
340
-
341
-
342
- def add_cloud_filtering_msk(
343
- clouds_list: List[xr.Dataset],
344
- elt_pos_infos: pd.DataFrame,
345
- mask_label: str,
346
- mask_value: int = 255,
347
- ):
348
- """
349
- Add a uint16 mask labeled 'mask_label' to the clouds in clouds_list.
350
- (in-line function)
351
-
352
- TODO only used in tests
353
-
354
- :param clouds_list: Input list of clouds
355
- :param elt_pos_infos: pandas dataframe
356
- composed of cst.POINT_CLOUD_COORD_EPI_GEOM_I,
357
- cst.POINT_CLOUD_COORD_EPI_GEOM_J, cst.POINT_CLOUD_ID_IM_EPI columns
358
- as computed in the create_combined_cloud function.
359
- Those information are used to retrieve the point position
360
- in its original epipolar image.
361
- :param mask_label: label to give to the mask in the datasets
362
- :param mask_value: filtered elements value in the mask
363
- """
364
-
365
- # Verify that the elt_pos_infos is consistent
366
- if (
367
- elt_pos_infos is None
368
- or cst.POINT_CLOUD_COORD_EPI_GEOM_I not in elt_pos_infos.columns
369
- or cst.POINT_CLOUD_COORD_EPI_GEOM_J not in elt_pos_infos.columns
370
- or cst.POINT_CLOUD_ID_IM_EPI not in elt_pos_infos.columns
371
- ):
372
- logging.warning(
373
- "Cannot generate filtered elements mask, "
374
- "no information about the point's"
375
- " original position in the epipolar image is given"
376
- )
377
-
378
- else:
379
- elt_index = elt_pos_infos.loc[:, cst.POINT_CLOUD_ID_IM_EPI].to_numpy()
380
-
381
- min_elt_index = np.min(elt_index)
382
- max_elt_index = np.max(elt_index)
383
-
384
- if min_elt_index < 0 or max_elt_index > len(clouds_list) - 1:
385
- raise RuntimeError(
386
- "Index indicated in the elt_pos_infos pandas. "
387
- "DataFrame is not coherent with the clouds list given in input"
388
- )
389
-
390
- # create and add mask to each element of clouds_list
391
- for cloud_id, cloud_item in enumerate(clouds_list):
392
- if mask_label not in cloud_item:
393
- nb_row = cloud_item.coords[cst.ROW].data.shape[0]
394
- nb_col = cloud_item.coords[cst.COL].data.shape[0]
395
- msk = np.zeros((nb_row, nb_col), dtype=np.uint16)
396
- else:
397
- msk = cloud_item[mask_label].values
398
-
399
- cur_elt_index = np.argwhere(elt_index == cloud_id)
400
-
401
- for elt_pos in range(cur_elt_index.shape[0]):
402
- i = int(
403
- elt_pos_infos.loc[
404
- cur_elt_index[elt_pos],
405
- cst.POINT_CLOUD_COORD_EPI_GEOM_I,
406
- ].iat[0]
407
- )
408
- j = int(
409
- elt_pos_infos.loc[
410
- cur_elt_index[elt_pos],
411
- cst.POINT_CLOUD_COORD_EPI_GEOM_J,
412
- ].iat[0]
413
- )
414
-
415
- try:
416
- msk[i, j] = mask_value
417
- except Exception as index_error:
418
- raise RuntimeError(
419
- "Point at location ({},{}) is not accessible "
420
- "in an image of size ({},{})".format(
421
- i, j, msk.shape[0], msk.shape[1]
422
- )
423
- ) from index_error
424
-
425
- cloud_item[mask_label] = ([cst.ROW, cst.COL], msk)
426
-
427
-
428
- def create_polygon_from_list_points(list_points):
429
- """
430
- Create a Shapely polygon from list of points
431
-
432
- :param list_points: list of (x, y) coordinates
433
- :type list_points: list
434
-
435
- :return: Polygon
436
- :rtype: shapely Polygon
437
-
438
- """
439
-
440
- list_shapely_points = []
441
- for point in list_points:
442
- list_shapely_points.append((point[0], point[1]))
443
-
444
- # Add first
445
- first_point = list_points[0]
446
- list_shapely_points.append((first_point[0], first_point[1]))
447
-
448
- poly = geometry.Polygon(list_shapely_points)
449
-
450
- return poly
451
-
452
-
453
- def compute_epsg_from_point_cloud(list_epipolar_point_clouds):
454
- """
455
- Compute epsg to use from list of tif point clouds
456
-
457
- :param list_epipolar_point_clouds: list of epipolar point clouds
458
- :type list_epipolar_point_clouds: list(dict)
459
-
460
- :return: epsg
461
- :rtype: int
462
- """
463
-
464
- # Get epsg from first point cloud
465
- pc_keys = list(list_epipolar_point_clouds.keys())
466
- point_cloud = list_epipolar_point_clouds[pc_keys[0]]
467
- tif_size = inputs.rasterio_get_size(point_cloud[cst.X])
468
-
469
- tile_size = 100
470
- grid = tiling.generate_tiling_grid(
471
- 0,
472
- 0,
473
- tif_size[0],
474
- tif_size[1],
475
- tile_size,
476
- tile_size,
477
- )
478
-
479
- can_compute_epsg = False
480
- x_y_min_max = None
481
- for row in range(grid.shape[0]):
482
- for col in range(grid.shape[1]):
483
- if not can_compute_epsg:
484
- # define window
485
- window = rio.windows.Window.from_slices(
486
- (
487
- grid[row, col, 0],
488
- grid[row, col, 1],
489
- ),
490
- (
491
- grid[row, col, 2],
492
- grid[row, col, 3],
493
- ),
494
- )
495
- # compute min max
496
- x_y_min_max = get_min_max_band(
497
- point_cloud[cst.X],
498
- point_cloud[cst.Y],
499
- point_cloud[cst.Z],
500
- point_cloud[cst.PC_EPSG],
501
- 4326,
502
- window=window,
503
- )
504
-
505
- if not any(np.isnan(x_y_min_max)):
506
- can_compute_epsg = True
507
-
508
- x_mean = (x_y_min_max[0] + x_y_min_max[1]) / 2
509
- y_mean = (x_y_min_max[2] + x_y_min_max[3]) / 2
510
-
511
- epsg = preprocessing.get_utm_zone_as_epsg_code(x_mean, y_mean)
512
-
513
- logging.info("EPSG code: {}".format(epsg))
514
-
515
- return epsg
516
-
517
-
518
- def intersect_polygons(poly1, poly2):
519
- """
520
- Check if two polygons intersect each other
521
-
522
- :param poly1: polygon
523
- :type poly1: shapely Polygon
524
- :param poly2: polygon
525
- :type poly2: shapely Polygon
526
-
527
- :return: True ff intersects
528
- :rtype: bool
529
-
530
- """
531
-
532
- return poly1.intersects(poly2)
533
-
534
-
535
- def get_min_max_band(
536
- image_path_x, image_path_y, image_path_z, epsg_in, epsg_utm, window=None
537
- ):
538
- """
539
- The purpose of this function is only to get the min and max values in
540
- row and col.
541
- of these input images, to do so:
542
- - Convert input images into a point cloud
543
- - Project the point cloud using the global EPSG code
544
- - Get the min and max values in row and col
545
-
546
- :param image_path_x: path to the X image to read
547
- :type image_path_x: str
548
- :param image_path_y: path to the Y image to read
549
- :type image_path_y: str
550
- :param image_path_z: path to the Z image to read
551
- :type image_path_z: str
552
- :param epsg_in: the EPSG in what the input images coordinates are
553
- :type epsg_in: integer
554
- :param epsg_utm: the EPSG code of the UTM referencial in common
555
- for all the input clouds
556
- :type epsg_utm: integer
557
- :param window: specify a region to open inside the image
558
- :type window: rasterio window
559
- :return: an array that contains [xmin, xmax, ymin, ymax] and the code epsg
560
- in which the point cloud is projected
561
- """
562
- cloud_data = {}
563
- with rio.open(image_path_x) as image_x:
564
- with rio.open(image_path_y) as image_y:
565
- with rio.open(image_path_z) as image_z:
566
- if window is None:
567
- band_x = image_x.read(1)
568
- band_y = image_y.read(1)
569
- band_z = image_z.read(1)
570
- else:
571
- band_x = image_x.read(1, window=window)
572
- band_y = image_y.read(1, window=window)
573
- band_z = image_z.read(1, window=window)
574
-
575
- cloud_data[cst.X] = np.ravel(band_x)
576
- cloud_data[cst.Y] = np.ravel(band_y)
577
- cloud_data[cst.Z] = np.ravel(band_z)
578
-
579
- pd_cloud = pd.DataFrame(cloud_data, columns=[cst.X, cst.Y, cst.Z])
580
-
581
- pd_cloud = pd_cloud.drop(
582
- pd_cloud.index[
583
- (pd_cloud[cst.X] == 0.0) # pylint: disable=E1136
584
- | (pd_cloud[cst.Y] == 0.0) # pylint: disable=E1136
585
- ]
586
- )
587
- pd_cloud = pd_cloud.drop(
588
- pd_cloud.index[
589
- (np.isnan(pd_cloud[cst.X])) # pylint: disable=E1136
590
- | (np.isnan(pd_cloud[cst.Y])) # pylint: disable=E1136
591
- ]
592
- )
593
-
594
- lon_med, lat_med, _ = pd_cloud.median()
595
- xmin = np.nan
596
- xmax = np.nan
597
- ymin = np.nan
598
- ymax = np.nan
599
- if not np.isnan(lon_med) and not np.isnan(lat_med):
600
- projection.point_cloud_conversion_dataframe(pd_cloud, epsg_in, epsg_utm)
601
-
602
- xmin = pd_cloud[cst.X].min()
603
- xmax = pd_cloud[cst.X].max()
604
- ymin = pd_cloud[cst.Y].min()
605
- ymax = pd_cloud[cst.Y].max()
606
-
607
- return [xmin, xmax, ymin, ymax]
608
-
609
-
610
- def convert_to_polygon(x_y_min_max):
611
- """
612
- Resample a bounding box and convert it into an shapely polygon
613
-
614
- :param x_y_min_max: the x/y coordinates of the upper left and lower
615
- right points
616
- :type x_y_min_max: an array of float [x_min, x_max, y_min, y_max]
617
-
618
- :return: an shapely polygon
619
- """
620
-
621
- x_min, x_max, y_min, y_max = x_y_min_max
622
- points = []
623
- for x_coord in np.linspace(x_min, x_max, 5):
624
- points.append((x_coord, y_min, 0))
625
- for y_cord in np.linspace(y_min, y_max, 5):
626
- points.append((x_max, y_cord, 0))
627
- for x_coord in np.linspace(x_min, x_max, 5)[::-1]:
628
- points.append((x_coord, y_max, 0))
629
- for y_cord in np.linspace(y_min, y_max, 5)[::-1]:
630
- points.append((x_min, y_cord, 0))
631
-
632
- return create_polygon_from_list_points(points)
633
-
634
-
635
- def filter_cloud_tif(pd_cloud, bounds):
636
- """
637
- Remove from the merged cloud all points that are out of the
638
- terrain tile boundaries.
639
-
640
- :param pd_cloud: point cloud
641
- :type pd_cloud: pandas dataframe
642
- :param bounds: terrain tile bounds
643
- :type bounds: array of float [xmin, ymin, xmax, ymax]
644
-
645
- :return: the epsg out
646
- :rtype: int
647
- """
648
- cond_x_min = pd_cloud[cst.X] < bounds[0]
649
- cond_x_max = pd_cloud[cst.X] > bounds[1]
650
- cond_y_min = pd_cloud[cst.Y] < bounds[2]
651
- cond_y_max = pd_cloud[cst.Y] > bounds[3]
652
- pd_cloud = pd_cloud.drop(
653
- pd_cloud.index[cond_x_min | cond_x_max | cond_y_min | cond_y_max]
654
- )
655
-
656
-
657
- def read_band(
658
- band_name, band_path, window, cloud_data_bands, cloud_data_types, cloud_data
659
- ):
660
- """
661
- Extract from tif point cloud and put in carsdataset point cloud
662
-
663
- :param band_name: type of point cloud data
664
- :type band_name: str
665
- :param band_path: path of the tif point cloud file
666
- :type band_path: str
667
- :param window: window to use
668
- :type window: dict
669
- :param cloud_data_bands: list of point cloud
670
- :type cloud_data_bands: list
671
- :param cloud_data: point cloud numpy dict
672
- :type cloud_data: dict
673
- """
674
- # Determine type
675
- band_type = inputs.rasterio_get_image_type(band_path)
676
- if cst.POINT_CLOUD_MSK in band_name:
677
- band_type = "uint8"
678
- if (
679
- cst.POINT_CLOUD_CLASSIF_KEY_ROOT in band_name
680
- or cst.POINT_CLOUD_FILLING_KEY_ROOT in band_name
681
- ):
682
- band_type = "boolean"
683
- with rio.open(band_path) as band_file:
684
- if band_file.count == 1:
685
- cloud_data_bands.append(band_name)
686
- cloud_data_types.append(band_type)
687
- cloud_data[band_name] = np.ravel(band_file.read(1, window=window))
688
- else:
689
- descriptions = inputs.get_descriptions_bands(band_path)
690
- for id_band, band_desc in enumerate(descriptions):
691
- band_full_name = "{}_{}".format(band_name, band_desc)
692
- cloud_data_bands.append(band_full_name)
693
- cloud_data_types.append(band_type)
694
- cloud_data[band_full_name] = np.ravel(
695
- band_file.read(1 + id_band, window=window)
696
- )
697
-
698
-
699
- def read_image_full(band_path, window=None, squeeze=False):
700
- """
701
- Read image with window
702
-
703
- :param band_path: path to image
704
- :param window: window
705
- :param squeeze: squeeze data if true
706
-
707
- :return array
708
- """
709
-
710
- with rio.open(band_path) as desc_band:
711
- data = desc_band.read(window=window)
712
- if squeeze:
713
- data = np.squeeze(data)
714
-
715
- return data
716
-
717
-
718
- def get_bounds(
719
- list_epipolar_point_clouds,
720
- epsg,
721
- roi_poly=None,
722
- ):
723
- """
724
- Get bounds of clouds
725
-
726
- :param list_epipolar_point_clouds: list of clouds
727
- :type list_epipolar_point_clouds: dict
728
- :param epsg: epsg of wanted roi
729
- :param roi_poly: crop with given roi
730
-
731
- :return bounds
732
- """
733
- xmin_list = []
734
- xmax_list = []
735
- ymin_list = []
736
- ymax_list = []
737
-
738
- for _, point_cloud in list_epipolar_point_clouds.items():
739
-
740
- local_x_y_min_max = get_min_max_band(
741
- point_cloud[cst.X],
742
- point_cloud[cst.Y],
743
- point_cloud[cst.Z],
744
- point_cloud[cst.PC_EPSG],
745
- epsg,
746
- )
747
-
748
- xmin_list.append(local_x_y_min_max[0])
749
- xmax_list.append(local_x_y_min_max[1])
750
- ymin_list.append(local_x_y_min_max[2])
751
- ymax_list.append(local_x_y_min_max[3])
752
-
753
- # Define a terrain tiling from the terrain bounds (in terrain epsg)
754
- global_xmin = min(xmin_list)
755
- global_xmax = max(xmax_list)
756
- global_ymin = min(ymin_list)
757
- global_ymax = max(ymax_list)
758
-
759
- if roi_poly is not None:
760
- (
761
- global_xmin,
762
- global_ymin,
763
- global_xmax,
764
- global_ymax,
765
- ) = preprocessing.crop_terrain_bounds_with_roi(
766
- roi_poly, global_xmin, global_ymin, global_xmax, global_ymax
767
- )
768
-
769
- terrain_bbox = [global_xmin, global_ymin, global_xmax, global_ymax]
770
-
771
- logging.info("terrain bbox in epsg {}: {}".format(str(epsg), terrain_bbox))
772
-
773
- return terrain_bbox
774
-
775
-
776
- def compute_max_nb_point_clouds(list_epipolar_point_clouds_by_tiles):
777
- """
778
- Compute the maximum number of point clouds superposing.
779
-
780
- :param list_epipolar_point_clouds_by_tiles: list of tiled point clouds
781
- :type list_epipolar_point_clouds_by_tiles: list(CarsDataset)
782
-
783
- :return: max number of point clouds
784
- :rtype: int
785
-
786
- """
787
-
788
- # Create polygon for each CarsDataset
789
-
790
- list_pc_polygon = []
791
- for epi_pc_cars_ds in list_epipolar_point_clouds_by_tiles:
792
- if "xmin" in epi_pc_cars_ds.attributes:
793
- xmin = epi_pc_cars_ds.attributes["xmin"]
794
- xmax = epi_pc_cars_ds.attributes["xmax"]
795
- ymin = epi_pc_cars_ds.attributes["ymin"]
796
- ymax = epi_pc_cars_ds.attributes["ymax"]
797
-
798
- x_y_min_max = [xmin, xmax, ymin, ymax]
799
- list_pc_polygon.append((convert_to_polygon(x_y_min_max), 1))
800
-
801
- # Compute polygon intersection. A polygon is reprensented with a tuple:
802
- # (shapely_polygon, nb_polygon intersection)
803
-
804
- list_intersected_polygons = []
805
-
806
- for poly in list_pc_polygon:
807
- if len(list_intersected_polygons) == 0:
808
- list_intersected_polygons.append(poly)
809
- else:
810
- new_poly_list = []
811
- for seen_poly in list_intersected_polygons:
812
- if poly[0].intersects(seen_poly[0]):
813
- # Compute intersection
814
- intersect_poly = poly[0].intersection(seen_poly[0])
815
- new_poly_list.append(
816
- (intersect_poly, poly[1] + seen_poly[1])
817
- )
818
- list_intersected_polygons += new_poly_list
819
-
820
- # Get max of intersection
821
- nb_pc = 0
822
- for poly in list_intersected_polygons:
823
- nb_pc = max(nb_pc, poly[1])
824
-
825
- return nb_pc
826
-
827
-
828
- def compute_average_distance(list_epipolar_point_clouds_by_tiles):
829
- """
830
- Compute average distance between points
831
-
832
-
833
- :param list_epipolar_point_clouds_by_tiles: list of tiled point clouds
834
- :type list_epipolar_point_clouds_by_tiles: list(CarsDataset)
835
-
836
- :return: average distance between points
837
- :rtype: float
838
-
839
- """
840
-
841
- # Get average for each point
842
- list_average_dist = []
843
- for epi_pc_cars_ds in list_epipolar_point_clouds_by_tiles:
844
- if "xmin" in epi_pc_cars_ds.attributes:
845
- xmin = epi_pc_cars_ds.attributes["xmin"]
846
- xmax = epi_pc_cars_ds.attributes["xmax"]
847
- ymin = epi_pc_cars_ds.attributes["ymin"]
848
- ymax = epi_pc_cars_ds.attributes["ymax"]
849
- data_epsg = epi_pc_cars_ds.attributes["epsg"]
850
-
851
- x_y_min_max = [xmin, xmax, ymin, ymax]
852
- # Create polygon
853
- poly = convert_to_polygon(x_y_min_max)
854
- # Transform polygon to epsg meter
855
- epsg_meter = 4978
856
- meter_poly = projection.polygon_projection(
857
- poly, data_epsg, epsg_meter
858
- )
859
-
860
- # Compute perimeter in meter
861
- perimeter_meters = length(meter_poly)
862
- # Compute perimeter in pixel
863
- nb_row = np.max(epi_pc_cars_ds.tiling_grid[:, :, 1])
864
- nb_col = np.max(epi_pc_cars_ds.tiling_grid[:, :, 3])
865
- perimeter_pixels = 2 * nb_row + 2 * nb_col
866
- # Compute average distance
867
- list_average_dist.append(perimeter_meters / perimeter_pixels)
868
-
869
- return max(list_average_dist)