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