cars 1.0.0a1__cp313-cp313-win_amd64.whl → 1.0.0a3__cp313-cp313-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.cp313-win_amd64.dll.a +0 -0
  12. cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp313-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.cp313-win_amd64.dll.a +0 -0
  18. cars/applications/dense_matching/cpp/dense_matching_cpp.cp313-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
@@ -208,6 +208,7 @@ class SimpleGaussian(
208
208
  self,
209
209
  point_clouds,
210
210
  epsg,
211
+ output_crs,
211
212
  resolution,
212
213
  orchestrator=None,
213
214
  dsm_file_name=None,
@@ -256,6 +257,8 @@ class SimpleGaussian(
256
257
  :type point_clouds: CarsDataset filled with pandas.DataFrame
257
258
  :param epsg: epsg of raster data
258
259
  :type epsg: str
260
+ :param output_crs: output_crs of raster data
261
+ :type output_crs: str
259
262
  :param resolution: resolution of raster data (in target CRS unit)
260
263
  :type resolution: float
261
264
  :param orchestrator: orchestrator used
@@ -267,7 +270,7 @@ class SimpleGaussian(
267
270
  :type color_file_name: str
268
271
  :param mask_file_name: path of color
269
272
  :type mask_file_name: str
270
- :param classif_file_name: path of color
273
+ :param classif_file_name: path of classification
271
274
  :type classif_file_name: str
272
275
  :param performance_map_file_name: path of performance map file
273
276
  :type performance_map_file_name: str
@@ -317,6 +320,27 @@ class SimpleGaussian(
317
320
  # Get if color, mask and stats are saved
318
321
  save_intermediate_data = self.used_config["save_intermediate_data"]
319
322
 
323
+ keep_dir = (
324
+ len(
325
+ list(
326
+ filter(
327
+ lambda x: x is not None,
328
+ [
329
+ weights_file_name,
330
+ color_file_name,
331
+ mask_file_name,
332
+ classif_file_name,
333
+ performance_map_file_name,
334
+ ambiguity_file_name,
335
+ contributing_pair_file_name,
336
+ filling_file_name,
337
+ ],
338
+ )
339
+ )
340
+ )
341
+ > 0
342
+ )
343
+
320
344
  if not self.color_dtype:
321
345
  self.color_dtype = color_dtype
322
346
 
@@ -333,7 +357,7 @@ class SimpleGaussian(
333
357
  if dump_dir is not None:
334
358
  out_dump_dir = dump_dir
335
359
  safe_makedirs(dump_dir)
336
- if not save_intermediate_data:
360
+ if not save_intermediate_data and not keep_dir:
337
361
  self.orchestrator.add_to_clean(dump_dir)
338
362
  else:
339
363
  out_dump_dir = self.orchestrator.out_dir
@@ -512,6 +536,7 @@ class SimpleGaussian(
512
536
  out_classif_file_name = os.path.join(
513
537
  out_dump_dir, "classification.tif"
514
538
  )
539
+
515
540
  if out_classif_file_name is not None:
516
541
  list_computed_layers += ["classif"]
517
542
  self.orchestrator.add_to_save_lists(
@@ -644,7 +669,7 @@ class SimpleGaussian(
644
669
  )
645
670
  elif save_intermediate_data:
646
671
  # File is not part of the official product, write it in dump_dir
647
- out_source_pc = os.path.join(out_dump_dir, "source_pc.tif")
672
+ out_source_pc = os.path.join(out_dump_dir, "contributing_pair.tif")
648
673
  if out_source_pc:
649
674
  list_computed_layers += ["source_pc"]
650
675
  self.orchestrator.add_to_save_lists(
@@ -653,7 +678,7 @@ class SimpleGaussian(
653
678
  terrain_raster,
654
679
  dtype=np.uint8,
655
680
  nodata=self.msk_no_data,
656
- cars_ds_name="source_pc",
681
+ cars_ds_name="contributing_pair",
657
682
  optional_data=True,
658
683
  )
659
684
 
@@ -680,18 +705,6 @@ class SimpleGaussian(
680
705
 
681
706
  # TODO Check that intervals indeed exist!
682
707
  if save_intermediate_data:
683
- out_confidence = os.path.join(out_dump_dir, "confidence.tif")
684
- list_computed_layers += ["confidence"]
685
- self.orchestrator.add_to_save_lists(
686
- out_confidence,
687
- cst.RASTER_CONFIDENCE,
688
- terrain_raster,
689
- dtype=np.float32,
690
- nodata=self.msk_no_data,
691
- cars_ds_name="confidence",
692
- optional_data=True,
693
- )
694
-
695
708
  list_computed_layers += [cst.POINT_CLOUD_LAYER_SUP_OR_INF_ROOT]
696
709
  out_dsm_inf_file_name = os.path.join(out_dump_dir, "dsm_inf.tif")
697
710
  self.orchestrator.add_to_save_lists(
@@ -829,7 +842,7 @@ class SimpleGaussian(
829
842
  "driver": "GTiff",
830
843
  "dtype": "float32",
831
844
  "transform": transform,
832
- "crs": "EPSG:{}".format(epsg),
845
+ "crs": output_crs.to_wkt(),
833
846
  "tiled": True,
834
847
  }
835
848
  )
@@ -284,14 +284,10 @@ def resample_image( # noqa: C901
284
284
  (nb_bands, region[3] - region[1], region[2] - region[0]),
285
285
  dtype=np.float32,
286
286
  )
287
- nodata = 0
288
- if nodata is not None or mask is not None:
289
- msk = np.empty(
290
- (nb_bands, region[3] - region[1], region[2] - region[0]),
291
- dtype=np.float32,
292
- )
293
- else:
294
- msk = None
287
+ msk = np.empty(
288
+ (nb_bands, region[3] - region[1], region[2] - region[0]),
289
+ dtype=np.float32,
290
+ )
295
291
 
296
292
  ystart = 0
297
293
  with rio.open(grid["path"]) as grid_reader, rio.open(img) as img_reader:
@@ -513,51 +509,50 @@ def oversampling_func(
513
509
  resamp[:, ystart : ystart + ysize, xstart : xstart + xsize] = block_resamp
514
510
 
515
511
  # create msk
516
- if nodata is not None or mask is not None:
517
- if in_sensor:
518
- # get mask in source geometry
519
- nodata_index = img_as_array == nodata
520
-
521
- if mask is not None:
522
- with rio.open(mask) as msk_reader:
523
- msk_as_array = msk_reader.read(1, window=img_window)
524
- msk_as_array = np.array([msk_as_array] * img_as_array.shape[0])
525
- else:
526
- msk_as_array = np.zeros(img_as_array.shape)
512
+ if in_sensor:
513
+ # get mask in source geometry
514
+ if mask is not None:
515
+ with rio.open(mask) as msk_reader:
516
+ msk_as_array = msk_reader.read(1, window=img_window)
517
+ msk_as_array = np.array([msk_as_array] * img_as_array.shape[0])
518
+ else:
519
+ msk_as_array = np.zeros(img_as_array.shape)
527
520
 
521
+ if nodata is not None:
522
+ nodata_index = img_as_array == nodata
528
523
  msk_as_array[nodata_index] = nodata_msk
529
524
 
530
- # resample mask
531
- block_msk = cresample.grid(
532
- msk_as_array,
533
- grid_as_array,
534
- oversampling,
535
- interpolator=interpolator_mask,
536
- nodata=nodata_msk,
537
- )
525
+ # resample mask
526
+ block_msk = cresample.grid(
527
+ msk_as_array,
528
+ grid_as_array,
529
+ oversampling,
530
+ interpolator=interpolator_mask,
531
+ nodata=nodata_msk,
532
+ )
538
533
 
539
- if interpolator_mask == "bicubic":
540
- block_msk = np.where(
541
- block_msk >= 0.5,
542
- 1,
543
- np.where(block_msk < 0.5, 0, block_msk),
544
- ).astype(int)
545
-
546
- block_msk = block_msk[
547
- ...,
548
- ext_region[1] : ext_region[3] - 1,
549
- ext_region[0] : ext_region[2] - 1,
550
- ]
551
- else:
552
- block_msk = np.full(
553
- (
554
- nb_bands,
555
- block_region[3] - block_region[1],
556
- block_region[2] - block_region[0],
557
- ),
558
- fill_value=nodata_msk,
559
- )
534
+ if interpolator_mask == "bicubic":
535
+ block_msk = np.where(
536
+ block_msk >= 0.5,
537
+ 1,
538
+ np.where(block_msk < 0.5, 0, block_msk),
539
+ ).astype(int)
540
+
541
+ block_msk = block_msk[
542
+ ...,
543
+ ext_region[1] : ext_region[3] - 1,
544
+ ext_region[0] : ext_region[2] - 1,
545
+ ]
546
+ else:
547
+ block_msk = np.full(
548
+ (
549
+ nb_bands,
550
+ block_region[3] - block_region[1],
551
+ block_region[2] - block_region[0],
552
+ ),
553
+ fill_value=nodata_msk,
554
+ )
560
555
 
561
- msk[:, ystart : ystart + ysize, xstart : xstart + xsize] = block_msk
556
+ msk[:, ystart : ystart + ysize, xstart : xstart + xsize] = block_msk
562
557
 
563
558
  return resamp, msk
@@ -224,7 +224,7 @@ class Sift(SparseMatching, short_name=["sift"]):
224
224
  "sift_matching_threshold": And(float, lambda x: x > 0),
225
225
  "sift_n_octave": And(int, lambda x: x > 0),
226
226
  "sift_n_scale_per_octave": And(int, lambda x: x > 0),
227
- "sift_peak_threshold": Or(float, None),
227
+ "sift_peak_threshold": Or(float, int),
228
228
  "sift_edge_threshold": float,
229
229
  "sift_magnification": And(float, lambda x: x > 0),
230
230
  "sift_window_size": And(int, lambda x: x > 0),
@@ -514,26 +514,6 @@ class Sift(SparseMatching, short_name=["sift"]):
514
514
  epipolar_disparity_map_left.attributes.update(
515
515
  epipolar_image_left.attributes
516
516
  )
517
- # check sift_peak_threshold with image type
518
- # only if sift_peak_threshold is None
519
- tmp_sift_peak_threshold = self.sift_peak_threshold
520
- if not self.sift_peak_threshold:
521
- logging.info("The sift_peak_threshold is set to auto-mode.")
522
- # sift_peak_threshold is None or not specified
523
- # check input type
524
- if np.issubdtype(
525
- epipolar_disparity_map_left.attributes["image_type"],
526
- np.uint8,
527
- ):
528
- tmp_sift_peak_threshold = 1
529
- else:
530
- tmp_sift_peak_threshold = 20
531
- logging.info(
532
- "The sift_peak_threshold will be set to {}.".format(
533
- tmp_sift_peak_threshold
534
- )
535
- )
536
- self.sift_peak_threshold = tmp_sift_peak_threshold
537
517
 
538
518
  # Save disparity maps
539
519
  if self.save_intermediate_data:
@@ -628,7 +608,7 @@ class Sift(SparseMatching, short_name=["sift"]):
628
608
  matching_threshold=self.sift_matching_threshold,
629
609
  n_octave=self.sift_n_octave,
630
610
  n_scale_per_octave=self.sift_n_scale_per_octave,
631
- peak_threshold=tmp_sift_peak_threshold,
611
+ peak_threshold=self.sift_peak_threshold,
632
612
  edge_threshold=self.sift_edge_threshold,
633
613
  magnification=self.sift_magnification,
634
614
  window_size=self.sift_window_size,
@@ -170,55 +170,6 @@ def compute_disp_min_disp_max(
170
170
  return dmin, dmax
171
171
 
172
172
 
173
- @cars_profile(name="Clustering matches")
174
- def clustering_matches(
175
- triangulated_matches,
176
- connection_val=3.0,
177
- nb_pts_threshold=80,
178
- clusters_distance_threshold: float = None,
179
- filtered_elt_pos: bool = False,
180
- ):
181
- """
182
- Filter triangulated matches
183
-
184
- :param pd_cloud: triangulated_matches
185
- :type pd_cloud: pandas Dataframe
186
- :param connection_val: distance to use
187
- to consider that two points are connected
188
- :param nb_pts_threshold: number of points to use
189
- to identify small clusters to filter
190
- :param clusters_distance_threshold: distance to use
191
- to consider if two points clusters are far from each other or not
192
- (set to None to deactivate this level of filtering)
193
- :param filtered_elt_pos: if filtered_elt_pos is set to True,
194
- the removed points positions in their original
195
- epipolar images are returned, otherwise it is set to None
196
-
197
- :return: filtered_matches
198
- :rtype: pandas Dataframe
199
-
200
- """
201
-
202
- filtered_pandora_matches, _ = (
203
- outlier_removal_algo.small_component_filtering(
204
- triangulated_matches,
205
- connection_val=connection_val,
206
- nb_pts_threshold=nb_pts_threshold,
207
- clusters_distance_threshold=clusters_distance_threshold,
208
- filtered_elt_pos=filtered_elt_pos,
209
- )
210
- )
211
-
212
- filtered_pandora_matches_dataframe = pandas.DataFrame(
213
- filtered_pandora_matches
214
- )
215
- filtered_pandora_matches_dataframe.attrs["epsg"] = (
216
- triangulated_matches.attrs["epsg"]
217
- )
218
-
219
- return filtered_pandora_matches_dataframe
220
-
221
-
222
173
  @cars_profile(name="filter_point_cloud_matches")
223
174
  def filter_point_cloud_matches(
224
175
  pd_cloud,
@@ -1044,7 +1044,7 @@ def triangulation_wrapper(
1044
1044
  ambiguity_map = None
1045
1045
  perf_ambiguity_threshold = None
1046
1046
  if use_ambiguity:
1047
- ambiguity_map = disp_ref["confidence_from_ambiguity.cars_1"]
1047
+ ambiguity_map = disp_ref["ambiguity"]
1048
1048
  perf_ambiguity_threshold = performance_maps_parameters[
1049
1049
  "perf_ambiguity_threshold"
1050
1050
  ]
@@ -246,7 +246,8 @@ def compute_performance_map(
246
246
  )
247
247
 
248
248
  if ambiguity_map is not None:
249
- ambiguity_map = 1 - ambiguity_map.values
249
+ # ambiguity is already ambiguity, not confidence from ambiguity
250
+ ambiguity_map = ambiguity_map.values
250
251
  mask_ambi = ambiguity_map > perf_ambiguity_threshold
251
252
  w_ambi = ambiguity_map / perf_ambiguity_threshold
252
253
  w_ambi[mask_ambi] = 1
cars/bundleadjustment.py CHANGED
@@ -15,6 +15,7 @@ import geopandas as gpd
15
15
  import numpy as np
16
16
  import pandas as pd
17
17
  import rasterio as rio
18
+ import yaml
18
19
 
19
20
  try:
20
21
  from rpcfit import rpc_fit
@@ -34,6 +35,7 @@ from shareloc.geomodels.geomodel import GeoModel
34
35
  from shareloc.geomodels.los import LOS
35
36
  from shareloc.proj_utils import coordinates_conversion
36
37
 
38
+ from cars.pipelines.parameters import sensor_inputs
37
39
  from cars.pipelines.pipeline import Pipeline
38
40
 
39
41
 
@@ -550,14 +552,26 @@ def new_rpcs_from_matches(
550
552
  return None
551
553
 
552
554
 
553
- def cars_bundle_adjustment(conf, no_run_sparse):
555
+ def cars_bundle_adjustment(conf, no_run_sparse, output_format="yaml"):
554
556
  """
555
557
  cars-bundleadjustement main:
556
558
  - Launch CARS to compute homologous points (run sparse matching)
557
559
  - Compute new RPCs
558
560
  """
559
- with open(conf, encoding="utf-8") as reader:
560
- conf_as_dict = json.load(reader)
561
+ _, ext = os.path.splitext(conf)
562
+ ext = ext.lower()
563
+
564
+ if ext == ".json":
565
+ with open(conf, encoding="utf-8") as reader:
566
+ conf_as_dict = json.load(reader)
567
+ elif ext in [".yaml", ".yml"]:
568
+ with open(conf, encoding="utf-8") as reader:
569
+ conf_as_dict = yaml.safe_load(reader)
570
+ else:
571
+ raise ValueError(
572
+ f"Unsupported configuration file format: {ext}. "
573
+ "Please use .json, .yaml, or .yml"
574
+ )
561
575
 
562
576
  conf_dirname = os.path.dirname(conf)
563
577
  out_dir = os.path.abspath(
@@ -576,6 +590,7 @@ def cars_bundle_adjustment(conf, no_run_sparse):
576
590
  ]
577
591
  sparse_matching_config["output"]["directory"] = sparse_matching
578
592
  sparse_matching_config["output"]["product_level"] = []
593
+ sparse_matching_config["advanced"] = {}
579
594
  sparse_matching_config["advanced"]["epipolar_resolutions"] = [1]
580
595
  if "sparse_matching.sift" not in sparse_matching_config["applications"]:
581
596
  sparse_matching_config["applications"]["sparse_matching.sift"] = {}
@@ -595,6 +610,9 @@ def cars_bundle_adjustment(conf, no_run_sparse):
595
610
  sparse_matching_pipeline.run()
596
611
 
597
612
  # create new refined rpcs
613
+ conf_as_dict["inputs"] = sensor_inputs.sensors_check_inputs(
614
+ conf_as_dict["inputs"], config_dir=conf_dirname
615
+ )
598
616
  separate = bundle_adjustment_config.pop("separate")
599
617
  refined_rpcs = new_rpcs_from_matches(
600
618
  conf_as_dict["inputs"]["sensors"],
@@ -623,9 +641,15 @@ def cars_bundle_adjustment(conf, no_run_sparse):
623
641
  raw_config["inputs"]["pairing"] = pairing
624
642
  raw_config["output"]["directory"] = raw
625
643
 
626
- raw_cfg_file = raw_config["output"]["directory"] + ".json"
627
- with open(raw_cfg_file, "w", encoding="utf8") as json_writer:
628
- json.dump(raw_config, json_writer, indent=2)
644
+ # output config file
645
+ raw_cfg_file = raw_config["output"]["directory"] + (
646
+ ".yaml" if output_format == "yaml" else ".json"
647
+ )
648
+ with open(raw_cfg_file, "w", encoding="utf8") as writer:
649
+ if output_format == "yaml":
650
+ yaml.safe_dump(raw_config, writer, sort_keys=False)
651
+ else:
652
+ json.dump(raw_config, writer, indent=2)
629
653
 
630
654
  if refined_rpcs is not None:
631
655
  # create configuration file + launch cars dense matching
@@ -645,9 +669,14 @@ def cars_bundle_adjustment(conf, no_run_sparse):
645
669
  refined_config["inputs"]["pairing"] = pairing
646
670
  refined_config["output"]["directory"] = refined
647
671
 
648
- refined_cfg_file = refined_config["output"]["directory"] + ".json"
649
- with open(refined_cfg_file, "w", encoding="utf8") as json_writer:
650
- json.dump(refined_config, json_writer, indent=2)
672
+ refined_cfg_file = refined_config["output"]["directory"] + (
673
+ ".yaml" if output_format == "yaml" else ".json"
674
+ )
675
+ with open(refined_cfg_file, "w", encoding="utf8") as writer:
676
+ if output_format == "yaml":
677
+ yaml.safe_dump(refined_config, writer, sort_keys=False)
678
+ else:
679
+ json.dump(refined_config, writer, indent=2)
651
680
 
652
681
 
653
682
  def cli():
@@ -679,8 +708,8 @@ key and its associated value:
679
708
  ```
680
709
 
681
710
  - Parameters "pairing" and "separate" are mandatory.
682
- - Parameters "nb_decimals" (default value: 0) and "min_matches" \
683
- (default value: 100) are optional.
711
+ - Parameters "nb_decimals" (default value: 0), "min_matches" \
712
+ (default value: 100) and "output_format" (default value: yaml) are optional.
684
713
 
685
714
  ### Generation of homologous points calculated by pair
686
715
 
@@ -704,7 +733,18 @@ number of matches per zone required to calculate these statistics."""
704
733
  )
705
734
  parser.add_argument("conf", type=str, help="Configuration File")
706
735
  parser.add_argument("--no-run-sparse", action="store_true")
736
+ parser.add_argument(
737
+ "--output-format",
738
+ type=str,
739
+ default="json",
740
+ choices=["json", "yaml", "JSON", "YAML"],
741
+ help="Output format for generated configuration files "
742
+ "(json or yaml, case-insensitive). Default: json",
743
+ )
744
+
707
745
  args = parser.parse_args()
746
+ # normalize format to lowercase
747
+ args.output_format = args.output_format.lower()
708
748
  cars_bundle_adjustment(**vars(args))
709
749
 
710
750
 
cars/cars.py CHANGED
@@ -33,6 +33,8 @@ import os
33
33
  import sys
34
34
  import warnings
35
35
 
36
+ import yaml
37
+
36
38
  # CARS imports
37
39
  from cars import __version__
38
40
  from cars.core import cars_logging
@@ -87,9 +89,17 @@ def main_cli(args, dry_run=False): # noqa: C901
87
89
  from cars.pipelines.pipeline import Pipeline
88
90
 
89
91
  try:
90
- # Transform conf file to dict
91
- with open(args.conf, "r", encoding="utf8") as fstream:
92
- config = json.load(fstream)
92
+ # Check file extension and load configuration
93
+ config_path = args.conf
94
+ ext = os.path.splitext(config_path)[1].lower()
95
+ if ext == ".json":
96
+ with open(config_path, "r", encoding="utf8") as fstream:
97
+ config = json.load(fstream)
98
+ elif ext in [".yaml", ".yml"]:
99
+ with open(config_path, "r", encoding="utf8") as fstream:
100
+ config = yaml.safe_load(fstream)
101
+ else:
102
+ raise ValueError("Configuration file must be .json or .yaml/.yml")
93
103
 
94
104
  # Cars 0.9.0 API change, check if the configfile seems to use the old
95
105
  # API by looking for the deprecated out_dir key
@@ -109,7 +119,7 @@ def main_cli(args, dry_run=False): # noqa: C901
109
119
  config["output"]["directory"] = config["output"]["out_dir"]
110
120
  del config["output"]["out_dir"]
111
121
 
112
- config_json_dir = os.path.abspath(os.path.dirname(args.conf))
122
+ config_dir = os.path.abspath(os.path.dirname(config_path))
113
123
  pipeline_name = config.get("advanced", {}).get("pipeline", "default")
114
124
 
115
125
  # Logging configuration with args Loglevel
@@ -126,7 +136,7 @@ def main_cli(args, dry_run=False): # noqa: C901
126
136
 
127
137
  # Generate pipeline and check conf
128
138
  cars_logging.add_progress_message("Check configuration...")
129
- used_pipeline = Pipeline(pipeline_name, config, config_json_dir)
139
+ used_pipeline = Pipeline(pipeline_name, config, config_dir)
130
140
  cars_logging.add_progress_message("CARS pipeline is started.")
131
141
  if not dry_run:
132
142
  # run pipeline
cars/core/constants.py CHANGED
@@ -184,7 +184,7 @@ DSM_INF_MEAN = "dsm_inf_mean"
184
184
  DSM_INF_STD = "dsm_inf_std"
185
185
  DSM_SUP_MEAN = "dsm_sup_mean"
186
186
  DSM_SUP_STD = "dsm_sup_std"
187
- DSM_CONFIDENCE_AMBIGUITY = "ambiguity"
187
+ DSM_AMBIGUITY = "ambiguity"
188
188
  DSM_CONFIDENCE = "confidence"
189
189
  DSM_PERFORMANCE_MAP = "performance_map"
190
190
  DSM_SOURCE_PC = "source_pc"