cars 1.0.0rc1__cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.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.
- cars/__init__.py +74 -0
- cars/applications/__init__.py +37 -0
- cars/applications/application.py +117 -0
- cars/applications/application_constants.py +29 -0
- cars/applications/application_template.py +146 -0
- cars/applications/auxiliary_filling/__init__.py +29 -0
- cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +104 -0
- cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
- cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +630 -0
- cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
- cars/applications/dem_generation/__init__.py +30 -0
- cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
- cars/applications/dem_generation/bulldozer_config/base_config.yaml +42 -0
- cars/applications/dem_generation/bulldozer_dem_app.py +655 -0
- cars/applications/dem_generation/bulldozer_memory.py +55 -0
- cars/applications/dem_generation/dem_generation_algo.py +107 -0
- cars/applications/dem_generation/dem_generation_constants.py +32 -0
- cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
- cars/applications/dense_match_filling/__init__.py +30 -0
- cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
- cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
- cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
- cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
- cars/applications/dense_match_filling/zero_padding_app.py +302 -0
- cars/applications/dense_matching/__init__.py +30 -0
- cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
- cars/applications/dense_matching/census_mccnn_sgm_app.py +1460 -0
- cars/applications/dense_matching/cpp/__init__.py +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-312-i386-linux-gnu.so +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
- cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
- cars/applications/dense_matching/cpp/meson.build +9 -0
- cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
- cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
- cars/applications/dense_matching/dense_matching_algo.py +401 -0
- cars/applications/dense_matching/dense_matching_constants.py +89 -0
- cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
- cars/applications/dense_matching/disparity_grid_algo.py +588 -0
- cars/applications/dense_matching/loaders/__init__.py +23 -0
- cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
- cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
- cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
- cars/applications/dense_matching/loaders/config_mapping.json +13 -0
- cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
- cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
- cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
- cars/applications/dsm_filling/__init__.py +32 -0
- cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
- cars/applications/dsm_filling/border_interpolation_app.py +270 -0
- cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
- cars/applications/dsm_filling/bulldozer_filling_app.py +279 -0
- cars/applications/dsm_filling/exogenous_filling_app.py +333 -0
- cars/applications/grid_generation/__init__.py +30 -0
- cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
- cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
- cars/applications/grid_generation/grid_correction_app.py +496 -0
- cars/applications/grid_generation/grid_generation_algo.py +388 -0
- cars/applications/grid_generation/grid_generation_constants.py +46 -0
- cars/applications/grid_generation/transform_grid.py +88 -0
- cars/applications/ground_truth_reprojection/__init__.py +30 -0
- cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
- cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
- cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
- cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
- cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
- cars/applications/point_cloud_outlier_removal/small_components_app.py +527 -0
- cars/applications/point_cloud_outlier_removal/statistical_app.py +531 -0
- cars/applications/rasterization/__init__.py +30 -0
- cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
- cars/applications/rasterization/rasterization_algo.py +534 -0
- cars/applications/rasterization/rasterization_constants.py +38 -0
- cars/applications/rasterization/rasterization_wrappers.py +634 -0
- cars/applications/rasterization/simple_gaussian_app.py +1152 -0
- cars/applications/resampling/__init__.py +28 -0
- cars/applications/resampling/abstract_resampling_app.py +187 -0
- cars/applications/resampling/bicubic_resampling_app.py +762 -0
- cars/applications/resampling/resampling_algo.py +614 -0
- cars/applications/resampling/resampling_constants.py +36 -0
- cars/applications/resampling/resampling_wrappers.py +309 -0
- cars/applications/sparse_matching/__init__.py +30 -0
- cars/applications/sparse_matching/abstract_sparse_matching_app.py +498 -0
- cars/applications/sparse_matching/sift_app.py +735 -0
- cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
- cars/applications/sparse_matching/sparse_matching_constants.py +68 -0
- cars/applications/sparse_matching/sparse_matching_wrappers.py +238 -0
- cars/applications/triangulation/__init__.py +32 -0
- cars/applications/triangulation/abstract_triangulation_app.py +227 -0
- cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
- cars/applications/triangulation/pc_transform.py +552 -0
- cars/applications/triangulation/triangulation_algo.py +371 -0
- cars/applications/triangulation/triangulation_constants.py +38 -0
- cars/applications/triangulation/triangulation_wrappers.py +259 -0
- cars/bundleadjustment.py +757 -0
- cars/cars.py +177 -0
- cars/conf/__init__.py +23 -0
- cars/conf/geoid/egm96.grd +0 -0
- cars/conf/geoid/egm96.grd.hdr +15 -0
- cars/conf/input_parameters.py +156 -0
- cars/conf/mask_cst.py +35 -0
- cars/core/__init__.py +23 -0
- cars/core/cars_logging.py +402 -0
- cars/core/constants.py +191 -0
- cars/core/constants_disparity.py +50 -0
- cars/core/datasets.py +140 -0
- cars/core/geometry/__init__.py +27 -0
- cars/core/geometry/abstract_geometry.py +1119 -0
- cars/core/geometry/shareloc_geometry.py +598 -0
- cars/core/inputs.py +568 -0
- cars/core/outputs.py +176 -0
- cars/core/preprocessing.py +722 -0
- cars/core/projection.py +843 -0
- cars/core/roi_tools.py +215 -0
- cars/core/tiling.py +774 -0
- cars/core/utils.py +164 -0
- cars/data_structures/__init__.py +23 -0
- cars/data_structures/cars_dataset.py +1541 -0
- cars/data_structures/cars_dict.py +74 -0
- cars/data_structures/corresponding_tiles_tools.py +186 -0
- cars/data_structures/dataframe_converter.py +185 -0
- cars/data_structures/format_transformation.py +297 -0
- cars/devibrate.py +689 -0
- cars/extractroi.py +264 -0
- cars/orchestrator/__init__.py +23 -0
- cars/orchestrator/achievement_tracker.py +125 -0
- cars/orchestrator/cluster/__init__.py +37 -0
- cars/orchestrator/cluster/abstract_cluster.py +244 -0
- cars/orchestrator/cluster/abstract_dask_cluster.py +375 -0
- cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
- cars/orchestrator/cluster/dask_config/README.md +94 -0
- cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
- cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
- cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
- cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
- cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
- cars/orchestrator/cluster/local_dask_cluster.py +116 -0
- cars/orchestrator/cluster/log_wrapper.py +1075 -0
- cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
- cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
- cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
- cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
- cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +873 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
- cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
- cars/orchestrator/cluster/sequential_cluster.py +139 -0
- cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
- cars/orchestrator/orchestrator.py +905 -0
- cars/orchestrator/orchestrator_constants.py +29 -0
- cars/orchestrator/registry/__init__.py +23 -0
- cars/orchestrator/registry/abstract_registry.py +143 -0
- cars/orchestrator/registry/compute_registry.py +106 -0
- cars/orchestrator/registry/id_generator.py +116 -0
- cars/orchestrator/registry/replacer_registry.py +213 -0
- cars/orchestrator/registry/saver_registry.py +363 -0
- cars/orchestrator/registry/unseen_registry.py +118 -0
- cars/orchestrator/tiles_profiler.py +279 -0
- cars/pipelines/__init__.py +26 -0
- cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
- cars/pipelines/conf_resolution/conf_first_resolution.yaml +2 -0
- cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
- cars/pipelines/default/__init__.py +26 -0
- cars/pipelines/default/default_pipeline.py +786 -0
- cars/pipelines/parameters/__init__.py +0 -0
- cars/pipelines/parameters/advanced_parameters.py +417 -0
- cars/pipelines/parameters/advanced_parameters_constants.py +69 -0
- cars/pipelines/parameters/application_parameters.py +71 -0
- cars/pipelines/parameters/depth_map_inputs.py +0 -0
- cars/pipelines/parameters/dsm_inputs.py +918 -0
- cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
- cars/pipelines/parameters/output_constants.py +52 -0
- cars/pipelines/parameters/output_parameters.py +454 -0
- cars/pipelines/parameters/sensor_inputs.py +842 -0
- cars/pipelines/parameters/sensor_inputs_constants.py +49 -0
- cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
- cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
- cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
- cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
- cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
- cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
- cars/pipelines/pipeline.py +119 -0
- cars/pipelines/pipeline_constants.py +31 -0
- cars/pipelines/pipeline_template.py +139 -0
- cars/pipelines/unit/__init__.py +26 -0
- cars/pipelines/unit/unit_pipeline.py +2850 -0
- cars/starter.py +167 -0
- cars-1.0.0rc1.dist-info/METADATA +292 -0
- cars-1.0.0rc1.dist-info/RECORD +200 -0
- cars-1.0.0rc1.dist-info/WHEEL +6 -0
- cars-1.0.0rc1.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,2850 @@
|
|
|
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
|
+
# attribute-defined-outside-init is disabled so that we can create and use
|
|
23
|
+
# attributes however we need, to stick to the "everything is attribute" logic
|
|
24
|
+
# introduced in issue#895
|
|
25
|
+
# pylint: disable=attribute-defined-outside-init
|
|
26
|
+
# pylint: disable=too-many-nested-blocks
|
|
27
|
+
# pylint: disable=C0302
|
|
28
|
+
"""
|
|
29
|
+
CARS default pipeline class file
|
|
30
|
+
"""
|
|
31
|
+
# Standard imports
|
|
32
|
+
from __future__ import print_function
|
|
33
|
+
|
|
34
|
+
import copy
|
|
35
|
+
import logging
|
|
36
|
+
import os
|
|
37
|
+
import warnings
|
|
38
|
+
from collections import OrderedDict
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
import rasterio
|
|
42
|
+
from pyproj import CRS
|
|
43
|
+
from rasterio.errors import NodataShadowWarning
|
|
44
|
+
|
|
45
|
+
import cars.applications.sparse_matching.sparse_matching_constants as sm_cst
|
|
46
|
+
from cars import __version__
|
|
47
|
+
|
|
48
|
+
# CARS imports
|
|
49
|
+
from cars.applications import application_constants
|
|
50
|
+
from cars.applications.application import Application
|
|
51
|
+
from cars.applications.dem_generation import (
|
|
52
|
+
dem_generation_wrappers as dem_wrappers,
|
|
53
|
+
)
|
|
54
|
+
from cars.applications.grid_generation import grid_correction_app
|
|
55
|
+
from cars.applications.grid_generation.transform_grid import transform_grid_func
|
|
56
|
+
from cars.core import preprocessing, projection, roi_tools
|
|
57
|
+
from cars.core.geometry.abstract_geometry import AbstractGeometry
|
|
58
|
+
from cars.core.inputs import (
|
|
59
|
+
get_descriptions_bands,
|
|
60
|
+
rasterio_get_crs,
|
|
61
|
+
rasterio_get_epsg,
|
|
62
|
+
rasterio_get_size,
|
|
63
|
+
read_vector,
|
|
64
|
+
)
|
|
65
|
+
from cars.core.utils import safe_makedirs
|
|
66
|
+
from cars.data_structures import cars_dataset
|
|
67
|
+
from cars.orchestrator import orchestrator
|
|
68
|
+
from cars.orchestrator.cluster.log_wrapper import cars_profile
|
|
69
|
+
from cars.pipelines.parameters import advanced_parameters
|
|
70
|
+
from cars.pipelines.parameters import advanced_parameters_constants as adv_cst
|
|
71
|
+
from cars.pipelines.parameters import application_parameters, dsm_inputs
|
|
72
|
+
from cars.pipelines.parameters import dsm_inputs_constants as dsm_cst
|
|
73
|
+
from cars.pipelines.parameters import output_constants as out_cst
|
|
74
|
+
from cars.pipelines.parameters import output_parameters, sensor_inputs
|
|
75
|
+
from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
|
|
76
|
+
from cars.pipelines.parameters.advanced_parameters_constants import (
|
|
77
|
+
USE_ENDOGENOUS_DEM,
|
|
78
|
+
)
|
|
79
|
+
from cars.pipelines.parameters.output_constants import AUXILIARY
|
|
80
|
+
from cars.pipelines.pipeline import Pipeline
|
|
81
|
+
from cars.pipelines.pipeline_constants import (
|
|
82
|
+
ADVANCED,
|
|
83
|
+
APPLICATIONS,
|
|
84
|
+
INPUT,
|
|
85
|
+
ORCHESTRATOR,
|
|
86
|
+
OUTPUT,
|
|
87
|
+
)
|
|
88
|
+
from cars.pipelines.pipeline_template import PipelineTemplate
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@Pipeline.register(
|
|
92
|
+
"unit",
|
|
93
|
+
)
|
|
94
|
+
class UnitPipeline(PipelineTemplate):
|
|
95
|
+
"""
|
|
96
|
+
UnitPipeline
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# pylint: disable=too-many-instance-attributes
|
|
100
|
+
|
|
101
|
+
def __init__(self, conf, config_dir=None): # noqa: C901
|
|
102
|
+
"""
|
|
103
|
+
Creates pipeline
|
|
104
|
+
|
|
105
|
+
Directly creates class attributes:
|
|
106
|
+
used_conf
|
|
107
|
+
generate_terrain_products
|
|
108
|
+
debug_with_roi
|
|
109
|
+
save_output_dsm
|
|
110
|
+
save_output_depth_map
|
|
111
|
+
save_output_point_clouds
|
|
112
|
+
geom_plugin_without_dem_and_geoid
|
|
113
|
+
geom_plugin_with_dem_and_geoid
|
|
114
|
+
|
|
115
|
+
:param pipeline_name: name of the pipeline.
|
|
116
|
+
:type pipeline_name: str
|
|
117
|
+
:param cfg: configuration {'matching_cost_method': value}
|
|
118
|
+
:type cfg: dictionary
|
|
119
|
+
:param config_dir: path to dir containing json/yaml
|
|
120
|
+
:type config_dir: str
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# Used conf
|
|
124
|
+
self.used_conf = {}
|
|
125
|
+
# refined conf
|
|
126
|
+
self.refined_conf = {}
|
|
127
|
+
|
|
128
|
+
# Transform relative path to absolute path
|
|
129
|
+
if config_dir is not None:
|
|
130
|
+
config_dir = os.path.abspath(config_dir)
|
|
131
|
+
|
|
132
|
+
# Check global conf
|
|
133
|
+
self.check_global_schema(conf)
|
|
134
|
+
|
|
135
|
+
# Check conf orchestrator
|
|
136
|
+
self.used_conf[ORCHESTRATOR] = self.check_orchestrator(
|
|
137
|
+
conf.get(ORCHESTRATOR, None)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Check conf inputs
|
|
141
|
+
inputs = self.check_inputs(conf[INPUT], config_dir=config_dir)
|
|
142
|
+
self.used_conf[INPUT] = inputs
|
|
143
|
+
self.refined_conf[INPUT] = copy.deepcopy(inputs)
|
|
144
|
+
|
|
145
|
+
# Check advanced parameters
|
|
146
|
+
# TODO static method in the base class
|
|
147
|
+
output_dem_dir = os.path.join(
|
|
148
|
+
conf[OUTPUT][out_cst.OUT_DIRECTORY], "dump_dir", "initial_elevation"
|
|
149
|
+
)
|
|
150
|
+
safe_makedirs(output_dem_dir)
|
|
151
|
+
(
|
|
152
|
+
inputs,
|
|
153
|
+
advanced,
|
|
154
|
+
self.geometry_plugin,
|
|
155
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
156
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
157
|
+
self.scaling_coeff,
|
|
158
|
+
self.land_cover_map,
|
|
159
|
+
self.classification_to_config_mapping,
|
|
160
|
+
) = advanced_parameters.check_advanced_parameters(
|
|
161
|
+
inputs,
|
|
162
|
+
conf.get(ADVANCED, {}),
|
|
163
|
+
check_epipolar_a_priori=True,
|
|
164
|
+
output_dem_dir=output_dem_dir,
|
|
165
|
+
)
|
|
166
|
+
self.used_conf[ADVANCED] = advanced
|
|
167
|
+
|
|
168
|
+
self.refined_conf[ADVANCED] = copy.deepcopy(advanced)
|
|
169
|
+
# Refined conf: resolutions 1
|
|
170
|
+
self.refined_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS] = [1]
|
|
171
|
+
|
|
172
|
+
# Get ROI
|
|
173
|
+
(
|
|
174
|
+
self.input_roi_poly,
|
|
175
|
+
self.input_roi_epsg,
|
|
176
|
+
) = roi_tools.generate_roi_poly_from_inputs(
|
|
177
|
+
self.used_conf[INPUT][sens_cst.ROI]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self.debug_with_roi = self.used_conf[ADVANCED][adv_cst.DEBUG_WITH_ROI]
|
|
181
|
+
|
|
182
|
+
# Check conf output
|
|
183
|
+
(
|
|
184
|
+
output,
|
|
185
|
+
self.scaling_coeff,
|
|
186
|
+
) = self.check_output(inputs, conf[OUTPUT], self.scaling_coeff)
|
|
187
|
+
|
|
188
|
+
self.used_conf[OUTPUT] = output
|
|
189
|
+
self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
|
|
190
|
+
self.dump_dir = os.path.join(self.out_dir, "dump_dir")
|
|
191
|
+
|
|
192
|
+
self.refined_conf[OUTPUT] = copy.deepcopy(output)
|
|
193
|
+
|
|
194
|
+
prod_level = output[out_cst.PRODUCT_LEVEL]
|
|
195
|
+
|
|
196
|
+
self.save_output_dsm = "dsm" in prod_level
|
|
197
|
+
self.save_output_depth_map = "depth_map" in prod_level
|
|
198
|
+
self.save_output_point_cloud = "point_cloud" in prod_level
|
|
199
|
+
self.save_output_classif_for_filling = False
|
|
200
|
+
|
|
201
|
+
self.output_level_none = not (
|
|
202
|
+
self.save_output_dsm
|
|
203
|
+
or self.save_output_depth_map
|
|
204
|
+
or self.save_output_point_cloud
|
|
205
|
+
)
|
|
206
|
+
self.sensors_in_inputs = sens_cst.SENSORS in self.used_conf[INPUT]
|
|
207
|
+
|
|
208
|
+
# Used classification values, for filling -> will be masked
|
|
209
|
+
self.used_classif_values_for_filling = self.get_classif_values_filling(
|
|
210
|
+
self.used_conf[INPUT]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
self.dsms_in_inputs = dsm_cst.DSMS in self.used_conf[INPUT]
|
|
214
|
+
|
|
215
|
+
self.phasing = self.used_conf[ADVANCED][adv_cst.PHASING]
|
|
216
|
+
|
|
217
|
+
self.compute_depth_map = (
|
|
218
|
+
self.sensors_in_inputs
|
|
219
|
+
and (not self.output_level_none)
|
|
220
|
+
and not self.dsms_in_inputs
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if self.output_level_none:
|
|
224
|
+
self.infer_conditions_from_applications(conf)
|
|
225
|
+
|
|
226
|
+
self.save_all_intermediate_data = self.used_conf[ADVANCED][
|
|
227
|
+
adv_cst.SAVE_INTERMEDIATE_DATA
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
if isinstance(
|
|
231
|
+
self.used_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS], list
|
|
232
|
+
):
|
|
233
|
+
if len(self.used_conf[ADVANCED][adv_cst.EPIPOLAR_RESOLUTIONS]) > 1:
|
|
234
|
+
raise RuntimeError(
|
|
235
|
+
"For the unit pipeline, "
|
|
236
|
+
"the epipolar resolution has to "
|
|
237
|
+
"be a single value"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
self.res_resamp = self.used_conf[ADVANCED][
|
|
241
|
+
adv_cst.EPIPOLAR_RESOLUTIONS
|
|
242
|
+
][0]
|
|
243
|
+
else:
|
|
244
|
+
self.res_resamp = self.used_conf[ADVANCED][
|
|
245
|
+
adv_cst.EPIPOLAR_RESOLUTIONS
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
self.save_all_point_clouds_by_pair = self.used_conf[OUTPUT].get(
|
|
249
|
+
out_cst.SAVE_BY_PAIR, False
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Check conf application
|
|
253
|
+
application_conf = self.check_applications(conf.get(APPLICATIONS, {}))
|
|
254
|
+
|
|
255
|
+
if self.sensors_in_inputs and not self.dsms_in_inputs:
|
|
256
|
+
# Check conf application vs inputs application
|
|
257
|
+
application_conf = self.check_applications_with_inputs(
|
|
258
|
+
self.used_conf[INPUT], application_conf, self.res_resamp
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self.used_conf[APPLICATIONS] = application_conf
|
|
262
|
+
|
|
263
|
+
self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
|
|
264
|
+
|
|
265
|
+
def quit_on_app(self, app_name):
|
|
266
|
+
"""
|
|
267
|
+
Returns whether the pipeline should end after
|
|
268
|
+
the application was called.
|
|
269
|
+
|
|
270
|
+
Only works if the output_level is empty, so that
|
|
271
|
+
the control is instead given to
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
if not self.output_level_none:
|
|
275
|
+
# custom quit step was not set, never quit early
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
return self.app_values[app_name] >= self.last_application_to_run
|
|
279
|
+
|
|
280
|
+
def infer_conditions_from_applications(self, conf):
|
|
281
|
+
"""
|
|
282
|
+
Fills the condition booleans used later in the pipeline by going
|
|
283
|
+
through the applications and infering which application we should
|
|
284
|
+
end the pipeline on.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
self.last_application_to_run = 0
|
|
288
|
+
|
|
289
|
+
sensor_to_depth_apps = {
|
|
290
|
+
"grid_generation": 1, # and 5
|
|
291
|
+
"resampling": 2, # and 8
|
|
292
|
+
"sparse_matching": 4,
|
|
293
|
+
"ground_truth_reprojection": 6,
|
|
294
|
+
"dense_matching": 8,
|
|
295
|
+
"dense_match_filling": 9,
|
|
296
|
+
"triangulation": 11,
|
|
297
|
+
"point_cloud_outlier_removal.1": 12,
|
|
298
|
+
"point_cloud_outlier_removal.2": 13,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
depth_to_dsm_apps = {
|
|
302
|
+
"point_cloud_rasterization": 15,
|
|
303
|
+
"dem_generation": 16,
|
|
304
|
+
"dsm_filling.1": 17,
|
|
305
|
+
"dsm_filling.2": 18,
|
|
306
|
+
"dsm_filling.3": 19,
|
|
307
|
+
"auxiliary_filling": 20,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
self.app_values = {}
|
|
311
|
+
self.app_values.update(sensor_to_depth_apps)
|
|
312
|
+
self.app_values.update(depth_to_dsm_apps)
|
|
313
|
+
|
|
314
|
+
app_conf = conf.get(APPLICATIONS, {})
|
|
315
|
+
for key in app_conf:
|
|
316
|
+
|
|
317
|
+
if adv_cst.SAVE_INTERMEDIATE_DATA not in app_conf[key]:
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
if not app_conf[key][adv_cst.SAVE_INTERMEDIATE_DATA]:
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
if key in sensor_to_depth_apps:
|
|
324
|
+
|
|
325
|
+
if not self.sensors_in_inputs:
|
|
326
|
+
warn_msg = (
|
|
327
|
+
"The application {} can only be used when sensor "
|
|
328
|
+
"images are given as an input. "
|
|
329
|
+
"Its configuration will be ignored."
|
|
330
|
+
).format(key)
|
|
331
|
+
logging.warning(warn_msg)
|
|
332
|
+
|
|
333
|
+
elif self.sensors_in_inputs and not self.dsms_in_inputs:
|
|
334
|
+
self.compute_depth_map = True
|
|
335
|
+
self.last_application_to_run = max(
|
|
336
|
+
self.last_application_to_run, self.app_values[key]
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
elif key in depth_to_dsm_apps:
|
|
340
|
+
|
|
341
|
+
if not (self.sensors_in_inputs or self.dsms_in_inputs):
|
|
342
|
+
warn_msg = (
|
|
343
|
+
"The application {} can only be used when sensor "
|
|
344
|
+
"images or depth maps are given as an input. "
|
|
345
|
+
"Its configuration will be ignored."
|
|
346
|
+
).format(key)
|
|
347
|
+
logging.warning(warn_msg)
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
if self.sensors_in_inputs and not self.dsms_in_inputs:
|
|
351
|
+
self.compute_depth_map = True
|
|
352
|
+
|
|
353
|
+
# enabled to start the depth map to dsm process
|
|
354
|
+
self.save_output_dsm = True
|
|
355
|
+
|
|
356
|
+
self.last_application_to_run = max(
|
|
357
|
+
self.last_application_to_run, self.app_values[key]
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
else:
|
|
361
|
+
warn_msg = (
|
|
362
|
+
"The application {} was not recognized. Its configuration"
|
|
363
|
+
"will be ignored."
|
|
364
|
+
).format(key)
|
|
365
|
+
logging.warning(warn_msg)
|
|
366
|
+
|
|
367
|
+
if not (self.compute_depth_map or self.save_output_dsm):
|
|
368
|
+
log_msg = (
|
|
369
|
+
"No product level was given. CARS has not detected any "
|
|
370
|
+
"data you wish to save. No computation will be done."
|
|
371
|
+
)
|
|
372
|
+
logging.info(log_msg)
|
|
373
|
+
else:
|
|
374
|
+
log_msg = (
|
|
375
|
+
"No product level was given. CARS has detected that you "
|
|
376
|
+
+ "wish to run up to the {} application.".format(
|
|
377
|
+
next(
|
|
378
|
+
k
|
|
379
|
+
for k, v in self.app_values.items()
|
|
380
|
+
if v == self.last_application_to_run
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
logging.warning(log_msg)
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def check_inputs(conf, config_dir=None):
|
|
388
|
+
"""
|
|
389
|
+
Check the inputs given
|
|
390
|
+
|
|
391
|
+
:param conf: configuration of inputs
|
|
392
|
+
:type conf: dict
|
|
393
|
+
:param config_dir: directory of used json/yaml, if
|
|
394
|
+
user filled paths with relative paths
|
|
395
|
+
:type config_dir: str
|
|
396
|
+
|
|
397
|
+
:return: overloaded inputs
|
|
398
|
+
:rtype: dict
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
output_config = {}
|
|
402
|
+
if sens_cst.SENSORS in conf and dsm_cst.DSMS not in conf:
|
|
403
|
+
output_config = sensor_inputs.sensors_check_inputs(
|
|
404
|
+
conf, config_dir=config_dir
|
|
405
|
+
)
|
|
406
|
+
elif dsm_cst.DSMS in conf:
|
|
407
|
+
output_config = {
|
|
408
|
+
**output_config,
|
|
409
|
+
**dsm_inputs.check_dsm_inputs(conf, config_dir=config_dir),
|
|
410
|
+
}
|
|
411
|
+
else:
|
|
412
|
+
raise RuntimeError("No sensors or dsms in inputs")
|
|
413
|
+
return output_config
|
|
414
|
+
|
|
415
|
+
def save_configurations(self):
|
|
416
|
+
"""
|
|
417
|
+
Save used_conf and refined_conf configurations
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
cars_dataset.save_dict(
|
|
421
|
+
self.used_conf,
|
|
422
|
+
os.path.join(self.out_dir, "current_res_used_conf.yaml"),
|
|
423
|
+
)
|
|
424
|
+
cars_dataset.save_dict(
|
|
425
|
+
self.refined_conf,
|
|
426
|
+
os.path.join(self.out_dir, "refined_conf.yaml"),
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
def check_output(self, inputs, conf, scaling_coeff):
|
|
430
|
+
"""
|
|
431
|
+
Check the output given
|
|
432
|
+
|
|
433
|
+
:param conf: configuration of output
|
|
434
|
+
:type conf: dict
|
|
435
|
+
:param scaling_coeff: scaling factor for resolution
|
|
436
|
+
:type scaling_coeff: float
|
|
437
|
+
:return: overloader output
|
|
438
|
+
:rtype: dict
|
|
439
|
+
"""
|
|
440
|
+
return output_parameters.check_output_parameters(
|
|
441
|
+
inputs, conf, scaling_coeff
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def check_applications( # noqa: C901 : too complex
|
|
445
|
+
self,
|
|
446
|
+
conf,
|
|
447
|
+
):
|
|
448
|
+
"""
|
|
449
|
+
Check the given configuration for applications,
|
|
450
|
+
and generates needed applications for pipeline.
|
|
451
|
+
|
|
452
|
+
:param conf: configuration of applications
|
|
453
|
+
:type conf: dict
|
|
454
|
+
"""
|
|
455
|
+
scaling_coeff = self.scaling_coeff
|
|
456
|
+
|
|
457
|
+
needed_applications = application_parameters.get_needed_apps(
|
|
458
|
+
self.sensors_in_inputs,
|
|
459
|
+
self.save_output_dsm,
|
|
460
|
+
self.save_output_point_cloud,
|
|
461
|
+
conf,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Check if all specified applications are used
|
|
465
|
+
# Application in terrain_application are note used in
|
|
466
|
+
# the sensors_to_dense_depth_maps pipeline
|
|
467
|
+
for app_key in conf.keys():
|
|
468
|
+
if app_key not in needed_applications:
|
|
469
|
+
msg = (
|
|
470
|
+
f"No {app_key} application used in the "
|
|
471
|
+
+ "default Cars pipeline"
|
|
472
|
+
)
|
|
473
|
+
logging.error(msg)
|
|
474
|
+
raise NameError(msg)
|
|
475
|
+
|
|
476
|
+
# Initialize used config
|
|
477
|
+
used_conf = {}
|
|
478
|
+
|
|
479
|
+
for app_key in needed_applications:
|
|
480
|
+
used_conf[app_key] = conf.get(app_key, {})
|
|
481
|
+
if used_conf[app_key] is None:
|
|
482
|
+
continue
|
|
483
|
+
used_conf[app_key]["save_intermediate_data"] = (
|
|
484
|
+
self.save_all_intermediate_data
|
|
485
|
+
or used_conf[app_key].get("save_intermediate_data", False)
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
if app_key == "auxiliary_filling":
|
|
489
|
+
if used_conf[app_key] is not None:
|
|
490
|
+
used_conf[app_key]["activated"] = used_conf[app_key].get(
|
|
491
|
+
"activated", True
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if app_key in [
|
|
495
|
+
"point_cloud_fusion",
|
|
496
|
+
]:
|
|
497
|
+
used_conf[app_key]["save_by_pair"] = used_conf[app_key].get(
|
|
498
|
+
"save_by_pair", self.save_all_point_clouds_by_pair
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
self.epipolar_grid_generation_application = None
|
|
502
|
+
self.resampling_application = None
|
|
503
|
+
self.ground_truth_reprojection = None
|
|
504
|
+
self.dense_match_filling = None
|
|
505
|
+
self.sparse_mtch_app = None
|
|
506
|
+
self.dense_matching_app = None
|
|
507
|
+
self.triangulation_application = None
|
|
508
|
+
self.dem_generation_application = None
|
|
509
|
+
self.pc_outlier_removal_apps = {}
|
|
510
|
+
self.rasterization_application = None
|
|
511
|
+
self.pc_fusion_application = None
|
|
512
|
+
self.dsm_filling_1_application = None
|
|
513
|
+
self.dsm_filling_2_application = None
|
|
514
|
+
self.dsm_filling_3_application = None
|
|
515
|
+
self.dsm_filling_apps = {}
|
|
516
|
+
|
|
517
|
+
if self.sensors_in_inputs:
|
|
518
|
+
# Epipolar grid generation
|
|
519
|
+
self.epipolar_grid_generation_application = Application(
|
|
520
|
+
"grid_generation",
|
|
521
|
+
cfg=used_conf.get("grid_generation", {}),
|
|
522
|
+
scaling_coeff=scaling_coeff,
|
|
523
|
+
)
|
|
524
|
+
used_conf["grid_generation"] = (
|
|
525
|
+
self.epipolar_grid_generation_application.get_conf()
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# image resampling
|
|
529
|
+
self.resampling_application = Application(
|
|
530
|
+
"resampling",
|
|
531
|
+
cfg=used_conf.get("resampling", {}),
|
|
532
|
+
scaling_coeff=scaling_coeff,
|
|
533
|
+
)
|
|
534
|
+
used_conf["resampling"] = self.resampling_application.get_conf()
|
|
535
|
+
|
|
536
|
+
# ground truth disparity map computation
|
|
537
|
+
if self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
|
|
538
|
+
used_conf["ground_truth_reprojection"][
|
|
539
|
+
"save_intermediate_data"
|
|
540
|
+
] = True
|
|
541
|
+
|
|
542
|
+
if isinstance(
|
|
543
|
+
self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM], str
|
|
544
|
+
):
|
|
545
|
+
self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM] = {
|
|
546
|
+
"dsm": self.used_conf[ADVANCED][
|
|
547
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
self.ground_truth_reprojection = Application(
|
|
552
|
+
"ground_truth_reprojection",
|
|
553
|
+
cfg=used_conf.get("ground_truth_reprojection", {}),
|
|
554
|
+
scaling_coeff=scaling_coeff,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# disparity filling
|
|
558
|
+
self.dense_match_filling = Application(
|
|
559
|
+
"dense_match_filling",
|
|
560
|
+
cfg=used_conf.get(
|
|
561
|
+
"dense_match_filling",
|
|
562
|
+
{"method": "zero_padding"},
|
|
563
|
+
),
|
|
564
|
+
scaling_coeff=scaling_coeff,
|
|
565
|
+
)
|
|
566
|
+
used_conf["dense_match_filling"] = (
|
|
567
|
+
self.dense_match_filling.get_conf()
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Sparse Matching
|
|
571
|
+
self.sparse_mtch_app = Application(
|
|
572
|
+
"sparse_matching",
|
|
573
|
+
cfg=used_conf.get("sparse_matching", {"method": "sift"}),
|
|
574
|
+
scaling_coeff=scaling_coeff,
|
|
575
|
+
)
|
|
576
|
+
used_conf["sparse_matching"] = self.sparse_mtch_app.get_conf()
|
|
577
|
+
|
|
578
|
+
# Matching
|
|
579
|
+
generate_performance_map = bool(
|
|
580
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
581
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
582
|
+
]
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
generate_ambiguity = (
|
|
586
|
+
self.used_conf[OUTPUT]
|
|
587
|
+
.get(out_cst.AUXILIARY, {})
|
|
588
|
+
.get(out_cst.AUX_AMBIGUITY, False)
|
|
589
|
+
)
|
|
590
|
+
dense_matching_config = used_conf.get("dense_matching", {})
|
|
591
|
+
if generate_ambiguity is True:
|
|
592
|
+
dense_matching_config["generate_ambiguity"] = True
|
|
593
|
+
|
|
594
|
+
if (
|
|
595
|
+
generate_performance_map is True
|
|
596
|
+
and dense_matching_config.get("performance_map_method", None)
|
|
597
|
+
is None
|
|
598
|
+
):
|
|
599
|
+
dense_matching_config["performance_map_method"] = "risk"
|
|
600
|
+
|
|
601
|
+
# particular case for some epipolar resolutions
|
|
602
|
+
if not dense_matching_config:
|
|
603
|
+
used_conf["dense_matching"]["performance_map_method"] = [
|
|
604
|
+
"risk",
|
|
605
|
+
"intervals",
|
|
606
|
+
]
|
|
607
|
+
|
|
608
|
+
self.dense_matching_app = Application(
|
|
609
|
+
"dense_matching",
|
|
610
|
+
cfg=dense_matching_config,
|
|
611
|
+
scaling_coeff=scaling_coeff,
|
|
612
|
+
)
|
|
613
|
+
used_conf["dense_matching"] = self.dense_matching_app.get_conf()
|
|
614
|
+
|
|
615
|
+
# Triangulation
|
|
616
|
+
self.triangulation_application = Application(
|
|
617
|
+
"triangulation",
|
|
618
|
+
cfg=used_conf.get("triangulation", {}),
|
|
619
|
+
scaling_coeff=scaling_coeff,
|
|
620
|
+
)
|
|
621
|
+
used_conf["triangulation"] = (
|
|
622
|
+
self.triangulation_application.get_conf()
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# MNT generation
|
|
626
|
+
self.dem_generation_application = Application(
|
|
627
|
+
"dem_generation",
|
|
628
|
+
cfg=used_conf.get("dem_generation", {}),
|
|
629
|
+
scaling_coeff=scaling_coeff,
|
|
630
|
+
)
|
|
631
|
+
used_conf["dem_generation"] = (
|
|
632
|
+
self.dem_generation_application.get_conf()
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
for app_key, app_conf in used_conf.items():
|
|
636
|
+
if not app_key.startswith("point_cloud_outlier_removal"):
|
|
637
|
+
continue
|
|
638
|
+
|
|
639
|
+
if app_conf is None:
|
|
640
|
+
self.pc_outlier_removal_apps = {}
|
|
641
|
+
# keep over multiple runs
|
|
642
|
+
used_conf["point_cloud_outlier_removal"] = None
|
|
643
|
+
break
|
|
644
|
+
|
|
645
|
+
if app_key in self.pc_outlier_removal_apps:
|
|
646
|
+
msg = (
|
|
647
|
+
f"The key {app_key} is defined twice in the input "
|
|
648
|
+
"configuration."
|
|
649
|
+
)
|
|
650
|
+
logging.error(msg)
|
|
651
|
+
raise NameError(msg)
|
|
652
|
+
|
|
653
|
+
if app_key[27:] == ".1":
|
|
654
|
+
app_conf.setdefault("method", "small_components")
|
|
655
|
+
if app_key[27:] == ".2":
|
|
656
|
+
app_conf.setdefault("method", "statistical")
|
|
657
|
+
|
|
658
|
+
self.pc_outlier_removal_apps[app_key] = Application(
|
|
659
|
+
"point_cloud_outlier_removal",
|
|
660
|
+
cfg=app_conf,
|
|
661
|
+
scaling_coeff=scaling_coeff,
|
|
662
|
+
)
|
|
663
|
+
used_conf[app_key] = self.pc_outlier_removal_apps[
|
|
664
|
+
app_key
|
|
665
|
+
].get_conf()
|
|
666
|
+
|
|
667
|
+
methods_str = "\n".join(
|
|
668
|
+
f" - {k}={a.used_method}"
|
|
669
|
+
for k, a in self.pc_outlier_removal_apps.items()
|
|
670
|
+
)
|
|
671
|
+
logging.info(
|
|
672
|
+
"{} point cloud outlier removal apps registered:\n{}".format(
|
|
673
|
+
len(self.pc_outlier_removal_apps), methods_str
|
|
674
|
+
)
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
if self.save_output_dsm or self.save_output_point_cloud:
|
|
678
|
+
|
|
679
|
+
if self.save_output_dsm:
|
|
680
|
+
|
|
681
|
+
# Rasterization
|
|
682
|
+
self.rasterization_application = Application(
|
|
683
|
+
"point_cloud_rasterization",
|
|
684
|
+
cfg=used_conf.get("point_cloud_rasterization", {}),
|
|
685
|
+
scaling_coeff=scaling_coeff,
|
|
686
|
+
)
|
|
687
|
+
used_conf["point_cloud_rasterization"] = (
|
|
688
|
+
self.rasterization_application.get_conf()
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
for app_key, app_conf in used_conf.items():
|
|
692
|
+
if not app_key.startswith("dsm_filling"):
|
|
693
|
+
continue
|
|
694
|
+
|
|
695
|
+
if app_conf is None:
|
|
696
|
+
self.dsm_filling_apps = {}
|
|
697
|
+
# keep over multiple runs
|
|
698
|
+
used_conf["dsm_filling"] = None
|
|
699
|
+
break
|
|
700
|
+
|
|
701
|
+
if app_key in self.dsm_filling_apps:
|
|
702
|
+
msg = (
|
|
703
|
+
f"The key {app_key} is defined twice in the input "
|
|
704
|
+
"configuration."
|
|
705
|
+
)
|
|
706
|
+
logging.error(msg)
|
|
707
|
+
raise NameError(msg)
|
|
708
|
+
|
|
709
|
+
if app_key[11:] == ".1":
|
|
710
|
+
app_conf.setdefault("method", "exogenous_filling")
|
|
711
|
+
if app_key[11:] == ".2":
|
|
712
|
+
app_conf.setdefault("method", "bulldozer")
|
|
713
|
+
if app_key[11:] == ".3":
|
|
714
|
+
app_conf.setdefault("method", "border_interpolation")
|
|
715
|
+
|
|
716
|
+
self.dsm_filling_apps[app_key] = Application(
|
|
717
|
+
"dsm_filling",
|
|
718
|
+
cfg=app_conf,
|
|
719
|
+
scaling_coeff=scaling_coeff,
|
|
720
|
+
)
|
|
721
|
+
used_conf[app_key] = self.dsm_filling_apps[
|
|
722
|
+
app_key
|
|
723
|
+
].get_conf()
|
|
724
|
+
|
|
725
|
+
methods_str = "\n".join(
|
|
726
|
+
f" - {k}={a.used_method}"
|
|
727
|
+
for k, a in self.dsm_filling_apps.items()
|
|
728
|
+
)
|
|
729
|
+
logging.info(
|
|
730
|
+
"{} dsm filling apps registered:\n{}".format(
|
|
731
|
+
len(self.dsm_filling_apps), methods_str
|
|
732
|
+
)
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
# Auxiliary filling
|
|
736
|
+
self.auxiliary_filling_application = Application(
|
|
737
|
+
"auxiliary_filling",
|
|
738
|
+
cfg=conf.get("auxiliary_filling", {}),
|
|
739
|
+
scaling_coeff=scaling_coeff,
|
|
740
|
+
)
|
|
741
|
+
used_conf["auxiliary_filling"] = (
|
|
742
|
+
self.auxiliary_filling_application.get_conf()
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
if any(
|
|
746
|
+
app_obj.classification != ["nodata"]
|
|
747
|
+
for app_key, app_obj in self.dsm_filling_apps.items()
|
|
748
|
+
):
|
|
749
|
+
self.save_output_classif_for_filling = True
|
|
750
|
+
|
|
751
|
+
return used_conf
|
|
752
|
+
|
|
753
|
+
def get_classif_values_filling(self, inputs):
|
|
754
|
+
"""
|
|
755
|
+
Get values in classif, used for filling
|
|
756
|
+
|
|
757
|
+
:param inputs: inputs
|
|
758
|
+
:type inputs: dict
|
|
759
|
+
|
|
760
|
+
:return: list of values
|
|
761
|
+
:rtype: list
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
filling_classif_values = []
|
|
765
|
+
|
|
766
|
+
if sens_cst.FILLING not in inputs or inputs[sens_cst.FILLING] is None:
|
|
767
|
+
logging.info("No filling in input configuration")
|
|
768
|
+
return None
|
|
769
|
+
|
|
770
|
+
filling_classif_values = []
|
|
771
|
+
for _, classif_values in inputs[sens_cst.FILLING].items():
|
|
772
|
+
# Add new value to filling bands
|
|
773
|
+
if classif_values is not None:
|
|
774
|
+
if isinstance(classif_values, str):
|
|
775
|
+
classif_values = [classif_values]
|
|
776
|
+
filling_classif_values += classif_values
|
|
777
|
+
|
|
778
|
+
simplified_list = list(OrderedDict.fromkeys(filling_classif_values))
|
|
779
|
+
res_as_string_list = [str(value) for value in simplified_list]
|
|
780
|
+
return res_as_string_list
|
|
781
|
+
|
|
782
|
+
def check_applications_with_inputs( # noqa: C901 : too complex
|
|
783
|
+
self, inputs_conf, application_conf, epipolar_resolution
|
|
784
|
+
):
|
|
785
|
+
"""
|
|
786
|
+
Check for each application the input and output configuration
|
|
787
|
+
consistency
|
|
788
|
+
|
|
789
|
+
:param inputs_conf: inputs checked configuration
|
|
790
|
+
:type inputs_conf: dict
|
|
791
|
+
:param application_conf: application checked configuration
|
|
792
|
+
:type application_conf: dict
|
|
793
|
+
:param epipolar_resolution: epipolar resolution
|
|
794
|
+
:type epipolar_resolution: int
|
|
795
|
+
"""
|
|
796
|
+
|
|
797
|
+
initial_elevation = (
|
|
798
|
+
inputs_conf[sens_cst.INITIAL_ELEVATION]["dem"] is not None
|
|
799
|
+
)
|
|
800
|
+
if self.sparse_mtch_app.elevation_delta_lower_bound is None:
|
|
801
|
+
self.sparse_mtch_app.used_config["elevation_delta_lower_bound"] = (
|
|
802
|
+
-500 if initial_elevation else -1000
|
|
803
|
+
)
|
|
804
|
+
self.sparse_mtch_app.elevation_delta_lower_bound = (
|
|
805
|
+
self.sparse_mtch_app.used_config["elevation_delta_lower_bound"]
|
|
806
|
+
)
|
|
807
|
+
if self.sparse_mtch_app.elevation_delta_upper_bound is None:
|
|
808
|
+
self.sparse_mtch_app.used_config["elevation_delta_upper_bound"] = (
|
|
809
|
+
1000 if initial_elevation else 9000
|
|
810
|
+
)
|
|
811
|
+
self.sparse_mtch_app.elevation_delta_upper_bound = (
|
|
812
|
+
self.sparse_mtch_app.used_config["elevation_delta_upper_bound"]
|
|
813
|
+
)
|
|
814
|
+
application_conf["sparse_matching"] = self.sparse_mtch_app.get_conf()
|
|
815
|
+
|
|
816
|
+
# check classification application parameter compare
|
|
817
|
+
# to each sensors inputs classification list
|
|
818
|
+
for application_key in application_conf:
|
|
819
|
+
if application_conf[application_key] is None:
|
|
820
|
+
continue
|
|
821
|
+
if "classification" in application_conf[application_key]:
|
|
822
|
+
for item in inputs_conf["sensors"]:
|
|
823
|
+
if "classification" in inputs_conf["sensors"][item].keys():
|
|
824
|
+
if inputs_conf["sensors"][item]["classification"]:
|
|
825
|
+
descriptions = get_descriptions_bands(
|
|
826
|
+
inputs_conf["sensors"][item]["classification"]
|
|
827
|
+
)
|
|
828
|
+
if application_conf[application_key][
|
|
829
|
+
"classification"
|
|
830
|
+
] and not set(
|
|
831
|
+
application_conf[application_key][
|
|
832
|
+
"classification"
|
|
833
|
+
]
|
|
834
|
+
).issubset(
|
|
835
|
+
set(descriptions) | {"nodata"}
|
|
836
|
+
):
|
|
837
|
+
raise RuntimeError(
|
|
838
|
+
"The {} bands description {} ".format(
|
|
839
|
+
inputs_conf["sensors"][item][
|
|
840
|
+
"classification"
|
|
841
|
+
],
|
|
842
|
+
list(descriptions),
|
|
843
|
+
)
|
|
844
|
+
+ "and the {} config are not ".format(
|
|
845
|
+
application_key
|
|
846
|
+
)
|
|
847
|
+
+ "consistent: {}".format(
|
|
848
|
+
application_conf[application_key][
|
|
849
|
+
"classification"
|
|
850
|
+
]
|
|
851
|
+
)
|
|
852
|
+
)
|
|
853
|
+
for key1, key2 in inputs_conf["pairing"]:
|
|
854
|
+
corr_cfg = self.dense_matching_app.loader.get_conf()
|
|
855
|
+
nodata_left = inputs_conf["sensors"][key2]["image"]["no_data"]
|
|
856
|
+
nodata_right = inputs_conf["sensors"][key2]["image"]["no_data"]
|
|
857
|
+
bands_left = list(
|
|
858
|
+
inputs_conf["sensors"][key1]["image"]["bands"].keys()
|
|
859
|
+
)
|
|
860
|
+
bands_right = list(
|
|
861
|
+
inputs_conf["sensors"][key2]["image"]["bands"].keys()
|
|
862
|
+
)
|
|
863
|
+
values_classif_left = None
|
|
864
|
+
values_classif_right = None
|
|
865
|
+
if (
|
|
866
|
+
"classification" in inputs_conf["sensors"][key1]
|
|
867
|
+
and inputs_conf["sensors"][key1]["classification"] is not None
|
|
868
|
+
):
|
|
869
|
+
values_classif_left = inputs_conf["sensors"][key1][
|
|
870
|
+
"classification"
|
|
871
|
+
]["values"]
|
|
872
|
+
values_classif_left = list(map(str, values_classif_left))
|
|
873
|
+
if (
|
|
874
|
+
"classification" in inputs_conf["sensors"][key2]
|
|
875
|
+
and inputs_conf["sensors"][key2]["classification"] is not None
|
|
876
|
+
):
|
|
877
|
+
values_classif_right = inputs_conf["sensors"][key2][
|
|
878
|
+
"classification"
|
|
879
|
+
]["values"]
|
|
880
|
+
values_classif_right = list(map(str, values_classif_right))
|
|
881
|
+
self.dense_matching_app.corr_config = (
|
|
882
|
+
self.dense_matching_app.loader.check_conf(
|
|
883
|
+
corr_cfg,
|
|
884
|
+
nodata_left,
|
|
885
|
+
nodata_right,
|
|
886
|
+
bands_left,
|
|
887
|
+
bands_right,
|
|
888
|
+
values_classif_left,
|
|
889
|
+
values_classif_right,
|
|
890
|
+
)
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
# Change the step regarding the resolution
|
|
894
|
+
# For the small resolution, the resampling perform better
|
|
895
|
+
# with a small step
|
|
896
|
+
# For the higher ones, a step at 30 should be better
|
|
897
|
+
first_image_path = next(iter(inputs_conf["sensors"].values()))["image"][
|
|
898
|
+
"bands"
|
|
899
|
+
]["b0"]["path"]
|
|
900
|
+
first_image_size = rasterio_get_size(first_image_path)
|
|
901
|
+
size_low_res_img_row = first_image_size[0] // epipolar_resolution
|
|
902
|
+
size_low_res_img_col = first_image_size[1] // epipolar_resolution
|
|
903
|
+
if epipolar_resolution > 1:
|
|
904
|
+
if size_low_res_img_row <= 900 and size_low_res_img_col <= 900:
|
|
905
|
+
application_conf["grid_generation"]["epi_step"] = (
|
|
906
|
+
epipolar_resolution * 5
|
|
907
|
+
)
|
|
908
|
+
else:
|
|
909
|
+
application_conf["grid_generation"]["epi_step"] = (
|
|
910
|
+
epipolar_resolution * 30
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
return application_conf
|
|
914
|
+
|
|
915
|
+
def generate_grid_correction_on_dem(self, pair_key, geo_plugin_on_dem):
|
|
916
|
+
"""
|
|
917
|
+
Generate the epipolar grid correction for a given pair, using given dem
|
|
918
|
+
"""
|
|
919
|
+
|
|
920
|
+
# Generate new grids with dem
|
|
921
|
+
# Generate rectification grids
|
|
922
|
+
(
|
|
923
|
+
grid_left_new_dem,
|
|
924
|
+
grid_right_new_dem,
|
|
925
|
+
) = self.epipolar_grid_generation_application.run(
|
|
926
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
927
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
928
|
+
geo_plugin_on_dem,
|
|
929
|
+
orchestrator=self.cars_orchestrator,
|
|
930
|
+
pair_folder=os.path.join(
|
|
931
|
+
self.dump_dir,
|
|
932
|
+
"epipolar_grid_generation",
|
|
933
|
+
"new_dem",
|
|
934
|
+
pair_key,
|
|
935
|
+
),
|
|
936
|
+
pair_key=pair_key,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
if self.pairs[pair_key].get("sensor_matches_left", None) is None:
|
|
940
|
+
logging.error(
|
|
941
|
+
"No sensor matches available to compute grid correction"
|
|
942
|
+
)
|
|
943
|
+
return None
|
|
944
|
+
|
|
945
|
+
# Generate new matches with new grids
|
|
946
|
+
new_grid_matches_array = geo_plugin_on_dem.transform_matches_from_grids(
|
|
947
|
+
self.pairs[pair_key]["sensor_matches_left"],
|
|
948
|
+
self.pairs[pair_key]["sensor_matches_right"],
|
|
949
|
+
grid_left_new_dem,
|
|
950
|
+
grid_right_new_dem,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
# Generate grid_correction
|
|
954
|
+
# Compute grid correction
|
|
955
|
+
(
|
|
956
|
+
new_grid_correction_coef,
|
|
957
|
+
_,
|
|
958
|
+
_,
|
|
959
|
+
_,
|
|
960
|
+
) = grid_correction_app.estimate_right_grid_correction(
|
|
961
|
+
new_grid_matches_array,
|
|
962
|
+
grid_right_new_dem,
|
|
963
|
+
save_matches=False,
|
|
964
|
+
minimum_nb_matches=0,
|
|
965
|
+
pair_folder=os.path.join(
|
|
966
|
+
self.dump_dir, "grid_correction", " new_dem", pair_key
|
|
967
|
+
),
|
|
968
|
+
pair_key=pair_key,
|
|
969
|
+
orchestrator=self.cars_orchestrator,
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
return new_grid_correction_coef
|
|
973
|
+
|
|
974
|
+
def sensor_to_depth_maps(self): # noqa: C901
|
|
975
|
+
"""
|
|
976
|
+
Creates the depth map from the sensor images given in the input,
|
|
977
|
+
by following the CARS pipeline's steps.
|
|
978
|
+
"""
|
|
979
|
+
# pylint:disable=too-many-return-statements
|
|
980
|
+
inputs = self.used_conf[INPUT]
|
|
981
|
+
output = self.used_conf[OUTPUT]
|
|
982
|
+
|
|
983
|
+
# Initialize epsg for terrain tiles
|
|
984
|
+
self.phasing = self.used_conf[ADVANCED][adv_cst.PHASING]
|
|
985
|
+
|
|
986
|
+
if self.phasing is not None:
|
|
987
|
+
self.epsg = self.phasing["epsg"]
|
|
988
|
+
else:
|
|
989
|
+
self.epsg = output[out_cst.EPSG]
|
|
990
|
+
|
|
991
|
+
if self.epsg is not None:
|
|
992
|
+
# Compute roi polygon, in output EPSG
|
|
993
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
994
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
self.resolution = output[out_cst.RESOLUTION]
|
|
998
|
+
|
|
999
|
+
# List of terrain roi corresponding to each epipolar pair
|
|
1000
|
+
# Used to generate final terrain roi
|
|
1001
|
+
self.list_terrain_roi = []
|
|
1002
|
+
|
|
1003
|
+
# Polygons representing the intersection of each pair of images
|
|
1004
|
+
# Used to fill the final DSM only inside of those Polygons
|
|
1005
|
+
self.list_intersection_poly = []
|
|
1006
|
+
|
|
1007
|
+
# initialize lists of points
|
|
1008
|
+
self.list_epipolar_point_clouds = []
|
|
1009
|
+
self.list_sensor_pairs = sensor_inputs.generate_inputs(
|
|
1010
|
+
inputs, self.geom_plugin_without_dem_and_geoid
|
|
1011
|
+
)
|
|
1012
|
+
logging.info(
|
|
1013
|
+
"Received {} stereo pairs configurations".format(
|
|
1014
|
+
len(self.list_sensor_pairs)
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
output_parameters.intialize_product_index(
|
|
1019
|
+
self.cars_orchestrator,
|
|
1020
|
+
output["product_level"],
|
|
1021
|
+
[sensor_pair[0] for sensor_pair in self.list_sensor_pairs],
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
# pairs is a dict used to store the CarsDataset of
|
|
1025
|
+
# all pairs, easily retrievable with pair keys
|
|
1026
|
+
self.pairs = {}
|
|
1027
|
+
|
|
1028
|
+
# triangulated_matches_list is used to store triangulated matches
|
|
1029
|
+
# used in dem generation
|
|
1030
|
+
self.triangulated_matches_list = []
|
|
1031
|
+
|
|
1032
|
+
save_matches = self.sparse_mtch_app.get_save_matches()
|
|
1033
|
+
|
|
1034
|
+
save_corrected_grid = (
|
|
1035
|
+
self.epipolar_grid_generation_application.get_save_grids()
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
for (
|
|
1039
|
+
pair_key,
|
|
1040
|
+
sensor_image_left,
|
|
1041
|
+
sensor_image_right,
|
|
1042
|
+
) in self.list_sensor_pairs:
|
|
1043
|
+
|
|
1044
|
+
# initialize pairs for current pair
|
|
1045
|
+
self.pairs[pair_key] = {}
|
|
1046
|
+
self.pairs[pair_key]["sensor_image_left"] = sensor_image_left
|
|
1047
|
+
self.pairs[pair_key]["sensor_image_right"] = sensor_image_right
|
|
1048
|
+
|
|
1049
|
+
# Run applications
|
|
1050
|
+
|
|
1051
|
+
# Run grid generation
|
|
1052
|
+
# We generate grids with dem if it is provided.
|
|
1053
|
+
# If not provided, grid are generated without dem and a dem
|
|
1054
|
+
# will be generated, to use later for a new grid generation**
|
|
1055
|
+
|
|
1056
|
+
if inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None:
|
|
1057
|
+
geom_plugin = self.geom_plugin_without_dem_and_geoid
|
|
1058
|
+
|
|
1059
|
+
else:
|
|
1060
|
+
geom_plugin = self.geom_plugin_with_dem_and_geoid
|
|
1061
|
+
|
|
1062
|
+
# Generate rectification grids
|
|
1063
|
+
(
|
|
1064
|
+
self.pairs[pair_key]["grid_left"],
|
|
1065
|
+
self.pairs[pair_key]["grid_right"],
|
|
1066
|
+
) = self.epipolar_grid_generation_application.run(
|
|
1067
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1068
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1069
|
+
geom_plugin,
|
|
1070
|
+
orchestrator=self.cars_orchestrator,
|
|
1071
|
+
pair_folder=os.path.join(
|
|
1072
|
+
self.dump_dir,
|
|
1073
|
+
"epipolar_grid_generation",
|
|
1074
|
+
"initial",
|
|
1075
|
+
pair_key,
|
|
1076
|
+
),
|
|
1077
|
+
pair_key=pair_key,
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
if self.quit_on_app("grid_generation"):
|
|
1081
|
+
continue # keep iterating over pairs, but don't go further
|
|
1082
|
+
|
|
1083
|
+
if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (
|
|
1084
|
+
None,
|
|
1085
|
+
{},
|
|
1086
|
+
):
|
|
1087
|
+
# Run resampling only if needed:
|
|
1088
|
+
# no a priori
|
|
1089
|
+
|
|
1090
|
+
# Get required bands of first resampling
|
|
1091
|
+
required_bands = self.sparse_mtch_app.get_required_bands()
|
|
1092
|
+
|
|
1093
|
+
# Run first epipolar resampling
|
|
1094
|
+
(
|
|
1095
|
+
self.pairs[pair_key]["epipolar_image_left"],
|
|
1096
|
+
self.pairs[pair_key]["epipolar_image_right"],
|
|
1097
|
+
) = self.resampling_application.run(
|
|
1098
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1099
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1100
|
+
self.pairs[pair_key]["grid_left"],
|
|
1101
|
+
self.pairs[pair_key]["grid_right"],
|
|
1102
|
+
geom_plugin,
|
|
1103
|
+
orchestrator=self.cars_orchestrator,
|
|
1104
|
+
pair_folder=os.path.join(
|
|
1105
|
+
self.dump_dir, "resampling", "initial", pair_key
|
|
1106
|
+
),
|
|
1107
|
+
pair_key=pair_key,
|
|
1108
|
+
margins_fun=self.sparse_mtch_app.get_margins_fun(),
|
|
1109
|
+
tile_width=None,
|
|
1110
|
+
tile_height=None,
|
|
1111
|
+
required_bands=required_bands,
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
if self.quit_on_app("resampling"):
|
|
1115
|
+
continue # keep iterating over pairs, but don't go further
|
|
1116
|
+
|
|
1117
|
+
if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (
|
|
1118
|
+
None,
|
|
1119
|
+
{},
|
|
1120
|
+
):
|
|
1121
|
+
# Run epipolar sparse_matching application
|
|
1122
|
+
(
|
|
1123
|
+
self.pairs[pair_key]["epipolar_matches_left"],
|
|
1124
|
+
_,
|
|
1125
|
+
) = self.sparse_mtch_app.run(
|
|
1126
|
+
self.pairs[pair_key]["epipolar_image_left"],
|
|
1127
|
+
self.pairs[pair_key]["epipolar_image_right"],
|
|
1128
|
+
self.pairs[pair_key]["grid_left"]["disp_to_alt_ratio"],
|
|
1129
|
+
orchestrator=self.cars_orchestrator,
|
|
1130
|
+
pair_folder=os.path.join(
|
|
1131
|
+
self.dump_dir, "sparse_matching", pair_key
|
|
1132
|
+
),
|
|
1133
|
+
pair_key=pair_key,
|
|
1134
|
+
classif_bands_to_mask=self.used_classif_values_for_filling,
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
# Run cluster breakpoint to compute sifts: force computation
|
|
1138
|
+
self.cars_orchestrator.breakpoint()
|
|
1139
|
+
|
|
1140
|
+
minimum_nb_matches = self.sparse_mtch_app.get_minimum_nb_matches()
|
|
1141
|
+
|
|
1142
|
+
# Run grid correction application
|
|
1143
|
+
if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
|
|
1144
|
+
# Estimate grid correction if no epipolar a priori
|
|
1145
|
+
# Filter and save matches
|
|
1146
|
+
self.pairs[pair_key]["matches_array"] = (
|
|
1147
|
+
self.sparse_mtch_app.filter_matches(
|
|
1148
|
+
self.pairs[pair_key]["epipolar_matches_left"],
|
|
1149
|
+
self.pairs[pair_key]["grid_left"],
|
|
1150
|
+
self.pairs[pair_key]["grid_right"],
|
|
1151
|
+
geom_plugin,
|
|
1152
|
+
orchestrator=self.cars_orchestrator,
|
|
1153
|
+
pair_key=pair_key,
|
|
1154
|
+
pair_folder=os.path.join(
|
|
1155
|
+
self.dump_dir, "sparse_matching", pair_key
|
|
1156
|
+
),
|
|
1157
|
+
save_matches=(self.sparse_mtch_app.get_save_matches()),
|
|
1158
|
+
)
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
# Compute grid correction
|
|
1162
|
+
(
|
|
1163
|
+
self.pairs[pair_key]["grid_correction_coef"],
|
|
1164
|
+
self.pairs[pair_key]["corrected_matches_array"],
|
|
1165
|
+
_,
|
|
1166
|
+
_,
|
|
1167
|
+
) = grid_correction_app.estimate_right_grid_correction(
|
|
1168
|
+
self.pairs[pair_key]["matches_array"],
|
|
1169
|
+
self.pairs[pair_key]["grid_right"],
|
|
1170
|
+
save_matches=save_matches,
|
|
1171
|
+
minimum_nb_matches=minimum_nb_matches,
|
|
1172
|
+
pair_folder=os.path.join(
|
|
1173
|
+
self.dump_dir, "grid_correction", "initial", pair_key
|
|
1174
|
+
),
|
|
1175
|
+
pair_key=pair_key,
|
|
1176
|
+
orchestrator=self.cars_orchestrator,
|
|
1177
|
+
)
|
|
1178
|
+
# Correct grid right
|
|
1179
|
+
self.pairs[pair_key]["corrected_grid_right"] = (
|
|
1180
|
+
grid_correction_app.correct_grid(
|
|
1181
|
+
self.pairs[pair_key]["grid_right"],
|
|
1182
|
+
self.pairs[pair_key]["grid_correction_coef"],
|
|
1183
|
+
os.path.join(
|
|
1184
|
+
self.dump_dir,
|
|
1185
|
+
"grid_correction",
|
|
1186
|
+
"initial",
|
|
1187
|
+
pair_key,
|
|
1188
|
+
),
|
|
1189
|
+
save_corrected_grid,
|
|
1190
|
+
)
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
|
|
1194
|
+
pair_key
|
|
1195
|
+
]["grid_left"]
|
|
1196
|
+
|
|
1197
|
+
if self.quit_on_app("sparse_matching"):
|
|
1198
|
+
continue
|
|
1199
|
+
|
|
1200
|
+
# Shrink disparity intervals according to SIFT disparities
|
|
1201
|
+
disp_to_alt_ratio = self.pairs[pair_key]["grid_left"][
|
|
1202
|
+
"disp_to_alt_ratio"
|
|
1203
|
+
]
|
|
1204
|
+
disp_bounds_params = (
|
|
1205
|
+
self.sparse_mtch_app.disparity_bounds_estimation
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
if disp_bounds_params["activated"]:
|
|
1209
|
+
matches = self.pairs[pair_key]["matches_array"]
|
|
1210
|
+
sift_disp = matches[:, 2] - matches[:, 0]
|
|
1211
|
+
disp_min = np.percentile(
|
|
1212
|
+
sift_disp, disp_bounds_params["percentile"]
|
|
1213
|
+
)
|
|
1214
|
+
disp_max = np.percentile(
|
|
1215
|
+
sift_disp, 100 - disp_bounds_params["percentile"]
|
|
1216
|
+
)
|
|
1217
|
+
logging.info(
|
|
1218
|
+
"Global disparity interval without margin : "
|
|
1219
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1220
|
+
)
|
|
1221
|
+
disp_min -= (
|
|
1222
|
+
disp_bounds_params["upper_margin"] / disp_to_alt_ratio
|
|
1223
|
+
)
|
|
1224
|
+
disp_max += (
|
|
1225
|
+
disp_bounds_params["lower_margin"] / disp_to_alt_ratio
|
|
1226
|
+
)
|
|
1227
|
+
logging.info(
|
|
1228
|
+
"Global disparity interval with margin : "
|
|
1229
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1230
|
+
)
|
|
1231
|
+
else:
|
|
1232
|
+
disp_min = (
|
|
1233
|
+
-self.sparse_mtch_app.elevation_delta_upper_bound
|
|
1234
|
+
/ disp_to_alt_ratio
|
|
1235
|
+
)
|
|
1236
|
+
disp_max = (
|
|
1237
|
+
-self.sparse_mtch_app.elevation_delta_lower_bound
|
|
1238
|
+
/ disp_to_alt_ratio
|
|
1239
|
+
)
|
|
1240
|
+
logging.info(
|
|
1241
|
+
"Global disparity interval : "
|
|
1242
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
if self.epsg is None:
|
|
1246
|
+
# compute epsg
|
|
1247
|
+
# Epsg uses global disparity min and max
|
|
1248
|
+
self.epsg = preprocessing.compute_epsg(
|
|
1249
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1250
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1251
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1252
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1253
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1254
|
+
disp_min=0,
|
|
1255
|
+
disp_max=0,
|
|
1256
|
+
)
|
|
1257
|
+
# Compute roi polygon, in input EPSG
|
|
1258
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
1259
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
# Clean grids at the end of processing if required. Note that this will
|
|
1263
|
+
# also clean refined grids
|
|
1264
|
+
if not (save_corrected_grid or save_matches):
|
|
1265
|
+
self.cars_orchestrator.add_to_clean(
|
|
1266
|
+
os.path.join(self.dump_dir, "grid_correction")
|
|
1267
|
+
)
|
|
1268
|
+
# grids file are already cleaned in the application, but the tree
|
|
1269
|
+
# structure should also be cleaned
|
|
1270
|
+
if not save_corrected_grid:
|
|
1271
|
+
|
|
1272
|
+
self.cars_orchestrator.add_to_clean(
|
|
1273
|
+
os.path.join(self.dump_dir, "epipolar_grid_generation")
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
# quit if any app in the loop over the pairs was the last one
|
|
1277
|
+
if (
|
|
1278
|
+
self.quit_on_app("grid_generation")
|
|
1279
|
+
or self.quit_on_app("resampling")
|
|
1280
|
+
or self.quit_on_app("sparse_matching")
|
|
1281
|
+
):
|
|
1282
|
+
return True
|
|
1283
|
+
|
|
1284
|
+
if not self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
|
|
1285
|
+
# Use a priori
|
|
1286
|
+
dem_median = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
|
|
1287
|
+
adv_cst.DEM_MEDIAN
|
|
1288
|
+
]
|
|
1289
|
+
dem_min = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
|
|
1290
|
+
adv_cst.DEM_MIN
|
|
1291
|
+
]
|
|
1292
|
+
dem_max = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][
|
|
1293
|
+
adv_cst.DEM_MAX
|
|
1294
|
+
]
|
|
1295
|
+
|
|
1296
|
+
# Define param
|
|
1297
|
+
use_global_disp_range = self.dense_matching_app.use_global_disp_range
|
|
1298
|
+
|
|
1299
|
+
self.pairs_names = [
|
|
1300
|
+
pair_name for pair_name, _, _ in self.list_sensor_pairs
|
|
1301
|
+
]
|
|
1302
|
+
|
|
1303
|
+
for _, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
|
|
1304
|
+
# Geometry plugin with dem will be used for the grid generation
|
|
1305
|
+
geom_plugin = self.geom_plugin_with_dem_and_geoid
|
|
1306
|
+
|
|
1307
|
+
if self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
|
|
1308
|
+
save_matches = True
|
|
1309
|
+
|
|
1310
|
+
(
|
|
1311
|
+
self.pairs[pair_key]["sensor_matches_left"],
|
|
1312
|
+
self.pairs[pair_key]["sensor_matches_right"],
|
|
1313
|
+
) = geom_plugin.get_sensor_matches(
|
|
1314
|
+
self.pairs[pair_key]["corrected_matches_array"],
|
|
1315
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1316
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1317
|
+
pair_folder=os.path.join(
|
|
1318
|
+
self.out_dir, "dsm/sensor_matches", pair_key
|
|
1319
|
+
),
|
|
1320
|
+
save_matches=save_matches,
|
|
1321
|
+
)
|
|
1322
|
+
elif (
|
|
1323
|
+
not self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI]
|
|
1324
|
+
in (None, {})
|
|
1325
|
+
and not self.use_sift_a_priori
|
|
1326
|
+
):
|
|
1327
|
+
# Use epipolar a priori
|
|
1328
|
+
# load the disparity range
|
|
1329
|
+
if use_global_disp_range:
|
|
1330
|
+
[dmin, dmax] = self.used_conf[ADVANCED][
|
|
1331
|
+
adv_cst.EPIPOLAR_A_PRIORI
|
|
1332
|
+
][pair_key][adv_cst.DISPARITY_RANGE]
|
|
1333
|
+
|
|
1334
|
+
advanced_parameters.update_conf(
|
|
1335
|
+
self.refined_conf,
|
|
1336
|
+
dmin=dmin,
|
|
1337
|
+
dmax=dmax,
|
|
1338
|
+
pair_key=pair_key,
|
|
1339
|
+
)
|
|
1340
|
+
else:
|
|
1341
|
+
|
|
1342
|
+
# load the grid correction coefficient
|
|
1343
|
+
self.pairs[pair_key][
|
|
1344
|
+
"grid_correction_coef"
|
|
1345
|
+
] = self.used_conf[ADVANCED][adv_cst.EPIPOLAR_A_PRIORI][
|
|
1346
|
+
pair_key
|
|
1347
|
+
][
|
|
1348
|
+
adv_cst.GRID_CORRECTION
|
|
1349
|
+
]
|
|
1350
|
+
self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
|
|
1351
|
+
pair_key
|
|
1352
|
+
]["grid_left"]
|
|
1353
|
+
# no correction if the grid correction coefs are None
|
|
1354
|
+
if self.pairs[pair_key]["grid_correction_coef"] is None:
|
|
1355
|
+
self.pairs[pair_key]["corrected_grid_right"] = (
|
|
1356
|
+
self.pairs[pair_key]["grid_right"]
|
|
1357
|
+
)
|
|
1358
|
+
else:
|
|
1359
|
+
# Correct grid right with provided epipolar a priori
|
|
1360
|
+
self.pairs[pair_key]["corrected_grid_right"] = (
|
|
1361
|
+
grid_correction_app.correct_grid_from_1d(
|
|
1362
|
+
self.pairs[pair_key]["grid_right"],
|
|
1363
|
+
self.pairs[pair_key]["grid_correction_coef"],
|
|
1364
|
+
save_corrected_grid,
|
|
1365
|
+
os.path.join(
|
|
1366
|
+
self.dump_dir, "grid_correction", pair_key
|
|
1367
|
+
),
|
|
1368
|
+
)
|
|
1369
|
+
)
|
|
1370
|
+
else:
|
|
1371
|
+
# Correct grids with former matches
|
|
1372
|
+
# Transform matches to new grids
|
|
1373
|
+
|
|
1374
|
+
save_matches = self.sparse_mtch_app.get_save_matches()
|
|
1375
|
+
|
|
1376
|
+
self.sensor_matches_left = os.path.join(
|
|
1377
|
+
self.first_res_out_dir,
|
|
1378
|
+
"dsm/sensor_matches",
|
|
1379
|
+
pair_key,
|
|
1380
|
+
"sensor_matches_left.npy",
|
|
1381
|
+
)
|
|
1382
|
+
self.sensor_matches_right = os.path.join(
|
|
1383
|
+
self.first_res_out_dir,
|
|
1384
|
+
"dsm/sensor_matches",
|
|
1385
|
+
pair_key,
|
|
1386
|
+
"sensor_matches_right.npy",
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
self.pairs[pair_key]["sensor_matches_left"] = np.load(
|
|
1390
|
+
self.sensor_matches_left
|
|
1391
|
+
)
|
|
1392
|
+
self.pairs[pair_key]["sensor_matches_right"] = np.load(
|
|
1393
|
+
self.sensor_matches_right
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
new_grid_matches_array = (
|
|
1397
|
+
geom_plugin.transform_matches_from_grids(
|
|
1398
|
+
self.pairs[pair_key]["sensor_matches_left"],
|
|
1399
|
+
self.pairs[pair_key]["sensor_matches_right"],
|
|
1400
|
+
self.pairs[pair_key]["grid_left"],
|
|
1401
|
+
self.pairs[pair_key]["grid_right"],
|
|
1402
|
+
)
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
# Estimate grid_correction
|
|
1406
|
+
(
|
|
1407
|
+
self.pairs[pair_key]["grid_correction_coef"],
|
|
1408
|
+
self.pairs[pair_key]["corrected_matches_array"],
|
|
1409
|
+
_,
|
|
1410
|
+
_,
|
|
1411
|
+
) = grid_correction_app.estimate_right_grid_correction(
|
|
1412
|
+
new_grid_matches_array,
|
|
1413
|
+
self.pairs[pair_key]["grid_right"],
|
|
1414
|
+
save_matches=save_matches,
|
|
1415
|
+
minimum_nb_matches=minimum_nb_matches,
|
|
1416
|
+
pair_folder=os.path.join(
|
|
1417
|
+
self.dump_dir, "grid_correction", "new", pair_key
|
|
1418
|
+
),
|
|
1419
|
+
pair_key=pair_key,
|
|
1420
|
+
orchestrator=self.cars_orchestrator,
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
# Correct grid right
|
|
1424
|
+
|
|
1425
|
+
self.pairs[pair_key]["corrected_grid_right"] = (
|
|
1426
|
+
grid_correction_app.correct_grid(
|
|
1427
|
+
self.pairs[pair_key]["grid_right"],
|
|
1428
|
+
self.pairs[pair_key]["grid_correction_coef"],
|
|
1429
|
+
os.path.join(
|
|
1430
|
+
self.dump_dir,
|
|
1431
|
+
"grid_correction",
|
|
1432
|
+
"new",
|
|
1433
|
+
pair_key,
|
|
1434
|
+
),
|
|
1435
|
+
save_corrected_grid,
|
|
1436
|
+
)
|
|
1437
|
+
)
|
|
1438
|
+
|
|
1439
|
+
# Use the new grid as uncorrected grid
|
|
1440
|
+
self.pairs[pair_key]["corrected_grid_left"] = self.pairs[
|
|
1441
|
+
pair_key
|
|
1442
|
+
]["grid_left"]
|
|
1443
|
+
|
|
1444
|
+
# Run epipolar resampling
|
|
1445
|
+
self.pairs[pair_key]["corrected_grid_left"] = transform_grid_func(
|
|
1446
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1447
|
+
self.res_resamp,
|
|
1448
|
+
)
|
|
1449
|
+
|
|
1450
|
+
self.pairs[pair_key]["corrected_grid_right"] = transform_grid_func(
|
|
1451
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1452
|
+
self.res_resamp,
|
|
1453
|
+
right=True,
|
|
1454
|
+
)
|
|
1455
|
+
|
|
1456
|
+
# Update refined_conf configuration with epipolar a priori
|
|
1457
|
+
advanced_parameters.update_conf(
|
|
1458
|
+
self.refined_conf,
|
|
1459
|
+
grid_correction_coef=self.pairs[pair_key][
|
|
1460
|
+
"grid_correction_coef"
|
|
1461
|
+
],
|
|
1462
|
+
pair_key=pair_key,
|
|
1463
|
+
reference_dem=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
1464
|
+
sens_cst.DEM_PATH
|
|
1465
|
+
],
|
|
1466
|
+
)
|
|
1467
|
+
# saved used configuration
|
|
1468
|
+
self.save_configurations()
|
|
1469
|
+
|
|
1470
|
+
# Generate min and max disp grids
|
|
1471
|
+
# Global disparity min and max will be computed from
|
|
1472
|
+
# these grids
|
|
1473
|
+
dense_matching_pair_folder = os.path.join(
|
|
1474
|
+
self.dump_dir, "dense_matching", pair_key
|
|
1475
|
+
)
|
|
1476
|
+
|
|
1477
|
+
if self.which_resolution in ("first", "single") and self.used_conf[
|
|
1478
|
+
ADVANCED
|
|
1479
|
+
][adv_cst.TERRAIN_A_PRIORI] in (None, {}):
|
|
1480
|
+
dmin = disp_min / self.res_resamp
|
|
1481
|
+
dmax = disp_max / self.res_resamp
|
|
1482
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1483
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1484
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1485
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1486
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1487
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1488
|
+
dmin=dmin,
|
|
1489
|
+
dmax=dmax,
|
|
1490
|
+
pair_folder=dense_matching_pair_folder,
|
|
1491
|
+
orchestrator=self.cars_orchestrator,
|
|
1492
|
+
)
|
|
1493
|
+
)
|
|
1494
|
+
|
|
1495
|
+
dsp_marg = self.sparse_mtch_app.get_disparity_margin()
|
|
1496
|
+
updating_infos = {
|
|
1497
|
+
application_constants.APPLICATION_TAG: {
|
|
1498
|
+
sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
|
|
1499
|
+
pair_key: {
|
|
1500
|
+
sm_cst.DISPARITY_MARGIN_PARAM_TAG: dsp_marg,
|
|
1501
|
+
sm_cst.MINIMUM_DISPARITY_TAG: dmin,
|
|
1502
|
+
sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
self.cars_orchestrator.update_out_info(updating_infos)
|
|
1508
|
+
|
|
1509
|
+
advanced_parameters.update_conf(
|
|
1510
|
+
self.refined_conf,
|
|
1511
|
+
dmin=dmin,
|
|
1512
|
+
dmax=dmax,
|
|
1513
|
+
pair_key=pair_key,
|
|
1514
|
+
)
|
|
1515
|
+
else:
|
|
1516
|
+
# Generate min and max disp grids from dems
|
|
1517
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1518
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1519
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1520
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1521
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1522
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1523
|
+
dem_min=dem_min,
|
|
1524
|
+
dem_max=dem_max,
|
|
1525
|
+
dem_median=dem_median,
|
|
1526
|
+
pair_folder=dense_matching_pair_folder,
|
|
1527
|
+
orchestrator=self.cars_orchestrator,
|
|
1528
|
+
)
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
if use_global_disp_range:
|
|
1532
|
+
# Generate min and max disp grids from constants
|
|
1533
|
+
# sensor image is not used here
|
|
1534
|
+
# TODO remove when only local diparity range will be used
|
|
1535
|
+
|
|
1536
|
+
if self.use_sift_a_priori:
|
|
1537
|
+
dmin = self.pairs[pair_key]["disp_range_grid"][
|
|
1538
|
+
"global_min"
|
|
1539
|
+
]
|
|
1540
|
+
dmax = self.pairs[pair_key]["disp_range_grid"][
|
|
1541
|
+
"global_max"
|
|
1542
|
+
]
|
|
1543
|
+
|
|
1544
|
+
# update orchestrator_out_json
|
|
1545
|
+
marg = self.sparse_mtch_app.get_disparity_margin()
|
|
1546
|
+
updating_infos = {
|
|
1547
|
+
application_constants.APPLICATION_TAG: {
|
|
1548
|
+
sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
|
|
1549
|
+
pair_key: {
|
|
1550
|
+
sm_cst.DISPARITY_MARGIN_PARAM_TAG: marg,
|
|
1551
|
+
sm_cst.MINIMUM_DISPARITY_TAG: dmin,
|
|
1552
|
+
sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
self.cars_orchestrator.update_out_info(updating_infos)
|
|
1558
|
+
|
|
1559
|
+
advanced_parameters.update_conf(
|
|
1560
|
+
self.refined_conf,
|
|
1561
|
+
dmin=dmin,
|
|
1562
|
+
dmax=dmax,
|
|
1563
|
+
pair_key=pair_key,
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1567
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1568
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1569
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1570
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1571
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1572
|
+
dmin=dmin,
|
|
1573
|
+
dmax=dmax,
|
|
1574
|
+
pair_folder=dense_matching_pair_folder,
|
|
1575
|
+
orchestrator=self.cars_orchestrator,
|
|
1576
|
+
)
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
# TODO add in metadata.json max diff max - min
|
|
1580
|
+
# Update used_conf configuration with epipolar a priori
|
|
1581
|
+
# Add global min and max computed with grids
|
|
1582
|
+
advanced_parameters.update_conf(
|
|
1583
|
+
self.refined_conf,
|
|
1584
|
+
dmin=self.pairs[pair_key]["disp_range_grid"]["global_min"],
|
|
1585
|
+
dmax=self.pairs[pair_key]["disp_range_grid"]["global_max"],
|
|
1586
|
+
pair_key=pair_key,
|
|
1587
|
+
)
|
|
1588
|
+
advanced_parameters.update_conf(
|
|
1589
|
+
self.refined_conf,
|
|
1590
|
+
dmin=self.pairs[pair_key]["disp_range_grid"]["global_min"],
|
|
1591
|
+
dmax=self.pairs[pair_key]["disp_range_grid"]["global_max"],
|
|
1592
|
+
pair_key=pair_key,
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
# saved used configuration
|
|
1596
|
+
self.save_configurations()
|
|
1597
|
+
|
|
1598
|
+
# end of for loop, to finish computing disparity range grids
|
|
1599
|
+
|
|
1600
|
+
for cloud_id, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
|
|
1601
|
+
|
|
1602
|
+
# Generate roi
|
|
1603
|
+
epipolar_roi = preprocessing.compute_epipolar_roi(
|
|
1604
|
+
self.input_roi_poly,
|
|
1605
|
+
self.input_roi_epsg,
|
|
1606
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1607
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1608
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1609
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1610
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1611
|
+
os.path.join(self.dump_dir, "compute_epipolar_roi", pair_key),
|
|
1612
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"]["global_min"],
|
|
1613
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"]["global_max"],
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
# Generate new epipolar images
|
|
1617
|
+
# Generated with corrected grids
|
|
1618
|
+
# Optimal size is computed for the worst case scenario
|
|
1619
|
+
# found with epipolar disparity range grids
|
|
1620
|
+
|
|
1621
|
+
(
|
|
1622
|
+
optimum_tile_size,
|
|
1623
|
+
local_tile_optimal_size_fun,
|
|
1624
|
+
) = self.dense_matching_app.get_optimal_tile_size(
|
|
1625
|
+
self.pairs[pair_key]["disp_range_grid"],
|
|
1626
|
+
self.cars_orchestrator.cluster.checked_conf_cluster[
|
|
1627
|
+
"max_ram_per_worker"
|
|
1628
|
+
],
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
# Get required bands of third resampling
|
|
1632
|
+
required_bands = self.dense_matching_app.get_required_bands()
|
|
1633
|
+
|
|
1634
|
+
# Add left required bands for texture
|
|
1635
|
+
required_bands["left"] = sorted(
|
|
1636
|
+
set(required_bands["left"]).union(set(self.texture_bands))
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
# Find index of texture band in left_dataset
|
|
1640
|
+
texture_bands_indices = [
|
|
1641
|
+
required_bands["left"].index(band)
|
|
1642
|
+
for band in self.texture_bands
|
|
1643
|
+
]
|
|
1644
|
+
|
|
1645
|
+
# Get margins used in dense matching,
|
|
1646
|
+
dense_matching_margins_fun = (
|
|
1647
|
+
self.dense_matching_app.get_margins_fun(
|
|
1648
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1649
|
+
self.pairs[pair_key]["disp_range_grid"],
|
|
1650
|
+
)
|
|
1651
|
+
)
|
|
1652
|
+
|
|
1653
|
+
# Quick fix to reduce memory usage
|
|
1654
|
+
if self.res_resamp >= 16:
|
|
1655
|
+
optimum_tile_size = 200
|
|
1656
|
+
|
|
1657
|
+
# Run third epipolar resampling
|
|
1658
|
+
(
|
|
1659
|
+
new_epipolar_image_left,
|
|
1660
|
+
new_epipolar_image_right,
|
|
1661
|
+
) = self.resampling_application.run(
|
|
1662
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1663
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1664
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1665
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1666
|
+
geom_plugin,
|
|
1667
|
+
orchestrator=self.cars_orchestrator,
|
|
1668
|
+
pair_folder=os.path.join(
|
|
1669
|
+
self.dump_dir, "resampling", "corrected_grid", pair_key
|
|
1670
|
+
),
|
|
1671
|
+
pair_key=pair_key,
|
|
1672
|
+
margins_fun=dense_matching_margins_fun,
|
|
1673
|
+
tile_width=optimum_tile_size,
|
|
1674
|
+
tile_height=optimum_tile_size,
|
|
1675
|
+
add_classif=True,
|
|
1676
|
+
epipolar_roi=epipolar_roi,
|
|
1677
|
+
resolution=self.res_resamp,
|
|
1678
|
+
required_bands=required_bands,
|
|
1679
|
+
texture_bands=self.texture_bands,
|
|
1680
|
+
)
|
|
1681
|
+
|
|
1682
|
+
# Run ground truth dsm computation
|
|
1683
|
+
if self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
|
|
1684
|
+
self.used_conf["applications"]["ground_truth_reprojection"][
|
|
1685
|
+
"save_intermediate_data"
|
|
1686
|
+
] = True
|
|
1687
|
+
new_geomplugin_dsm = AbstractGeometry( # pylint: disable=E0110
|
|
1688
|
+
self.geometry_plugin,
|
|
1689
|
+
dem=self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
|
|
1690
|
+
adv_cst.INPUT_GROUND_TRUTH_DSM
|
|
1691
|
+
],
|
|
1692
|
+
geoid=self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
|
|
1693
|
+
adv_cst.INPUT_GEOID
|
|
1694
|
+
],
|
|
1695
|
+
scaling_coeff=self.scaling_coeff,
|
|
1696
|
+
)
|
|
1697
|
+
self.ground_truth_reprojection.run(
|
|
1698
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1699
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1700
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1701
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1702
|
+
new_geomplugin_dsm,
|
|
1703
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1704
|
+
self.pairs[pair_key]["corrected_grid_left"][
|
|
1705
|
+
"disp_to_alt_ratio"
|
|
1706
|
+
],
|
|
1707
|
+
self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
|
|
1708
|
+
adv_cst.INPUT_AUX_PATH
|
|
1709
|
+
],
|
|
1710
|
+
self.used_conf[ADVANCED][adv_cst.GROUND_TRUTH_DSM][
|
|
1711
|
+
adv_cst.INPUT_AUX_INTERP
|
|
1712
|
+
],
|
|
1713
|
+
orchestrator=self.cars_orchestrator,
|
|
1714
|
+
pair_folder=os.path.join(
|
|
1715
|
+
self.dump_dir, "ground_truth_reprojection", pair_key
|
|
1716
|
+
),
|
|
1717
|
+
)
|
|
1718
|
+
|
|
1719
|
+
if self.epsg is None:
|
|
1720
|
+
# compute epsg
|
|
1721
|
+
# Epsg uses global disparity min and max
|
|
1722
|
+
self.epsg = preprocessing.compute_epsg(
|
|
1723
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1724
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1725
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1726
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1727
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1728
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"][
|
|
1729
|
+
"global_min"
|
|
1730
|
+
],
|
|
1731
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"][
|
|
1732
|
+
"global_max"
|
|
1733
|
+
],
|
|
1734
|
+
)
|
|
1735
|
+
# Compute roi polygon, in input EPSG
|
|
1736
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
1737
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
1738
|
+
)
|
|
1739
|
+
|
|
1740
|
+
self.vertical_crs = projection.get_output_crs(self.epsg, output)
|
|
1741
|
+
|
|
1742
|
+
if (
|
|
1743
|
+
self.save_output_dsm
|
|
1744
|
+
or self.save_output_point_cloud
|
|
1745
|
+
or self.dense_matching_app.get_method() == "auto"
|
|
1746
|
+
):
|
|
1747
|
+
# Compute terrain bounding box /roi related to
|
|
1748
|
+
# current images
|
|
1749
|
+
(current_terrain_roi_bbox, intersection_poly) = (
|
|
1750
|
+
preprocessing.compute_terrain_bbox(
|
|
1751
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1752
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1753
|
+
new_epipolar_image_left,
|
|
1754
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1755
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1756
|
+
self.epsg,
|
|
1757
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1758
|
+
resolution=self.resolution,
|
|
1759
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"][
|
|
1760
|
+
"global_min"
|
|
1761
|
+
],
|
|
1762
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"][
|
|
1763
|
+
"global_max"
|
|
1764
|
+
],
|
|
1765
|
+
roi_poly=(
|
|
1766
|
+
None if self.debug_with_roi else self.roi_poly
|
|
1767
|
+
),
|
|
1768
|
+
orchestrator=self.cars_orchestrator,
|
|
1769
|
+
pair_key=pair_key,
|
|
1770
|
+
pair_folder=os.path.join(
|
|
1771
|
+
self.dump_dir, "terrain_bbox", pair_key
|
|
1772
|
+
),
|
|
1773
|
+
check_inputs=False,
|
|
1774
|
+
)
|
|
1775
|
+
)
|
|
1776
|
+
self.list_terrain_roi.append(current_terrain_roi_bbox)
|
|
1777
|
+
self.list_intersection_poly.append(intersection_poly)
|
|
1778
|
+
|
|
1779
|
+
# compute terrain bounds for later use
|
|
1780
|
+
(
|
|
1781
|
+
self.terrain_bounds,
|
|
1782
|
+
self.optimal_terrain_tile_width,
|
|
1783
|
+
) = preprocessing.compute_terrain_bounds(
|
|
1784
|
+
self.list_terrain_roi,
|
|
1785
|
+
roi_poly=(None if self.debug_with_roi else self.roi_poly),
|
|
1786
|
+
resolution=self.resolution,
|
|
1787
|
+
)
|
|
1788
|
+
|
|
1789
|
+
if self.which_resolution not in ("final", "single"):
|
|
1790
|
+
self.terrain_bounds = dem_wrappers.modify_terrain_bounds(
|
|
1791
|
+
self.terrain_bounds,
|
|
1792
|
+
self.dem_generation_application.margin[0],
|
|
1793
|
+
self.dem_generation_application.margin[1],
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
if self.dense_matching_app.get_method() == "auto":
|
|
1797
|
+
# Copy the initial corr_config in order to keep
|
|
1798
|
+
# the inputs that have already been checked
|
|
1799
|
+
corr_cfg = self.dense_matching_app.corr_config.copy()
|
|
1800
|
+
|
|
1801
|
+
# Find the conf that correspond to the land cover map
|
|
1802
|
+
conf = self.dense_matching_app.loader.find_auto_conf(
|
|
1803
|
+
intersection_poly,
|
|
1804
|
+
self.land_cover_map,
|
|
1805
|
+
self.classification_to_config_mapping,
|
|
1806
|
+
self.epsg,
|
|
1807
|
+
)
|
|
1808
|
+
|
|
1809
|
+
# Update the used_conf if order to reinitialize
|
|
1810
|
+
# the dense matching app
|
|
1811
|
+
# Because we kept the information regarding the ambiguity,
|
|
1812
|
+
# performance_map calculus..
|
|
1813
|
+
self.used_conf["applications"]["dense_matching"][
|
|
1814
|
+
"loader_conf"
|
|
1815
|
+
] = conf
|
|
1816
|
+
self.used_conf["applications"]["dense_matching"][
|
|
1817
|
+
"method"
|
|
1818
|
+
] = "custom"
|
|
1819
|
+
|
|
1820
|
+
# Re initialization of the dense matching application
|
|
1821
|
+
self.dense_matching_app = Application(
|
|
1822
|
+
"dense_matching",
|
|
1823
|
+
cfg=self.used_conf["applications"]["dense_matching"],
|
|
1824
|
+
)
|
|
1825
|
+
|
|
1826
|
+
# Update the corr_config with the inputs that have
|
|
1827
|
+
# already been checked
|
|
1828
|
+
self.dense_matching_app.corr_config["input"] = corr_cfg["input"]
|
|
1829
|
+
|
|
1830
|
+
# Run epipolar matching application
|
|
1831
|
+
epipolar_disparity_map = self.dense_matching_app.run(
|
|
1832
|
+
new_epipolar_image_left,
|
|
1833
|
+
new_epipolar_image_right,
|
|
1834
|
+
local_tile_optimal_size_fun,
|
|
1835
|
+
orchestrator=self.cars_orchestrator,
|
|
1836
|
+
pair_folder=os.path.join(
|
|
1837
|
+
self.dump_dir, "dense_matching", pair_key
|
|
1838
|
+
),
|
|
1839
|
+
pair_key=pair_key,
|
|
1840
|
+
disp_range_grid=self.pairs[pair_key]["disp_range_grid"],
|
|
1841
|
+
compute_disparity_masks=False,
|
|
1842
|
+
margins_to_keep=sum(
|
|
1843
|
+
app.get_epipolar_margin()
|
|
1844
|
+
for _, app in self.pc_outlier_removal_apps.items()
|
|
1845
|
+
),
|
|
1846
|
+
texture_bands=texture_bands_indices,
|
|
1847
|
+
classif_bands_to_mask=self.used_classif_values_for_filling,
|
|
1848
|
+
)
|
|
1849
|
+
|
|
1850
|
+
if self.quit_on_app("dense_matching"):
|
|
1851
|
+
continue # keep iterating over pairs, but don't go further
|
|
1852
|
+
|
|
1853
|
+
# Fill with zeros
|
|
1854
|
+
(filled_epipolar_disparity_map) = self.dense_match_filling.run(
|
|
1855
|
+
epipolar_disparity_map,
|
|
1856
|
+
orchestrator=self.cars_orchestrator,
|
|
1857
|
+
pair_folder=os.path.join(
|
|
1858
|
+
self.dump_dir, "dense_match_filling", pair_key
|
|
1859
|
+
),
|
|
1860
|
+
pair_key=pair_key,
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
if self.quit_on_app("dense_match_filling"):
|
|
1864
|
+
continue # keep iterating over pairs, but don't go further
|
|
1865
|
+
|
|
1866
|
+
if isinstance(output[sens_cst.GEOID], str):
|
|
1867
|
+
output_geoid_path = output[sens_cst.GEOID]
|
|
1868
|
+
elif (
|
|
1869
|
+
isinstance(output[sens_cst.GEOID], bool)
|
|
1870
|
+
and output[sens_cst.GEOID]
|
|
1871
|
+
):
|
|
1872
|
+
package_path = os.path.dirname(__file__)
|
|
1873
|
+
output_geoid_path = os.path.join(
|
|
1874
|
+
package_path,
|
|
1875
|
+
"..",
|
|
1876
|
+
"..",
|
|
1877
|
+
"conf",
|
|
1878
|
+
sensor_inputs.CARS_GEOID_PATH,
|
|
1879
|
+
)
|
|
1880
|
+
else:
|
|
1881
|
+
# default case : stay on the ellipsoid
|
|
1882
|
+
output_geoid_path = None
|
|
1883
|
+
|
|
1884
|
+
depth_map_dir = None
|
|
1885
|
+
if self.save_output_depth_map:
|
|
1886
|
+
depth_map_dir = os.path.join(
|
|
1887
|
+
self.out_dir, "depth_map", pair_key
|
|
1888
|
+
)
|
|
1889
|
+
safe_makedirs(depth_map_dir)
|
|
1890
|
+
|
|
1891
|
+
point_cloud_dir = None
|
|
1892
|
+
if self.save_output_point_cloud:
|
|
1893
|
+
point_cloud_dir = os.path.join(
|
|
1894
|
+
self.out_dir, "point_cloud", pair_key
|
|
1895
|
+
)
|
|
1896
|
+
safe_makedirs(point_cloud_dir)
|
|
1897
|
+
|
|
1898
|
+
triangulation_point_cloud_dir = (
|
|
1899
|
+
point_cloud_dir
|
|
1900
|
+
if (point_cloud_dir and len(self.pc_outlier_removal_apps) == 0)
|
|
1901
|
+
else None
|
|
1902
|
+
)
|
|
1903
|
+
|
|
1904
|
+
# Run epipolar triangulation application
|
|
1905
|
+
epipolar_point_cloud = self.triangulation_application.run(
|
|
1906
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1907
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1908
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1909
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1910
|
+
filled_epipolar_disparity_map,
|
|
1911
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
1912
|
+
new_epipolar_image_left,
|
|
1913
|
+
epsg=self.epsg,
|
|
1914
|
+
denoising_overload_fun=None,
|
|
1915
|
+
source_pc_names=self.pairs_names,
|
|
1916
|
+
orchestrator=self.cars_orchestrator,
|
|
1917
|
+
pair_dump_dir=os.path.join(
|
|
1918
|
+
self.dump_dir, "triangulation", pair_key
|
|
1919
|
+
),
|
|
1920
|
+
pair_key=pair_key,
|
|
1921
|
+
uncorrected_grid_right=self.pairs[pair_key]["grid_right"],
|
|
1922
|
+
geoid_path=output_geoid_path,
|
|
1923
|
+
cloud_id=cloud_id,
|
|
1924
|
+
performance_maps_param=(
|
|
1925
|
+
self.dense_matching_app.get_performance_map_parameters()
|
|
1926
|
+
),
|
|
1927
|
+
depth_map_dir=depth_map_dir,
|
|
1928
|
+
point_cloud_dir=triangulation_point_cloud_dir,
|
|
1929
|
+
save_output_coordinates=(len(self.pc_outlier_removal_apps) == 0)
|
|
1930
|
+
and (
|
|
1931
|
+
self.save_output_depth_map or self.save_output_point_cloud
|
|
1932
|
+
),
|
|
1933
|
+
save_output_color=bool(depth_map_dir)
|
|
1934
|
+
and self.auxiliary[out_cst.AUX_IMAGE],
|
|
1935
|
+
save_output_classification=bool(depth_map_dir)
|
|
1936
|
+
and self.auxiliary[out_cst.AUX_CLASSIFICATION],
|
|
1937
|
+
save_output_filling=bool(depth_map_dir)
|
|
1938
|
+
and self.auxiliary[out_cst.AUX_FILLING],
|
|
1939
|
+
save_output_performance_map=bool(depth_map_dir)
|
|
1940
|
+
and self.auxiliary[out_cst.AUX_PERFORMANCE_MAP],
|
|
1941
|
+
save_output_ambiguity=bool(depth_map_dir)
|
|
1942
|
+
and self.auxiliary[out_cst.AUX_AMBIGUITY],
|
|
1943
|
+
)
|
|
1944
|
+
|
|
1945
|
+
if self.quit_on_app("triangulation"):
|
|
1946
|
+
continue # keep iterating over pairs, but don't go further
|
|
1947
|
+
|
|
1948
|
+
filtered_epipolar_point_cloud = epipolar_point_cloud
|
|
1949
|
+
for app_key, app in self.pc_outlier_removal_apps.items():
|
|
1950
|
+
|
|
1951
|
+
app_key_is_last = (
|
|
1952
|
+
app_key == list(self.pc_outlier_removal_apps)[-1]
|
|
1953
|
+
)
|
|
1954
|
+
filtering_depth_map_dir = (
|
|
1955
|
+
depth_map_dir if app_key_is_last else None
|
|
1956
|
+
)
|
|
1957
|
+
filtering_point_cloud_dir = (
|
|
1958
|
+
point_cloud_dir if app_key_is_last else None
|
|
1959
|
+
)
|
|
1960
|
+
|
|
1961
|
+
filtered_epipolar_point_cloud = app.run(
|
|
1962
|
+
filtered_epipolar_point_cloud,
|
|
1963
|
+
depth_map_dir=filtering_depth_map_dir,
|
|
1964
|
+
point_cloud_dir=filtering_point_cloud_dir,
|
|
1965
|
+
dump_dir=os.path.join(
|
|
1966
|
+
self.dump_dir,
|
|
1967
|
+
( # pylint: disable=inconsistent-quotes
|
|
1968
|
+
f"pc_outlier_removal"
|
|
1969
|
+
f"{str(app_key[27:]).replace('.', '_')}"
|
|
1970
|
+
),
|
|
1971
|
+
pair_key,
|
|
1972
|
+
),
|
|
1973
|
+
epsg=self.epsg,
|
|
1974
|
+
orchestrator=self.cars_orchestrator,
|
|
1975
|
+
)
|
|
1976
|
+
if self.quit_on_app("point_cloud_outlier_removal"):
|
|
1977
|
+
continue # keep iterating over pairs, but don't go further
|
|
1978
|
+
|
|
1979
|
+
self.list_epipolar_point_clouds.append(
|
|
1980
|
+
filtered_epipolar_point_cloud
|
|
1981
|
+
)
|
|
1982
|
+
|
|
1983
|
+
# quit if any app in the loop over the pairs was the last one
|
|
1984
|
+
# pylint:disable=too-many-boolean-expressions
|
|
1985
|
+
if (
|
|
1986
|
+
self.quit_on_app("dense_matching")
|
|
1987
|
+
or self.quit_on_app("dense_match_filling")
|
|
1988
|
+
or self.quit_on_app("triangulation")
|
|
1989
|
+
or self.quit_on_app("point_cloud_outlier_removal.1")
|
|
1990
|
+
or self.quit_on_app("point_cloud_outlier_removal.2")
|
|
1991
|
+
):
|
|
1992
|
+
return True
|
|
1993
|
+
|
|
1994
|
+
return False
|
|
1995
|
+
|
|
1996
|
+
def rasterize_point_cloud(self):
|
|
1997
|
+
"""
|
|
1998
|
+
Final step of the pipeline: rasterize the point
|
|
1999
|
+
cloud created in the prior steps.
|
|
2000
|
+
"""
|
|
2001
|
+
|
|
2002
|
+
self.rasterization_dump_dir = os.path.join(
|
|
2003
|
+
self.dump_dir, "rasterization"
|
|
2004
|
+
)
|
|
2005
|
+
|
|
2006
|
+
dsm_file_name = (
|
|
2007
|
+
os.path.join(
|
|
2008
|
+
self.out_dir,
|
|
2009
|
+
out_cst.DSM_DIRECTORY,
|
|
2010
|
+
"dsm.tif",
|
|
2011
|
+
)
|
|
2012
|
+
if self.save_output_dsm
|
|
2013
|
+
else None
|
|
2014
|
+
)
|
|
2015
|
+
|
|
2016
|
+
weights_file_name = (
|
|
2017
|
+
os.path.join(
|
|
2018
|
+
self.out_dir,
|
|
2019
|
+
out_cst.DSM_DIRECTORY,
|
|
2020
|
+
"weights.tif",
|
|
2021
|
+
)
|
|
2022
|
+
if self.save_output_dsm
|
|
2023
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_WEIGHTS]
|
|
2024
|
+
else None
|
|
2025
|
+
)
|
|
2026
|
+
|
|
2027
|
+
color_file_name = (
|
|
2028
|
+
os.path.join(
|
|
2029
|
+
self.out_dir,
|
|
2030
|
+
out_cst.DSM_DIRECTORY,
|
|
2031
|
+
"image.tif",
|
|
2032
|
+
)
|
|
2033
|
+
if self.save_output_dsm
|
|
2034
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
|
|
2035
|
+
else None
|
|
2036
|
+
)
|
|
2037
|
+
|
|
2038
|
+
performance_map_file_name = (
|
|
2039
|
+
os.path.join(
|
|
2040
|
+
self.out_dir,
|
|
2041
|
+
out_cst.DSM_DIRECTORY,
|
|
2042
|
+
"performance_map.tif",
|
|
2043
|
+
)
|
|
2044
|
+
if self.save_output_dsm
|
|
2045
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2046
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
2047
|
+
]
|
|
2048
|
+
else None
|
|
2049
|
+
)
|
|
2050
|
+
|
|
2051
|
+
ambiguity_file_name = (
|
|
2052
|
+
os.path.join(
|
|
2053
|
+
self.out_dir,
|
|
2054
|
+
out_cst.DSM_DIRECTORY,
|
|
2055
|
+
"ambiguity.tif",
|
|
2056
|
+
)
|
|
2057
|
+
if self.save_output_dsm
|
|
2058
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_AMBIGUITY]
|
|
2059
|
+
else None
|
|
2060
|
+
)
|
|
2061
|
+
|
|
2062
|
+
classif_file_name = (
|
|
2063
|
+
os.path.join(
|
|
2064
|
+
self.out_dir,
|
|
2065
|
+
out_cst.DSM_DIRECTORY,
|
|
2066
|
+
"classification.tif",
|
|
2067
|
+
)
|
|
2068
|
+
if self.save_output_dsm
|
|
2069
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2070
|
+
out_cst.AUX_CLASSIFICATION
|
|
2071
|
+
]
|
|
2072
|
+
else None
|
|
2073
|
+
)
|
|
2074
|
+
|
|
2075
|
+
if classif_file_name is None and self.save_output_classif_for_filling:
|
|
2076
|
+
classif_file_name = os.path.join(
|
|
2077
|
+
self.rasterization_dump_dir,
|
|
2078
|
+
"classification_for_filling.tif",
|
|
2079
|
+
)
|
|
2080
|
+
|
|
2081
|
+
contributing_pair_file_name = (
|
|
2082
|
+
os.path.join(
|
|
2083
|
+
self.out_dir,
|
|
2084
|
+
out_cst.DSM_DIRECTORY,
|
|
2085
|
+
"contributing_pair.tif",
|
|
2086
|
+
)
|
|
2087
|
+
if self.save_output_dsm
|
|
2088
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2089
|
+
out_cst.AUX_CONTRIBUTING_PAIR
|
|
2090
|
+
]
|
|
2091
|
+
else None
|
|
2092
|
+
)
|
|
2093
|
+
|
|
2094
|
+
filling_file_name = (
|
|
2095
|
+
os.path.join(
|
|
2096
|
+
self.out_dir,
|
|
2097
|
+
out_cst.DSM_DIRECTORY,
|
|
2098
|
+
"filling.tif",
|
|
2099
|
+
)
|
|
2100
|
+
if self.save_output_dsm
|
|
2101
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
|
|
2102
|
+
else None
|
|
2103
|
+
)
|
|
2104
|
+
|
|
2105
|
+
# rasterize point cloud
|
|
2106
|
+
_ = self.rasterization_application.run(
|
|
2107
|
+
self.point_cloud_to_rasterize,
|
|
2108
|
+
self.epsg,
|
|
2109
|
+
self.vertical_crs,
|
|
2110
|
+
resolution=self.resolution,
|
|
2111
|
+
orchestrator=self.cars_orchestrator,
|
|
2112
|
+
dsm_file_name=dsm_file_name,
|
|
2113
|
+
weights_file_name=weights_file_name,
|
|
2114
|
+
color_file_name=color_file_name,
|
|
2115
|
+
classif_file_name=classif_file_name,
|
|
2116
|
+
performance_map_file_name=performance_map_file_name,
|
|
2117
|
+
ambiguity_file_name=ambiguity_file_name,
|
|
2118
|
+
contributing_pair_file_name=contributing_pair_file_name,
|
|
2119
|
+
filling_file_name=filling_file_name,
|
|
2120
|
+
color_dtype=self.color_type,
|
|
2121
|
+
dump_dir=self.rasterization_dump_dir,
|
|
2122
|
+
performance_map_classes=self.used_conf[OUTPUT][AUXILIARY][
|
|
2123
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
2124
|
+
],
|
|
2125
|
+
phasing=self.phasing,
|
|
2126
|
+
)
|
|
2127
|
+
|
|
2128
|
+
# Cleaning: don't keep terrain bbox if save_intermediate_data
|
|
2129
|
+
# is not activated
|
|
2130
|
+
if not self.used_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA]:
|
|
2131
|
+
self.cars_orchestrator.add_to_clean(
|
|
2132
|
+
os.path.join(self.dump_dir, "terrain_bbox")
|
|
2133
|
+
)
|
|
2134
|
+
|
|
2135
|
+
if self.quit_on_app("point_cloud_rasterization"):
|
|
2136
|
+
return True
|
|
2137
|
+
|
|
2138
|
+
# dsm needs to be saved before filling
|
|
2139
|
+
self.cars_orchestrator.breakpoint()
|
|
2140
|
+
|
|
2141
|
+
if self.generate_dems:
|
|
2142
|
+
dsm_file_name = (
|
|
2143
|
+
os.path.join(
|
|
2144
|
+
self.out_dir,
|
|
2145
|
+
out_cst.DSM_DIRECTORY,
|
|
2146
|
+
"dsm.tif",
|
|
2147
|
+
)
|
|
2148
|
+
if self.save_output_dsm
|
|
2149
|
+
else None
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2152
|
+
dem_min_file_name = (
|
|
2153
|
+
os.path.join(
|
|
2154
|
+
self.out_dir,
|
|
2155
|
+
out_cst.DSM_DIRECTORY,
|
|
2156
|
+
"dem_min.tif",
|
|
2157
|
+
)
|
|
2158
|
+
if self.save_output_dsm
|
|
2159
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2160
|
+
out_cst.AUX_DEM_MIN
|
|
2161
|
+
]
|
|
2162
|
+
else None
|
|
2163
|
+
)
|
|
2164
|
+
|
|
2165
|
+
dem_max_file_name = (
|
|
2166
|
+
os.path.join(
|
|
2167
|
+
self.out_dir,
|
|
2168
|
+
out_cst.DSM_DIRECTORY,
|
|
2169
|
+
"dem_max.tif",
|
|
2170
|
+
)
|
|
2171
|
+
if self.save_output_dsm
|
|
2172
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2173
|
+
out_cst.AUX_DEM_MAX
|
|
2174
|
+
]
|
|
2175
|
+
else None
|
|
2176
|
+
)
|
|
2177
|
+
|
|
2178
|
+
dem_median_file_name = (
|
|
2179
|
+
os.path.join(
|
|
2180
|
+
self.out_dir,
|
|
2181
|
+
out_cst.DSM_DIRECTORY,
|
|
2182
|
+
"dem_median.tif",
|
|
2183
|
+
)
|
|
2184
|
+
if self.save_output_dsm
|
|
2185
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2186
|
+
out_cst.AUX_DEM_MEDIAN
|
|
2187
|
+
]
|
|
2188
|
+
else None
|
|
2189
|
+
)
|
|
2190
|
+
|
|
2191
|
+
dem_generation_output_dir = os.path.join(
|
|
2192
|
+
self.dump_dir, "dem_generation"
|
|
2193
|
+
)
|
|
2194
|
+
safe_makedirs(dem_generation_output_dir)
|
|
2195
|
+
if not self.dem_generation_application.used_config[
|
|
2196
|
+
"save_intermediate_data"
|
|
2197
|
+
]:
|
|
2198
|
+
self.cars_orchestrator.add_to_clean(dem_generation_output_dir)
|
|
2199
|
+
|
|
2200
|
+
# Use initial elevation if provided, and generate dems
|
|
2201
|
+
_, paths, _ = self.dem_generation_application.run(
|
|
2202
|
+
dsm_file_name,
|
|
2203
|
+
dem_generation_output_dir,
|
|
2204
|
+
dem_min_file_name,
|
|
2205
|
+
dem_max_file_name,
|
|
2206
|
+
dem_median_file_name,
|
|
2207
|
+
input_geoid=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
2208
|
+
sens_cst.GEOID
|
|
2209
|
+
],
|
|
2210
|
+
output_geoid=self.used_conf[OUTPUT][out_cst.OUT_GEOID],
|
|
2211
|
+
initial_elevation=(
|
|
2212
|
+
self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
2213
|
+
sens_cst.DEM_PATH
|
|
2214
|
+
]
|
|
2215
|
+
),
|
|
2216
|
+
default_alt=self.geom_plugin_with_dem_and_geoid.default_alt,
|
|
2217
|
+
cars_orchestrator=self.cars_orchestrator,
|
|
2218
|
+
)
|
|
2219
|
+
|
|
2220
|
+
# Update refined conf configuration with dem paths
|
|
2221
|
+
dem_median = paths["dem_median"]
|
|
2222
|
+
dem_min = paths["dem_min"]
|
|
2223
|
+
dem_max = paths["dem_max"]
|
|
2224
|
+
|
|
2225
|
+
advanced_parameters.update_conf(
|
|
2226
|
+
self.refined_conf,
|
|
2227
|
+
dem_median=dem_median,
|
|
2228
|
+
dem_min=dem_min,
|
|
2229
|
+
dem_max=dem_max,
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
if self.used_conf[ADVANCED][USE_ENDOGENOUS_DEM]:
|
|
2233
|
+
# Generate new geom plugin with dem
|
|
2234
|
+
output_dem_dir = os.path.join(
|
|
2235
|
+
self.dump_dir, "initial_elevation"
|
|
2236
|
+
)
|
|
2237
|
+
new_geom_plugin = (
|
|
2238
|
+
sensor_inputs.generate_geometry_plugin_with_dem(
|
|
2239
|
+
self.geometry_plugin,
|
|
2240
|
+
self.used_conf[INPUT],
|
|
2241
|
+
dem=dem_median,
|
|
2242
|
+
output_dem_dir=output_dem_dir,
|
|
2243
|
+
)
|
|
2244
|
+
)
|
|
2245
|
+
|
|
2246
|
+
for (
|
|
2247
|
+
pair_key,
|
|
2248
|
+
_,
|
|
2249
|
+
_,
|
|
2250
|
+
) in self.list_sensor_pairs:
|
|
2251
|
+
new_grid_correction_coef = (
|
|
2252
|
+
self.generate_grid_correction_on_dem(
|
|
2253
|
+
pair_key,
|
|
2254
|
+
new_geom_plugin,
|
|
2255
|
+
)
|
|
2256
|
+
)
|
|
2257
|
+
if new_grid_correction_coef is not None:
|
|
2258
|
+
# Update refined_conf configuration with epipolar
|
|
2259
|
+
# a priori
|
|
2260
|
+
advanced_parameters.update_conf(
|
|
2261
|
+
self.refined_conf,
|
|
2262
|
+
grid_correction_coef=new_grid_correction_coef,
|
|
2263
|
+
pair_key=pair_key,
|
|
2264
|
+
reference_dem=dem_median,
|
|
2265
|
+
)
|
|
2266
|
+
|
|
2267
|
+
# saved used configuration
|
|
2268
|
+
self.save_configurations()
|
|
2269
|
+
|
|
2270
|
+
return False
|
|
2271
|
+
|
|
2272
|
+
def filling(self): # noqa: C901 : too complex
|
|
2273
|
+
"""
|
|
2274
|
+
Fill the dsm
|
|
2275
|
+
"""
|
|
2276
|
+
|
|
2277
|
+
dsm_file_name = (
|
|
2278
|
+
os.path.join(
|
|
2279
|
+
self.out_dir,
|
|
2280
|
+
out_cst.DSM_DIRECTORY,
|
|
2281
|
+
"dsm.tif",
|
|
2282
|
+
)
|
|
2283
|
+
if self.save_output_dsm
|
|
2284
|
+
else None
|
|
2285
|
+
)
|
|
2286
|
+
|
|
2287
|
+
if self.dsms_in_inputs:
|
|
2288
|
+
dsms_merging_dump_dir = os.path.join(self.dump_dir, "dsms_merging")
|
|
2289
|
+
|
|
2290
|
+
dsm_dict = self.used_conf[INPUT][dsm_cst.DSMS]
|
|
2291
|
+
dict_path = {}
|
|
2292
|
+
for key in dsm_dict.keys():
|
|
2293
|
+
for path_name in dsm_dict[key].keys():
|
|
2294
|
+
if dsm_dict[key][path_name] is not None:
|
|
2295
|
+
if isinstance(dsm_dict[key][path_name], str):
|
|
2296
|
+
if path_name not in dict_path:
|
|
2297
|
+
dict_path[path_name] = [
|
|
2298
|
+
dsm_dict[key][path_name]
|
|
2299
|
+
]
|
|
2300
|
+
else:
|
|
2301
|
+
dict_path[path_name].append(
|
|
2302
|
+
dsm_dict[key][path_name]
|
|
2303
|
+
)
|
|
2304
|
+
|
|
2305
|
+
color_file_name = (
|
|
2306
|
+
os.path.join(
|
|
2307
|
+
self.out_dir,
|
|
2308
|
+
out_cst.DSM_DIRECTORY,
|
|
2309
|
+
"image.tif",
|
|
2310
|
+
)
|
|
2311
|
+
if "texture" in dict_path
|
|
2312
|
+
or self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
|
|
2313
|
+
else None
|
|
2314
|
+
)
|
|
2315
|
+
|
|
2316
|
+
performance_map_file_name = (
|
|
2317
|
+
os.path.join(
|
|
2318
|
+
self.out_dir,
|
|
2319
|
+
out_cst.DSM_DIRECTORY,
|
|
2320
|
+
"performance_map.tif",
|
|
2321
|
+
)
|
|
2322
|
+
if "performance_map" in dict_path
|
|
2323
|
+
else None
|
|
2324
|
+
)
|
|
2325
|
+
|
|
2326
|
+
ambiguity_bool = any("ambiguity" in key for key in dict_path)
|
|
2327
|
+
ambiguity_file_name = (
|
|
2328
|
+
os.path.join(
|
|
2329
|
+
self.out_dir,
|
|
2330
|
+
out_cst.DSM_DIRECTORY,
|
|
2331
|
+
)
|
|
2332
|
+
if ambiguity_bool
|
|
2333
|
+
else None
|
|
2334
|
+
)
|
|
2335
|
+
|
|
2336
|
+
classif_file_name = (
|
|
2337
|
+
os.path.join(
|
|
2338
|
+
self.out_dir,
|
|
2339
|
+
out_cst.DSM_DIRECTORY,
|
|
2340
|
+
"classification.tif",
|
|
2341
|
+
)
|
|
2342
|
+
if "classification" in dict_path
|
|
2343
|
+
or self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2344
|
+
out_cst.AUX_CLASSIFICATION
|
|
2345
|
+
]
|
|
2346
|
+
else None
|
|
2347
|
+
)
|
|
2348
|
+
|
|
2349
|
+
contributing_all_pair_file_name = (
|
|
2350
|
+
os.path.join(
|
|
2351
|
+
self.out_dir,
|
|
2352
|
+
out_cst.DSM_DIRECTORY,
|
|
2353
|
+
"contributing_pair.tif",
|
|
2354
|
+
)
|
|
2355
|
+
if "contributing_pair" in dict_path
|
|
2356
|
+
else None
|
|
2357
|
+
)
|
|
2358
|
+
|
|
2359
|
+
filling_file_name = (
|
|
2360
|
+
os.path.join(
|
|
2361
|
+
self.out_dir,
|
|
2362
|
+
out_cst.DSM_DIRECTORY,
|
|
2363
|
+
"filling.tif",
|
|
2364
|
+
)
|
|
2365
|
+
if "filling" in dict_path
|
|
2366
|
+
else None
|
|
2367
|
+
)
|
|
2368
|
+
|
|
2369
|
+
self.epsg = rasterio_get_epsg(dict_path["dsm"][0])
|
|
2370
|
+
self.vertical_crs = rasterio_get_crs(dict_path["dsm"][0])
|
|
2371
|
+
|
|
2372
|
+
# Compute roi polygon, in input EPSG
|
|
2373
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
2374
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
2375
|
+
)
|
|
2376
|
+
|
|
2377
|
+
_ = dsm_inputs.merge_dsm_infos(
|
|
2378
|
+
dict_path,
|
|
2379
|
+
self.cars_orchestrator,
|
|
2380
|
+
self.roi_poly,
|
|
2381
|
+
self.used_conf[ADVANCED][adv_cst.DSM_MERGING_TILE_SIZE],
|
|
2382
|
+
dsms_merging_dump_dir,
|
|
2383
|
+
dsm_file_name,
|
|
2384
|
+
color_file_name,
|
|
2385
|
+
classif_file_name,
|
|
2386
|
+
filling_file_name,
|
|
2387
|
+
performance_map_file_name,
|
|
2388
|
+
ambiguity_file_name,
|
|
2389
|
+
contributing_all_pair_file_name,
|
|
2390
|
+
)
|
|
2391
|
+
|
|
2392
|
+
# dsm needs to be saved before filling
|
|
2393
|
+
self.cars_orchestrator.breakpoint()
|
|
2394
|
+
else:
|
|
2395
|
+
color_file_name = (
|
|
2396
|
+
os.path.join(
|
|
2397
|
+
self.out_dir,
|
|
2398
|
+
out_cst.DSM_DIRECTORY,
|
|
2399
|
+
"image.tif",
|
|
2400
|
+
)
|
|
2401
|
+
if self.save_output_dsm
|
|
2402
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
|
|
2403
|
+
else None
|
|
2404
|
+
)
|
|
2405
|
+
|
|
2406
|
+
classif_file_name = (
|
|
2407
|
+
os.path.join(
|
|
2408
|
+
self.out_dir,
|
|
2409
|
+
out_cst.DSM_DIRECTORY,
|
|
2410
|
+
"classification.tif",
|
|
2411
|
+
)
|
|
2412
|
+
if self.save_output_dsm
|
|
2413
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2414
|
+
out_cst.AUX_CLASSIFICATION
|
|
2415
|
+
]
|
|
2416
|
+
else None
|
|
2417
|
+
)
|
|
2418
|
+
|
|
2419
|
+
if (
|
|
2420
|
+
classif_file_name is None
|
|
2421
|
+
and self.save_output_classif_for_filling
|
|
2422
|
+
):
|
|
2423
|
+
classif_file_name = os.path.join(
|
|
2424
|
+
self.rasterization_dump_dir,
|
|
2425
|
+
"classification_for_filling.tif",
|
|
2426
|
+
)
|
|
2427
|
+
|
|
2428
|
+
filling_file_name = (
|
|
2429
|
+
os.path.join(
|
|
2430
|
+
self.out_dir,
|
|
2431
|
+
out_cst.DSM_DIRECTORY,
|
|
2432
|
+
"filling.tif",
|
|
2433
|
+
)
|
|
2434
|
+
if self.save_output_dsm
|
|
2435
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2436
|
+
out_cst.AUX_FILLING
|
|
2437
|
+
]
|
|
2438
|
+
else None
|
|
2439
|
+
)
|
|
2440
|
+
|
|
2441
|
+
if not hasattr(self, "list_intersection_poly"):
|
|
2442
|
+
if (
|
|
2443
|
+
self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
2444
|
+
sens_cst.DEM_PATH
|
|
2445
|
+
]
|
|
2446
|
+
is not None
|
|
2447
|
+
and self.sensors_in_inputs
|
|
2448
|
+
):
|
|
2449
|
+
self.list_sensor_pairs = sensor_inputs.generate_inputs(
|
|
2450
|
+
self.used_conf[INPUT],
|
|
2451
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
2452
|
+
)
|
|
2453
|
+
|
|
2454
|
+
self.list_intersection_poly = []
|
|
2455
|
+
for _, (
|
|
2456
|
+
pair_key,
|
|
2457
|
+
sensor_image_left,
|
|
2458
|
+
sensor_image_right,
|
|
2459
|
+
) in enumerate(self.list_sensor_pairs):
|
|
2460
|
+
pair_folder = os.path.join(
|
|
2461
|
+
self.dump_dir, "terrain_bbox", pair_key
|
|
2462
|
+
)
|
|
2463
|
+
safe_makedirs(pair_folder)
|
|
2464
|
+
geojson1 = os.path.join(
|
|
2465
|
+
pair_folder, "left_envelope.geojson"
|
|
2466
|
+
)
|
|
2467
|
+
geojson2 = os.path.join(
|
|
2468
|
+
pair_folder, "right_envelope.geojson"
|
|
2469
|
+
)
|
|
2470
|
+
out_envelopes_intersection = os.path.join(
|
|
2471
|
+
pair_folder, "envelopes_intersection.geojson"
|
|
2472
|
+
)
|
|
2473
|
+
|
|
2474
|
+
inter_poly, _ = projection.ground_intersection_envelopes(
|
|
2475
|
+
sensor_image_left[sens_cst.INPUT_IMG]["bands"]["b0"][
|
|
2476
|
+
"path"
|
|
2477
|
+
],
|
|
2478
|
+
sensor_image_right[sens_cst.INPUT_IMG]["bands"]["b0"][
|
|
2479
|
+
"path"
|
|
2480
|
+
],
|
|
2481
|
+
sensor_image_left[sens_cst.INPUT_GEO_MODEL],
|
|
2482
|
+
sensor_image_right[sens_cst.INPUT_GEO_MODEL],
|
|
2483
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
2484
|
+
geojson1,
|
|
2485
|
+
geojson2,
|
|
2486
|
+
out_envelopes_intersection,
|
|
2487
|
+
envelope_file_driver="GeoJSON",
|
|
2488
|
+
intersect_file_driver="GeoJSON",
|
|
2489
|
+
)
|
|
2490
|
+
|
|
2491
|
+
# Retrieve bounding box of the grd inters of the envelopes
|
|
2492
|
+
inter_poly, inter_epsg = read_vector(
|
|
2493
|
+
out_envelopes_intersection
|
|
2494
|
+
)
|
|
2495
|
+
|
|
2496
|
+
# Project polygon if epsg is different
|
|
2497
|
+
if self.vertical_crs != CRS(inter_epsg):
|
|
2498
|
+
inter_poly = projection.polygon_projection_crs(
|
|
2499
|
+
inter_poly, CRS(inter_epsg), self.vertical_crs
|
|
2500
|
+
)
|
|
2501
|
+
|
|
2502
|
+
self.list_intersection_poly.append(inter_poly)
|
|
2503
|
+
else:
|
|
2504
|
+
self.list_intersection_poly = None
|
|
2505
|
+
|
|
2506
|
+
dtm_file_name = None
|
|
2507
|
+
for app_key, app in self.dsm_filling_apps.items():
|
|
2508
|
+
|
|
2509
|
+
app_dump_dir = os.path.join(
|
|
2510
|
+
self.dump_dir, app_key.replace(".", "_")
|
|
2511
|
+
)
|
|
2512
|
+
|
|
2513
|
+
if app.get_conf()["method"] == "exogenous_filling":
|
|
2514
|
+
_ = app.run(
|
|
2515
|
+
dsm_file=dsm_file_name,
|
|
2516
|
+
classif_file=classif_file_name,
|
|
2517
|
+
filling_file=filling_file_name,
|
|
2518
|
+
dump_dir=app_dump_dir,
|
|
2519
|
+
roi_polys=self.list_intersection_poly,
|
|
2520
|
+
roi_epsg=self.epsg,
|
|
2521
|
+
output_geoid=self.used_conf[OUTPUT][sens_cst.GEOID],
|
|
2522
|
+
geom_plugin=self.geom_plugin_with_dem_and_geoid,
|
|
2523
|
+
)
|
|
2524
|
+
elif app.get_conf()["method"] == "bulldozer":
|
|
2525
|
+
dtm_file_name = app.run(
|
|
2526
|
+
dsm_file=dsm_file_name,
|
|
2527
|
+
classif_file=classif_file_name,
|
|
2528
|
+
filling_file=filling_file_name,
|
|
2529
|
+
dump_dir=app_dump_dir,
|
|
2530
|
+
roi_polys=self.list_intersection_poly,
|
|
2531
|
+
roi_epsg=self.epsg,
|
|
2532
|
+
orchestrator=self.cars_orchestrator,
|
|
2533
|
+
)
|
|
2534
|
+
elif app.get_conf()["method"] == "border_interpolation":
|
|
2535
|
+
_ = app.run(
|
|
2536
|
+
dsm_file=dsm_file_name,
|
|
2537
|
+
classif_file=classif_file_name,
|
|
2538
|
+
filling_file=filling_file_name,
|
|
2539
|
+
dtm_file=dtm_file_name,
|
|
2540
|
+
dump_dir=app_dump_dir,
|
|
2541
|
+
roi_polys=self.list_intersection_poly,
|
|
2542
|
+
roi_epsg=self.epsg,
|
|
2543
|
+
)
|
|
2544
|
+
|
|
2545
|
+
if not app.save_intermediate_data:
|
|
2546
|
+
self.cars_orchestrator.add_to_clean(app_dump_dir)
|
|
2547
|
+
|
|
2548
|
+
_ = self.auxiliary_filling_application.run(
|
|
2549
|
+
dsm_file=dsm_file_name,
|
|
2550
|
+
color_file=color_file_name,
|
|
2551
|
+
classif_file=classif_file_name,
|
|
2552
|
+
dump_dir=self.dump_dir,
|
|
2553
|
+
sensor_inputs=self.used_conf[INPUT].get("sensors"),
|
|
2554
|
+
pairing=self.used_conf[INPUT].get("pairing"),
|
|
2555
|
+
geom_plugin=self.geom_plugin_with_dem_and_geoid,
|
|
2556
|
+
texture_bands=self.texture_bands,
|
|
2557
|
+
output_geoid=self.used_conf[OUTPUT][sens_cst.GEOID],
|
|
2558
|
+
orchestrator=self.cars_orchestrator,
|
|
2559
|
+
)
|
|
2560
|
+
self.cars_orchestrator.breakpoint()
|
|
2561
|
+
|
|
2562
|
+
if (
|
|
2563
|
+
classif_file_name is not None
|
|
2564
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2565
|
+
out_cst.AUX_CLASSIFICATION
|
|
2566
|
+
]
|
|
2567
|
+
):
|
|
2568
|
+
self.merge_classif_bands(
|
|
2569
|
+
classif_file_name,
|
|
2570
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
2571
|
+
out_cst.AUX_CLASSIFICATION
|
|
2572
|
+
],
|
|
2573
|
+
dsm_file_name,
|
|
2574
|
+
)
|
|
2575
|
+
if (
|
|
2576
|
+
filling_file_name is not None
|
|
2577
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
|
|
2578
|
+
):
|
|
2579
|
+
self.merge_filling_bands(
|
|
2580
|
+
filling_file_name,
|
|
2581
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING],
|
|
2582
|
+
dsm_file_name,
|
|
2583
|
+
)
|
|
2584
|
+
|
|
2585
|
+
return self.quit_on_app("auxiliary_filling")
|
|
2586
|
+
|
|
2587
|
+
@cars_profile(name="merge filling bands", interval=0.5)
|
|
2588
|
+
def merge_filling_bands(self, filling_path, aux_filling, dsm_file):
|
|
2589
|
+
"""
|
|
2590
|
+
Merge filling bands to get mono band in output
|
|
2591
|
+
"""
|
|
2592
|
+
|
|
2593
|
+
with rasterio.open(dsm_file) as in_dsm:
|
|
2594
|
+
dsm_msk = in_dsm.read_masks(1)
|
|
2595
|
+
|
|
2596
|
+
with rasterio.open(filling_path) as src:
|
|
2597
|
+
nb_bands = src.count
|
|
2598
|
+
|
|
2599
|
+
if nb_bands == 1:
|
|
2600
|
+
return False
|
|
2601
|
+
|
|
2602
|
+
filling_multi_bands = src.read()
|
|
2603
|
+
filling_mono_bands = np.zeros(filling_multi_bands.shape[1:3])
|
|
2604
|
+
descriptions = src.descriptions
|
|
2605
|
+
dict_temp = {name: i for i, name in enumerate(descriptions)}
|
|
2606
|
+
profile = src.profile
|
|
2607
|
+
|
|
2608
|
+
with warnings.catch_warnings():
|
|
2609
|
+
warnings.simplefilter("ignore", NodataShadowWarning)
|
|
2610
|
+
filling_mask = src.read_masks(1)
|
|
2611
|
+
|
|
2612
|
+
filling_mono_bands[filling_mask == 0] = 0
|
|
2613
|
+
|
|
2614
|
+
filling_bands_list = {
|
|
2615
|
+
"fill_with_geoid": ["zeros_padding", "filling_exogenous"],
|
|
2616
|
+
"interpolate_from_borders": [
|
|
2617
|
+
"zeros_padding",
|
|
2618
|
+
"bulldozer",
|
|
2619
|
+
"border_interpolation",
|
|
2620
|
+
],
|
|
2621
|
+
"fill_with_endogenous_dem": [
|
|
2622
|
+
"zeros_padding",
|
|
2623
|
+
"filling_exogenous",
|
|
2624
|
+
"bulldozer",
|
|
2625
|
+
],
|
|
2626
|
+
"fill_with_exogenous_dem": ["zeros_padding", "bulldozer"],
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
# To get the right footprint
|
|
2630
|
+
filling_mono_bands = np.logical_or(dsm_msk, filling_mask).astype(
|
|
2631
|
+
np.uint8
|
|
2632
|
+
)
|
|
2633
|
+
|
|
2634
|
+
# to keep the previous classif convention
|
|
2635
|
+
filling_mono_bands[filling_mono_bands == 0] = src.nodata
|
|
2636
|
+
filling_mono_bands[filling_mono_bands == 1] = 0
|
|
2637
|
+
|
|
2638
|
+
no_match = False
|
|
2639
|
+
for key, value in aux_filling.items():
|
|
2640
|
+
if isinstance(value, str):
|
|
2641
|
+
value = [value]
|
|
2642
|
+
|
|
2643
|
+
if isinstance(value, list):
|
|
2644
|
+
for elem in value:
|
|
2645
|
+
if elem != "other":
|
|
2646
|
+
filling_method = filling_bands_list[elem]
|
|
2647
|
+
|
|
2648
|
+
if all(
|
|
2649
|
+
method in descriptions
|
|
2650
|
+
for method in filling_method
|
|
2651
|
+
):
|
|
2652
|
+
indices_true = [
|
|
2653
|
+
dict_temp[m] for m in filling_method
|
|
2654
|
+
]
|
|
2655
|
+
|
|
2656
|
+
mask_true = np.all(
|
|
2657
|
+
filling_multi_bands[indices_true, :, :]
|
|
2658
|
+
== 1,
|
|
2659
|
+
axis=0,
|
|
2660
|
+
)
|
|
2661
|
+
|
|
2662
|
+
indices_false = [
|
|
2663
|
+
i
|
|
2664
|
+
for i in range(filling_multi_bands.shape[0])
|
|
2665
|
+
if i not in indices_true
|
|
2666
|
+
]
|
|
2667
|
+
|
|
2668
|
+
mask_false = np.all(
|
|
2669
|
+
filling_multi_bands[indices_false, :, :]
|
|
2670
|
+
== 0,
|
|
2671
|
+
axis=0,
|
|
2672
|
+
)
|
|
2673
|
+
|
|
2674
|
+
mask = mask_true & mask_false
|
|
2675
|
+
|
|
2676
|
+
filling_mono_bands[mask] = key
|
|
2677
|
+
else:
|
|
2678
|
+
no_match = True
|
|
2679
|
+
|
|
2680
|
+
if no_match:
|
|
2681
|
+
mask_1 = np.all(
|
|
2682
|
+
filling_multi_bands[1:, :, :] == 1,
|
|
2683
|
+
axis=0,
|
|
2684
|
+
)
|
|
2685
|
+
|
|
2686
|
+
mask_2 = np.all(
|
|
2687
|
+
filling_mono_bands == 0,
|
|
2688
|
+
axis=0,
|
|
2689
|
+
)
|
|
2690
|
+
|
|
2691
|
+
filling_mono_bands[mask_1 & mask_2] = (
|
|
2692
|
+
aux_filling["other"] if "other" in aux_filling else 50
|
|
2693
|
+
)
|
|
2694
|
+
|
|
2695
|
+
profile.update(count=1, dtype=filling_mono_bands.dtype)
|
|
2696
|
+
with rasterio.open(filling_path, "w", **profile) as src:
|
|
2697
|
+
src.write(filling_mono_bands, 1)
|
|
2698
|
+
|
|
2699
|
+
return True
|
|
2700
|
+
|
|
2701
|
+
@cars_profile(name="merge classif bands", interval=0.5)
|
|
2702
|
+
def merge_classif_bands(self, classif_path, aux_classif, dsm_file):
|
|
2703
|
+
"""
|
|
2704
|
+
Merge classif bands to get mono band in output
|
|
2705
|
+
"""
|
|
2706
|
+
with rasterio.open(dsm_file) as in_dsm:
|
|
2707
|
+
dsm_msk = in_dsm.read_masks(1)
|
|
2708
|
+
|
|
2709
|
+
with rasterio.open(classif_path) as src:
|
|
2710
|
+
nb_bands = src.count
|
|
2711
|
+
|
|
2712
|
+
if nb_bands == 1:
|
|
2713
|
+
return False
|
|
2714
|
+
|
|
2715
|
+
classif_multi_bands = src.read()
|
|
2716
|
+
classif_mono_band = np.zeros(classif_multi_bands.shape[1:3])
|
|
2717
|
+
descriptions = src.descriptions
|
|
2718
|
+
profile = src.profile
|
|
2719
|
+
classif_mask = src.read_masks(1)
|
|
2720
|
+
classif_mono_band[classif_mask == 0] = 0
|
|
2721
|
+
|
|
2722
|
+
# To get the right footprint
|
|
2723
|
+
classif_mono_band = np.logical_or(dsm_msk, classif_mask).astype(
|
|
2724
|
+
np.uint8
|
|
2725
|
+
)
|
|
2726
|
+
|
|
2727
|
+
# to keep the previous classif convention
|
|
2728
|
+
classif_mono_band[classif_mono_band == 0] = src.nodata
|
|
2729
|
+
classif_mono_band[classif_mono_band == 1] = 0
|
|
2730
|
+
|
|
2731
|
+
for key, value in aux_classif.items():
|
|
2732
|
+
if isinstance(value, int):
|
|
2733
|
+
num_band = descriptions.index(str(value))
|
|
2734
|
+
mask_1 = classif_mono_band == 0
|
|
2735
|
+
mask_2 = classif_multi_bands[num_band, :, :] == 1
|
|
2736
|
+
classif_mono_band[mask_1 & mask_2] = key
|
|
2737
|
+
elif isinstance(value, list):
|
|
2738
|
+
for elem in value:
|
|
2739
|
+
num_band = descriptions.index(str(elem))
|
|
2740
|
+
mask_1 = classif_mono_band == 0
|
|
2741
|
+
mask_2 = classif_multi_bands[num_band, :, :] == 1
|
|
2742
|
+
classif_mono_band[mask_1 & mask_2] = key
|
|
2743
|
+
|
|
2744
|
+
profile.update(count=1, dtype=classif_mono_band.dtype)
|
|
2745
|
+
with rasterio.open(classif_path, "w", **profile) as src:
|
|
2746
|
+
src.write(classif_mono_band, 1)
|
|
2747
|
+
|
|
2748
|
+
return True
|
|
2749
|
+
|
|
2750
|
+
@cars_profile(name="Preprocess depth maps", interval=0.5)
|
|
2751
|
+
def preprocess_depth_maps(self):
|
|
2752
|
+
"""
|
|
2753
|
+
Adds multiple processing steps to the depth maps :
|
|
2754
|
+
Merging.
|
|
2755
|
+
Creates the point cloud that will be rasterized in
|
|
2756
|
+
the last step of the pipeline.
|
|
2757
|
+
"""
|
|
2758
|
+
|
|
2759
|
+
self.point_cloud_to_rasterize = (
|
|
2760
|
+
self.list_epipolar_point_clouds,
|
|
2761
|
+
self.terrain_bounds,
|
|
2762
|
+
)
|
|
2763
|
+
self.color_type = self.point_cloud_to_rasterize[0][0].attributes.get(
|
|
2764
|
+
"color_type", None
|
|
2765
|
+
)
|
|
2766
|
+
|
|
2767
|
+
@cars_profile(name="Final cleanup", interval=0.5)
|
|
2768
|
+
def final_cleanup(self):
|
|
2769
|
+
"""
|
|
2770
|
+
Clean temporary files and directory at the end of cars processing
|
|
2771
|
+
"""
|
|
2772
|
+
|
|
2773
|
+
if not self.used_conf[ADVANCED][adv_cst.SAVE_INTERMEDIATE_DATA]:
|
|
2774
|
+
# delete everything in tile_processing if save_intermediate_data is
|
|
2775
|
+
# not activated
|
|
2776
|
+
self.cars_orchestrator.add_to_clean(
|
|
2777
|
+
os.path.join(self.dump_dir, "tile_processing")
|
|
2778
|
+
)
|
|
2779
|
+
|
|
2780
|
+
# Remove dump_dir if no intermediate data should be written
|
|
2781
|
+
if (
|
|
2782
|
+
not any(
|
|
2783
|
+
app.get("save_intermediate_data", False) is True
|
|
2784
|
+
for app in self.used_conf[APPLICATIONS].values()
|
|
2785
|
+
if app is not None
|
|
2786
|
+
)
|
|
2787
|
+
and not self.dsms_in_inputs
|
|
2788
|
+
):
|
|
2789
|
+
self.cars_orchestrator.add_to_clean(self.dump_dir)
|
|
2790
|
+
|
|
2791
|
+
@cars_profile(name="run_unit_pipeline", interval=0.5)
|
|
2792
|
+
def run( # pylint: disable=too-many-positional-arguments
|
|
2793
|
+
self,
|
|
2794
|
+
generate_dems=False,
|
|
2795
|
+
which_resolution="single",
|
|
2796
|
+
use_sift_a_priori=False,
|
|
2797
|
+
first_res_out_dir=None,
|
|
2798
|
+
log_dir=None,
|
|
2799
|
+
): # noqa C901
|
|
2800
|
+
"""
|
|
2801
|
+
Run pipeline
|
|
2802
|
+
|
|
2803
|
+
"""
|
|
2804
|
+
if log_dir is not None:
|
|
2805
|
+
self.log_dir = log_dir
|
|
2806
|
+
else:
|
|
2807
|
+
self.log_dir = os.path.join(self.out_dir, "logs")
|
|
2808
|
+
|
|
2809
|
+
self.first_res_out_dir = first_res_out_dir
|
|
2810
|
+
self.texture_bands = self.used_conf[OUTPUT][AUXILIARY][
|
|
2811
|
+
out_cst.AUX_IMAGE
|
|
2812
|
+
]
|
|
2813
|
+
|
|
2814
|
+
self.auxiliary = self.used_conf[OUTPUT][out_cst.AUXILIARY]
|
|
2815
|
+
|
|
2816
|
+
self.use_sift_a_priori = use_sift_a_priori
|
|
2817
|
+
|
|
2818
|
+
self.generate_dems = generate_dems
|
|
2819
|
+
|
|
2820
|
+
self.which_resolution = which_resolution
|
|
2821
|
+
|
|
2822
|
+
# saved used configuration
|
|
2823
|
+
self.save_configurations()
|
|
2824
|
+
# start cars orchestrator
|
|
2825
|
+
with orchestrator.Orchestrator(
|
|
2826
|
+
orchestrator_conf=self.used_conf[ORCHESTRATOR],
|
|
2827
|
+
out_dir=self.out_dir,
|
|
2828
|
+
log_dir=self.log_dir,
|
|
2829
|
+
out_yaml_path=os.path.join(
|
|
2830
|
+
self.out_dir,
|
|
2831
|
+
out_cst.INFO_FILENAME,
|
|
2832
|
+
),
|
|
2833
|
+
) as self.cars_orchestrator:
|
|
2834
|
+
# initialize out_json
|
|
2835
|
+
self.cars_orchestrator.update_out_info({"version": __version__})
|
|
2836
|
+
|
|
2837
|
+
if not self.dsms_in_inputs:
|
|
2838
|
+
if self.compute_depth_map:
|
|
2839
|
+
self.sensor_to_depth_maps()
|
|
2840
|
+
|
|
2841
|
+
if self.save_output_dsm or self.save_output_point_cloud:
|
|
2842
|
+
self.preprocess_depth_maps()
|
|
2843
|
+
|
|
2844
|
+
if self.save_output_dsm:
|
|
2845
|
+
self.rasterize_point_cloud()
|
|
2846
|
+
self.filling()
|
|
2847
|
+
else:
|
|
2848
|
+
self.filling()
|
|
2849
|
+
|
|
2850
|
+
self.final_cleanup()
|