cars 1.0.0rc2__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cars might be problematic. Click here for more details.
- cars/__init__.py +86 -0
- cars/applications/__init__.py +40 -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 +105 -0
- cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
- cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -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 +641 -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 +1461 -0
- cars/applications/dense_matching/cpp/__init__.py +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.dll.a +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.pyd +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 +597 -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 +278 -0
- cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
- cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
- cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
- cars/applications/dsm_merging/__init__.py +28 -0
- cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
- cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
- cars/applications/grid_correction/__init__.py +30 -0
- cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
- cars/applications/grid_correction/grid_correction_app.py +557 -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_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 +522 -0
- cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -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 +639 -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 +760 -0
- cars/applications/resampling/resampling_algo.py +590 -0
- cars/applications/resampling/resampling_constants.py +36 -0
- cars/applications/resampling/resampling_wrappers.py +309 -0
- cars/applications/sensors_subsampling/__init__.py +32 -0
- cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
- cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
- cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
- cars/applications/sparse_matching/__init__.py +30 -0
- cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
- cars/applications/sparse_matching/sift_app.py +724 -0
- cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
- cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
- cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -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 +750 -0
- cars/cars.py +179 -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 +1544 -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 +250 -0
- cars/orchestrator/cluster/abstract_dask_cluster.py +381 -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 +728 -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 +986 -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/memory_tools.py +47 -0
- cars/orchestrator/orchestrator.py +755 -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 +4 -0
- cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
- cars/pipelines/default/__init__.py +26 -0
- cars/pipelines/default/default_pipeline.py +1088 -0
- cars/pipelines/filling/__init__.py +26 -0
- cars/pipelines/filling/filling.py +981 -0
- cars/pipelines/formatting/__init__.py +26 -0
- cars/pipelines/formatting/formatting.py +186 -0
- cars/pipelines/merging/__init__.py +26 -0
- cars/pipelines/merging/merging.py +439 -0
- cars/pipelines/parameters/__init__.py +0 -0
- cars/pipelines/parameters/advanced_parameters.py +256 -0
- cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
- cars/pipelines/parameters/application_parameters.py +72 -0
- cars/pipelines/parameters/depth_map_inputs.py +0 -0
- cars/pipelines/parameters/dsm_inputs.py +349 -0
- cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
- cars/pipelines/parameters/output_constants.py +52 -0
- cars/pipelines/parameters/output_parameters.py +438 -0
- cars/pipelines/parameters/sensor_inputs.py +859 -0
- cars/pipelines/parameters/sensor_inputs_constants.py +51 -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 +38 -0
- cars/pipelines/pipeline_template.py +135 -0
- cars/pipelines/subsampling/__init__.py +26 -0
- cars/pipelines/subsampling/subsampling.py +358 -0
- cars/pipelines/surface_modeling/__init__.py +26 -0
- cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
- cars/pipelines/tie_points/__init__.py +26 -0
- cars/pipelines/tie_points/tie_points.py +536 -0
- cars/starter.py +167 -0
- cars-1.0.0rc2.dist-info/DELVEWHEEL +2 -0
- cars-1.0.0rc2.dist-info/METADATA +289 -0
- cars-1.0.0rc2.dist-info/RECORD +225 -0
- cars-1.0.0rc2.dist-info/WHEEL +4 -0
- cars-1.0.0rc2.dist-info/entry_points.txt +8 -0
- cars.libs/libgcc_s_seh-1-b2494fcbd4d80cf2c98fdd5261f6d850.dll +0 -0
- cars.libs/libstdc++-6-e9b0d12ae0e9555bbae55e8dfd08c3f7.dll +0 -0
- cars.libs/libwinpthread-1-7882d1b093714ccdfaf4e0789a817792.dll +0 -0
|
@@ -0,0 +1,2098 @@
|
|
|
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 surface modeling 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 json_checker import Checker, OptionalKey
|
|
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.core import preprocessing, projection, roi_tools
|
|
55
|
+
from cars.core.geometry.abstract_geometry import AbstractGeometry
|
|
56
|
+
from cars.core.inputs import get_descriptions_bands
|
|
57
|
+
from cars.core.utils import safe_makedirs
|
|
58
|
+
from cars.data_structures import cars_dataset
|
|
59
|
+
from cars.orchestrator import orchestrator
|
|
60
|
+
from cars.orchestrator.cluster.log_wrapper import cars_profile
|
|
61
|
+
from cars.pipelines.parameters import advanced_parameters
|
|
62
|
+
from cars.pipelines.parameters import advanced_parameters_constants as adv_cst
|
|
63
|
+
from cars.pipelines.parameters import application_parameters
|
|
64
|
+
from cars.pipelines.parameters import output_constants as out_cst
|
|
65
|
+
from cars.pipelines.parameters import output_parameters, sensor_inputs
|
|
66
|
+
from cars.pipelines.parameters import sensor_inputs_constants as sens_cst
|
|
67
|
+
from cars.pipelines.parameters.advanced_parameters_constants import (
|
|
68
|
+
USE_ENDOGENOUS_DEM,
|
|
69
|
+
)
|
|
70
|
+
from cars.pipelines.parameters.output_constants import AUXILIARY
|
|
71
|
+
from cars.pipelines.pipeline import Pipeline
|
|
72
|
+
from cars.pipelines.pipeline_constants import (
|
|
73
|
+
ADVANCED,
|
|
74
|
+
APPLICATIONS,
|
|
75
|
+
INPUT,
|
|
76
|
+
ORCHESTRATOR,
|
|
77
|
+
OUTPUT,
|
|
78
|
+
TIE_POINTS,
|
|
79
|
+
)
|
|
80
|
+
from cars.pipelines.pipeline_template import PipelineTemplate
|
|
81
|
+
from cars.pipelines.tie_points.tie_points import TiePointsPipeline
|
|
82
|
+
|
|
83
|
+
PIPELINE = "surface_modeling"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@Pipeline.register(PIPELINE)
|
|
87
|
+
class SurfaceModelingPipeline(PipelineTemplate):
|
|
88
|
+
"""
|
|
89
|
+
SurfaceModelingPipeline
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# pylint: disable=too-many-instance-attributes
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
conf,
|
|
97
|
+
config_dir=None,
|
|
98
|
+
): # noqa: C901
|
|
99
|
+
"""
|
|
100
|
+
Creates pipeline
|
|
101
|
+
|
|
102
|
+
Directly creates class attributes:
|
|
103
|
+
used_conf
|
|
104
|
+
generate_terrain_products
|
|
105
|
+
debug_with_roi
|
|
106
|
+
save_output_dsm
|
|
107
|
+
save_output_depth_map
|
|
108
|
+
save_output_point_clouds
|
|
109
|
+
geom_plugin_without_dem_and_geoid
|
|
110
|
+
geom_plugin_with_dem_and_geoid
|
|
111
|
+
|
|
112
|
+
:param pipeline_name: name of the pipeline.
|
|
113
|
+
:type pipeline_name: str
|
|
114
|
+
:param cfg: configuration {'matching_cost_method': value}
|
|
115
|
+
:type cfg: dictionary
|
|
116
|
+
:param config_dir: path to dir containing json/yaml
|
|
117
|
+
:type config_dir: str
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
# Used conf
|
|
121
|
+
self.used_conf = {}
|
|
122
|
+
# refined conf
|
|
123
|
+
self.refined_conf = {}
|
|
124
|
+
|
|
125
|
+
# Transform relative path to absolute path
|
|
126
|
+
if config_dir is not None:
|
|
127
|
+
config_dir = os.path.abspath(config_dir)
|
|
128
|
+
self.config_dir = config_dir
|
|
129
|
+
|
|
130
|
+
# Check global conf
|
|
131
|
+
self.check_global_schema(conf)
|
|
132
|
+
|
|
133
|
+
if PIPELINE in conf:
|
|
134
|
+
self.check_pipeline_conf(conf)
|
|
135
|
+
|
|
136
|
+
self.out_dir = conf[OUTPUT][out_cst.OUT_DIRECTORY]
|
|
137
|
+
|
|
138
|
+
# Check conf orchestrator
|
|
139
|
+
self.used_conf[ORCHESTRATOR] = self.check_orchestrator(
|
|
140
|
+
conf.get(ORCHESTRATOR, None)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Check conf inputs
|
|
144
|
+
inputs = self.check_inputs(conf[INPUT], config_dir=config_dir)
|
|
145
|
+
self.used_conf[INPUT] = inputs
|
|
146
|
+
self.refined_conf[INPUT] = copy.deepcopy(inputs)
|
|
147
|
+
|
|
148
|
+
initial_elevation = (
|
|
149
|
+
inputs[sens_cst.INITIAL_ELEVATION]["dem"] is not None
|
|
150
|
+
)
|
|
151
|
+
self.elevation_delta_lower_bound = -500 if initial_elevation else -1000
|
|
152
|
+
self.elevation_delta_upper_bound = 1000 if initial_elevation else 9000
|
|
153
|
+
|
|
154
|
+
self.dem_scaling_coeff = None
|
|
155
|
+
if inputs[sens_cst.LOW_RES_DSM] is not None:
|
|
156
|
+
low_res_dsm = rasterio.open(inputs[sens_cst.LOW_RES_DSM])
|
|
157
|
+
self.dem_scaling_coeff = np.mean(low_res_dsm.res) * 2
|
|
158
|
+
|
|
159
|
+
# Init tie points pipelines
|
|
160
|
+
if TIE_POINTS in conf and conf[TIE_POINTS] is None:
|
|
161
|
+
self.tie_points_pipelines = None
|
|
162
|
+
else:
|
|
163
|
+
self.tie_points_pipelines = {}
|
|
164
|
+
for key1, key2 in inputs[sens_cst.PAIRING]:
|
|
165
|
+
pair_key = key1 + "_" + key2
|
|
166
|
+
tie_points_config = {}
|
|
167
|
+
tie_points_input = {}
|
|
168
|
+
tie_points_input[sens_cst.SENSORS] = {
|
|
169
|
+
key1: inputs[sens_cst.SENSORS][key1],
|
|
170
|
+
key2: inputs[sens_cst.SENSORS][key2],
|
|
171
|
+
}
|
|
172
|
+
tie_points_input[sens_cst.PAIRING] = [[key1, key2]]
|
|
173
|
+
tie_points_input[sens_cst.LOADERS] = inputs[sens_cst.LOADERS]
|
|
174
|
+
tie_points_input[sens_cst.INITIAL_ELEVATION] = inputs[
|
|
175
|
+
sens_cst.INITIAL_ELEVATION
|
|
176
|
+
]
|
|
177
|
+
tie_points_config[INPUT] = tie_points_input
|
|
178
|
+
tie_points_output = os.path.join(
|
|
179
|
+
self.out_dir, TIE_POINTS, pair_key
|
|
180
|
+
)
|
|
181
|
+
tie_points_config["output"] = {"directory": tie_points_output}
|
|
182
|
+
if TIE_POINTS in conf:
|
|
183
|
+
tie_points_config[TIE_POINTS] = conf[TIE_POINTS]
|
|
184
|
+
self.tie_points_pipelines[pair_key] = TiePointsPipeline(
|
|
185
|
+
tie_points_config,
|
|
186
|
+
config_dir=self.config_dir,
|
|
187
|
+
)
|
|
188
|
+
self.used_conf[TIE_POINTS] = {}
|
|
189
|
+
self.used_conf[TIE_POINTS][APPLICATIONS] = (
|
|
190
|
+
self.tie_points_pipelines[pair_key].used_conf[TIE_POINTS][
|
|
191
|
+
APPLICATIONS
|
|
192
|
+
]
|
|
193
|
+
)
|
|
194
|
+
self.used_conf[TIE_POINTS][ADVANCED] = (
|
|
195
|
+
self.tie_points_pipelines[pair_key].used_conf[TIE_POINTS][
|
|
196
|
+
ADVANCED
|
|
197
|
+
]
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Check advanced parameters
|
|
201
|
+
# TODO static method in the base class
|
|
202
|
+
output_dem_dir = os.path.join(
|
|
203
|
+
self.out_dir, "dump_dir", "initial_elevation"
|
|
204
|
+
)
|
|
205
|
+
pipeline_conf = conf.get(PIPELINE, {})
|
|
206
|
+
self.used_conf[PIPELINE] = {}
|
|
207
|
+
safe_makedirs(output_dem_dir)
|
|
208
|
+
(
|
|
209
|
+
inputs,
|
|
210
|
+
advanced,
|
|
211
|
+
self.geometry_plugin,
|
|
212
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
213
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
214
|
+
self.scaling_coeff,
|
|
215
|
+
self.land_cover_map,
|
|
216
|
+
self.classification_to_config_mapping,
|
|
217
|
+
) = advanced_parameters.check_advanced_parameters(
|
|
218
|
+
inputs,
|
|
219
|
+
pipeline_conf.get(ADVANCED, {}),
|
|
220
|
+
output_dem_dir=output_dem_dir,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
self.used_conf[PIPELINE][ADVANCED] = advanced
|
|
224
|
+
|
|
225
|
+
self.refined_conf[ADVANCED] = copy.deepcopy(advanced)
|
|
226
|
+
# Refined conf: resolutions 1
|
|
227
|
+
self.refined_conf[ADVANCED][adv_cst.RESOLUTIONS] = [1]
|
|
228
|
+
|
|
229
|
+
# Get ROI
|
|
230
|
+
(
|
|
231
|
+
self.input_roi_poly,
|
|
232
|
+
self.input_roi_epsg,
|
|
233
|
+
) = roi_tools.generate_roi_poly_from_inputs(
|
|
234
|
+
self.used_conf[INPUT][sens_cst.ROI]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
self.debug_with_roi = self.used_conf[PIPELINE][ADVANCED][
|
|
238
|
+
adv_cst.DEBUG_WITH_ROI
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
# Check conf output
|
|
242
|
+
(
|
|
243
|
+
output,
|
|
244
|
+
self.scaling_coeff,
|
|
245
|
+
) = self.check_output(inputs, conf[OUTPUT], self.scaling_coeff)
|
|
246
|
+
|
|
247
|
+
self.used_conf[OUTPUT] = output
|
|
248
|
+
self.out_dir = self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY]
|
|
249
|
+
self.dump_dir = os.path.join(self.out_dir, "dump_dir")
|
|
250
|
+
|
|
251
|
+
self.refined_conf[OUTPUT] = copy.deepcopy(output)
|
|
252
|
+
|
|
253
|
+
prod_level = output[out_cst.PRODUCT_LEVEL]
|
|
254
|
+
|
|
255
|
+
self.save_output_dsm = "dsm" in prod_level
|
|
256
|
+
self.save_output_depth_map = "depth_map" in prod_level
|
|
257
|
+
self.save_output_point_cloud = "point_cloud" in prod_level
|
|
258
|
+
|
|
259
|
+
self.output_level_none = not (
|
|
260
|
+
self.save_output_dsm
|
|
261
|
+
or self.save_output_depth_map
|
|
262
|
+
or self.save_output_point_cloud
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Used classification values, for filling -> will be masked
|
|
266
|
+
self.used_classif_values_for_filling = self.get_classif_values_filling(
|
|
267
|
+
self.used_conf[INPUT]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
self.phasing = self.used_conf[PIPELINE][ADVANCED][adv_cst.PHASING]
|
|
271
|
+
|
|
272
|
+
self.compute_depth_map = not self.output_level_none
|
|
273
|
+
|
|
274
|
+
if self.output_level_none:
|
|
275
|
+
self.infer_conditions_from_applications(conf[PIPELINE])
|
|
276
|
+
|
|
277
|
+
self.save_all_intermediate_data = self.used_conf[PIPELINE][ADVANCED][
|
|
278
|
+
adv_cst.SAVE_INTERMEDIATE_DATA
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
self.save_all_point_clouds_by_pair = self.used_conf[OUTPUT].get(
|
|
282
|
+
out_cst.SAVE_BY_PAIR, False
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Check conf application
|
|
286
|
+
application_conf = self.check_applications(
|
|
287
|
+
pipeline_conf.get(APPLICATIONS, {})
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Check conf application vs inputs application
|
|
291
|
+
application_conf = self.check_applications_with_inputs(
|
|
292
|
+
self.used_conf[INPUT], application_conf
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
self.used_conf[PIPELINE][APPLICATIONS] = application_conf
|
|
296
|
+
|
|
297
|
+
def check_pipeline_conf(self, conf):
|
|
298
|
+
"""
|
|
299
|
+
Check pipeline configuration
|
|
300
|
+
"""
|
|
301
|
+
# Validate inputs
|
|
302
|
+
pipeline_schema = {
|
|
303
|
+
OptionalKey(ADVANCED): dict,
|
|
304
|
+
OptionalKey(APPLICATIONS): dict,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
checker_inputs = Checker(pipeline_schema)
|
|
308
|
+
checker_inputs.validate(conf[PIPELINE])
|
|
309
|
+
|
|
310
|
+
def quit_on_app(self, app_name):
|
|
311
|
+
"""
|
|
312
|
+
Returns whether the pipeline should end after
|
|
313
|
+
the application was called.
|
|
314
|
+
|
|
315
|
+
Only works if the output_level is empty, so that
|
|
316
|
+
the control is instead given to
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
if not self.output_level_none:
|
|
320
|
+
# custom quit step was not set, never quit early
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
return self.app_values[app_name] >= self.last_application_to_run
|
|
324
|
+
|
|
325
|
+
def infer_conditions_from_applications(self, conf):
|
|
326
|
+
"""
|
|
327
|
+
Fills the condition booleans used later in the pipeline by going
|
|
328
|
+
through the applications and infering which application we should
|
|
329
|
+
end the pipeline on.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
self.last_application_to_run = 0
|
|
333
|
+
|
|
334
|
+
sensor_to_depth_apps = {
|
|
335
|
+
"dem_generation": 1,
|
|
336
|
+
"grid_generation": 2,
|
|
337
|
+
"grid_correction": 3,
|
|
338
|
+
"resampling": 4,
|
|
339
|
+
"ground_truth_reprojection": 7,
|
|
340
|
+
"dense_matching": 9,
|
|
341
|
+
"dense_match_filling": 10,
|
|
342
|
+
"triangulation": 12,
|
|
343
|
+
"point_cloud_outlier_removal.1": 13,
|
|
344
|
+
"point_cloud_outlier_removal.2": 14,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
depth_to_dsm_apps = {
|
|
348
|
+
"point_cloud_rasterization": 16,
|
|
349
|
+
"dsm_filling.1": 18,
|
|
350
|
+
"dsm_filling.2": 19,
|
|
351
|
+
"dsm_filling.3": 20,
|
|
352
|
+
"auxiliary_filling": 21,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
self.app_values = {}
|
|
356
|
+
self.app_values.update(sensor_to_depth_apps)
|
|
357
|
+
self.app_values.update(depth_to_dsm_apps)
|
|
358
|
+
|
|
359
|
+
app_conf = conf.get(APPLICATIONS, {})
|
|
360
|
+
for key in app_conf:
|
|
361
|
+
|
|
362
|
+
if adv_cst.SAVE_INTERMEDIATE_DATA not in app_conf[key]:
|
|
363
|
+
continue
|
|
364
|
+
|
|
365
|
+
if not app_conf[key][adv_cst.SAVE_INTERMEDIATE_DATA]:
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
if key in sensor_to_depth_apps:
|
|
369
|
+
self.compute_depth_map = True
|
|
370
|
+
self.last_application_to_run = max(
|
|
371
|
+
self.last_application_to_run, self.app_values[key]
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
elif key in depth_to_dsm_apps:
|
|
375
|
+
self.compute_depth_map = True
|
|
376
|
+
|
|
377
|
+
# enabled to start the depth map to dsm process
|
|
378
|
+
self.save_output_dsm = True
|
|
379
|
+
|
|
380
|
+
self.last_application_to_run = max(
|
|
381
|
+
self.last_application_to_run, self.app_values[key]
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
else:
|
|
385
|
+
warn_msg = (
|
|
386
|
+
"The application {} was not recognized. Its configuration"
|
|
387
|
+
"will be ignored."
|
|
388
|
+
).format(key)
|
|
389
|
+
logging.warning(warn_msg)
|
|
390
|
+
|
|
391
|
+
if not (self.compute_depth_map or self.save_output_dsm):
|
|
392
|
+
log_msg = (
|
|
393
|
+
"No product level was given. CARS has not detected any "
|
|
394
|
+
"data you wish to save. No computation will be done."
|
|
395
|
+
)
|
|
396
|
+
logging.info(log_msg)
|
|
397
|
+
else:
|
|
398
|
+
log_msg = (
|
|
399
|
+
"No product level was given. CARS has detected that you "
|
|
400
|
+
+ "wish to run up to the {} application.".format(
|
|
401
|
+
next(
|
|
402
|
+
k
|
|
403
|
+
for k, v in self.app_values.items()
|
|
404
|
+
if v == self.last_application_to_run
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
logging.warning(log_msg)
|
|
409
|
+
|
|
410
|
+
@staticmethod
|
|
411
|
+
def check_inputs(conf, config_dir=None):
|
|
412
|
+
"""
|
|
413
|
+
Check the inputs given
|
|
414
|
+
|
|
415
|
+
:param conf: configuration of inputs
|
|
416
|
+
:type conf: dict
|
|
417
|
+
:param config_dir: directory of used json/yaml, if
|
|
418
|
+
user filled paths with relative paths
|
|
419
|
+
:type config_dir: str
|
|
420
|
+
|
|
421
|
+
:return: overloaded inputs
|
|
422
|
+
:rtype: dict
|
|
423
|
+
"""
|
|
424
|
+
return sensor_inputs.sensors_check_inputs(conf, config_dir=config_dir)
|
|
425
|
+
|
|
426
|
+
def save_configurations(self):
|
|
427
|
+
"""
|
|
428
|
+
Save used_conf and refined_conf configurations
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
cars_dataset.save_dict(
|
|
432
|
+
self.used_conf,
|
|
433
|
+
os.path.join(self.out_dir, "current_res_used_conf.yaml"),
|
|
434
|
+
)
|
|
435
|
+
cars_dataset.save_dict(
|
|
436
|
+
self.refined_conf,
|
|
437
|
+
os.path.join(self.out_dir, "refined_conf.yaml"),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def check_output(self, inputs, conf, scaling_coeff):
|
|
441
|
+
"""
|
|
442
|
+
Check the output given
|
|
443
|
+
|
|
444
|
+
:param conf: configuration of output
|
|
445
|
+
:type conf: dict
|
|
446
|
+
:param scaling_coeff: scaling factor for resolution
|
|
447
|
+
:type scaling_coeff: float
|
|
448
|
+
:return: overloader output
|
|
449
|
+
:rtype: dict
|
|
450
|
+
"""
|
|
451
|
+
return output_parameters.check_output_parameters(
|
|
452
|
+
inputs, conf, scaling_coeff
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
def check_applications( # noqa: C901 : too complex
|
|
456
|
+
self,
|
|
457
|
+
conf,
|
|
458
|
+
):
|
|
459
|
+
"""
|
|
460
|
+
Check the given configuration for applications,
|
|
461
|
+
and generates needed applications for pipeline.
|
|
462
|
+
|
|
463
|
+
:param conf: configuration of applications
|
|
464
|
+
:type conf: dict
|
|
465
|
+
"""
|
|
466
|
+
scaling_coeff = self.scaling_coeff
|
|
467
|
+
|
|
468
|
+
needed_applications = application_parameters.get_needed_apps(
|
|
469
|
+
True,
|
|
470
|
+
self.save_output_dsm,
|
|
471
|
+
self.save_output_point_cloud,
|
|
472
|
+
conf,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Check if all specified applications are used
|
|
476
|
+
# Application in terrain_application are note used in
|
|
477
|
+
# the sensors_to_dense_depth_maps pipeline
|
|
478
|
+
for app_key in conf.keys():
|
|
479
|
+
if app_key not in needed_applications:
|
|
480
|
+
msg = (
|
|
481
|
+
f"No {app_key} application used in the "
|
|
482
|
+
+ "default Cars pipeline"
|
|
483
|
+
)
|
|
484
|
+
logging.error(msg)
|
|
485
|
+
raise NameError(msg)
|
|
486
|
+
|
|
487
|
+
# Initialize used config
|
|
488
|
+
used_conf = {}
|
|
489
|
+
|
|
490
|
+
for app_key in needed_applications:
|
|
491
|
+
used_conf[app_key] = conf.get(app_key, {})
|
|
492
|
+
if used_conf[app_key] is None:
|
|
493
|
+
continue
|
|
494
|
+
used_conf[app_key]["save_intermediate_data"] = (
|
|
495
|
+
self.save_all_intermediate_data
|
|
496
|
+
or used_conf[app_key].get("save_intermediate_data", False)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
self.epipolar_grid_generation_application = None
|
|
500
|
+
self.resampling_application = None
|
|
501
|
+
self.ground_truth_reprojection = None
|
|
502
|
+
self.dense_match_filling = None
|
|
503
|
+
self.sparse_mtch_app = None
|
|
504
|
+
self.dense_matching_app = None
|
|
505
|
+
self.triangulation_application = None
|
|
506
|
+
self.dem_generation_application = None
|
|
507
|
+
self.pc_outlier_removal_apps = {}
|
|
508
|
+
self.rasterization_application = None
|
|
509
|
+
self.pc_fusion_application = None
|
|
510
|
+
self.grid_correction_app = None
|
|
511
|
+
|
|
512
|
+
# Epipolar grid generation
|
|
513
|
+
self.epipolar_grid_generation_application = Application(
|
|
514
|
+
"grid_generation",
|
|
515
|
+
cfg=used_conf.get("grid_generation", {}),
|
|
516
|
+
scaling_coeff=scaling_coeff,
|
|
517
|
+
)
|
|
518
|
+
used_conf["grid_generation"] = (
|
|
519
|
+
self.epipolar_grid_generation_application.get_conf()
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Epipolar grid correction
|
|
523
|
+
self.grid_correction_app = Application(
|
|
524
|
+
"grid_correction",
|
|
525
|
+
cfg=used_conf.get("grid_correction", {}),
|
|
526
|
+
)
|
|
527
|
+
used_conf["grid_correction"] = self.grid_correction_app.get_conf()
|
|
528
|
+
|
|
529
|
+
# image resampling
|
|
530
|
+
self.resampling_application = Application(
|
|
531
|
+
"resampling",
|
|
532
|
+
cfg=used_conf.get("resampling", {}),
|
|
533
|
+
scaling_coeff=scaling_coeff,
|
|
534
|
+
)
|
|
535
|
+
used_conf["resampling"] = self.resampling_application.get_conf()
|
|
536
|
+
|
|
537
|
+
# ground truth disparity map computation
|
|
538
|
+
if self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
|
|
539
|
+
used_conf["ground_truth_reprojection"][
|
|
540
|
+
"save_intermediate_data"
|
|
541
|
+
] = True
|
|
542
|
+
|
|
543
|
+
if isinstance(
|
|
544
|
+
self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM],
|
|
545
|
+
str,
|
|
546
|
+
):
|
|
547
|
+
self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM] = {
|
|
548
|
+
"dsm": self.used_conf[PIPELINE][ADVANCED][
|
|
549
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
self.ground_truth_reprojection = Application(
|
|
554
|
+
"ground_truth_reprojection",
|
|
555
|
+
cfg=used_conf.get("ground_truth_reprojection", {}),
|
|
556
|
+
scaling_coeff=scaling_coeff,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
# disparity filling
|
|
560
|
+
self.dense_match_filling = Application(
|
|
561
|
+
"dense_match_filling",
|
|
562
|
+
cfg=used_conf.get(
|
|
563
|
+
"dense_match_filling",
|
|
564
|
+
{"method": "zero_padding"},
|
|
565
|
+
),
|
|
566
|
+
scaling_coeff=scaling_coeff,
|
|
567
|
+
)
|
|
568
|
+
used_conf["dense_match_filling"] = self.dense_match_filling.get_conf()
|
|
569
|
+
|
|
570
|
+
# Matching
|
|
571
|
+
generate_performance_map = bool(
|
|
572
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
573
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
574
|
+
]
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
generate_ambiguity = (
|
|
578
|
+
self.used_conf[OUTPUT]
|
|
579
|
+
.get(out_cst.AUXILIARY, {})
|
|
580
|
+
.get(out_cst.AUX_AMBIGUITY, False)
|
|
581
|
+
)
|
|
582
|
+
dense_matching_config = used_conf.get("dense_matching", {})
|
|
583
|
+
if generate_ambiguity is True:
|
|
584
|
+
dense_matching_config["generate_ambiguity"] = True
|
|
585
|
+
|
|
586
|
+
if (
|
|
587
|
+
generate_performance_map is True
|
|
588
|
+
and dense_matching_config.get("performance_map_method", None)
|
|
589
|
+
is None
|
|
590
|
+
):
|
|
591
|
+
dense_matching_config["performance_map_method"] = "risk"
|
|
592
|
+
|
|
593
|
+
# particular case for some epipolar resolutions
|
|
594
|
+
if not dense_matching_config:
|
|
595
|
+
used_conf["dense_matching"]["performance_map_method"] = [
|
|
596
|
+
"risk",
|
|
597
|
+
"intervals",
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
self.dense_matching_app = Application(
|
|
601
|
+
"dense_matching",
|
|
602
|
+
cfg=dense_matching_config,
|
|
603
|
+
scaling_coeff=scaling_coeff,
|
|
604
|
+
)
|
|
605
|
+
used_conf["dense_matching"] = self.dense_matching_app.get_conf()
|
|
606
|
+
|
|
607
|
+
# Triangulation
|
|
608
|
+
self.triangulation_application = Application(
|
|
609
|
+
"triangulation",
|
|
610
|
+
cfg=used_conf.get("triangulation", {}),
|
|
611
|
+
scaling_coeff=scaling_coeff,
|
|
612
|
+
)
|
|
613
|
+
used_conf["triangulation"] = self.triangulation_application.get_conf()
|
|
614
|
+
|
|
615
|
+
# MNT generation
|
|
616
|
+
self.dem_generation_application = Application(
|
|
617
|
+
"dem_generation",
|
|
618
|
+
cfg=used_conf.get("dem_generation", {}),
|
|
619
|
+
scaling_coeff=(
|
|
620
|
+
self.dem_scaling_coeff
|
|
621
|
+
if self.dem_scaling_coeff is not None
|
|
622
|
+
else scaling_coeff
|
|
623
|
+
),
|
|
624
|
+
)
|
|
625
|
+
used_conf["dem_generation"] = self.dem_generation_application.get_conf()
|
|
626
|
+
|
|
627
|
+
for app_key, app_conf in used_conf.items():
|
|
628
|
+
if not app_key.startswith("point_cloud_outlier_removal"):
|
|
629
|
+
continue
|
|
630
|
+
|
|
631
|
+
if app_conf is None:
|
|
632
|
+
self.pc_outlier_removal_apps = {}
|
|
633
|
+
# keep over multiple runs
|
|
634
|
+
used_conf["point_cloud_outlier_removal"] = None
|
|
635
|
+
break
|
|
636
|
+
|
|
637
|
+
if app_key in self.pc_outlier_removal_apps:
|
|
638
|
+
msg = (
|
|
639
|
+
f"The key {app_key} is defined twice in the input "
|
|
640
|
+
"configuration."
|
|
641
|
+
)
|
|
642
|
+
logging.error(msg)
|
|
643
|
+
raise NameError(msg)
|
|
644
|
+
|
|
645
|
+
if app_key[27:] == ".1":
|
|
646
|
+
app_conf.setdefault("method", "small_components")
|
|
647
|
+
if app_key[27:] == ".2":
|
|
648
|
+
app_conf.setdefault("method", "statistical")
|
|
649
|
+
|
|
650
|
+
self.pc_outlier_removal_apps[app_key] = Application(
|
|
651
|
+
"point_cloud_outlier_removal",
|
|
652
|
+
cfg=app_conf,
|
|
653
|
+
scaling_coeff=scaling_coeff,
|
|
654
|
+
)
|
|
655
|
+
used_conf[app_key] = self.pc_outlier_removal_apps[
|
|
656
|
+
app_key
|
|
657
|
+
].get_conf()
|
|
658
|
+
|
|
659
|
+
methods_str = "\n".join(
|
|
660
|
+
f" - {k}={a.used_method}"
|
|
661
|
+
for k, a in self.pc_outlier_removal_apps.items()
|
|
662
|
+
)
|
|
663
|
+
logging.info(
|
|
664
|
+
"{} point cloud outlier removal apps registered:\n{}".format(
|
|
665
|
+
len(self.pc_outlier_removal_apps), methods_str
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
if self.save_output_dsm:
|
|
670
|
+
# Rasterization
|
|
671
|
+
self.rasterization_application = Application(
|
|
672
|
+
"point_cloud_rasterization",
|
|
673
|
+
cfg=used_conf.get("point_cloud_rasterization", {}),
|
|
674
|
+
scaling_coeff=scaling_coeff,
|
|
675
|
+
)
|
|
676
|
+
used_conf["point_cloud_rasterization"] = (
|
|
677
|
+
self.rasterization_application.get_conf()
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
return used_conf
|
|
681
|
+
|
|
682
|
+
def get_classif_values_filling(self, inputs):
|
|
683
|
+
"""
|
|
684
|
+
Get values in classif, used for filling
|
|
685
|
+
|
|
686
|
+
:param inputs: inputs
|
|
687
|
+
:type inputs: dict
|
|
688
|
+
|
|
689
|
+
:return: list of values
|
|
690
|
+
:rtype: list
|
|
691
|
+
"""
|
|
692
|
+
|
|
693
|
+
filling_classif_values = []
|
|
694
|
+
|
|
695
|
+
if sens_cst.FILLING not in inputs or inputs[sens_cst.FILLING] is None:
|
|
696
|
+
logging.info("No filling in input configuration")
|
|
697
|
+
return None
|
|
698
|
+
|
|
699
|
+
filling_classif_values = []
|
|
700
|
+
for _, classif_values in inputs[sens_cst.FILLING].items():
|
|
701
|
+
# Add new value to filling bands
|
|
702
|
+
if classif_values is not None:
|
|
703
|
+
if isinstance(classif_values, str):
|
|
704
|
+
classif_values = [classif_values]
|
|
705
|
+
filling_classif_values += classif_values
|
|
706
|
+
|
|
707
|
+
simplified_list = list(OrderedDict.fromkeys(filling_classif_values))
|
|
708
|
+
res_as_string_list = [str(value) for value in simplified_list]
|
|
709
|
+
return res_as_string_list
|
|
710
|
+
|
|
711
|
+
def check_applications_with_inputs( # noqa: C901 : too complex
|
|
712
|
+
self, inputs_conf, application_conf
|
|
713
|
+
):
|
|
714
|
+
"""
|
|
715
|
+
Check for each application the input and output configuration
|
|
716
|
+
consistency
|
|
717
|
+
|
|
718
|
+
:param inputs_conf: inputs checked configuration
|
|
719
|
+
:type inputs_conf: dict
|
|
720
|
+
:param application_conf: application checked configuration
|
|
721
|
+
:type application_conf: dict
|
|
722
|
+
"""
|
|
723
|
+
|
|
724
|
+
# check classification application parameter compare
|
|
725
|
+
# to each sensors inputs classification list
|
|
726
|
+
for application_key in application_conf:
|
|
727
|
+
if application_conf[application_key] is None:
|
|
728
|
+
continue
|
|
729
|
+
if "classification" in application_conf[application_key]:
|
|
730
|
+
for item in inputs_conf["sensors"]:
|
|
731
|
+
if "classification" in inputs_conf["sensors"][item].keys():
|
|
732
|
+
if inputs_conf["sensors"][item]["classification"]:
|
|
733
|
+
descriptions = get_descriptions_bands(
|
|
734
|
+
inputs_conf["sensors"][item]["classification"]
|
|
735
|
+
)
|
|
736
|
+
if application_conf[application_key][
|
|
737
|
+
"classification"
|
|
738
|
+
] and not set(
|
|
739
|
+
application_conf[application_key][
|
|
740
|
+
"classification"
|
|
741
|
+
]
|
|
742
|
+
).issubset(
|
|
743
|
+
set(descriptions) | {"nodata"}
|
|
744
|
+
):
|
|
745
|
+
raise RuntimeError(
|
|
746
|
+
"The {} bands description {} ".format(
|
|
747
|
+
inputs_conf["sensors"][item][
|
|
748
|
+
"classification"
|
|
749
|
+
],
|
|
750
|
+
list(descriptions),
|
|
751
|
+
)
|
|
752
|
+
+ "and the {} config are not ".format(
|
|
753
|
+
application_key
|
|
754
|
+
)
|
|
755
|
+
+ "consistent: {}".format(
|
|
756
|
+
application_conf[application_key][
|
|
757
|
+
"classification"
|
|
758
|
+
]
|
|
759
|
+
)
|
|
760
|
+
)
|
|
761
|
+
for key1, key2 in inputs_conf["pairing"]:
|
|
762
|
+
corr_cfg = self.dense_matching_app.loader.get_conf()
|
|
763
|
+
nodata_left = inputs_conf["sensors"][key2]["image"]["no_data"]
|
|
764
|
+
nodata_right = inputs_conf["sensors"][key2]["image"]["no_data"]
|
|
765
|
+
bands_left = list(
|
|
766
|
+
inputs_conf["sensors"][key1]["image"]["bands"].keys()
|
|
767
|
+
)
|
|
768
|
+
bands_right = list(
|
|
769
|
+
inputs_conf["sensors"][key2]["image"]["bands"].keys()
|
|
770
|
+
)
|
|
771
|
+
values_classif_left = None
|
|
772
|
+
values_classif_right = None
|
|
773
|
+
if (
|
|
774
|
+
"classification" in inputs_conf["sensors"][key1]
|
|
775
|
+
and inputs_conf["sensors"][key1]["classification"] is not None
|
|
776
|
+
):
|
|
777
|
+
values_classif_left = inputs_conf["sensors"][key1][
|
|
778
|
+
"classification"
|
|
779
|
+
]["values"]
|
|
780
|
+
values_classif_left = list(map(str, values_classif_left))
|
|
781
|
+
if (
|
|
782
|
+
"classification" in inputs_conf["sensors"][key2]
|
|
783
|
+
and inputs_conf["sensors"][key2]["classification"] is not None
|
|
784
|
+
):
|
|
785
|
+
values_classif_right = inputs_conf["sensors"][key2][
|
|
786
|
+
"classification"
|
|
787
|
+
]["values"]
|
|
788
|
+
values_classif_right = list(map(str, values_classif_right))
|
|
789
|
+
self.dense_matching_app.corr_config = (
|
|
790
|
+
self.dense_matching_app.loader.check_conf(
|
|
791
|
+
corr_cfg,
|
|
792
|
+
nodata_left,
|
|
793
|
+
nodata_right,
|
|
794
|
+
bands_left,
|
|
795
|
+
bands_right,
|
|
796
|
+
values_classif_left,
|
|
797
|
+
values_classif_right,
|
|
798
|
+
)
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
return application_conf
|
|
802
|
+
|
|
803
|
+
def sensor_to_depth_maps(self): # noqa: C901
|
|
804
|
+
"""
|
|
805
|
+
Creates the depth map from the sensor images given in the input,
|
|
806
|
+
by following the CARS pipeline's steps.
|
|
807
|
+
"""
|
|
808
|
+
# pylint:disable=too-many-return-statements
|
|
809
|
+
inputs = self.used_conf[INPUT]
|
|
810
|
+
output = self.used_conf[OUTPUT]
|
|
811
|
+
|
|
812
|
+
# Initialize epsg for terrain tiles
|
|
813
|
+
self.phasing = self.used_conf[PIPELINE][ADVANCED][adv_cst.PHASING]
|
|
814
|
+
|
|
815
|
+
if self.phasing is not None:
|
|
816
|
+
self.epsg = self.phasing["epsg"]
|
|
817
|
+
else:
|
|
818
|
+
self.epsg = output[out_cst.EPSG]
|
|
819
|
+
|
|
820
|
+
if self.epsg is not None:
|
|
821
|
+
# Compute roi polygon, in output EPSG
|
|
822
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
823
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
self.resolution = output[out_cst.RESOLUTION]
|
|
827
|
+
|
|
828
|
+
# List of terrain roi corresponding to each epipolar pair
|
|
829
|
+
# Used to generate final terrain roi
|
|
830
|
+
self.list_terrain_roi = []
|
|
831
|
+
|
|
832
|
+
# Polygons representing the intersection of each pair of images
|
|
833
|
+
# Used to fill the final DSM only inside of those Polygons
|
|
834
|
+
self.list_intersection_poly = []
|
|
835
|
+
|
|
836
|
+
# initialize lists of points
|
|
837
|
+
self.list_epipolar_point_clouds = []
|
|
838
|
+
sensor_inputs.load_geomodels(
|
|
839
|
+
inputs, self.geom_plugin_without_dem_and_geoid
|
|
840
|
+
)
|
|
841
|
+
self.list_sensor_pairs = sensor_inputs.generate_pairs(
|
|
842
|
+
self.used_conf[INPUT]
|
|
843
|
+
)
|
|
844
|
+
logging.info(
|
|
845
|
+
"Received {} stereo pairs configurations".format(
|
|
846
|
+
len(self.list_sensor_pairs)
|
|
847
|
+
)
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
output_parameters.intialize_product_index(
|
|
851
|
+
self.cars_orchestrator,
|
|
852
|
+
output["product_level"],
|
|
853
|
+
[sensor_pair[0] for sensor_pair in self.list_sensor_pairs],
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
# pairs is a dict used to store the CarsDataset of
|
|
857
|
+
# all pairs, easily retrievable with pair keys
|
|
858
|
+
self.pairs = {}
|
|
859
|
+
|
|
860
|
+
# triangulated_matches_list is used to store triangulated matches
|
|
861
|
+
# used in dem generation
|
|
862
|
+
self.triangulated_matches_list = []
|
|
863
|
+
|
|
864
|
+
save_corrected_grid = (
|
|
865
|
+
self.epipolar_grid_generation_application.get_save_grids()
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
# Compute dems
|
|
869
|
+
dems = {}
|
|
870
|
+
if self.used_conf[INPUT][sens_cst.LOW_RES_DSM] is not None:
|
|
871
|
+
# Create dsm directory
|
|
872
|
+
dsm_dir = os.path.join(
|
|
873
|
+
self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY],
|
|
874
|
+
"dsm",
|
|
875
|
+
)
|
|
876
|
+
safe_makedirs(dsm_dir)
|
|
877
|
+
|
|
878
|
+
# DSM file name
|
|
879
|
+
dsm_file_name = self.used_conf[INPUT][sens_cst.LOW_RES_DSM]
|
|
880
|
+
|
|
881
|
+
dem_generation_output_dir = os.path.join(
|
|
882
|
+
self.dump_dir, "dem_generation"
|
|
883
|
+
)
|
|
884
|
+
safe_makedirs(dem_generation_output_dir)
|
|
885
|
+
|
|
886
|
+
# Use initial elevation if provided, and generate dems
|
|
887
|
+
_, paths, _ = self.dem_generation_application.run(
|
|
888
|
+
dsm_file_name,
|
|
889
|
+
dem_generation_output_dir,
|
|
890
|
+
input_geoid=self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
891
|
+
sens_cst.GEOID
|
|
892
|
+
],
|
|
893
|
+
output_geoid=self.used_conf[OUTPUT][out_cst.OUT_GEOID],
|
|
894
|
+
initial_elevation=(
|
|
895
|
+
self.used_conf[INPUT][sens_cst.INITIAL_ELEVATION][
|
|
896
|
+
sens_cst.DEM_PATH
|
|
897
|
+
]
|
|
898
|
+
),
|
|
899
|
+
default_alt=self.geom_plugin_with_dem_and_geoid.default_alt,
|
|
900
|
+
cars_orchestrator=self.cars_orchestrator,
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
if self.quit_on_app("dem_generation"):
|
|
904
|
+
return True
|
|
905
|
+
|
|
906
|
+
dem_median = paths["dem_median"]
|
|
907
|
+
dem_min = paths["dem_min"]
|
|
908
|
+
dem_max = paths["dem_max"]
|
|
909
|
+
|
|
910
|
+
dems["dem_median"] = dem_median
|
|
911
|
+
dems["dem_min"] = dem_min
|
|
912
|
+
dems["dem_max"] = dem_max
|
|
913
|
+
|
|
914
|
+
# Override initial elevation
|
|
915
|
+
if (
|
|
916
|
+
inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None
|
|
917
|
+
or "dem_median"
|
|
918
|
+
in inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH]
|
|
919
|
+
):
|
|
920
|
+
inputs[sens_cst.INITIAL_ELEVATION][
|
|
921
|
+
sens_cst.DEM_PATH
|
|
922
|
+
] = dem_median
|
|
923
|
+
elif (
|
|
924
|
+
inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH]
|
|
925
|
+
is not None
|
|
926
|
+
and self.used_conf[PIPELINE][ADVANCED][USE_ENDOGENOUS_DEM]
|
|
927
|
+
):
|
|
928
|
+
inputs[sens_cst.INITIAL_ELEVATION][
|
|
929
|
+
sens_cst.DEM_PATH
|
|
930
|
+
] = dem_median
|
|
931
|
+
|
|
932
|
+
# Check advanced parameters with new initial elevation
|
|
933
|
+
output_dem_dir = os.path.join(
|
|
934
|
+
self.used_conf[OUTPUT][out_cst.OUT_DIRECTORY],
|
|
935
|
+
"dump_dir",
|
|
936
|
+
"initial_elevation",
|
|
937
|
+
)
|
|
938
|
+
safe_makedirs(output_dem_dir)
|
|
939
|
+
(
|
|
940
|
+
inputs,
|
|
941
|
+
_,
|
|
942
|
+
self.geometry_plugin,
|
|
943
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
944
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
945
|
+
_,
|
|
946
|
+
_,
|
|
947
|
+
_,
|
|
948
|
+
) = advanced_parameters.check_advanced_parameters(
|
|
949
|
+
inputs,
|
|
950
|
+
self.used_conf.get(PIPELINE, {}).get(ADVANCED, {}),
|
|
951
|
+
output_dem_dir=output_dem_dir,
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
for (
|
|
955
|
+
pair_key,
|
|
956
|
+
sensor_image_left,
|
|
957
|
+
sensor_image_right,
|
|
958
|
+
) in self.list_sensor_pairs:
|
|
959
|
+
# initialize pairs for current pair
|
|
960
|
+
self.pairs[pair_key] = {}
|
|
961
|
+
self.pairs[pair_key]["sensor_image_left"] = sensor_image_left
|
|
962
|
+
self.pairs[pair_key]["sensor_image_right"] = sensor_image_right
|
|
963
|
+
|
|
964
|
+
# Run applications
|
|
965
|
+
|
|
966
|
+
# Run grid generation
|
|
967
|
+
# We generate grids with dem if it is provided.
|
|
968
|
+
# If not provided, grid are generated without dem and a dem
|
|
969
|
+
# will be generated, to use later for a new grid generation**
|
|
970
|
+
|
|
971
|
+
if inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None:
|
|
972
|
+
geom_plugin = self.geom_plugin_without_dem_and_geoid
|
|
973
|
+
|
|
974
|
+
else:
|
|
975
|
+
geom_plugin = self.geom_plugin_with_dem_and_geoid
|
|
976
|
+
|
|
977
|
+
# Generate rectification grids
|
|
978
|
+
(
|
|
979
|
+
self.pairs[pair_key]["grid_left"],
|
|
980
|
+
self.pairs[pair_key]["grid_right"],
|
|
981
|
+
) = self.epipolar_grid_generation_application.run(
|
|
982
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
983
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
984
|
+
geom_plugin,
|
|
985
|
+
orchestrator=self.cars_orchestrator,
|
|
986
|
+
pair_folder=os.path.join(
|
|
987
|
+
self.dump_dir,
|
|
988
|
+
"epipolar_grid_generation",
|
|
989
|
+
"initial",
|
|
990
|
+
pair_key,
|
|
991
|
+
),
|
|
992
|
+
pair_key=pair_key,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
if self.quit_on_app("grid_generation"):
|
|
996
|
+
continue # keep iterating over pairs, but don't go further
|
|
997
|
+
|
|
998
|
+
# Prepare tie point pipeline
|
|
999
|
+
|
|
1000
|
+
# Update tie points pipeline with rectification grids
|
|
1001
|
+
if self.tie_points_pipelines is not None:
|
|
1002
|
+
tie_points_config = self.tie_points_pipelines[
|
|
1003
|
+
pair_key
|
|
1004
|
+
].used_conf
|
|
1005
|
+
image_keys = list(tie_points_config[INPUT][sens_cst.SENSORS])
|
|
1006
|
+
tie_points_config[INPUT][sens_cst.RECTIFICATION_GRIDS] = {
|
|
1007
|
+
image_keys[0]: self.pairs[pair_key]["grid_left"],
|
|
1008
|
+
image_keys[1]: self.pairs[pair_key]["grid_right"],
|
|
1009
|
+
}
|
|
1010
|
+
tie_points_pipeline = TiePointsPipeline(
|
|
1011
|
+
tie_points_config,
|
|
1012
|
+
config_dir=self.config_dir,
|
|
1013
|
+
)
|
|
1014
|
+
sparse_mtch_app = tie_points_pipeline.sparse_matching_app
|
|
1015
|
+
|
|
1016
|
+
tie_points_output = tie_points_config[OUTPUT][
|
|
1017
|
+
out_cst.OUT_DIRECTORY
|
|
1018
|
+
]
|
|
1019
|
+
|
|
1020
|
+
# If dem are provided, launch disparity grids generation
|
|
1021
|
+
disp_range_grid_dir = os.path.join(
|
|
1022
|
+
tie_points_output, "disparity_grids"
|
|
1023
|
+
)
|
|
1024
|
+
disp_range_grid = None
|
|
1025
|
+
if dems:
|
|
1026
|
+
disp_range_grid = (
|
|
1027
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1028
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1029
|
+
self.pairs[pair_key]["grid_right"],
|
|
1030
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1031
|
+
dem_min=dem_min,
|
|
1032
|
+
dem_max=dem_max,
|
|
1033
|
+
pair_folder=disp_range_grid_dir,
|
|
1034
|
+
orchestrator=self.cars_orchestrator,
|
|
1035
|
+
)
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
# Launch tie points pipeline
|
|
1039
|
+
tie_points_pipeline.run(
|
|
1040
|
+
disp_range_grid=disp_range_grid,
|
|
1041
|
+
log_dir=self.log_dir,
|
|
1042
|
+
cars_orchestrator=self.cars_orchestrator,
|
|
1043
|
+
)
|
|
1044
|
+
self.pairs[pair_key]["matches_array"] = np.load(
|
|
1045
|
+
os.path.join(
|
|
1046
|
+
tie_points_output, pair_key, "filtered_matches.npy"
|
|
1047
|
+
)
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
minimum_nb_matches = (
|
|
1051
|
+
self.grid_correction_app.get_minimum_nb_matches()
|
|
1052
|
+
)
|
|
1053
|
+
nb_matches = self.pairs[pair_key]["matches_array"].shape[0]
|
|
1054
|
+
save_matches = sparse_mtch_app.get_save_matches()
|
|
1055
|
+
|
|
1056
|
+
if nb_matches > minimum_nb_matches:
|
|
1057
|
+
# Compute grid correction
|
|
1058
|
+
(self.pairs[pair_key]["corrected_grid_right"], _, _, _) = (
|
|
1059
|
+
self.grid_correction_app.run(
|
|
1060
|
+
self.pairs[pair_key]["matches_array"],
|
|
1061
|
+
self.pairs[pair_key]["grid_right"],
|
|
1062
|
+
save_matches=save_matches,
|
|
1063
|
+
pair_folder=os.path.join(
|
|
1064
|
+
self.dump_dir,
|
|
1065
|
+
"grid_correction",
|
|
1066
|
+
"initial",
|
|
1067
|
+
pair_key,
|
|
1068
|
+
),
|
|
1069
|
+
pair_key=pair_key,
|
|
1070
|
+
orchestrator=self.cars_orchestrator,
|
|
1071
|
+
save_corrected_grid=save_corrected_grid,
|
|
1072
|
+
)
|
|
1073
|
+
)
|
|
1074
|
+
else:
|
|
1075
|
+
logging.warning(
|
|
1076
|
+
"Grid correction is not applied because number of "
|
|
1077
|
+
"matches found ({}) is less than minimum number of "
|
|
1078
|
+
"matches required for grid correction ({})".format(
|
|
1079
|
+
nb_matches,
|
|
1080
|
+
minimum_nb_matches,
|
|
1081
|
+
)
|
|
1082
|
+
)
|
|
1083
|
+
self.pairs[pair_key]["corrected_grid_right"] = self.pairs[
|
|
1084
|
+
pair_key
|
|
1085
|
+
]["grid_right"]
|
|
1086
|
+
else:
|
|
1087
|
+
self.pairs[pair_key]["corrected_grid_right"] = self.pairs[
|
|
1088
|
+
pair_key
|
|
1089
|
+
]["grid_right"]
|
|
1090
|
+
|
|
1091
|
+
self.pairs[pair_key]["corrected_grid_left"] = self.pairs[pair_key][
|
|
1092
|
+
"grid_left"
|
|
1093
|
+
]
|
|
1094
|
+
|
|
1095
|
+
# Shrink disparity intervals according to SIFT disparities
|
|
1096
|
+
disp_to_alt_ratio = self.pairs[pair_key]["grid_left"][
|
|
1097
|
+
"disp_to_alt_ratio"
|
|
1098
|
+
]
|
|
1099
|
+
if self.tie_points_pipelines is not None:
|
|
1100
|
+
disp_bounds_params = sparse_mtch_app.disparity_bounds_estimation
|
|
1101
|
+
matches = self.pairs[pair_key]["matches_array"]
|
|
1102
|
+
if disp_bounds_params["activated"] and matches.shape[0] > 0:
|
|
1103
|
+
sift_disp = matches[:, 2] - matches[:, 0]
|
|
1104
|
+
disp_min = np.percentile(
|
|
1105
|
+
sift_disp, disp_bounds_params["percentile"]
|
|
1106
|
+
)
|
|
1107
|
+
disp_max = np.percentile(
|
|
1108
|
+
sift_disp, 100 - disp_bounds_params["percentile"]
|
|
1109
|
+
)
|
|
1110
|
+
logging.info(
|
|
1111
|
+
"Global disparity interval without margin : "
|
|
1112
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1113
|
+
)
|
|
1114
|
+
disp_min -= (
|
|
1115
|
+
disp_bounds_params["upper_margin"] / disp_to_alt_ratio
|
|
1116
|
+
)
|
|
1117
|
+
disp_max += (
|
|
1118
|
+
disp_bounds_params["lower_margin"] / disp_to_alt_ratio
|
|
1119
|
+
)
|
|
1120
|
+
logging.info(
|
|
1121
|
+
"Global disparity interval with margin : "
|
|
1122
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1123
|
+
)
|
|
1124
|
+
else:
|
|
1125
|
+
disp_min = (
|
|
1126
|
+
-self.elevation_delta_upper_bound / disp_to_alt_ratio
|
|
1127
|
+
)
|
|
1128
|
+
disp_max = (
|
|
1129
|
+
-self.elevation_delta_lower_bound / disp_to_alt_ratio
|
|
1130
|
+
)
|
|
1131
|
+
logging.info(
|
|
1132
|
+
"Global disparity interval : "
|
|
1133
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1134
|
+
)
|
|
1135
|
+
else:
|
|
1136
|
+
disp_min = -self.elevation_delta_upper_bound / disp_to_alt_ratio
|
|
1137
|
+
disp_max = -self.elevation_delta_lower_bound / disp_to_alt_ratio
|
|
1138
|
+
logging.info(
|
|
1139
|
+
"Global disparity interval : "
|
|
1140
|
+
f"[{disp_min:.2f} pix, {disp_max:.2f} pix]"
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
if self.epsg is None:
|
|
1144
|
+
# compute epsg
|
|
1145
|
+
# Epsg uses global disparity min and max
|
|
1146
|
+
self.epsg = preprocessing.compute_epsg(
|
|
1147
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1148
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1149
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1150
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1151
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1152
|
+
disp_min=0,
|
|
1153
|
+
disp_max=0,
|
|
1154
|
+
)
|
|
1155
|
+
# Compute roi polygon, in input EPSG
|
|
1156
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
1157
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
# Clean grids at the end of processing if required. Note that this will
|
|
1161
|
+
# also clean refined grids
|
|
1162
|
+
if not save_corrected_grid:
|
|
1163
|
+
self.cars_orchestrator.add_to_clean(
|
|
1164
|
+
os.path.join(self.dump_dir, "grid_correction")
|
|
1165
|
+
)
|
|
1166
|
+
# grids file are already cleaned in the application, but the tree
|
|
1167
|
+
# structure should also be cleaned
|
|
1168
|
+
if not save_corrected_grid:
|
|
1169
|
+
|
|
1170
|
+
self.cars_orchestrator.add_to_clean(
|
|
1171
|
+
os.path.join(self.dump_dir, "epipolar_grid_generation")
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
# quit if any app in the loop over the pairs was the last one
|
|
1175
|
+
if self.quit_on_app("grid_generation") or self.quit_on_app(
|
|
1176
|
+
"resampling"
|
|
1177
|
+
):
|
|
1178
|
+
return True
|
|
1179
|
+
|
|
1180
|
+
# Define param
|
|
1181
|
+
use_global_disp_range = self.dense_matching_app.use_global_disp_range
|
|
1182
|
+
|
|
1183
|
+
self.pairs_names = [
|
|
1184
|
+
pair_name for pair_name, _, _ in self.list_sensor_pairs
|
|
1185
|
+
]
|
|
1186
|
+
|
|
1187
|
+
for _, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
|
|
1188
|
+
# Geometry plugin with dem will be used for the grid generation
|
|
1189
|
+
geom_plugin = self.geom_plugin_with_dem_and_geoid
|
|
1190
|
+
|
|
1191
|
+
# saved used configuration
|
|
1192
|
+
self.save_configurations()
|
|
1193
|
+
|
|
1194
|
+
# Generate min and max disp grids
|
|
1195
|
+
# Global disparity min and max will be computed from
|
|
1196
|
+
# these grids
|
|
1197
|
+
dense_matching_pair_folder = os.path.join(
|
|
1198
|
+
self.dump_dir, "dense_matching", pair_key
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
if self.which_resolution in ("first", "single") and dems in (
|
|
1202
|
+
None,
|
|
1203
|
+
{},
|
|
1204
|
+
):
|
|
1205
|
+
dmin = disp_min
|
|
1206
|
+
dmax = disp_max
|
|
1207
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1208
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1209
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1210
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1211
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1212
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1213
|
+
dmin=dmin,
|
|
1214
|
+
dmax=dmax,
|
|
1215
|
+
pair_folder=dense_matching_pair_folder,
|
|
1216
|
+
orchestrator=self.cars_orchestrator,
|
|
1217
|
+
)
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
updating_infos = {
|
|
1221
|
+
application_constants.APPLICATION_TAG: {
|
|
1222
|
+
sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
|
|
1223
|
+
pair_key: {
|
|
1224
|
+
sm_cst.MINIMUM_DISPARITY_TAG: dmin,
|
|
1225
|
+
sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
self.cars_orchestrator.update_out_info(updating_infos)
|
|
1231
|
+
else:
|
|
1232
|
+
# Generate min and max disp grids from dems
|
|
1233
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1234
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1235
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1236
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1237
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1238
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1239
|
+
dem_min=dem_min,
|
|
1240
|
+
dem_max=dem_max,
|
|
1241
|
+
pair_folder=dense_matching_pair_folder,
|
|
1242
|
+
orchestrator=self.cars_orchestrator,
|
|
1243
|
+
)
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
if use_global_disp_range:
|
|
1247
|
+
# Generate min and max disp grids from constants
|
|
1248
|
+
# sensor image is not used here
|
|
1249
|
+
# TODO remove when only local diparity range will be used
|
|
1250
|
+
dmin = self.pairs[pair_key]["disp_range_grid"]["global_min"]
|
|
1251
|
+
dmax = self.pairs[pair_key]["disp_range_grid"]["global_max"]
|
|
1252
|
+
|
|
1253
|
+
# update orchestrator_out_json
|
|
1254
|
+
updating_infos = {
|
|
1255
|
+
application_constants.APPLICATION_TAG: {
|
|
1256
|
+
sm_cst.DISPARITY_RANGE_COMPUTATION_TAG: {
|
|
1257
|
+
pair_key: {
|
|
1258
|
+
sm_cst.MINIMUM_DISPARITY_TAG: dmin,
|
|
1259
|
+
sm_cst.MAXIMUM_DISPARITY_TAG: dmax,
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
self.cars_orchestrator.update_out_info(updating_infos)
|
|
1265
|
+
|
|
1266
|
+
# generate_disparity_grids runs orchestrator.breakpoint()
|
|
1267
|
+
self.pairs[pair_key]["disp_range_grid"] = (
|
|
1268
|
+
self.dense_matching_app.generate_disparity_grids(
|
|
1269
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1270
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1271
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1272
|
+
dmin=dmin,
|
|
1273
|
+
dmax=dmax,
|
|
1274
|
+
pair_folder=dense_matching_pair_folder,
|
|
1275
|
+
orchestrator=self.cars_orchestrator,
|
|
1276
|
+
)
|
|
1277
|
+
)
|
|
1278
|
+
|
|
1279
|
+
# saved used configuration
|
|
1280
|
+
self.save_configurations()
|
|
1281
|
+
|
|
1282
|
+
# end of for loop, to finish computing disparity range grids
|
|
1283
|
+
|
|
1284
|
+
for cloud_id, (pair_key, _, _) in enumerate(self.list_sensor_pairs):
|
|
1285
|
+
|
|
1286
|
+
# Generate roi
|
|
1287
|
+
epipolar_roi = preprocessing.compute_epipolar_roi(
|
|
1288
|
+
self.input_roi_poly,
|
|
1289
|
+
self.input_roi_epsg,
|
|
1290
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1291
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1292
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1293
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1294
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1295
|
+
os.path.join(self.dump_dir, "compute_epipolar_roi", pair_key),
|
|
1296
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"]["global_min"],
|
|
1297
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"]["global_max"],
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
# Generate new epipolar images
|
|
1301
|
+
# Generated with corrected grids
|
|
1302
|
+
# Optimal size is computed for the worst case scenario
|
|
1303
|
+
# found with epipolar disparity range grids
|
|
1304
|
+
|
|
1305
|
+
(
|
|
1306
|
+
optimum_tile_size,
|
|
1307
|
+
local_tile_optimal_size_fun,
|
|
1308
|
+
) = self.dense_matching_app.get_optimal_tile_size(
|
|
1309
|
+
self.pairs[pair_key]["disp_range_grid"],
|
|
1310
|
+
self.cars_orchestrator.cluster.checked_conf_cluster[
|
|
1311
|
+
"max_ram_per_worker"
|
|
1312
|
+
],
|
|
1313
|
+
)
|
|
1314
|
+
|
|
1315
|
+
# Get required bands of third resampling
|
|
1316
|
+
required_bands = self.dense_matching_app.get_required_bands()
|
|
1317
|
+
|
|
1318
|
+
# Add left required bands for texture
|
|
1319
|
+
required_bands["left"] = sorted(
|
|
1320
|
+
set(required_bands["left"]).union(set(self.texture_bands))
|
|
1321
|
+
)
|
|
1322
|
+
|
|
1323
|
+
# Find index of texture band in left_dataset
|
|
1324
|
+
texture_bands_indices = [
|
|
1325
|
+
required_bands["left"].index(band)
|
|
1326
|
+
for band in self.texture_bands
|
|
1327
|
+
]
|
|
1328
|
+
|
|
1329
|
+
# Get margins used in dense matching,
|
|
1330
|
+
dense_matching_margins_fun = (
|
|
1331
|
+
self.dense_matching_app.get_margins_fun(
|
|
1332
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1333
|
+
self.pairs[pair_key]["disp_range_grid"],
|
|
1334
|
+
)
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
# Run third epipolar resampling
|
|
1338
|
+
(
|
|
1339
|
+
new_epipolar_image_left,
|
|
1340
|
+
new_epipolar_image_right,
|
|
1341
|
+
) = self.resampling_application.run(
|
|
1342
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1343
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1344
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1345
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1346
|
+
geom_plugin,
|
|
1347
|
+
orchestrator=self.cars_orchestrator,
|
|
1348
|
+
pair_folder=os.path.join(
|
|
1349
|
+
self.dump_dir, "resampling", "corrected_grid", pair_key
|
|
1350
|
+
),
|
|
1351
|
+
pair_key=pair_key,
|
|
1352
|
+
margins_fun=dense_matching_margins_fun,
|
|
1353
|
+
tile_width=optimum_tile_size,
|
|
1354
|
+
tile_height=optimum_tile_size,
|
|
1355
|
+
add_classif=True,
|
|
1356
|
+
epipolar_roi=epipolar_roi,
|
|
1357
|
+
required_bands=required_bands,
|
|
1358
|
+
texture_bands=self.texture_bands,
|
|
1359
|
+
)
|
|
1360
|
+
# Run ground truth dsm computation
|
|
1361
|
+
if self.used_conf[PIPELINE][ADVANCED][adv_cst.GROUND_TRUTH_DSM]:
|
|
1362
|
+
self.used_conf[PIPELINE][APPLICATIONS][
|
|
1363
|
+
"ground_truth_reprojection"
|
|
1364
|
+
]["save_intermediate_data"] = True
|
|
1365
|
+
new_geomplugin_dsm = AbstractGeometry( # pylint: disable=E0110
|
|
1366
|
+
self.geometry_plugin,
|
|
1367
|
+
dem=self.used_conf[PIPELINE][ADVANCED][
|
|
1368
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
1369
|
+
][adv_cst.INPUT_GROUND_TRUTH_DSM],
|
|
1370
|
+
geoid=self.used_conf[PIPELINE][ADVANCED][
|
|
1371
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
1372
|
+
][adv_cst.INPUT_GEOID],
|
|
1373
|
+
scaling_coeff=self.scaling_coeff,
|
|
1374
|
+
)
|
|
1375
|
+
self.ground_truth_reprojection.run(
|
|
1376
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1377
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1378
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1379
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1380
|
+
new_geomplugin_dsm,
|
|
1381
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1382
|
+
self.pairs[pair_key]["corrected_grid_left"][
|
|
1383
|
+
"disp_to_alt_ratio"
|
|
1384
|
+
],
|
|
1385
|
+
self.used_conf[PIPELINE][ADVANCED][
|
|
1386
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
1387
|
+
][adv_cst.INPUT_AUX_PATH],
|
|
1388
|
+
self.used_conf[PIPELINE][ADVANCED][
|
|
1389
|
+
adv_cst.GROUND_TRUTH_DSM
|
|
1390
|
+
][adv_cst.INPUT_AUX_INTERP],
|
|
1391
|
+
orchestrator=self.cars_orchestrator,
|
|
1392
|
+
pair_folder=os.path.join(
|
|
1393
|
+
self.dump_dir, "ground_truth_reprojection", pair_key
|
|
1394
|
+
),
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
if self.epsg is None:
|
|
1398
|
+
# compute epsg
|
|
1399
|
+
# Epsg uses global disparity min and max
|
|
1400
|
+
self.epsg = preprocessing.compute_epsg(
|
|
1401
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1402
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1403
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1404
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1405
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1406
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"][
|
|
1407
|
+
"global_min"
|
|
1408
|
+
],
|
|
1409
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"][
|
|
1410
|
+
"global_max"
|
|
1411
|
+
],
|
|
1412
|
+
)
|
|
1413
|
+
# Compute roi polygon, in input EPSG
|
|
1414
|
+
self.roi_poly = preprocessing.compute_roi_poly(
|
|
1415
|
+
self.input_roi_poly, self.input_roi_epsg, self.epsg
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
self.vertical_crs = projection.get_output_crs(self.epsg, output)
|
|
1419
|
+
|
|
1420
|
+
if (
|
|
1421
|
+
self.save_output_dsm
|
|
1422
|
+
or self.save_output_point_cloud
|
|
1423
|
+
or self.dense_matching_app.get_method() == "auto"
|
|
1424
|
+
):
|
|
1425
|
+
# Compute terrain bounding box /roi related to
|
|
1426
|
+
# current images
|
|
1427
|
+
(current_terrain_roi_bbox, intersection_poly) = (
|
|
1428
|
+
preprocessing.compute_terrain_bbox(
|
|
1429
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1430
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1431
|
+
new_epipolar_image_left,
|
|
1432
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1433
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1434
|
+
self.epsg,
|
|
1435
|
+
self.geom_plugin_with_dem_and_geoid,
|
|
1436
|
+
resolution=self.resolution,
|
|
1437
|
+
disp_min=self.pairs[pair_key]["disp_range_grid"][
|
|
1438
|
+
"global_min"
|
|
1439
|
+
],
|
|
1440
|
+
disp_max=self.pairs[pair_key]["disp_range_grid"][
|
|
1441
|
+
"global_max"
|
|
1442
|
+
],
|
|
1443
|
+
roi_poly=(
|
|
1444
|
+
None if self.debug_with_roi else self.roi_poly
|
|
1445
|
+
),
|
|
1446
|
+
orchestrator=self.cars_orchestrator,
|
|
1447
|
+
pair_key=pair_key,
|
|
1448
|
+
pair_folder=os.path.join(
|
|
1449
|
+
self.dump_dir, "terrain_bbox", pair_key
|
|
1450
|
+
),
|
|
1451
|
+
check_inputs=False,
|
|
1452
|
+
)
|
|
1453
|
+
)
|
|
1454
|
+
self.list_terrain_roi.append(current_terrain_roi_bbox)
|
|
1455
|
+
self.list_intersection_poly.append(intersection_poly)
|
|
1456
|
+
|
|
1457
|
+
# compute terrain bounds for later use
|
|
1458
|
+
(
|
|
1459
|
+
self.terrain_bounds,
|
|
1460
|
+
self.optimal_terrain_tile_width,
|
|
1461
|
+
) = preprocessing.compute_terrain_bounds(
|
|
1462
|
+
self.list_terrain_roi,
|
|
1463
|
+
roi_poly=(None if self.debug_with_roi else self.roi_poly),
|
|
1464
|
+
resolution=self.resolution,
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
if self.which_resolution not in ("final", "single"):
|
|
1468
|
+
self.terrain_bounds = dem_wrappers.modify_terrain_bounds(
|
|
1469
|
+
self.terrain_bounds,
|
|
1470
|
+
self.dem_generation_application.margin[0],
|
|
1471
|
+
self.dem_generation_application.margin[1],
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
if self.dense_matching_app.get_method() == "auto":
|
|
1475
|
+
# Copy the initial corr_config in order to keep
|
|
1476
|
+
# the inputs that have already been checked
|
|
1477
|
+
corr_cfg = self.dense_matching_app.corr_config.copy()
|
|
1478
|
+
|
|
1479
|
+
# Find the conf that correspond to the land cover map
|
|
1480
|
+
conf = self.dense_matching_app.loader.find_auto_conf(
|
|
1481
|
+
intersection_poly,
|
|
1482
|
+
self.land_cover_map,
|
|
1483
|
+
self.classification_to_config_mapping,
|
|
1484
|
+
self.epsg,
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
# Update the used_conf if order to reinitialize
|
|
1488
|
+
# the dense matching app
|
|
1489
|
+
# Because we kept the information regarding the ambiguity,
|
|
1490
|
+
# performance_map calculus..
|
|
1491
|
+
self.used_conf[PIPELINE][APPLICATIONS]["dense_matching"][
|
|
1492
|
+
"loader_conf"
|
|
1493
|
+
] = conf
|
|
1494
|
+
self.used_conf[PIPELINE][APPLICATIONS]["dense_matching"][
|
|
1495
|
+
"method"
|
|
1496
|
+
] = "custom"
|
|
1497
|
+
|
|
1498
|
+
# Re initialization of the dense matching application
|
|
1499
|
+
self.dense_matching_app = Application(
|
|
1500
|
+
"dense_matching",
|
|
1501
|
+
cfg=self.used_conf[PIPELINE][APPLICATIONS][
|
|
1502
|
+
"dense_matching"
|
|
1503
|
+
],
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
# Update the corr_config with the inputs that have
|
|
1507
|
+
# already been checked
|
|
1508
|
+
self.dense_matching_app.corr_config["input"] = corr_cfg["input"]
|
|
1509
|
+
|
|
1510
|
+
# Run epipolar matching application
|
|
1511
|
+
epipolar_disparity_map = self.dense_matching_app.run(
|
|
1512
|
+
new_epipolar_image_left,
|
|
1513
|
+
new_epipolar_image_right,
|
|
1514
|
+
local_tile_optimal_size_fun,
|
|
1515
|
+
orchestrator=self.cars_orchestrator,
|
|
1516
|
+
pair_folder=os.path.join(
|
|
1517
|
+
self.dump_dir, "dense_matching", pair_key
|
|
1518
|
+
),
|
|
1519
|
+
pair_key=pair_key,
|
|
1520
|
+
disp_range_grid=self.pairs[pair_key]["disp_range_grid"],
|
|
1521
|
+
compute_disparity_masks=False,
|
|
1522
|
+
margins_to_keep=sum(
|
|
1523
|
+
app.get_epipolar_margin()
|
|
1524
|
+
for _, app in self.pc_outlier_removal_apps.items()
|
|
1525
|
+
),
|
|
1526
|
+
texture_bands=texture_bands_indices,
|
|
1527
|
+
classif_bands_to_mask=self.used_classif_values_for_filling,
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
if self.quit_on_app("dense_matching"):
|
|
1531
|
+
continue # keep iterating over pairs, but don't go further
|
|
1532
|
+
|
|
1533
|
+
# Fill with zeros
|
|
1534
|
+
(filled_epipolar_disparity_map) = self.dense_match_filling.run(
|
|
1535
|
+
epipolar_disparity_map,
|
|
1536
|
+
orchestrator=self.cars_orchestrator,
|
|
1537
|
+
pair_folder=os.path.join(
|
|
1538
|
+
self.dump_dir, "dense_match_filling", pair_key
|
|
1539
|
+
),
|
|
1540
|
+
pair_key=pair_key,
|
|
1541
|
+
)
|
|
1542
|
+
|
|
1543
|
+
if self.quit_on_app("dense_match_filling"):
|
|
1544
|
+
continue # keep iterating over pairs, but don't go further
|
|
1545
|
+
|
|
1546
|
+
if isinstance(output[sens_cst.GEOID], str):
|
|
1547
|
+
output_geoid_path = output[sens_cst.GEOID]
|
|
1548
|
+
elif (
|
|
1549
|
+
isinstance(output[sens_cst.GEOID], bool)
|
|
1550
|
+
and output[sens_cst.GEOID]
|
|
1551
|
+
):
|
|
1552
|
+
package_path = os.path.dirname(__file__)
|
|
1553
|
+
output_geoid_path = os.path.join(
|
|
1554
|
+
package_path,
|
|
1555
|
+
"..",
|
|
1556
|
+
"..",
|
|
1557
|
+
"conf",
|
|
1558
|
+
sensor_inputs.CARS_GEOID_PATH,
|
|
1559
|
+
)
|
|
1560
|
+
else:
|
|
1561
|
+
# default case : stay on the ellipsoid
|
|
1562
|
+
output_geoid_path = None
|
|
1563
|
+
|
|
1564
|
+
depth_map_dir = None
|
|
1565
|
+
if self.save_output_depth_map:
|
|
1566
|
+
depth_map_dir = os.path.join(
|
|
1567
|
+
self.out_dir, "depth_map", pair_key
|
|
1568
|
+
)
|
|
1569
|
+
safe_makedirs(depth_map_dir)
|
|
1570
|
+
|
|
1571
|
+
point_cloud_dir = None
|
|
1572
|
+
if self.save_output_point_cloud:
|
|
1573
|
+
point_cloud_dir = os.path.join(
|
|
1574
|
+
self.out_dir, "point_cloud", pair_key
|
|
1575
|
+
)
|
|
1576
|
+
safe_makedirs(point_cloud_dir)
|
|
1577
|
+
|
|
1578
|
+
triangulation_point_cloud_dir = (
|
|
1579
|
+
point_cloud_dir
|
|
1580
|
+
if (point_cloud_dir and len(self.pc_outlier_removal_apps) == 0)
|
|
1581
|
+
else None
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
# Run epipolar triangulation application
|
|
1585
|
+
epipolar_point_cloud = self.triangulation_application.run(
|
|
1586
|
+
self.pairs[pair_key]["sensor_image_left"],
|
|
1587
|
+
self.pairs[pair_key]["sensor_image_right"],
|
|
1588
|
+
self.pairs[pair_key]["corrected_grid_left"],
|
|
1589
|
+
self.pairs[pair_key]["corrected_grid_right"],
|
|
1590
|
+
filled_epipolar_disparity_map,
|
|
1591
|
+
self.geom_plugin_without_dem_and_geoid,
|
|
1592
|
+
new_epipolar_image_left,
|
|
1593
|
+
epsg=self.epsg,
|
|
1594
|
+
denoising_overload_fun=None,
|
|
1595
|
+
source_pc_names=self.pairs_names,
|
|
1596
|
+
orchestrator=self.cars_orchestrator,
|
|
1597
|
+
pair_dump_dir=os.path.join(
|
|
1598
|
+
self.dump_dir, "triangulation", pair_key
|
|
1599
|
+
),
|
|
1600
|
+
pair_key=pair_key,
|
|
1601
|
+
uncorrected_grid_right=self.pairs[pair_key]["grid_right"],
|
|
1602
|
+
geoid_path=output_geoid_path,
|
|
1603
|
+
cloud_id=cloud_id,
|
|
1604
|
+
performance_maps_param=(
|
|
1605
|
+
self.dense_matching_app.get_performance_map_parameters()
|
|
1606
|
+
),
|
|
1607
|
+
depth_map_dir=depth_map_dir,
|
|
1608
|
+
point_cloud_dir=triangulation_point_cloud_dir,
|
|
1609
|
+
save_output_coordinates=(len(self.pc_outlier_removal_apps) == 0)
|
|
1610
|
+
and (
|
|
1611
|
+
self.save_output_depth_map or self.save_output_point_cloud
|
|
1612
|
+
),
|
|
1613
|
+
save_output_color=bool(depth_map_dir)
|
|
1614
|
+
and self.auxiliary[out_cst.AUX_IMAGE],
|
|
1615
|
+
save_output_classification=bool(depth_map_dir)
|
|
1616
|
+
and self.auxiliary[out_cst.AUX_CLASSIFICATION],
|
|
1617
|
+
save_output_filling=bool(depth_map_dir)
|
|
1618
|
+
and self.auxiliary[out_cst.AUX_FILLING],
|
|
1619
|
+
save_output_performance_map=bool(depth_map_dir)
|
|
1620
|
+
and self.auxiliary[out_cst.AUX_PERFORMANCE_MAP],
|
|
1621
|
+
save_output_ambiguity=bool(depth_map_dir)
|
|
1622
|
+
and self.auxiliary[out_cst.AUX_AMBIGUITY],
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
if self.quit_on_app("triangulation"):
|
|
1626
|
+
continue # keep iterating over pairs, but don't go further
|
|
1627
|
+
|
|
1628
|
+
filtered_epipolar_point_cloud = epipolar_point_cloud
|
|
1629
|
+
for app_key, app in self.pc_outlier_removal_apps.items():
|
|
1630
|
+
|
|
1631
|
+
app_key_is_last = (
|
|
1632
|
+
app_key == list(self.pc_outlier_removal_apps)[-1]
|
|
1633
|
+
)
|
|
1634
|
+
filtering_depth_map_dir = (
|
|
1635
|
+
depth_map_dir if app_key_is_last else None
|
|
1636
|
+
)
|
|
1637
|
+
filtering_point_cloud_dir = (
|
|
1638
|
+
point_cloud_dir if app_key_is_last else None
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
filtered_epipolar_point_cloud = app.run(
|
|
1642
|
+
filtered_epipolar_point_cloud,
|
|
1643
|
+
depth_map_dir=filtering_depth_map_dir,
|
|
1644
|
+
point_cloud_dir=filtering_point_cloud_dir,
|
|
1645
|
+
dump_dir=os.path.join(
|
|
1646
|
+
self.dump_dir,
|
|
1647
|
+
( # pylint: disable=inconsistent-quotes
|
|
1648
|
+
f"pc_outlier_removal"
|
|
1649
|
+
f"{str(app_key[27:]).replace('.', '_')}"
|
|
1650
|
+
),
|
|
1651
|
+
pair_key,
|
|
1652
|
+
),
|
|
1653
|
+
epsg=self.epsg,
|
|
1654
|
+
orchestrator=self.cars_orchestrator,
|
|
1655
|
+
)
|
|
1656
|
+
if self.quit_on_app("point_cloud_outlier_removal"):
|
|
1657
|
+
continue # keep iterating over pairs, but don't go further
|
|
1658
|
+
|
|
1659
|
+
self.list_epipolar_point_clouds.append(
|
|
1660
|
+
filtered_epipolar_point_cloud
|
|
1661
|
+
)
|
|
1662
|
+
|
|
1663
|
+
# quit if any app in the loop over the pairs was the last one
|
|
1664
|
+
# pylint:disable=too-many-boolean-expressions
|
|
1665
|
+
if (
|
|
1666
|
+
self.quit_on_app("dense_matching")
|
|
1667
|
+
or self.quit_on_app("dense_match_filling")
|
|
1668
|
+
or self.quit_on_app("triangulation")
|
|
1669
|
+
or self.quit_on_app("point_cloud_outlier_removal.1")
|
|
1670
|
+
or self.quit_on_app("point_cloud_outlier_removal.2")
|
|
1671
|
+
):
|
|
1672
|
+
return True
|
|
1673
|
+
|
|
1674
|
+
return False
|
|
1675
|
+
|
|
1676
|
+
def rasterize_point_cloud(self):
|
|
1677
|
+
"""
|
|
1678
|
+
Final step of the pipeline: rasterize the point
|
|
1679
|
+
cloud created in the prior steps.
|
|
1680
|
+
"""
|
|
1681
|
+
|
|
1682
|
+
self.rasterization_dump_dir = os.path.join(
|
|
1683
|
+
self.dump_dir, "rasterization"
|
|
1684
|
+
)
|
|
1685
|
+
|
|
1686
|
+
dsm_file_name = (
|
|
1687
|
+
os.path.join(
|
|
1688
|
+
self.out_dir,
|
|
1689
|
+
out_cst.DSM_DIRECTORY,
|
|
1690
|
+
"dsm.tif",
|
|
1691
|
+
)
|
|
1692
|
+
if self.save_output_dsm
|
|
1693
|
+
else None
|
|
1694
|
+
)
|
|
1695
|
+
|
|
1696
|
+
weights_file_name = (
|
|
1697
|
+
os.path.join(
|
|
1698
|
+
self.out_dir,
|
|
1699
|
+
out_cst.DSM_DIRECTORY,
|
|
1700
|
+
"weights.tif",
|
|
1701
|
+
)
|
|
1702
|
+
if self.save_output_dsm
|
|
1703
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_WEIGHTS]
|
|
1704
|
+
else None
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
color_file_name = (
|
|
1708
|
+
os.path.join(
|
|
1709
|
+
self.out_dir,
|
|
1710
|
+
out_cst.DSM_DIRECTORY,
|
|
1711
|
+
"image.tif",
|
|
1712
|
+
)
|
|
1713
|
+
if self.save_output_dsm
|
|
1714
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_IMAGE]
|
|
1715
|
+
else None
|
|
1716
|
+
)
|
|
1717
|
+
|
|
1718
|
+
performance_map_file_name = (
|
|
1719
|
+
os.path.join(
|
|
1720
|
+
self.out_dir,
|
|
1721
|
+
out_cst.DSM_DIRECTORY,
|
|
1722
|
+
"performance_map.tif",
|
|
1723
|
+
)
|
|
1724
|
+
if self.save_output_dsm
|
|
1725
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
1726
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
1727
|
+
]
|
|
1728
|
+
else None
|
|
1729
|
+
)
|
|
1730
|
+
|
|
1731
|
+
ambiguity_file_name = (
|
|
1732
|
+
os.path.join(
|
|
1733
|
+
self.out_dir,
|
|
1734
|
+
out_cst.DSM_DIRECTORY,
|
|
1735
|
+
"ambiguity.tif",
|
|
1736
|
+
)
|
|
1737
|
+
if self.save_output_dsm
|
|
1738
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_AMBIGUITY]
|
|
1739
|
+
else None
|
|
1740
|
+
)
|
|
1741
|
+
|
|
1742
|
+
classif_file_name = (
|
|
1743
|
+
os.path.join(
|
|
1744
|
+
self.out_dir,
|
|
1745
|
+
out_cst.DSM_DIRECTORY,
|
|
1746
|
+
"classification.tif",
|
|
1747
|
+
)
|
|
1748
|
+
if self.save_output_dsm
|
|
1749
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
1750
|
+
out_cst.AUX_CLASSIFICATION
|
|
1751
|
+
]
|
|
1752
|
+
else None
|
|
1753
|
+
)
|
|
1754
|
+
|
|
1755
|
+
contributing_pair_file_name = (
|
|
1756
|
+
os.path.join(
|
|
1757
|
+
self.out_dir,
|
|
1758
|
+
out_cst.DSM_DIRECTORY,
|
|
1759
|
+
"contributing_pair.tif",
|
|
1760
|
+
)
|
|
1761
|
+
if self.save_output_dsm
|
|
1762
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
1763
|
+
out_cst.AUX_CONTRIBUTING_PAIR
|
|
1764
|
+
]
|
|
1765
|
+
else None
|
|
1766
|
+
)
|
|
1767
|
+
|
|
1768
|
+
filling_file_name = (
|
|
1769
|
+
os.path.join(
|
|
1770
|
+
self.out_dir,
|
|
1771
|
+
out_cst.DSM_DIRECTORY,
|
|
1772
|
+
"filling.tif",
|
|
1773
|
+
)
|
|
1774
|
+
if self.save_output_dsm
|
|
1775
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
|
|
1776
|
+
else None
|
|
1777
|
+
)
|
|
1778
|
+
|
|
1779
|
+
# rasterize point cloud
|
|
1780
|
+
_ = self.rasterization_application.run(
|
|
1781
|
+
self.point_cloud_to_rasterize,
|
|
1782
|
+
self.epsg,
|
|
1783
|
+
self.vertical_crs,
|
|
1784
|
+
resolution=self.resolution,
|
|
1785
|
+
orchestrator=self.cars_orchestrator,
|
|
1786
|
+
dsm_file_name=dsm_file_name,
|
|
1787
|
+
weights_file_name=weights_file_name,
|
|
1788
|
+
color_file_name=color_file_name,
|
|
1789
|
+
classif_file_name=classif_file_name,
|
|
1790
|
+
performance_map_file_name=performance_map_file_name,
|
|
1791
|
+
ambiguity_file_name=ambiguity_file_name,
|
|
1792
|
+
contributing_pair_file_name=contributing_pair_file_name,
|
|
1793
|
+
filling_file_name=filling_file_name,
|
|
1794
|
+
color_dtype=self.color_type,
|
|
1795
|
+
dump_dir=self.rasterization_dump_dir,
|
|
1796
|
+
performance_map_classes=self.used_conf[OUTPUT][AUXILIARY][
|
|
1797
|
+
out_cst.AUX_PERFORMANCE_MAP
|
|
1798
|
+
],
|
|
1799
|
+
phasing=self.phasing,
|
|
1800
|
+
)
|
|
1801
|
+
|
|
1802
|
+
# Cleaning: don't keep terrain bbox if save_intermediate_data
|
|
1803
|
+
# is not activated
|
|
1804
|
+
if not self.used_conf[PIPELINE][ADVANCED][
|
|
1805
|
+
adv_cst.SAVE_INTERMEDIATE_DATA
|
|
1806
|
+
]:
|
|
1807
|
+
self.cars_orchestrator.add_to_clean(
|
|
1808
|
+
os.path.join(self.dump_dir, "terrain_bbox")
|
|
1809
|
+
)
|
|
1810
|
+
|
|
1811
|
+
if self.quit_on_app("point_cloud_rasterization"):
|
|
1812
|
+
return True
|
|
1813
|
+
|
|
1814
|
+
# dsm needs to be saved before filling
|
|
1815
|
+
self.cars_orchestrator.breakpoint()
|
|
1816
|
+
|
|
1817
|
+
# saved used configuration
|
|
1818
|
+
self.save_configurations()
|
|
1819
|
+
|
|
1820
|
+
if (
|
|
1821
|
+
classif_file_name is not None
|
|
1822
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
1823
|
+
out_cst.AUX_CLASSIFICATION
|
|
1824
|
+
]
|
|
1825
|
+
):
|
|
1826
|
+
self.merge_classif_bands(
|
|
1827
|
+
classif_file_name,
|
|
1828
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][
|
|
1829
|
+
out_cst.AUX_CLASSIFICATION
|
|
1830
|
+
],
|
|
1831
|
+
dsm_file_name,
|
|
1832
|
+
)
|
|
1833
|
+
|
|
1834
|
+
if (
|
|
1835
|
+
filling_file_name is not None
|
|
1836
|
+
and self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING]
|
|
1837
|
+
):
|
|
1838
|
+
self.merge_filling_bands(
|
|
1839
|
+
filling_file_name,
|
|
1840
|
+
self.used_conf[OUTPUT][out_cst.AUXILIARY][out_cst.AUX_FILLING],
|
|
1841
|
+
dsm_file_name,
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
return False
|
|
1845
|
+
|
|
1846
|
+
@cars_profile(name="merge filling bands", interval=0.5)
|
|
1847
|
+
def merge_filling_bands(self, filling_path, aux_filling, dsm_file):
|
|
1848
|
+
"""
|
|
1849
|
+
Merge filling bands to get mono band in output
|
|
1850
|
+
"""
|
|
1851
|
+
|
|
1852
|
+
with rasterio.open(dsm_file) as in_dsm:
|
|
1853
|
+
dsm_msk = in_dsm.read_masks(1)
|
|
1854
|
+
|
|
1855
|
+
with rasterio.open(filling_path) as src:
|
|
1856
|
+
nb_bands = src.count
|
|
1857
|
+
|
|
1858
|
+
if nb_bands == 1:
|
|
1859
|
+
return False
|
|
1860
|
+
|
|
1861
|
+
filling_multi_bands = src.read()
|
|
1862
|
+
filling_mono_bands = np.zeros(filling_multi_bands.shape[1:3])
|
|
1863
|
+
descriptions = src.descriptions
|
|
1864
|
+
dict_temp = {name: i for i, name in enumerate(descriptions)}
|
|
1865
|
+
profile = src.profile
|
|
1866
|
+
|
|
1867
|
+
with warnings.catch_warnings():
|
|
1868
|
+
warnings.simplefilter("ignore", NodataShadowWarning)
|
|
1869
|
+
filling_mask = src.read_masks(1)
|
|
1870
|
+
|
|
1871
|
+
filling_mono_bands[filling_mask == 0] = 0
|
|
1872
|
+
|
|
1873
|
+
filling_bands_list = {
|
|
1874
|
+
"fill_with_geoid": ["filling_exogenous"],
|
|
1875
|
+
"interpolate_from_borders": [
|
|
1876
|
+
"bulldozer",
|
|
1877
|
+
"border_interpolation",
|
|
1878
|
+
],
|
|
1879
|
+
"fill_with_endogenous_dem": [
|
|
1880
|
+
"filling_exogenous",
|
|
1881
|
+
"bulldozer",
|
|
1882
|
+
],
|
|
1883
|
+
"fill_with_exogenous_dem": ["bulldozer"],
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
# To get the right footprint
|
|
1887
|
+
filling_mono_bands = np.logical_or(dsm_msk, filling_mask).astype(
|
|
1888
|
+
np.uint8
|
|
1889
|
+
)
|
|
1890
|
+
|
|
1891
|
+
# to keep the previous classif convention
|
|
1892
|
+
filling_mono_bands[filling_mono_bands == 0] = src.nodata
|
|
1893
|
+
filling_mono_bands[filling_mono_bands == 1] = 0
|
|
1894
|
+
|
|
1895
|
+
no_match = False
|
|
1896
|
+
for key, value in aux_filling.items():
|
|
1897
|
+
if isinstance(value, str):
|
|
1898
|
+
value = [value]
|
|
1899
|
+
|
|
1900
|
+
if isinstance(value, list):
|
|
1901
|
+
for elem in value:
|
|
1902
|
+
if elem != "other":
|
|
1903
|
+
filling_method = filling_bands_list[elem]
|
|
1904
|
+
|
|
1905
|
+
if all(
|
|
1906
|
+
method in descriptions
|
|
1907
|
+
for method in filling_method
|
|
1908
|
+
):
|
|
1909
|
+
indices_true = [
|
|
1910
|
+
dict_temp[m] for m in filling_method
|
|
1911
|
+
]
|
|
1912
|
+
|
|
1913
|
+
mask_true = np.all(
|
|
1914
|
+
filling_multi_bands[indices_true, :, :]
|
|
1915
|
+
== 1,
|
|
1916
|
+
axis=0,
|
|
1917
|
+
)
|
|
1918
|
+
|
|
1919
|
+
indices_false = [
|
|
1920
|
+
i
|
|
1921
|
+
for i in range(filling_multi_bands.shape[0])
|
|
1922
|
+
if i not in indices_true
|
|
1923
|
+
]
|
|
1924
|
+
|
|
1925
|
+
mask_false = np.all(
|
|
1926
|
+
filling_multi_bands[indices_false, :, :]
|
|
1927
|
+
== 0,
|
|
1928
|
+
axis=0,
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
mask = mask_true & mask_false
|
|
1932
|
+
|
|
1933
|
+
filling_mono_bands[mask] = key
|
|
1934
|
+
else:
|
|
1935
|
+
no_match = True
|
|
1936
|
+
|
|
1937
|
+
if no_match:
|
|
1938
|
+
mask_1 = np.all(
|
|
1939
|
+
filling_multi_bands[1:, :, :] == 1,
|
|
1940
|
+
axis=0,
|
|
1941
|
+
)
|
|
1942
|
+
|
|
1943
|
+
mask_2 = np.all(
|
|
1944
|
+
filling_mono_bands == 0,
|
|
1945
|
+
axis=0,
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
filling_mono_bands[mask_1 & mask_2] = (
|
|
1949
|
+
aux_filling["other"] if "other" in aux_filling else 50
|
|
1950
|
+
)
|
|
1951
|
+
|
|
1952
|
+
profile.update(count=1, dtype=filling_mono_bands.dtype)
|
|
1953
|
+
with rasterio.open(filling_path, "w", **profile) as src:
|
|
1954
|
+
src.write(filling_mono_bands, 1)
|
|
1955
|
+
|
|
1956
|
+
return True
|
|
1957
|
+
|
|
1958
|
+
@cars_profile(name="merge classif bands", interval=0.5)
|
|
1959
|
+
def merge_classif_bands(self, classif_path, aux_classif, dsm_file):
|
|
1960
|
+
"""
|
|
1961
|
+
Merge classif bands to get mono band in output
|
|
1962
|
+
"""
|
|
1963
|
+
with rasterio.open(dsm_file) as in_dsm:
|
|
1964
|
+
dsm_msk = in_dsm.read_masks(1)
|
|
1965
|
+
|
|
1966
|
+
with rasterio.open(classif_path) as src:
|
|
1967
|
+
nb_bands = src.count
|
|
1968
|
+
|
|
1969
|
+
if nb_bands == 1:
|
|
1970
|
+
return False
|
|
1971
|
+
|
|
1972
|
+
classif_multi_bands = src.read()
|
|
1973
|
+
classif_mono_band = np.zeros(classif_multi_bands.shape[1:3])
|
|
1974
|
+
descriptions = src.descriptions
|
|
1975
|
+
profile = src.profile
|
|
1976
|
+
classif_mask = src.read_masks(1)
|
|
1977
|
+
classif_mono_band[classif_mask == 0] = 0
|
|
1978
|
+
|
|
1979
|
+
# To get the right footprint
|
|
1980
|
+
classif_mono_band = np.logical_or(dsm_msk, classif_mask).astype(
|
|
1981
|
+
np.uint8
|
|
1982
|
+
)
|
|
1983
|
+
|
|
1984
|
+
# to keep the previous classif convention
|
|
1985
|
+
classif_mono_band[classif_mono_band == 0] = src.nodata
|
|
1986
|
+
classif_mono_band[classif_mono_band == 1] = 0
|
|
1987
|
+
|
|
1988
|
+
for key, value in aux_classif.items():
|
|
1989
|
+
if isinstance(value, int):
|
|
1990
|
+
num_band = descriptions.index(str(value))
|
|
1991
|
+
mask_1 = classif_mono_band == 0
|
|
1992
|
+
mask_2 = classif_multi_bands[num_band, :, :] == 1
|
|
1993
|
+
classif_mono_band[mask_1 & mask_2] = key
|
|
1994
|
+
elif isinstance(value, list):
|
|
1995
|
+
for elem in value:
|
|
1996
|
+
num_band = descriptions.index(str(elem))
|
|
1997
|
+
mask_1 = classif_mono_band == 0
|
|
1998
|
+
mask_2 = classif_multi_bands[num_band, :, :] == 1
|
|
1999
|
+
classif_mono_band[mask_1 & mask_2] = key
|
|
2000
|
+
|
|
2001
|
+
profile.update(count=1, dtype=classif_mono_band.dtype)
|
|
2002
|
+
with rasterio.open(classif_path, "w", **profile) as src:
|
|
2003
|
+
src.write(classif_mono_band, 1)
|
|
2004
|
+
|
|
2005
|
+
return True
|
|
2006
|
+
|
|
2007
|
+
@cars_profile(name="Preprocess depth maps", interval=0.5)
|
|
2008
|
+
def preprocess_depth_maps(self):
|
|
2009
|
+
"""
|
|
2010
|
+
Adds multiple processing steps to the depth maps :
|
|
2011
|
+
Merging.
|
|
2012
|
+
Creates the point cloud that will be rasterized in
|
|
2013
|
+
the last step of the pipeline.
|
|
2014
|
+
"""
|
|
2015
|
+
|
|
2016
|
+
self.point_cloud_to_rasterize = (
|
|
2017
|
+
self.list_epipolar_point_clouds,
|
|
2018
|
+
self.terrain_bounds,
|
|
2019
|
+
)
|
|
2020
|
+
self.color_type = self.point_cloud_to_rasterize[0][0].attributes.get(
|
|
2021
|
+
"color_type", None
|
|
2022
|
+
)
|
|
2023
|
+
|
|
2024
|
+
@cars_profile(name="Final cleanup", interval=0.5)
|
|
2025
|
+
def final_cleanup(self):
|
|
2026
|
+
"""
|
|
2027
|
+
Clean temporary files and directory at the end of cars processing
|
|
2028
|
+
"""
|
|
2029
|
+
|
|
2030
|
+
if not self.used_conf[PIPELINE][ADVANCED][
|
|
2031
|
+
adv_cst.SAVE_INTERMEDIATE_DATA
|
|
2032
|
+
]:
|
|
2033
|
+
# delete everything in tile_processing if save_intermediate_data is
|
|
2034
|
+
# not activated
|
|
2035
|
+
self.cars_orchestrator.add_to_clean(
|
|
2036
|
+
os.path.join(self.dump_dir, "tile_processing")
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
self.cars_orchestrator.add_to_clean(
|
|
2040
|
+
os.path.join(self.out_dir, "tie_points")
|
|
2041
|
+
)
|
|
2042
|
+
|
|
2043
|
+
# Remove dump_dir if no intermediate data should be written
|
|
2044
|
+
if not any(
|
|
2045
|
+
app.get("save_intermediate_data", False) is True
|
|
2046
|
+
for app in self.used_conf[PIPELINE][APPLICATIONS].values()
|
|
2047
|
+
if app is not None
|
|
2048
|
+
):
|
|
2049
|
+
self.cars_orchestrator.add_to_clean(self.dump_dir)
|
|
2050
|
+
|
|
2051
|
+
@cars_profile(name="run_surface_modeling_pipeline", interval=0.5)
|
|
2052
|
+
def run(
|
|
2053
|
+
self,
|
|
2054
|
+
which_resolution="single",
|
|
2055
|
+
log_dir=None,
|
|
2056
|
+
): # noqa C901
|
|
2057
|
+
"""
|
|
2058
|
+
Run pipeline
|
|
2059
|
+
|
|
2060
|
+
"""
|
|
2061
|
+
if log_dir is not None:
|
|
2062
|
+
self.log_dir = log_dir
|
|
2063
|
+
else:
|
|
2064
|
+
self.log_dir = os.path.join(self.out_dir, "logs")
|
|
2065
|
+
|
|
2066
|
+
self.texture_bands = self.used_conf[OUTPUT][AUXILIARY][
|
|
2067
|
+
out_cst.AUX_IMAGE
|
|
2068
|
+
]
|
|
2069
|
+
|
|
2070
|
+
self.auxiliary = self.used_conf[OUTPUT][out_cst.AUXILIARY]
|
|
2071
|
+
|
|
2072
|
+
self.which_resolution = which_resolution
|
|
2073
|
+
|
|
2074
|
+
# saved used configuration
|
|
2075
|
+
self.save_configurations()
|
|
2076
|
+
# start cars orchestrator
|
|
2077
|
+
with orchestrator.Orchestrator(
|
|
2078
|
+
orchestrator_conf=self.used_conf[ORCHESTRATOR],
|
|
2079
|
+
out_dir=self.out_dir,
|
|
2080
|
+
log_dir=self.log_dir,
|
|
2081
|
+
out_yaml_path=os.path.join(
|
|
2082
|
+
self.out_dir,
|
|
2083
|
+
out_cst.INFO_FILENAME,
|
|
2084
|
+
),
|
|
2085
|
+
) as self.cars_orchestrator:
|
|
2086
|
+
# initialize out_json
|
|
2087
|
+
self.cars_orchestrator.update_out_info({"version": __version__})
|
|
2088
|
+
|
|
2089
|
+
if self.compute_depth_map:
|
|
2090
|
+
self.sensor_to_depth_maps()
|
|
2091
|
+
|
|
2092
|
+
if self.save_output_dsm or self.save_output_point_cloud:
|
|
2093
|
+
self.preprocess_depth_maps()
|
|
2094
|
+
|
|
2095
|
+
if self.save_output_dsm:
|
|
2096
|
+
self.rasterize_point_cloud()
|
|
2097
|
+
|
|
2098
|
+
self.final_cleanup()
|