From 28452d66b1e6c7f85e71cca838bc3fdb5b45b103 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 10 Oct 2024 16:11:51 +0200 Subject: [PATCH 1/2] feat: add relative altitude offsets --- .../dense_matching/census_mccnn_sgm.py | 165 ++++++++++++------ .../dense_matching/dense_matching.py | 6 + .../parameters/advanced_parameters.py | 22 +++ .../advanced_parameters_constants.py | 2 + .../parameters/sensor_inputs_constants.py | 2 + .../sensor_to_dense_dsm_pipeline.py | 58 ++++-- 6 files changed, 195 insertions(+), 60 deletions(-) diff --git a/cars/applications/dense_matching/census_mccnn_sgm.py b/cars/applications/dense_matching/census_mccnn_sgm.py index 947af58b..89b199c3 100644 --- a/cars/applications/dense_matching/census_mccnn_sgm.py +++ b/cars/applications/dense_matching/census_mccnn_sgm.py @@ -513,6 +513,8 @@ def generate_disparity_grids( # noqa: C901 geom_plugin_with_dem_and_geoid, dmin=None, dmax=None, + altitude_delta_min=None, + altitude_delta_max=None, dem_median=None, dem_min=None, dem_max=None, @@ -536,6 +538,10 @@ def generate_disparity_grids( # noqa: C901 :type dmin: float :param dmax: maximum disparity :type dmax: float + :param altitude_delta_max: Delta max of altitude + :type altitude_delta_max: int + :param altitude_delta_min: Delta min of altitude + :type altitude_delta_min: int :param dem_median: path to median dem :type dem_median: str :param dem_min: path to minimum dem @@ -612,73 +618,128 @@ def generate_disparity_grids( # noqa: C901 if None not in (dmin, dmax): # use global disparity range - if None not in (dem_min, dem_max): + if None not in (dem_min, dem_max) or None not in ( + altitude_delta_min, + altitude_delta_max, + ): raise RuntimeError("Mix between local and global mode") grid_min[:, :] = dmin grid_max[:, :] = dmax - elif None not in (dem_min, dem_max, dem_median): + elif None not in (dem_min, dem_max, dem_median) or None not in ( + altitude_delta_min, + altitude_delta_max, + ): # use local disparity if None not in (dmin, dmax): raise RuntimeError("Mix between local and global mode") - # dem min max are the same shape - # dem median is not , hence we crop it - # Get associated alti mean / min / max values dem_median_shape = inputs.rasterio_get_size(dem_median) dem_median_width, dem_median_height = dem_median_shape + min_row = 0 + min_col = 0 + max_row = dem_median_height + max_col = dem_median_width + # get epsg terrain_epsg = inputs.rasterio_get_epsg(dem_median) # Get epipolar position of all dem mean transform = inputs.rasterio_get_transform(dem_median) - # get terrain bounds dem min - dem_min_bounds = inputs.rasterio_get_bounds(dem_min) + if None not in (dem_min, dem_max, dem_median): + dem_min_shape = inputs.rasterio_get_size(dem_min) - # find roi in dem_mean - roi_points = np.array( - [ - [dem_min_bounds[0], dem_min_bounds[1]], - [dem_min_bounds[0], dem_min_bounds[3]], - [dem_min_bounds[2], dem_min_bounds[1]], - [dem_min_bounds[2], dem_min_bounds[3]], - ] - ) + if dem_median_shape != dem_min_shape: + # dem min max are the same shape + # dem median is not , hence we crop it - # Transform points to terrain_epsg (dem min is in 4326) - roi_points_terrain = points_cloud_conversion( - roi_points, - 4326, - terrain_epsg, - ) + # get terrain bounds dem min + dem_min_bounds = inputs.rasterio_get_bounds(dem_min) - # Get pixel roi in dem mean - pixel_roi_dem_mean = inputs.rasterio_get_pixel_points( - dem_median, roi_points_terrain - ) - roi_lower_row = np.floor(np.min(pixel_roi_dem_mean[:, 0])) - roi_upper_row = np.ceil(np.max(pixel_roi_dem_mean[:, 0])) - roi_lower_col = np.floor(np.min(pixel_roi_dem_mean[:, 1])) - roi_upper_col = np.ceil(np.max(pixel_roi_dem_mean[:, 1])) - - min_row = int(max(0, roi_lower_row)) - max_row = int( - min( - dem_median_height, # number of rows - roi_upper_row, + # find roi in dem_mean + roi_points = np.array( + [ + [dem_min_bounds[0], dem_min_bounds[1]], + [dem_min_bounds[0], dem_min_bounds[3]], + [dem_min_bounds[2], dem_min_bounds[1]], + [dem_min_bounds[2], dem_min_bounds[3]], + ] + ) + + # Transform points to terrain_epsg (dem min is in 4326) + roi_points_terrain = points_cloud_conversion( + roi_points, + 4326, + terrain_epsg, + ) + + # Get pixel roi in dem mean + pixel_roi_dem_mean = inputs.rasterio_get_pixel_points( + dem_median, roi_points_terrain + ) + roi_lower_row = np.floor(np.min(pixel_roi_dem_mean[:, 0])) + roi_upper_row = np.ceil(np.max(pixel_roi_dem_mean[:, 0])) + roi_lower_col = np.floor(np.min(pixel_roi_dem_mean[:, 1])) + roi_upper_col = np.ceil(np.max(pixel_roi_dem_mean[:, 1])) + + min_row = int(max(0, roi_lower_row)) + max_row = int( + min( + dem_median_height, # number of rows + roi_upper_row, + ) + ) + min_col = int(max(0, roi_lower_col)) + max_col = int( + min( + dem_median_width, # number of columns + roi_upper_col, + ) + ) + + elif ( + None not in (altitude_delta_min, altitude_delta_max) + and geom_plugin_with_dem_and_geoid.plugin_name + == "SharelocGeometry" + ): + roi_points_terrain = np.array( + [ + [ + geom_plugin_with_dem_and_geoid.roi_shareloc[1], + geom_plugin_with_dem_and_geoid.roi_shareloc[0], + ], + [ + geom_plugin_with_dem_and_geoid.roi_shareloc[1], + geom_plugin_with_dem_and_geoid.roi_shareloc[2], + ], + [ + geom_plugin_with_dem_and_geoid.roi_shareloc[3], + geom_plugin_with_dem_and_geoid.roi_shareloc[0], + ], + [ + geom_plugin_with_dem_and_geoid.roi_shareloc[3], + geom_plugin_with_dem_and_geoid.roi_shareloc[2], + ], + ] ) - ) - min_col = int(max(0, roi_lower_col)) - max_col = int( - min( - dem_median_width, # number of columns - roi_upper_col, + + pixel_roi_dem_mean = inputs.rasterio_get_pixel_points( + dem_median, roi_points_terrain ) - ) + + roi_lower_row = np.floor(np.min(pixel_roi_dem_mean[:, 0])) + roi_upper_row = np.ceil(np.max(pixel_roi_dem_mean[:, 0])) + roi_lower_col = np.floor(np.min(pixel_roi_dem_mean[:, 1])) + roi_upper_col = np.ceil(np.max(pixel_roi_dem_mean[:, 1])) + + min_row = int(max(0, roi_lower_row)) + max_row = int(min(dem_median_height, roi_upper_row)) + min_col = int(max(0, roi_lower_col)) + max_col = int(min(dem_median_width, roi_upper_col)) # compute terrain positions to use (all dem min and max) row_indexes = range(min_row, max_row) @@ -707,13 +768,17 @@ def generate_disparity_grids( # noqa: C901 lon_mean = terrain_position_lon_lat[:, 0] lat_mean = terrain_position_lon_lat[:, 1] - # dem min and max are in 4326 - dem_min_list = inputs.rasterio_get_values( - dem_min, lon_mean, lat_mean, points_cloud_conversion - ) - dem_max_list = inputs.rasterio_get_values( - dem_max, lon_mean, lat_mean, points_cloud_conversion - ) + if None not in (dem_min, dem_max, dem_median): + # dem min and max are in 4326 + dem_min_list = inputs.rasterio_get_values( + dem_min, lon_mean, lat_mean, points_cloud_conversion + ) + dem_max_list = inputs.rasterio_get_values( + dem_max, lon_mean, lat_mean, points_cloud_conversion + ) + else: + dem_min_list = dem_median_list - altitude_delta_min + dem_max_list = dem_median_list + altitude_delta_max # sensors physical positions ( diff --git a/cars/applications/dense_matching/dense_matching.py b/cars/applications/dense_matching/dense_matching.py index 6be50170..bf6c6450 100644 --- a/cars/applications/dense_matching/dense_matching.py +++ b/cars/applications/dense_matching/dense_matching.py @@ -128,6 +128,8 @@ def generate_disparity_grids( geom_plugin_with_dem_and_geoid, dmin=None, dmax=None, + altitude_delta_min=None, + altitude_delta_max=None, dem_min=None, dem_max=None, pair_folder=None, @@ -154,6 +156,10 @@ def generate_disparity_grids( :type dmin: float :param dmax: maximum disparity :type dmax: float + :param altitude_delta_max: Delta max of altitude + :type altitude_delta_max: int + :param altitude_delta_min: Delta min of altitude + :type altitude_delta_min: int :param dem_min: path to minimum dem :type dem_min: str :param dem_max: path to maximum dem diff --git a/cars/pipelines/parameters/advanced_parameters.py b/cars/pipelines/parameters/advanced_parameters.py index 8ae4a631..ec1b2a5a 100644 --- a/cars/pipelines/parameters/advanced_parameters.py +++ b/cars/pipelines/parameters/advanced_parameters.py @@ -100,10 +100,22 @@ def check_advanced_parameters(conf, check_epipolar_a_priori=True): overloaded_conf[adv_cst.TERRAIN_A_PRIORI][adv_cst.DEM_MAX] = ( overloaded_conf[adv_cst.TERRAIN_A_PRIORI].get(adv_cst.DEM_MAX, None) ) + overloaded_conf[adv_cst.TERRAIN_A_PRIORI][ + adv_cst.ALTITUDE_DELTA_MIN + ] = overloaded_conf[adv_cst.TERRAIN_A_PRIORI].get( + adv_cst.ALTITUDE_DELTA_MIN, None + ) + overloaded_conf[adv_cst.TERRAIN_A_PRIORI][ + adv_cst.ALTITUDE_DELTA_MAX + ] = overloaded_conf[adv_cst.TERRAIN_A_PRIORI].get( + adv_cst.ALTITUDE_DELTA_MAX, None + ) terrain_a_priori_schema = { adv_cst.DEM_MEDIAN: str, adv_cst.DEM_MIN: Or(str, None), # TODO mandatory with local disp adv_cst.DEM_MAX: Or(str, None), + adv_cst.ALTITUDE_DELTA_MIN: Or(int, None), + adv_cst.ALTITUDE_DELTA_MAX: Or(int, None), } checker_terrain = Checker(terrain_a_priori_schema) checker_terrain.validate(overloaded_conf[adv_cst.TERRAIN_A_PRIORI]) @@ -148,6 +160,8 @@ def update_conf( dem_median=None, dem_min=None, dem_max=None, + altitude_delta_max=None, + altitude_delta_min=None, ): """ Update the conf with grid correction and disparity range @@ -194,3 +208,11 @@ def update_conf( conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI]["dem_min"] = dem_min if dem_max is not None: conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI]["dem_max"] = dem_max + if altitude_delta_max is not None: + conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][ + "altitude_delta_max" + ] = altitude_delta_max + if altitude_delta_min is not None: + conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][ + "altitude_delta_min" + ] = altitude_delta_min diff --git a/cars/pipelines/parameters/advanced_parameters_constants.py b/cars/pipelines/parameters/advanced_parameters_constants.py index 2e9741fa..98d5872d 100644 --- a/cars/pipelines/parameters/advanced_parameters_constants.py +++ b/cars/pipelines/parameters/advanced_parameters_constants.py @@ -40,3 +40,5 @@ DEM_MEDIAN = "dem_median" DEM_MIN = "dem_min" DEM_MAX = "dem_max" +ALTITUDE_DELTA_MAX = "altitude_delta_max" +ALTITUDE_DELTA_MIN = "altitude_delta_min" diff --git a/cars/pipelines/parameters/sensor_inputs_constants.py b/cars/pipelines/parameters/sensor_inputs_constants.py index 7ef2a88e..ef003f63 100644 --- a/cars/pipelines/parameters/sensor_inputs_constants.py +++ b/cars/pipelines/parameters/sensor_inputs_constants.py @@ -34,6 +34,8 @@ ROI = "roi" GEOID = "geoid" DEM_PATH = "dem" +ALTITUDE_DELTA_MIN = "altitude_delta_min" +ALTITUDE_DELTA_MAX = "altitude_delta_max" INPUT_IMG = "image" INPUT_MSK = "mask" diff --git a/cars/pipelines/sensor_to_dense_dsm/sensor_to_dense_dsm_pipeline.py b/cars/pipelines/sensor_to_dense_dsm/sensor_to_dense_dsm_pipeline.py index 18bdc286..213e5f5a 100644 --- a/cars/pipelines/sensor_to_dense_dsm/sensor_to_dense_dsm_pipeline.py +++ b/cars/pipelines/sensor_to_dense_dsm/sensor_to_dense_dsm_pipeline.py @@ -640,12 +640,24 @@ def run(self): # noqa C901 # We generate grids with dem if it is provided. # If not provided, grid are generated without dem and a dem # will be generated, to use later for a new grid generation** + altitude_delta_min = self.inputs.get( + sens_cst.INITIAL_ELEVATION, {} + ).get(sens_cst.ALTITUDE_DELTA_MIN, None) + altitude_delta_max = self.inputs.get( + sens_cst.INITIAL_ELEVATION, {} + ).get(sens_cst.ALTITUDE_DELTA_MAX, None) if ( self.inputs[sens_cst.INITIAL_ELEVATION][sens_cst.DEM_PATH] is None ): geom_plugin = self.geom_plugin_without_dem_and_geoid + + if None not in (altitude_delta_min, altitude_delta_max): + raise RuntimeError( + "Dem path is mandatory for " + "the use of altitude deltas" + ) else: geom_plugin = self.geom_plugin_with_dem_and_geoid @@ -873,7 +885,12 @@ def run(self): # noqa C901 dem_max = self.used_conf[ADVANCED][adv_cst.TERRAIN_A_PRIORI][ adv_cst.DEM_MAX ] - + altitude_delta_min = self.used_conf[ADVANCED][ + adv_cst.TERRAIN_A_PRIORI + ][adv_cst.ALTITUDE_DELTA_MIN] + altitude_delta_max = self.used_conf[ADVANCED][ + adv_cst.TERRAIN_A_PRIORI + ][adv_cst.ALTITUDE_DELTA_MAX] else: dem_generation_output_dir = os.path.join( dump_dir, "dem_generation" @@ -917,12 +934,20 @@ def run(self): # noqa C901 dem_max = dem.attributes[dem_gen_cst.DEM_MAX_PATH] # update used configuration with terrain a priori - advanced_parameters.update_conf( - self.used_conf, - dem_median=dem_median, - dem_min=dem_min, - dem_max=dem_max, - ) + if None not in (altitude_delta_min, altitude_delta_max): + advanced_parameters.update_conf( + self.used_conf, + dem_median=dem_median, + altitude_delta_min=altitude_delta_min, + altitude_delta_max=altitude_delta_max, + ) + else: + advanced_parameters.update_conf( + self.used_conf, + dem_median=dem_median, + dem_min=dem_min, + dem_max=dem_max, + ) # Define param use_global_disp_range = ( @@ -946,11 +971,11 @@ def run(self): # noqa C901 is False ): - if not ( + if ( self.inputs[sens_cst.INITIAL_ELEVATION][ sens_cst.DEM_PATH ] - is not None + is None ): # Generate grids with new MNT ( @@ -1149,7 +1174,7 @@ def run(self): # noqa C901 pair_folder=dense_matching_pair_folder, ) ) - else: + elif None in (altitude_delta_min, altitude_delta_max): # Generate min and max disp grids from dems disp_range_grid = ( self.dense_matching_app.generate_disparity_grids( @@ -1162,6 +1187,19 @@ def run(self): # noqa C901 pair_folder=dense_matching_pair_folder, ) ) + else: + # Generate min and max disp grids from deltas + disp_range_grid = ( + self.dense_matching_app.generate_disparity_grids( + pairs[pair_key]["sensor_image_right"], + pairs[pair_key]["corrected_grid_right"], + self.geom_plugin_with_dem_and_geoid, + altitude_delta_min=altitude_delta_min, + altitude_delta_max=altitude_delta_max, + dem_median=dem_median, + pair_folder=dense_matching_pair_folder, + ) + ) # Get margins used in dense matching, dense_matching_margins_fun = ( From 96673333eac70b596b4c87ecf18844e5f2e2a1d0 Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 11 Oct 2024 10:28:23 +0200 Subject: [PATCH 2/2] doc: add the relative altitude offsets in the doc --- docs/source/usage.rst | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index d7313419..e6798f57 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -181,15 +181,28 @@ The structure follows this organisation: The attribute contains all informations about initial elevation: dem path, geoid and default altitude - +-----------------------+--------------------------------+--------+----------------------+----------------------------+ - | Name | Description | Type | Default value | Required | - +=======================+================================+========+======================+============================+ - | *dem* | Path to DEM tiles | string | None | No | - +-----------------------+--------------------------------+--------+----------------------+----------------------------+ - | *geoid* | Geoid path | string | Cars internal geoid | No | - +-----------------------+--------------------------------+--------+----------------------+----------------------------+ + +-----------------------+----------------------------------------------------------------------------+--------+----------------------+----------------------------+----------------------------+ + | Name | Description | Type | Available value | Default value | Required | + +=======================+============================================================================+========+======================+============================+============================+ + | *dem* | Path to DEM tiles | string | | None | No | + +-----------------------+----------------------------------------------------------------------------+--------+----------------------+----------------------------+----------------------------+ + | *geoid* | Geoid path | string | | Cars internal geoid | No | + +-----------------------+----------------------------------------------------------------------------+--------+----------------------+----------------------------+----------------------------+ + | *altitude_delta_min* | constant delta in altitude (meters) between dem median and dem min | int | should be > 0 | None | No | + +-----------------------+----------------------------------------------------------------------------+--------+----------------------+----------------------------+----------------------------+ + | *altitude_delta_max* | constant delta in altitude (meters) between dem max and dem median | int | should be > 0 | None | No | + +-----------------------+----------------------------------------------------------------------------+--------+----------------------+----------------------------+----------------------------+ + + If no DEM path is provided, an internal dem is generated with sparse matches. If no geoid is provided, the default cars geoid is used (egm96). If no delta is provided, the dem_min and max generated with sparse matches will be used. + + The Deltas are used following this formula : + + .. code-block:: python + + dem_min = initial_elevation - altitude_delta_min + dem_max = initial_elevation + altitude_delta_max - If no DEM path is provided, an internal dem is generated with sparse matches. If no geoid is provided, the default cars geoid is used (egm96). + .. warning:: Dem path is mandatory for the use of the altitude deltas. When there is no DEM data available, a default height above ellipsoid of 0 is used (no coverage for some points or pixels with no_data in the DEM tiles) @@ -202,7 +215,9 @@ The structure follows this organisation: "inputs": { "initial_elevation": { "dem": "/path/to/srtm.tif", - "geoid": "/path/to/geoid.tif" + "geoid": "/path/to/geoid.tif", + "altitude_delta_min": 10, + "altitude_delta_max": 40 } } }