cars 1.0.0a1__cp311-cp311-win_amd64.whl → 1.0.0a3__cp311-cp311-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 (81) hide show
  1. cars/__init__.py +4 -4
  2. cars/applications/application.py +14 -6
  3. cars/applications/application_template.py +22 -0
  4. cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +15 -10
  5. cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +7 -6
  6. cars/applications/dem_generation/abstract_dem_generation_app.py +9 -5
  7. cars/applications/dem_generation/dem_generation_wrappers.py +48 -25
  8. cars/applications/dem_generation/dichotomic_generation_app.py +27 -9
  9. cars/applications/dem_generation/rasterization_app.py +85 -32
  10. cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +4 -0
  11. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp311-win_amd64.dll.a +0 -0
  12. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp311-win_amd64.pyd +0 -0
  13. cars/applications/dense_match_filling/fill_disp_algo.py +41 -12
  14. cars/applications/dense_match_filling/plane_app.py +11 -0
  15. cars/applications/dense_match_filling/zero_padding_app.py +11 -1
  16. cars/applications/dense_matching/census_mccnn_sgm_app.py +254 -548
  17. cars/applications/dense_matching/cpp/dense_matching_cpp.cp311-win_amd64.dll.a +0 -0
  18. cars/applications/dense_matching/cpp/dense_matching_cpp.cp311-win_amd64.pyd +0 -0
  19. cars/applications/dense_matching/dense_matching_algo.py +59 -11
  20. cars/applications/dense_matching/dense_matching_wrappers.py +51 -31
  21. cars/applications/dense_matching/disparity_grid_algo.py +566 -0
  22. cars/applications/dense_matching/loaders/config_mapping.json +13 -0
  23. cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
  24. cars/applications/dense_matching/loaders/pandora_loader.py +78 -1
  25. cars/applications/dsm_filling/border_interpolation_app.py +10 -5
  26. cars/applications/dsm_filling/bulldozer_filling_app.py +14 -7
  27. cars/applications/dsm_filling/exogenous_filling_app.py +10 -5
  28. cars/applications/grid_generation/grid_correction_app.py +0 -53
  29. cars/applications/grid_generation/transform_grid.py +5 -5
  30. cars/applications/point_cloud_fusion/pc_fusion_algo.py +17 -11
  31. cars/applications/point_cloud_fusion/pc_fusion_wrappers.py +3 -4
  32. cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +9 -5
  33. cars/applications/point_cloud_outlier_removal/small_components_app.py +5 -3
  34. cars/applications/point_cloud_outlier_removal/statistical_app.py +4 -2
  35. cars/applications/rasterization/abstract_pc_rasterization_app.py +1 -0
  36. cars/applications/rasterization/rasterization_algo.py +20 -27
  37. cars/applications/rasterization/rasterization_wrappers.py +6 -5
  38. cars/applications/rasterization/simple_gaussian_app.py +30 -17
  39. cars/applications/resampling/resampling_algo.py +44 -49
  40. cars/applications/sparse_matching/sift_app.py +2 -22
  41. cars/applications/sparse_matching/sparse_matching_wrappers.py +0 -49
  42. cars/applications/triangulation/line_of_sight_intersection_app.py +1 -1
  43. cars/applications/triangulation/triangulation_wrappers.py +2 -1
  44. cars/bundleadjustment.py +51 -11
  45. cars/cars.py +15 -5
  46. cars/core/constants.py +1 -1
  47. cars/core/geometry/abstract_geometry.py +166 -12
  48. cars/core/geometry/shareloc_geometry.py +61 -14
  49. cars/core/inputs.py +15 -0
  50. cars/core/projection.py +117 -0
  51. cars/data_structures/cars_dataset.py +7 -5
  52. cars/orchestrator/cluster/log_wrapper.py +1 -1
  53. cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +1 -1
  54. cars/orchestrator/orchestrator.py +1 -1
  55. cars/orchestrator/registry/saver_registry.py +0 -78
  56. cars/pipelines/default/default_pipeline.py +69 -52
  57. cars/pipelines/parameters/advanced_parameters.py +17 -0
  58. cars/pipelines/parameters/advanced_parameters_constants.py +4 -0
  59. cars/pipelines/parameters/depth_map_inputs.py +22 -67
  60. cars/pipelines/parameters/dsm_inputs.py +16 -29
  61. cars/pipelines/parameters/output_parameters.py +44 -8
  62. cars/pipelines/parameters/sensor_inputs.py +117 -24
  63. cars/pipelines/parameters/sensor_loaders/basic_sensor_loader.py +3 -3
  64. cars/pipelines/parameters/sensor_loaders/pivot_sensor_loader.py +2 -2
  65. cars/pipelines/parameters/sensor_loaders/sensor_loader.py +4 -6
  66. cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +2 -2
  67. cars/pipelines/pipeline.py +8 -8
  68. cars/pipelines/unit/unit_pipeline.py +276 -274
  69. cars/starter.py +20 -1
  70. cars-1.0.0a3.dist-info/DELVEWHEEL +2 -0
  71. {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/METADATA +3 -2
  72. {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/RECORD +77 -74
  73. cars.libs/libgcc_s_seh-1-ca70890bbc5723b6d0ea31e9c9cded2b.dll +0 -0
  74. cars.libs/libstdc++-6-00ee19f73d5122a1277c137b1c218401.dll +0 -0
  75. cars.libs/libwinpthread-1-f5042e8e3d21edce20c1bc99445f551b.dll +0 -0
  76. cars-1.0.0a1.dist-info/DELVEWHEEL +0 -2
  77. cars.libs/libgcc_s_seh-1-f2b6825d483bdf14050493af93b5997d.dll +0 -0
  78. cars.libs/libstdc++-6-6b0059df6bc601df5a0f18a5805eea05.dll +0 -0
  79. cars.libs/libwinpthread-1-e01b8e85fd67c2b861f64d4ccc7df607.dll +0 -0
  80. {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/WHEEL +0 -0
  81. {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,566 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf8
3
+ #
4
+ # Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
5
+ #
6
+ # This file is part of CARS
7
+ # (see https://github.com/CNES/cars).
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ """
22
+ this module contains the wrapper used in disparity grid computation.
23
+ """
24
+
25
+ # Standard imports
26
+ import itertools
27
+ import logging
28
+
29
+ # Third party imports
30
+ import affine
31
+ import numpy as np
32
+ import rasterio
33
+ import xarray as xr
34
+ from scipy.ndimage import maximum_filter, minimum_filter
35
+ from shareloc.proj_utils import transform_physical_point_to_index
36
+
37
+ # CARS imports
38
+ import cars.applications.dense_matching.dense_matching_constants as dm_cst
39
+ import cars.orchestrator.orchestrator as ocht
40
+ from cars.applications.dense_matching.dense_matching_algo import (
41
+ LinearInterpNearestExtrap,
42
+ )
43
+ from cars.core import inputs, projection
44
+ from cars.core.projection import point_cloud_conversion
45
+ from cars.data_structures import cars_dataset, cars_dict
46
+
47
+
48
+ def generate_disp_grids_dataset(
49
+ grid_min,
50
+ grid_max,
51
+ saving_info,
52
+ raster_profile,
53
+ window=None,
54
+ row_coords=None,
55
+ col_coords=None,
56
+ ):
57
+ """
58
+ Generate disparity grids xarray dataset
59
+
60
+ :param grid_min: disp grid min
61
+ :type grid_min: np.ndarray
62
+ :param grid_max: disp grid max
63
+ :type grid_max: np.ndarray
64
+ :param saving_info: saving infos
65
+ :type saving_info: dict
66
+ :param raster_profile: raster_profile
67
+ :type raster_profile: dict
68
+ :param row_coords: row cooordinates
69
+ :type row_coords: np.ndarray, optional
70
+ :param col_coords: col coordinates
71
+ :type col_coords: np.ndarray, optional
72
+
73
+ :return: disp range dataset
74
+ :rtype: xarray.Dataset
75
+ """
76
+
77
+ if row_coords is None:
78
+ row_coords = np.arange(0, grid_min.shape[0])
79
+
80
+ if col_coords is None:
81
+ col_coords = np.arange(0, grid_min.shape[1])
82
+
83
+ disp_range_tile = xr.Dataset(
84
+ data_vars={
85
+ dm_cst.DISP_MIN_GRID: (["row", "col"], grid_min),
86
+ dm_cst.DISP_MAX_GRID: (["row", "col"], grid_max),
87
+ },
88
+ coords={
89
+ "row": row_coords,
90
+ "col": col_coords,
91
+ },
92
+ )
93
+
94
+ cars_dataset.fill_dataset(
95
+ disp_range_tile,
96
+ saving_info=saving_info,
97
+ window=window,
98
+ profile=raster_profile,
99
+ attributes=None,
100
+ overlaps=None,
101
+ )
102
+
103
+ return disp_range_tile
104
+
105
+
106
+ def generate_disp_range_const_tile_wrapper(
107
+ row_range,
108
+ col_range,
109
+ dmin,
110
+ dmax,
111
+ raster_profile,
112
+ saving_info,
113
+ saving_info_global_infos,
114
+ ):
115
+ """
116
+ Generate disparity range dataset from constant dmin and dmax
117
+
118
+ :param row_range: Row range
119
+ :type row_range: list
120
+ :param col_range: Column range.
121
+ :type col_range: list
122
+ :param dmin: disparity minimum.
123
+ :type dmin: float
124
+ :param dmax: disparity maximum.
125
+ :type dmax: float
126
+ :param raster_profile: The raster profile.
127
+ :type raster_profile: dict
128
+ :param saving_info: The disp range grid saving information.
129
+ :type saving_info: dict
130
+ :param saving_info_global_infos: Global info saving infos.
131
+ :type saving_info_global_infos: dict
132
+
133
+ :return: Disparity range grid
134
+ :rtype: dict
135
+ """
136
+ grid_min = np.empty((len(row_range), len(col_range)))
137
+ grid_max = np.empty((len(row_range), len(col_range)))
138
+ grid_min[:, :] = dmin
139
+ grid_max[:, :] = dmax
140
+
141
+ mono_tile_saving_info = ocht.update_saving_infos(saving_info, row=0, col=0)
142
+ disp_range = generate_disp_grids_dataset(
143
+ grid_min, grid_max, mono_tile_saving_info, raster_profile, window=None
144
+ )
145
+
146
+ # Generate infos on global min and max
147
+ global_infos = cars_dict.CarsDict(
148
+ {"global_min": np.nanmin(dmin), "global_max": np.nanmin(dmax)}
149
+ )
150
+ cars_dataset.fill_dict(global_infos, saving_info=saving_info_global_infos)
151
+
152
+ return disp_range, global_infos
153
+
154
+
155
+ def generate_disp_range_from_dem_wrapper(
156
+ epipolar_grid_array_window,
157
+ full_epi_row_range,
158
+ full_epi_col_range,
159
+ sensor_image_right,
160
+ grid_right,
161
+ geom_plugin_with_dem_and_geoid,
162
+ dem_median,
163
+ dem_min,
164
+ dem_max,
165
+ altitude_delta_min,
166
+ altitude_delta_max,
167
+ raster_profile,
168
+ saving_info,
169
+ saving_info_global_infos,
170
+ filter_overlap,
171
+ disp_to_alt_ratio,
172
+ disp_min_threshold=None,
173
+ disp_max_threshold=None,
174
+ ):
175
+ """
176
+ Generate disparity range dataset from dems
177
+
178
+ :param epipolar_grid_array_window: The window of the epipolar grid array.
179
+ :type epipolar_grid_array_window: dict
180
+ :param full_epi_row_range: The full range of rows in the epipolar grid.
181
+ :type full_epi_row_range: list
182
+ :param full_epi_col_range: The full range of columns in the epipolar grid.
183
+ :type full_epi_col_range: list
184
+ :param sensor_image_right: The right sensor image.
185
+ :type sensor_image_right: dict
186
+ :param grid_right: The right epipolar grid.
187
+ :type grid_right: dict
188
+ :param geom_plugin_with_dem_and_geoid: The geometry plugin with DEM.
189
+ :type geom_plugin_with_dem_and_geoid: object
190
+ :param dem_median: Path of dem median.
191
+ :type dem_median: str
192
+ :param dem_min: Path of dem min.
193
+ :type dem_min: str
194
+ :param dem_max: Path of dem max.
195
+ :type dem_max: srt
196
+ :param altitude_delta_min: The minimum altitude delta.
197
+ :type altitude_delta_min: float
198
+ :param altitude_delta_max: The maximum altitude delta.
199
+ :type altitude_delta_max: float
200
+ :param raster_profile: The raster profile.
201
+ :type raster_profile: dict
202
+ :param saving_info: The disp range grid saving information.
203
+ :type saving_info: dict
204
+ :param saving_info_global_infos: Global info saving infos.
205
+ :type saving_info_global_infos: dict
206
+ :param filter_overlap: The overlap to use for filtering.
207
+ :type filter_overlap: int
208
+ :param disp_to_alt_ratio: disparity to altitude ratio
209
+ :type disp_to_alt_ratio: float
210
+ :param disp_min_threshold: The minimum disparity threshold.
211
+ :type disp_min_threshold: float, optional
212
+ :param disp_max_threshold: The maximum disparity threshold.
213
+ :type disp_max_threshold: float, optional
214
+
215
+ :return: Disparity range grid
216
+ :rtype: dict
217
+ """
218
+
219
+ # compute reverse matrix
220
+ transform_sensor = rasterio.Affine(
221
+ *np.abs(
222
+ inputs.rasterio_get_transform(
223
+ sensor_image_right["image"]["main_file"]
224
+ )
225
+ )
226
+ )
227
+
228
+ trans_inv_sensor = ~transform_sensor
229
+ # Transform to positive values
230
+ trans_inv_sensor = np.array(trans_inv_sensor)
231
+ trans_inv_sensor = np.reshape(trans_inv_sensor, (3, 3))
232
+ if trans_inv_sensor[0, 0] < 0:
233
+ trans_inv_sensor[0, :] *= -1
234
+ if trans_inv_sensor[1, 1] < 0:
235
+ trans_inv_sensor[1, :] *= -1
236
+ trans_inv_sensor = affine.Affine(*list(trans_inv_sensor.flatten()))
237
+
238
+ # Geometry plugin
239
+ geo_plugin = geom_plugin_with_dem_and_geoid
240
+
241
+ # get epsg
242
+ terrain_epsg = inputs.rasterio_get_epsg(dem_median)
243
+
244
+ # Get epipolar position of all dem mean
245
+ transform_dem_median = inputs.rasterio_get_transform(dem_median)
246
+
247
+ # use local disparity
248
+
249
+ # Get associated alti mean / min / max values
250
+ dem_median_shape = inputs.rasterio_get_size(dem_median)
251
+ dem_median_width, dem_median_height = dem_median_shape
252
+
253
+ # get corresponding window from epipolar_array_window
254
+ epi_grid_margin = filter_overlap + 1
255
+ epi_grid_row_min = epipolar_grid_array_window["row_min"]
256
+ epi_grid_row_max = epipolar_grid_array_window["row_max"]
257
+ epi_grid_col_min = epipolar_grid_array_window["col_min"]
258
+ epi_grid_col_max = epipolar_grid_array_window["col_max"]
259
+
260
+ def clip(value, min_value, max_value):
261
+ """
262
+ Clip a value inside bounds
263
+ """
264
+ return int(max(min_value, min(value, max_value)))
265
+
266
+ # Epi grid tile coordinate to use, with and without margins
267
+ epi_grid_row_min_with_margin = clip(
268
+ epi_grid_row_min - epi_grid_margin, 0, len(full_epi_row_range)
269
+ )
270
+ epi_grid_row_max_with_margin = clip(
271
+ epi_grid_row_max + epi_grid_margin, 0, len(full_epi_row_range)
272
+ )
273
+ epi_grid_col_min_with_margin = clip(
274
+ epi_grid_col_min - epi_grid_margin, 0, len(full_epi_col_range)
275
+ )
276
+ epi_grid_col_max_with_margin = clip(
277
+ epi_grid_col_max + epi_grid_margin, 0, len(full_epi_col_range)
278
+ )
279
+
280
+ # range to use for epipolar interpolation
281
+ row_range_with_margin = full_epi_row_range[
282
+ epi_grid_row_min_with_margin:epi_grid_row_max_with_margin
283
+ ]
284
+ row_range_no_margin = full_epi_row_range[epi_grid_row_min:epi_grid_row_max]
285
+ col_range_with_margin = full_epi_col_range[
286
+ epi_grid_col_min_with_margin:epi_grid_col_max_with_margin
287
+ ]
288
+ col_range_no_margin = full_epi_col_range[epi_grid_col_min:epi_grid_col_max]
289
+
290
+ # Loc on dem median
291
+ epi_bbox = [
292
+ (np.min(col_range_with_margin), np.min(row_range_with_margin)),
293
+ (np.min(col_range_with_margin), np.max(row_range_with_margin)),
294
+ (np.max(col_range_with_margin), np.min(row_range_with_margin)),
295
+ (np.max(col_range_with_margin), np.max(row_range_with_margin)),
296
+ ]
297
+ sensor_bbox = geo_plugin.sensor_position_from_grid(grid_right, epi_bbox)
298
+ row_sensor_bbox, col_sensor_bbox = transform_physical_point_to_index(
299
+ trans_inv_sensor, sensor_bbox[:, 1], sensor_bbox[:, 0]
300
+ )
301
+
302
+ terrain_bbox = geo_plugin.safe_direct_loc(
303
+ sensor_image_right["image"]["main_file"],
304
+ sensor_image_right["geomodel"],
305
+ col_sensor_bbox,
306
+ row_sensor_bbox,
307
+ )
308
+
309
+ # reshape terrain bbox
310
+ terrain_bbox = terrain_bbox[0:2].T
311
+ terrain_bbox[:, [1, 0]] = terrain_bbox[:, [0, 1]]
312
+
313
+ # get pixel location on dem median
314
+ pixel_roi_dem_mean = inputs.rasterio_get_pixel_points(
315
+ dem_median, terrain_bbox
316
+ )
317
+
318
+ # Add margins (for interpolation) and clip
319
+ dem_margin = 10 # arbitrary
320
+ roi_lower_row = np.floor(np.min(pixel_roi_dem_mean[:, 0])) - dem_margin
321
+ roi_upper_row = np.ceil(np.max(pixel_roi_dem_mean[:, 0])) + dem_margin
322
+ roi_lower_col = np.floor(np.min(pixel_roi_dem_mean[:, 1])) - dem_margin
323
+ roi_upper_col = np.ceil(np.max(pixel_roi_dem_mean[:, 1])) + dem_margin
324
+
325
+ min_row = clip(roi_lower_row, 0, dem_median_height)
326
+ max_row = clip(roi_upper_row, 0, dem_median_height)
327
+ min_col = clip(roi_lower_col, 0, dem_median_width)
328
+ max_col = clip(roi_upper_col, 0, dem_median_width)
329
+
330
+ # compute terrain positions to use (all dem min and max)
331
+ row_indexes = range(min_row, max_row)
332
+ col_indexes = range(min_col, max_col)
333
+ transformer = rasterio.transform.AffineTransformer(transform_dem_median)
334
+
335
+ indexes = np.array(list(itertools.product(row_indexes, col_indexes)))
336
+
337
+ row = indexes[:, 0]
338
+ col = indexes[:, 1]
339
+ x_mean, y_mean = transformer.xy(row, col)
340
+ terrain_positions = np.transpose(np.array([x_mean, y_mean]))
341
+
342
+ # dem mean in terrain_epsg
343
+ x_mean = terrain_positions[:, 0]
344
+ y_mean = terrain_positions[:, 1]
345
+
346
+ dem_median_list = inputs.rasterio_get_values(
347
+ dem_median, x_mean, y_mean, point_cloud_conversion
348
+ )
349
+
350
+ nan_mask = ~np.isnan(dem_median_list)
351
+
352
+ # transform to lon lat
353
+ terrain_position_lon_lat = projection.point_cloud_conversion(
354
+ terrain_positions, terrain_epsg, 4326
355
+ )
356
+ lon_mean = terrain_position_lon_lat[:, 0]
357
+ lat_mean = terrain_position_lon_lat[:, 1]
358
+
359
+ if None not in (dem_min, dem_max, dem_median):
360
+ # dem min and max are in 4326
361
+ dem_min_list = inputs.rasterio_get_values(
362
+ dem_min, lon_mean, lat_mean, point_cloud_conversion
363
+ )
364
+ dem_max_list = inputs.rasterio_get_values(
365
+ dem_max, lon_mean, lat_mean, point_cloud_conversion
366
+ )
367
+ nan_mask = nan_mask & ~np.isnan(dem_min_list) & ~np.isnan(dem_max_list)
368
+ else:
369
+ dem_min_list = dem_median_list - altitude_delta_min
370
+ dem_max_list = dem_median_list + altitude_delta_max
371
+
372
+ # filter nan value from input points
373
+ lon_mean = lon_mean[nan_mask]
374
+ lat_mean = lat_mean[nan_mask]
375
+ dem_median_list = dem_median_list[nan_mask]
376
+ dem_min_list = dem_min_list[nan_mask]
377
+ dem_max_list = dem_max_list[nan_mask]
378
+
379
+ # sensors physical positions
380
+ (
381
+ ind_cols_sensor,
382
+ ind_rows_sensor,
383
+ _,
384
+ ) = geom_plugin_with_dem_and_geoid.safe_inverse_loc(
385
+ sensor_image_right["image"]["main_file"],
386
+ sensor_image_right["geomodel"],
387
+ lat_mean,
388
+ lon_mean,
389
+ z_coord=dem_median_list,
390
+ )
391
+
392
+ # Generate epipolar disp grids
393
+ # Get epipolar positions
394
+ (epipolar_positions_row, epipolar_positions_col) = np.meshgrid(
395
+ col_range_with_margin,
396
+ row_range_with_margin,
397
+ )
398
+ epipolar_positions = np.stack(
399
+ [epipolar_positions_row, epipolar_positions_col], axis=2
400
+ )
401
+
402
+ # Get sensor position
403
+ sensors_positions = (
404
+ geom_plugin_with_dem_and_geoid.sensor_position_from_grid(
405
+ grid_right,
406
+ np.reshape(
407
+ epipolar_positions,
408
+ (
409
+ epipolar_positions.shape[0] * epipolar_positions.shape[1],
410
+ 2,
411
+ ),
412
+ ),
413
+ )
414
+ )
415
+
416
+ # Transform physical position to index
417
+ ind_rows_sensor_grid, ind_cols_sensor_grid = (
418
+ transform_physical_point_to_index(
419
+ trans_inv_sensor, sensors_positions[:, 1], sensors_positions[:, 0]
420
+ )
421
+ )
422
+
423
+ if len(ind_rows_sensor) < 5:
424
+ # QH6214 needs at least 4 points for interpolation
425
+
426
+ grid_min = np.empty(
427
+ (len(row_range_no_margin), len(col_range_no_margin))
428
+ )
429
+ grid_max = np.empty(
430
+ (len(row_range_no_margin), len(col_range_no_margin))
431
+ )
432
+ grid_min[:, :] = 0
433
+ grid_max[:, :] = 0
434
+
435
+ disp_range = generate_disp_grids_dataset(
436
+ grid_min,
437
+ grid_max,
438
+ saving_info,
439
+ raster_profile,
440
+ window=epipolar_grid_array_window,
441
+ row_coords=row_range_no_margin,
442
+ col_coords=col_range_no_margin,
443
+ )
444
+
445
+ # Generate infos on global min and max
446
+ global_infos = cars_dict.CarsDict(
447
+ {
448
+ "global_min": 0,
449
+ "global_max": 0,
450
+ }
451
+ )
452
+ cars_dataset.fill_dict(
453
+ global_infos, saving_info=saving_info_global_infos
454
+ )
455
+
456
+ return disp_range, global_infos
457
+
458
+ # Interpolate disparity
459
+ disp_min_points = -(dem_max_list - dem_median_list) / disp_to_alt_ratio
460
+ disp_max_points = -(dem_min_list - dem_median_list) / disp_to_alt_ratio
461
+
462
+ interp_min_linear = LinearInterpNearestExtrap(
463
+ list(zip(ind_rows_sensor, ind_cols_sensor)), # noqa: B905
464
+ disp_min_points,
465
+ )
466
+ interp_max_linear = LinearInterpNearestExtrap(
467
+ list(zip(ind_rows_sensor, ind_cols_sensor)), # noqa: B905
468
+ disp_max_points,
469
+ )
470
+
471
+ grid_min = np.reshape(
472
+ interp_min_linear(ind_rows_sensor_grid, ind_cols_sensor_grid),
473
+ (
474
+ epipolar_positions.shape[0],
475
+ epipolar_positions.shape[1],
476
+ ),
477
+ )
478
+
479
+ grid_max = np.reshape(
480
+ interp_max_linear(ind_rows_sensor_grid, ind_cols_sensor_grid),
481
+ (
482
+ epipolar_positions.shape[0],
483
+ epipolar_positions.shape[1],
484
+ ),
485
+ )
486
+
487
+ # Add margin
488
+ diff = grid_max - grid_min
489
+ logging.info("Max grid max - grid min : {} disp ".format(np.max(diff)))
490
+
491
+ if disp_min_threshold is not None:
492
+ if np.any(grid_min < disp_min_threshold):
493
+ logging.warning(
494
+ "Override disp_min with disp_min_threshold {}".format(
495
+ disp_min_threshold
496
+ )
497
+ )
498
+ grid_min[grid_min < disp_min_threshold] = disp_min_threshold
499
+ if disp_max_threshold is not None:
500
+ if np.any(grid_max > disp_max_threshold):
501
+ logging.warning(
502
+ "Override disp_max with disp_max_threshold {}".format(
503
+ disp_max_threshold
504
+ )
505
+ )
506
+ grid_max[grid_max > disp_max_threshold] = disp_max_threshold
507
+
508
+ # generate footprint
509
+ footprint_mask = create_circular_mask(filter_overlap, filter_overlap)
510
+ grid_min = minimum_filter(
511
+ grid_min, footprint=footprint_mask, mode="nearest"
512
+ )
513
+ grid_max = maximum_filter(
514
+ grid_max, footprint=footprint_mask, mode="nearest"
515
+ )
516
+
517
+ # Create xarray dataset
518
+ disp_range = generate_disp_grids_dataset(
519
+ grid_min,
520
+ grid_max,
521
+ saving_info,
522
+ raster_profile,
523
+ window=epipolar_grid_array_window,
524
+ row_coords=row_range_with_margin,
525
+ col_coords=col_range_with_margin,
526
+ )
527
+
528
+ # crop epipolar grid from margin added for propagation filter
529
+ disp_range = disp_range.sel(
530
+ row=list(row_range_no_margin), col=list(col_range_no_margin)
531
+ )
532
+
533
+ # Generate infos on global min and max
534
+ global_infos = cars_dict.CarsDict(
535
+ {
536
+ "global_min": np.floor(np.nanmin(grid_min)),
537
+ "global_max": np.ceil(np.nanmax(grid_max)),
538
+ }
539
+ )
540
+ cars_dataset.fill_dict(global_infos, saving_info=saving_info_global_infos)
541
+
542
+ return disp_range, global_infos
543
+
544
+
545
+ def create_circular_mask(height, width):
546
+ """
547
+ Create a circular mask for footprint around pixel
548
+
549
+ :param height: height of footprint
550
+ :type height: int
551
+ :param width: width of footprint
552
+ :type width: int
553
+
554
+ :return: mask representing circular footprint
555
+ :rtype: np.ndarray
556
+ """
557
+ center = (int(width / 2), int(height / 2))
558
+ radius = min(center[0], center[1], width - center[0], height - center[1])
559
+
560
+ y_grid, x_grid = np.ogrid[:height, :width]
561
+ dist_from_center = np.sqrt(
562
+ (x_grid - center[0]) ** 2 + (y_grid - center[1]) ** 2
563
+ )
564
+
565
+ mask = dist_from_center <= radius
566
+ return mask.astype(bool)
@@ -0,0 +1,13 @@
1
+ {
2
+ "10": "census_sgm_mountain_and_vegetation",
3
+ "20": "census_sgm_mountain_and_vegetation",
4
+ "30": "census_sgm_mountain_and_vegetation",
5
+ "40": "census_sgm_mountain_and_vegetation",
6
+ "50": "census_sgm_urban",
7
+ "60": "census_sgm_homogeneous",
8
+ "70": "census_sgm_mountain_and_vegetation",
9
+ "80": "census_sgm_mountain_and_vegetation",
10
+ "90": "census_sgm_mountain_and_vegetation",
11
+ "95": "census_sgm_mountain_and_vegetation",
12
+ "100": "census_sgm_mountain_and_vegetation"
13
+ }
@@ -31,6 +31,7 @@ from typing import Dict
31
31
 
32
32
  import numpy as np
33
33
  import pandora
34
+ import rasterio
34
35
  from json_checker import Checker, Or
35
36
  from pandora.check_configuration import (
36
37
  check_pipeline_section,
@@ -40,6 +41,10 @@ from pandora.check_configuration import (
40
41
  )
41
42
  from pandora.img_tools import get_metadata
42
43
  from pandora.state_machine import PandoraMachine
44
+ from rasterio.mask import mask
45
+ from shapely.geometry import mapping
46
+
47
+ from cars.core.projection import polygon_projection
43
48
 
44
49
 
45
50
  class PandoraLoader:
@@ -135,7 +140,7 @@ class PandoraLoader:
135
140
  # read conf
136
141
  with open(conf_file_path, "r", encoding="utf8") as fstream:
137
142
  conf = json.load(fstream)
138
- elif method_name == "census_sgm_default":
143
+ elif method_name in ("census_sgm_default", "auto"):
139
144
  # Use census sgm conf
140
145
  conf_file_path = os.path.join(
141
146
  package_path, "config_census_sgm_default.json"
@@ -300,6 +305,78 @@ class PandoraLoader:
300
305
 
301
306
  return self.pandora_config
302
307
 
308
+ def find_auto_conf(
309
+ self, intersection_poly, land_cover_map, classif_to_config_mapping, epsg
310
+ ):
311
+ """
312
+ Find the configuration that suits the most on the
313
+ land cover map based on the roi
314
+ """
315
+ package_path = os.path.dirname(__file__)
316
+
317
+ # construct the path to the land_cover_map
318
+ if os.path.dirname(land_cover_map) == "":
319
+ land_cover_map_path = os.path.join(package_path, land_cover_map)
320
+ else:
321
+ land_cover_map_path = land_cover_map
322
+
323
+ with rasterio.open(land_cover_map_path) as src:
324
+ # Project the polygon to the right epsg
325
+ if src.crs != epsg:
326
+ poly = polygon_projection(
327
+ intersection_poly, epsg, src.crs.to_epsg()
328
+ )
329
+ else:
330
+ poly = intersection_poly
331
+
332
+ # Use a buffer because the land_cover_map resolution is coarse
333
+ data_land_cover, _ = mask(
334
+ src, [mapping(poly)], crop=True, all_touched=True
335
+ )
336
+
337
+ # Find the most common class in the roi
338
+ data_squeeze = data_land_cover.squeeze()
339
+ valid_data = data_squeeze[data_squeeze != src.nodata]
340
+
341
+ most_common_class = None
342
+ if valid_data.size > 0:
343
+ classes, counts = np.unique(valid_data, return_counts=True)
344
+ max_index = np.argmax(counts)
345
+ most_common_class = classes[max_index]
346
+
347
+ # Construct the path to the classification to configuration mapping
348
+ if os.path.dirname(classif_to_config_mapping) == "":
349
+ conf_file_path = os.path.join(
350
+ package_path, classif_to_config_mapping
351
+ )
352
+ else:
353
+ conf_file_path = classif_to_config_mapping
354
+
355
+ # read conf
356
+ with open(conf_file_path, "r", encoding="utf8") as fstream:
357
+ conf_mapping = json.load(fstream)
358
+
359
+ # Find the configuration that corresponds to the most common class
360
+ corresponding_conf_name = conf_mapping.get(str(most_common_class), None)
361
+
362
+ # If no equivalence has been found, we use the default configuration
363
+ if corresponding_conf_name is None:
364
+ corresponding_conf_name = "census_sgm_default"
365
+
366
+ logging.info(
367
+ "The conf that has been chosen regarding the "
368
+ "world classification map is {}".format(corresponding_conf_name)
369
+ )
370
+
371
+ # We return the corresponding configuration
372
+ json_conf_name = os.path.join(
373
+ package_path, "config_" + corresponding_conf_name + ".json"
374
+ )
375
+ with open(json_conf_name, "r", encoding="utf8") as fstream:
376
+ conf = json.load(fstream)
377
+
378
+ return conf
379
+
303
380
  def check_conf(
304
381
  self,
305
382
  user_cfg,