cars 1.0.0a1__cp311-cp311-win_amd64.whl → 1.0.0a3__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cars might be problematic. Click here for more details.
- cars/__init__.py +4 -4
- cars/applications/application.py +14 -6
- cars/applications/application_template.py +22 -0
- cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +15 -10
- 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_wrappers.py +48 -25
- cars/applications/dem_generation/dichotomic_generation_app.py +27 -9
- cars/applications/dem_generation/rasterization_app.py +85 -32
- cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +4 -0
- cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp311-win_amd64.dll.a +0 -0
- cars/applications/dense_match_filling/cpp/dense_match_filling_cpp.cp311-win_amd64.pyd +0 -0
- cars/applications/dense_match_filling/fill_disp_algo.py +41 -12
- cars/applications/dense_match_filling/plane_app.py +11 -0
- cars/applications/dense_match_filling/zero_padding_app.py +11 -1
- cars/applications/dense_matching/census_mccnn_sgm_app.py +254 -548
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp311-win_amd64.dll.a +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp311-win_amd64.pyd +0 -0
- cars/applications/dense_matching/dense_matching_algo.py +59 -11
- cars/applications/dense_matching/dense_matching_wrappers.py +51 -31
- cars/applications/dense_matching/disparity_grid_algo.py +566 -0
- 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 +78 -1
- cars/applications/dsm_filling/border_interpolation_app.py +10 -5
- cars/applications/dsm_filling/bulldozer_filling_app.py +14 -7
- cars/applications/dsm_filling/exogenous_filling_app.py +10 -5
- cars/applications/grid_generation/grid_correction_app.py +0 -53
- cars/applications/grid_generation/transform_grid.py +5 -5
- cars/applications/point_cloud_fusion/pc_fusion_algo.py +17 -11
- cars/applications/point_cloud_fusion/pc_fusion_wrappers.py +3 -4
- cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +9 -5
- cars/applications/point_cloud_outlier_removal/small_components_app.py +5 -3
- cars/applications/point_cloud_outlier_removal/statistical_app.py +4 -2
- cars/applications/rasterization/abstract_pc_rasterization_app.py +1 -0
- cars/applications/rasterization/rasterization_algo.py +20 -27
- cars/applications/rasterization/rasterization_wrappers.py +6 -5
- cars/applications/rasterization/simple_gaussian_app.py +30 -17
- cars/applications/resampling/resampling_algo.py +44 -49
- cars/applications/sparse_matching/sift_app.py +2 -22
- cars/applications/sparse_matching/sparse_matching_wrappers.py +0 -49
- cars/applications/triangulation/line_of_sight_intersection_app.py +1 -1
- cars/applications/triangulation/triangulation_wrappers.py +2 -1
- cars/bundleadjustment.py +51 -11
- cars/cars.py +15 -5
- cars/core/constants.py +1 -1
- cars/core/geometry/abstract_geometry.py +166 -12
- cars/core/geometry/shareloc_geometry.py +61 -14
- cars/core/inputs.py +15 -0
- cars/core/projection.py +117 -0
- cars/data_structures/cars_dataset.py +7 -5
- cars/orchestrator/cluster/log_wrapper.py +1 -1
- cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +1 -1
- cars/orchestrator/orchestrator.py +1 -1
- cars/orchestrator/registry/saver_registry.py +0 -78
- cars/pipelines/default/default_pipeline.py +69 -52
- cars/pipelines/parameters/advanced_parameters.py +17 -0
- cars/pipelines/parameters/advanced_parameters_constants.py +4 -0
- cars/pipelines/parameters/depth_map_inputs.py +22 -67
- cars/pipelines/parameters/dsm_inputs.py +16 -29
- cars/pipelines/parameters/output_parameters.py +44 -8
- cars/pipelines/parameters/sensor_inputs.py +117 -24
- cars/pipelines/parameters/sensor_loaders/basic_sensor_loader.py +3 -3
- cars/pipelines/parameters/sensor_loaders/pivot_sensor_loader.py +2 -2
- cars/pipelines/parameters/sensor_loaders/sensor_loader.py +4 -6
- cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +2 -2
- cars/pipelines/pipeline.py +8 -8
- cars/pipelines/unit/unit_pipeline.py +276 -274
- cars/starter.py +20 -1
- cars-1.0.0a3.dist-info/DELVEWHEEL +2 -0
- {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/METADATA +3 -2
- {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/RECORD +77 -74
- cars.libs/libgcc_s_seh-1-ca70890bbc5723b6d0ea31e9c9cded2b.dll +0 -0
- cars.libs/libstdc++-6-00ee19f73d5122a1277c137b1c218401.dll +0 -0
- cars.libs/libwinpthread-1-f5042e8e3d21edce20c1bc99445f551b.dll +0 -0
- cars-1.0.0a1.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.0a1.dist-info → cars-1.0.0a3.dist-info}/WHEEL +0 -0
- {cars-1.0.0a1.dist-info → cars-1.0.0a3.dist-info}/entry_points.txt +0 -0
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
# See the License for the specific language governing permissions and
|
|
19
19
|
# limitations under the License.
|
|
20
20
|
#
|
|
21
|
+
|
|
22
|
+
# pylint: disable=C0302
|
|
23
|
+
|
|
21
24
|
"""
|
|
22
25
|
this module contains the abstract geometry class to use in the
|
|
23
26
|
geometry plugins
|
|
@@ -30,7 +33,7 @@ from typing import Dict, List, Tuple, Union
|
|
|
30
33
|
import numpy as np
|
|
31
34
|
import rasterio as rio
|
|
32
35
|
import xarray as xr
|
|
33
|
-
from json_checker import And, Checker
|
|
36
|
+
from json_checker import And, Checker, Or
|
|
34
37
|
from scipy import interpolate
|
|
35
38
|
from scipy.interpolate import LinearNDInterpolator
|
|
36
39
|
from shapely.geometry import Polygon
|
|
@@ -50,7 +53,7 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
50
53
|
|
|
51
54
|
available_plugins: Dict = {}
|
|
52
55
|
|
|
53
|
-
def __new__(cls, geometry_plugin_conf=None, **kwargs):
|
|
56
|
+
def __new__(cls, geometry_plugin_conf=None, scaling_coeff=1, **kwargs):
|
|
54
57
|
"""
|
|
55
58
|
Return the required plugin
|
|
56
59
|
:raises:
|
|
@@ -59,13 +62,17 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
59
62
|
:param geometry_plugin_conf: plugin name or plugin configuration
|
|
60
63
|
to instantiate
|
|
61
64
|
:type geometry_plugin_conf: str or dict
|
|
65
|
+
:param scaling_coeff: scaling factor for resolution
|
|
66
|
+
:type scaling_coeff: float
|
|
62
67
|
:return: a geometry_plugin object
|
|
63
68
|
"""
|
|
64
69
|
if geometry_plugin_conf is not None:
|
|
65
70
|
if isinstance(geometry_plugin_conf, str):
|
|
66
71
|
geometry_plugin = geometry_plugin_conf
|
|
67
72
|
elif isinstance(geometry_plugin_conf, dict):
|
|
68
|
-
geometry_plugin = geometry_plugin_conf.get(
|
|
73
|
+
geometry_plugin = geometry_plugin_conf.get(
|
|
74
|
+
"plugin_name", "SharelocGeometry"
|
|
75
|
+
)
|
|
69
76
|
else:
|
|
70
77
|
raise RuntimeError("Not a supported type")
|
|
71
78
|
|
|
@@ -98,13 +105,17 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
98
105
|
dem=None,
|
|
99
106
|
geoid=None,
|
|
100
107
|
default_alt=None,
|
|
108
|
+
scaling_coeff=1,
|
|
101
109
|
**kwargs,
|
|
102
110
|
):
|
|
103
111
|
|
|
112
|
+
self.scaling_coeff = scaling_coeff
|
|
113
|
+
|
|
104
114
|
config = self.check_conf(geometry_plugin_conf)
|
|
105
115
|
|
|
106
116
|
self.plugin_name = config["plugin_name"]
|
|
107
117
|
self.interpolator = config["interpolator"]
|
|
118
|
+
self.dem_roi_margin = config["dem_roi_margin"]
|
|
108
119
|
|
|
109
120
|
self.dem = dem
|
|
110
121
|
self.dem_roi = None
|
|
@@ -153,12 +164,18 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
153
164
|
conf = {"plugin_name": conf}
|
|
154
165
|
|
|
155
166
|
# overload conf
|
|
156
|
-
overloaded_conf["plugin_name"] = conf.get(
|
|
167
|
+
overloaded_conf["plugin_name"] = conf.get(
|
|
168
|
+
"plugin_name", "SharelocGeometry"
|
|
169
|
+
)
|
|
157
170
|
overloaded_conf["interpolator"] = conf.get("interpolator", "cubic")
|
|
171
|
+
overloaded_conf["dem_roi_margin"] = conf.get(
|
|
172
|
+
"dem_roi_margin", float(self.scaling_coeff * 0.012)
|
|
173
|
+
)
|
|
158
174
|
|
|
159
175
|
geometry_schema = {
|
|
160
176
|
"plugin_name": str,
|
|
161
177
|
"interpolator": And(str, lambda x: x in ["cubic", "linear"]),
|
|
178
|
+
"dem_roi_margin": Or(float, int),
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
# Check conf
|
|
@@ -372,7 +389,6 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
372
389
|
array of size [number of points, 2]. The last index indicates
|
|
373
390
|
the 'x' coordinate (last index set to 0) or the 'y' coordinate
|
|
374
391
|
(last index set to 1).
|
|
375
|
-
:param interpolator: interpolator to use
|
|
376
392
|
:return: sensors positions as a numpy array of size
|
|
377
393
|
[number of points, 2]. The last index indicates the 'x'
|
|
378
394
|
coordinate (last index set to 0) or
|
|
@@ -387,6 +403,9 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
387
403
|
f"Grid type {type(grid)} not a dict or RectificationGrid"
|
|
388
404
|
)
|
|
389
405
|
|
|
406
|
+
# Ensure positions is a numpy array
|
|
407
|
+
positions = np.asarray(positions)
|
|
408
|
+
|
|
390
409
|
# Get data
|
|
391
410
|
with rio.open(grid["path"]) as grid_data:
|
|
392
411
|
row_dep = grid_data.read(2)
|
|
@@ -403,17 +422,42 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
403
422
|
cols = np.arange(ori_col, last_col, step_col)
|
|
404
423
|
rows = np.arange(ori_row, last_row, step_row)
|
|
405
424
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
|
|
425
|
+
# Determine margin based on interpolator type
|
|
426
|
+
margin = 6 if self.interpolator == "cubic" else 3
|
|
427
|
+
|
|
428
|
+
# Find the bounds of positions to determine crop region
|
|
429
|
+
min_col = np.nanmin(positions[:, 0])
|
|
430
|
+
max_col = np.nanmax(positions[:, 0])
|
|
431
|
+
min_row = np.nanmin(positions[:, 1])
|
|
432
|
+
max_row = np.nanmax(positions[:, 1])
|
|
433
|
+
|
|
434
|
+
# Convert position bounds to grid indices with margin
|
|
435
|
+
min_col_idx = max(0, int((min_col - ori_col) / step_col) - margin)
|
|
436
|
+
max_col_idx = min(
|
|
437
|
+
len(cols) - 1, int((max_col - ori_col) / step_col) + margin
|
|
438
|
+
)
|
|
439
|
+
min_row_idx = max(0, int((min_row - ori_row) / step_row) - margin)
|
|
440
|
+
max_row_idx = min(
|
|
441
|
+
len(rows) - 1, int((max_row - ori_row) / step_row) + margin
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Crop the grids and coordinate arrays
|
|
445
|
+
cols_cropped = cols[min_col_idx : max_col_idx + 1]
|
|
446
|
+
rows_cropped = rows[min_row_idx : max_row_idx + 1]
|
|
447
|
+
sensor_row_positions_cropped = row_dep[
|
|
448
|
+
min_row_idx : max_row_idx + 1, min_col_idx : max_col_idx + 1
|
|
449
|
+
]
|
|
450
|
+
sensor_col_positions_cropped = col_dep[
|
|
451
|
+
min_row_idx : max_row_idx + 1, min_col_idx : max_col_idx + 1
|
|
452
|
+
]
|
|
409
453
|
|
|
410
454
|
# interpolate sensor positions
|
|
411
455
|
interpolator = interpolate.RegularGridInterpolator(
|
|
412
|
-
(
|
|
456
|
+
(cols_cropped, rows_cropped),
|
|
413
457
|
np.stack(
|
|
414
458
|
(
|
|
415
|
-
|
|
416
|
-
|
|
459
|
+
sensor_row_positions_cropped.transpose(),
|
|
460
|
+
sensor_col_positions_cropped.transpose(),
|
|
417
461
|
),
|
|
418
462
|
axis=2,
|
|
419
463
|
),
|
|
@@ -583,6 +627,55 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
583
627
|
:return: Latitude, Longitude, Altitude coordinates list as a numpy array
|
|
584
628
|
"""
|
|
585
629
|
|
|
630
|
+
def safe_direct_loc(
|
|
631
|
+
self,
|
|
632
|
+
sensor,
|
|
633
|
+
geomodel,
|
|
634
|
+
x_coord: np.array,
|
|
635
|
+
y_coord: np.array,
|
|
636
|
+
z_coord: np.array = None,
|
|
637
|
+
) -> np.ndarray:
|
|
638
|
+
"""
|
|
639
|
+
For a given image points list, compute the latitudes,
|
|
640
|
+
longitudes, altitudes
|
|
641
|
+
|
|
642
|
+
Advice: to be sure, use x,y,z list inputs only
|
|
643
|
+
|
|
644
|
+
:param sensor: path to sensor image
|
|
645
|
+
:param geomodel: path and attributes for geomodel
|
|
646
|
+
:param x_coord: X Coordinates list in input image sensor
|
|
647
|
+
:param y_coord: Y Coordinate list in input image sensor
|
|
648
|
+
:param z_coord: Z Altitude list coordinate to take the image
|
|
649
|
+
:return: Latitude, Longitude, Altitude coordinates list as a numpy array
|
|
650
|
+
"""
|
|
651
|
+
if len(x_coord) > 0:
|
|
652
|
+
ground_points = self.direct_loc(
|
|
653
|
+
sensor,
|
|
654
|
+
geomodel,
|
|
655
|
+
x_coord,
|
|
656
|
+
y_coord,
|
|
657
|
+
z_coord,
|
|
658
|
+
)
|
|
659
|
+
else:
|
|
660
|
+
logging.warning("Direct loc function launched on empty list")
|
|
661
|
+
return []
|
|
662
|
+
if z_coord is None:
|
|
663
|
+
status = np.any(np.isnan(ground_points), axis=0)
|
|
664
|
+
if sum(status) > 0:
|
|
665
|
+
logging.warning(
|
|
666
|
+
"{} errors have been detected on direct "
|
|
667
|
+
"loc and will be re-launched".format(sum(status))
|
|
668
|
+
)
|
|
669
|
+
ground_points_retry = self.direct_loc(
|
|
670
|
+
sensor,
|
|
671
|
+
geomodel,
|
|
672
|
+
x_coord[status],
|
|
673
|
+
y_coord[status],
|
|
674
|
+
np.array([0]),
|
|
675
|
+
)
|
|
676
|
+
ground_points[:, status] = ground_points_retry
|
|
677
|
+
return ground_points
|
|
678
|
+
|
|
586
679
|
@abstractmethod
|
|
587
680
|
def inverse_loc(
|
|
588
681
|
self,
|
|
@@ -606,8 +699,65 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
606
699
|
:return: X / Y / Z Coordinates list in input image as a numpy array
|
|
607
700
|
"""
|
|
608
701
|
|
|
702
|
+
def safe_inverse_loc(
|
|
703
|
+
self,
|
|
704
|
+
sensor,
|
|
705
|
+
geomodel,
|
|
706
|
+
lat_coord: np.array,
|
|
707
|
+
lon_coord: np.array,
|
|
708
|
+
z_coord: np.array = None,
|
|
709
|
+
) -> np.ndarray:
|
|
710
|
+
"""
|
|
711
|
+
For a given image points list, compute the latitudes,
|
|
712
|
+
longitudes, altitudes
|
|
713
|
+
|
|
714
|
+
Advice: to be sure, use x,y,z list inputs only
|
|
715
|
+
|
|
716
|
+
:param sensor: path to sensor image
|
|
717
|
+
:param geomodel: path and attributes for geomodel
|
|
718
|
+
:param lat_coord: latitute Coordinate list
|
|
719
|
+
:param lon_coord: longitude Coordinates list
|
|
720
|
+
:param z_coord: Z Altitude list
|
|
721
|
+
:return: X / Y / Z Coordinates list in input image as a numpy array
|
|
722
|
+
"""
|
|
723
|
+
if len(lat_coord) > 0:
|
|
724
|
+
image_points = self.inverse_loc(
|
|
725
|
+
sensor,
|
|
726
|
+
geomodel,
|
|
727
|
+
lat_coord,
|
|
728
|
+
lon_coord,
|
|
729
|
+
z_coord,
|
|
730
|
+
)
|
|
731
|
+
image_points = np.array(image_points)
|
|
732
|
+
else:
|
|
733
|
+
logging.warning("Inverse loc function launched on empty list")
|
|
734
|
+
return [], [], []
|
|
735
|
+
if z_coord is None:
|
|
736
|
+
image_points = np.array(image_points)
|
|
737
|
+
status = np.any(np.isnan(image_points), axis=0)
|
|
738
|
+
if sum(status) > 0:
|
|
739
|
+
logging.warning(
|
|
740
|
+
"{} errors have been detected on inverse "
|
|
741
|
+
"loc and will be re-launched".format(sum(status))
|
|
742
|
+
)
|
|
743
|
+
image_points_retry = self.inverse_loc(
|
|
744
|
+
sensor,
|
|
745
|
+
geomodel,
|
|
746
|
+
lat_coord[status],
|
|
747
|
+
lon_coord[status],
|
|
748
|
+
np.array([self.default_alt]),
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
image_points[:, status] = image_points_retry
|
|
752
|
+
return image_points[0], image_points[1], image_points[2]
|
|
753
|
+
|
|
609
754
|
def image_envelope(
|
|
610
|
-
self,
|
|
755
|
+
self,
|
|
756
|
+
sensor,
|
|
757
|
+
geomodel,
|
|
758
|
+
out_path=None,
|
|
759
|
+
out_driver="ESRI Shapefile",
|
|
760
|
+
elevation=None,
|
|
611
761
|
):
|
|
612
762
|
"""
|
|
613
763
|
Export the image footprint to a vector file
|
|
@@ -629,24 +779,28 @@ class AbstractGeometry(metaclass=ABCMeta):
|
|
|
629
779
|
geomodel,
|
|
630
780
|
np.array(shift_x),
|
|
631
781
|
np.array(shift_y),
|
|
782
|
+
elevation,
|
|
632
783
|
)
|
|
633
784
|
lat_upper_right, lon_upper_right, _ = self.direct_loc(
|
|
634
785
|
sensor,
|
|
635
786
|
geomodel,
|
|
636
787
|
np.array(img_size_x + shift_x),
|
|
637
788
|
np.array(shift_y),
|
|
789
|
+
elevation,
|
|
638
790
|
)
|
|
639
791
|
lat_bottom_left, lon_bottom_left, _ = self.direct_loc(
|
|
640
792
|
sensor,
|
|
641
793
|
geomodel,
|
|
642
794
|
np.array(shift_x),
|
|
643
795
|
np.array(img_size_y + shift_y),
|
|
796
|
+
elevation,
|
|
644
797
|
)
|
|
645
798
|
lat_bottom_right, lon_bottom_right, _ = self.direct_loc(
|
|
646
799
|
sensor,
|
|
647
800
|
geomodel,
|
|
648
801
|
np.array(img_size_x + shift_x),
|
|
649
802
|
np.array(img_size_y + shift_y),
|
|
803
|
+
elevation,
|
|
650
804
|
)
|
|
651
805
|
|
|
652
806
|
u_l = (lon_upper_left, lat_upper_left)
|
|
@@ -63,6 +63,7 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
63
63
|
geoid=None,
|
|
64
64
|
default_alt=None,
|
|
65
65
|
pairs_for_roi=None,
|
|
66
|
+
scaling_coeff=1,
|
|
66
67
|
):
|
|
67
68
|
|
|
68
69
|
super().__init__(
|
|
@@ -71,6 +72,7 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
71
72
|
geoid=geoid,
|
|
72
73
|
default_alt=default_alt,
|
|
73
74
|
pairs_for_roi=pairs_for_roi,
|
|
75
|
+
scaling_coeff=scaling_coeff,
|
|
74
76
|
)
|
|
75
77
|
|
|
76
78
|
self.dem_roi = None
|
|
@@ -91,7 +93,11 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
91
93
|
self.dem_roi_epsg = inputs.rasterio_get_epsg(dem)
|
|
92
94
|
|
|
93
95
|
self.roi_shareloc = self.get_roi(
|
|
94
|
-
pairs_for_roi,
|
|
96
|
+
pairs_for_roi,
|
|
97
|
+
self.dem_roi_epsg,
|
|
98
|
+
z_min=0,
|
|
99
|
+
z_max=0,
|
|
100
|
+
margin=self.dem_roi_margin,
|
|
95
101
|
)
|
|
96
102
|
# change convention
|
|
97
103
|
self.dem_roi = [
|
|
@@ -105,14 +111,26 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
105
111
|
|
|
106
112
|
# fill_nodata option should be set when dealing with void in DTM
|
|
107
113
|
# see shareloc DTM limitations in sphinx doc for further details
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
dtm_image = dtm_reader(
|
|
117
|
+
dem,
|
|
118
|
+
geoid,
|
|
119
|
+
roi=self.roi_shareloc,
|
|
120
|
+
roi_is_in_physical_space=True,
|
|
121
|
+
fill_nodata="mean",
|
|
122
|
+
fill_value=0.0,
|
|
123
|
+
)
|
|
124
|
+
except RuntimeError as err:
|
|
125
|
+
mss = "the roi bounds are"
|
|
126
|
+
if mss in str(err):
|
|
127
|
+
new_except_mss = (
|
|
128
|
+
f"The extent of the roi lies outside "
|
|
129
|
+
f"the extent of the initial elevation : {err}"
|
|
130
|
+
)
|
|
131
|
+
raise RuntimeError(new_except_mss) from err
|
|
132
|
+
raise
|
|
133
|
+
|
|
116
134
|
self.elevation = (
|
|
117
135
|
bindings_cpp.DTMIntersection( # pylint: disable=I1101
|
|
118
136
|
dtm_image.epsg,
|
|
@@ -125,7 +143,7 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
125
143
|
else:
|
|
126
144
|
self.elevation = default_alt
|
|
127
145
|
|
|
128
|
-
def get_roi(self, pairs_for_roi, epsg, margin=0.
|
|
146
|
+
def get_roi(self, pairs_for_roi, epsg, z_min=0, z_max=0, margin=0.012):
|
|
129
147
|
"""
|
|
130
148
|
Compute region of interest for intersection of DEM
|
|
131
149
|
|
|
@@ -138,27 +156,56 @@ class SharelocGeometry(AbstractGeometry):
|
|
|
138
156
|
"""
|
|
139
157
|
coords_list = []
|
|
140
158
|
for image1, geomodel1, image2, geomodel2 in pairs_for_roi:
|
|
141
|
-
# Footprint of left image
|
|
159
|
+
# Footprint of left image with altitude z_min
|
|
142
160
|
coords_list.extend(
|
|
143
|
-
self.image_envelope(
|
|
161
|
+
self.image_envelope(
|
|
162
|
+
image1["main_file"], geomodel1, elevation=z_min
|
|
163
|
+
)
|
|
144
164
|
)
|
|
145
|
-
# Footprint of
|
|
165
|
+
# Footprint of left image with altitude z_max
|
|
146
166
|
coords_list.extend(
|
|
147
|
-
self.image_envelope(
|
|
167
|
+
self.image_envelope(
|
|
168
|
+
image1["main_file"], geomodel1, elevation=z_max
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
# Footprint of right image with altitude z_min
|
|
172
|
+
coords_list.extend(
|
|
173
|
+
self.image_envelope(
|
|
174
|
+
image2["main_file"], geomodel2, elevation=z_min
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
# Footprint of right image with altitude z_max
|
|
178
|
+
coords_list.extend(
|
|
179
|
+
self.image_envelope(
|
|
180
|
+
image2["main_file"], geomodel2, elevation=z_max
|
|
181
|
+
)
|
|
148
182
|
)
|
|
149
183
|
# Footprint of rectification grid (with margins)
|
|
150
184
|
image1 = SharelocGeometry.load_image(image1["main_file"])
|
|
151
185
|
geomodel1 = self.load_geom_model(geomodel1)
|
|
152
186
|
geomodel2 = self.load_geom_model(geomodel2)
|
|
187
|
+
|
|
188
|
+
# With altitude z_min
|
|
153
189
|
epipolar_extent = rectif.get_epipolar_extent(
|
|
154
190
|
image1,
|
|
155
191
|
geomodel1,
|
|
156
192
|
geomodel2,
|
|
193
|
+
elevation=z_min,
|
|
157
194
|
grid_margin=self.rectification_grid_margin,
|
|
158
195
|
)
|
|
159
196
|
lat_min, lon_min, lat_max, lon_max = list(epipolar_extent)
|
|
160
197
|
coords_list.extend([(lon_min, lat_min), (lon_max, lat_max)])
|
|
161
198
|
|
|
199
|
+
# With altitude z_max
|
|
200
|
+
epipolar_extent = rectif.get_epipolar_extent(
|
|
201
|
+
image1,
|
|
202
|
+
geomodel1,
|
|
203
|
+
geomodel2,
|
|
204
|
+
elevation=z_max,
|
|
205
|
+
grid_margin=self.rectification_grid_margin,
|
|
206
|
+
)
|
|
207
|
+
lat_min, lon_min, lat_max, lon_max = list(epipolar_extent)
|
|
208
|
+
coords_list.extend([(lon_min, lat_min), (lon_max, lat_max)])
|
|
162
209
|
lon_list, lat_list = list(zip(*coords_list)) # noqa: B905
|
|
163
210
|
roi = [
|
|
164
211
|
min(lat_list) - margin,
|
cars/core/inputs.py
CHANGED
|
@@ -36,6 +36,7 @@ import numpy as np
|
|
|
36
36
|
import rasterio as rio
|
|
37
37
|
import xarray as xr
|
|
38
38
|
from json_checker import Checker
|
|
39
|
+
from pyproj import CRS
|
|
39
40
|
from rasterio.warp import Resampling, calculate_default_transform, reproject
|
|
40
41
|
from rasterio.windows import Window
|
|
41
42
|
from shapely.geometry import shape
|
|
@@ -378,6 +379,20 @@ def rasterio_get_epsg(raster_file: str) -> int:
|
|
|
378
379
|
return epsg
|
|
379
380
|
|
|
380
381
|
|
|
382
|
+
def rasterio_get_crs(raster_file: str) -> CRS:
|
|
383
|
+
"""
|
|
384
|
+
Get the crs of an image file
|
|
385
|
+
|
|
386
|
+
:param raster_file: Image file
|
|
387
|
+
:return: The crs of the given image
|
|
388
|
+
"""
|
|
389
|
+
crs = None
|
|
390
|
+
with rio.open(raster_file, "r") as descriptor:
|
|
391
|
+
crs = descriptor.crs
|
|
392
|
+
|
|
393
|
+
return crs
|
|
394
|
+
|
|
395
|
+
|
|
381
396
|
def rasterio_transform_epsg(file_name, new_epsg):
|
|
382
397
|
"""
|
|
383
398
|
Modify epsg of raster file
|
cars/core/projection.py
CHANGED
|
@@ -182,6 +182,22 @@ def polygon_projection(poly: Polygon, epsg_in: int, epsg_out: int) -> Polygon:
|
|
|
182
182
|
return poly
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
def polygon_projection_crs(poly: Polygon, crs_in: CRS, crs_out: CRS) -> Polygon:
|
|
186
|
+
"""
|
|
187
|
+
Projects a polygon from an initial crs to another
|
|
188
|
+
|
|
189
|
+
:param poly: poly to project
|
|
190
|
+
:param crs_in: initial crs
|
|
191
|
+
:param crs_out: final crs
|
|
192
|
+
:return: The polygon in the final projection
|
|
193
|
+
"""
|
|
194
|
+
# Project polygon between CRS (keep always_xy for compatibility)
|
|
195
|
+
project = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
196
|
+
poly = transform(project.transform, poly)
|
|
197
|
+
|
|
198
|
+
return poly
|
|
199
|
+
|
|
200
|
+
|
|
185
201
|
def geo_to_ecef(
|
|
186
202
|
lat: np.ndarray, lon: np.ndarray, alt: np.ndarray
|
|
187
203
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
@@ -351,6 +367,27 @@ def point_cloud_conversion(
|
|
|
351
367
|
return cloud_in
|
|
352
368
|
|
|
353
369
|
|
|
370
|
+
def point_cloud_conversion_crs(
|
|
371
|
+
cloud_in: np.ndarray, crs_in: int, crs_out: int
|
|
372
|
+
) -> np.ndarray:
|
|
373
|
+
"""
|
|
374
|
+
Convert a point cloud from a SRS to another one.
|
|
375
|
+
|
|
376
|
+
:param cloud_in: cloud to project
|
|
377
|
+
:param crs_in: crs of the input SRS
|
|
378
|
+
:param crs_out: crs of the output SRS
|
|
379
|
+
:return: Projected point cloud
|
|
380
|
+
"""
|
|
381
|
+
# Project point cloud between CRS (keep always_xy for compatibility)
|
|
382
|
+
cloud_in = np.array(cloud_in).T
|
|
383
|
+
transformer = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
384
|
+
|
|
385
|
+
cloud_in = transformer.transform(*cloud_in)
|
|
386
|
+
cloud_in = np.array(cloud_in).T
|
|
387
|
+
|
|
388
|
+
return cloud_in
|
|
389
|
+
|
|
390
|
+
|
|
354
391
|
def get_xyz_np_array_from_dataset(
|
|
355
392
|
cloud_in: xr.Dataset,
|
|
356
393
|
) -> Tuple[np.array, List[int]]:
|
|
@@ -721,3 +758,83 @@ def get_ground_angles(
|
|
|
721
758
|
convergence_angle = np.degrees(utils.angle_vectors(enu1, enu2))
|
|
722
759
|
|
|
723
760
|
return az1, elev_angle1, az2, elev_angle2, convergence_angle
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def get_output_crs(epsg, out_conf):
|
|
764
|
+
"""
|
|
765
|
+
Détermine le CRS de sortie en fonction de la config.
|
|
766
|
+
"""
|
|
767
|
+
geoid = out_conf.get("geoid")
|
|
768
|
+
crs_epsg = CRS(f"EPSG:{epsg}")
|
|
769
|
+
|
|
770
|
+
if len(crs_epsg.axis_info) != 2:
|
|
771
|
+
return crs_epsg # the user himself set a 3D CRS
|
|
772
|
+
|
|
773
|
+
geoid_is_path = isinstance(geoid, str)
|
|
774
|
+
|
|
775
|
+
if geoid_is_path: # user given geoid
|
|
776
|
+
vepsg = guess_vcrs_from_file_name(geoid)
|
|
777
|
+
if vepsg is None:
|
|
778
|
+
custom_wkt = (
|
|
779
|
+
'VERTCRS["Custom geoid height",'
|
|
780
|
+
+ f' VDATUM["Custom geoid model (file: {geoid})"],'
|
|
781
|
+
+ " CS[vertical,1],"
|
|
782
|
+
+ ' AXIS["gravity-related height (h)", up],'
|
|
783
|
+
+ ' LENGTHUNIT["metre", 1, ID["EPSG", 9001]]'
|
|
784
|
+
"]"
|
|
785
|
+
)
|
|
786
|
+
logging.warning(
|
|
787
|
+
"Could not create a known VCRS from the geoid file."
|
|
788
|
+
)
|
|
789
|
+
return CRS.from_wkt(
|
|
790
|
+
f'COMPOUNDCRS["EPSG:{epsg} + Custom geoid height",'
|
|
791
|
+
f" {crs_epsg.to_wkt()},"
|
|
792
|
+
f" {custom_wkt}]"
|
|
793
|
+
)
|
|
794
|
+
# a vepsg was found using the geoid file
|
|
795
|
+
return CRS(f"EPSG:{epsg}+{vepsg}")
|
|
796
|
+
|
|
797
|
+
if geoid: # geoid == True
|
|
798
|
+
return CRS(f"EPSG:{epsg}+5773")
|
|
799
|
+
|
|
800
|
+
# geoid == False
|
|
801
|
+
wgs84_wkt = (
|
|
802
|
+
'VERTCRS["WGS 84 ellipsoidal height",'
|
|
803
|
+
+ ' VDATUM["WGS 84"],'
|
|
804
|
+
+ " CS[vertical,1],"
|
|
805
|
+
+ ' AXIS["ellipsoidal height (h)", up],'
|
|
806
|
+
+ ' LENGTHUNIT["metre", 1, ID["EPSG", 9001]]'
|
|
807
|
+
"]"
|
|
808
|
+
)
|
|
809
|
+
logging.warning("The output VCRS is WGS84.")
|
|
810
|
+
|
|
811
|
+
return CRS.from_wkt(
|
|
812
|
+
f'COMPOUNDCRS["EPSG:{epsg} + WGS84 ellipsoidal height",'
|
|
813
|
+
f" {crs_epsg.to_wkt()},"
|
|
814
|
+
f" {wgs84_wkt}]"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def guess_vcrs_from_file_name(filepath):
|
|
819
|
+
"""
|
|
820
|
+
Tries to detect the geoid's EPSG from the file name
|
|
821
|
+
"""
|
|
822
|
+
filename = os.path.basename(filepath).lower()
|
|
823
|
+
|
|
824
|
+
known_models = {
|
|
825
|
+
"egm96": 5773, # EGM96 height
|
|
826
|
+
"egm_96": 5773, # alias
|
|
827
|
+
"egm 96": 5773, # alias
|
|
828
|
+
"egm1996": 5773, # alias
|
|
829
|
+
"egm08": 3855, # EGM2008 height
|
|
830
|
+
"egm_08": 3855, # alias
|
|
831
|
+
"egm 08": 3855, # alias
|
|
832
|
+
"egm2008": 3855, # alias
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
for key, vepsg in known_models.items():
|
|
836
|
+
if key in filename:
|
|
837
|
+
return vepsg
|
|
838
|
+
|
|
839
|
+
# aucun match connu
|
|
840
|
+
return None
|
|
@@ -38,6 +38,7 @@ from typing import Dict
|
|
|
38
38
|
# Third party imports
|
|
39
39
|
import numpy as np
|
|
40
40
|
import pandas
|
|
41
|
+
import pyproj
|
|
41
42
|
import rasterio as rio
|
|
42
43
|
import xarray as xr
|
|
43
44
|
from rasterio.profiles import DefaultGTiffProfile
|
|
@@ -1327,12 +1328,13 @@ def dict_profile_to_rio_profile(dict_profile: Dict) -> Dict:
|
|
|
1327
1328
|
crs = None
|
|
1328
1329
|
if "crs" in dict_profile:
|
|
1329
1330
|
if dict_profile["crs"] is not None:
|
|
1330
|
-
if
|
|
1331
|
-
crs
|
|
1332
|
-
|
|
1333
|
-
|
|
1331
|
+
if (
|
|
1332
|
+
isinstance(dict_profile["crs"], str)
|
|
1333
|
+
and "EPSG:" in dict_profile["crs"]
|
|
1334
|
+
):
|
|
1335
|
+
crs = pyproj.CRS(dict_profile["crs"].replace("EPSG:", ""))
|
|
1334
1336
|
else:
|
|
1335
|
-
crs =
|
|
1337
|
+
crs = pyproj.CRS(dict_profile["crs"])
|
|
1336
1338
|
|
|
1337
1339
|
rio_profile["crs"] = crs
|
|
1338
1340
|
rio_profile["transform"] = transform
|