diff --git a/cars/applications/sparse_matching/__init__.py b/cars/applications/sparse_matching/__init__.py index cac6153a..9e9436e7 100644 --- a/cars/applications/sparse_matching/__init__.py +++ b/cars/applications/sparse_matching/__init__.py @@ -25,4 +25,4 @@ from cars.applications.sparse_matching.sparse_matching import SparseMatching -from . import sift +from . import pandora_sparse_matching, sift diff --git a/cars/applications/sparse_matching/pandora_sparse_matching.py b/cars/applications/sparse_matching/pandora_sparse_matching.py new file mode 100644 index 00000000..0b540807 --- /dev/null +++ b/cars/applications/sparse_matching/pandora_sparse_matching.py @@ -0,0 +1,646 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of CARS +# (see https://github.com/CNES/cars). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +this module contains the pandora_sparse_matching application class. +""" + +# pylint: disable=too-many-lines +# pylint: disable= C0302 + + +import collections +import logging +import os +from typing import Dict, Tuple + +import numpy as np +import pandas +import xarray as xr +from json_checker import And, Checker, Or + +import cars.orchestrator.orchestrator as ocht +from cars.applications.dense_matching.loaders.pandora_loader import ( + PandoraLoader, +) +from cars.applications.sparse_matching import ( + sparse_matching_tools as pandora_tools, +) +from cars.applications.sparse_matching.sparse_matching import SparseMatching +from cars.core import constants_disparity as cst_disp +from cars.core.utils import safe_makedirs +from cars.data_structures import cars_dataset + + +class PandoraSparseMatching( + SparseMatching, short_name=["pandora"] +): # pylint: disable=R0903,disable=R0902 + """ + Pandora low resolution class + """ + + def __init__(self, conf=None): + """ + Init function of PandoraSparseMatching + + :param conf: configuration for matching and resolution + :return: an application_to_use object + """ + + super().__init__(conf=conf) + + self.method = self.used_config["method"] + self.resolution = self.used_config["resolution"] + self.loader_conf = self.used_config["loader_conf"] + self.strip_margin = self.used_config["strip_margin"] + self.disparity_margin = self.used_config["disparity_margin"] + self.epipolar_error_upper_bound = self.used_config[ + "epipolar_error_upper_bound" + ] + self.epipolar_error_maximum_bias = self.used_config[ + "epipolar_error_maximum_bias" + ] + + self.elevation_delta_lower_bound = self.used_config[ + "elevation_delta_lower_bound" + ] + self.elevation_delta_upper_bound = self.used_config[ + "elevation_delta_upper_bound" + ] + + # minimum number of matches to continue with + self.minimum_nb_matches = self.used_config["minimum_nb_matches"] + + # filter + self.connection_val = self.used_config["connection_val"] + self.nb_pts_threshold = self.used_config["nb_pts_threshold"] + self.clusters_distance_threshold = self.used_config[ + "clusters_distance_threshold" + ] + self.filtered_elt_pos = self.used_config["filtered_elt_pos"] + self.matches_filter_knn = self.used_config["matches_filter_knn"] + self.matches_filter_dev_factor = self.used_config[ + "matches_filter_dev_factor" + ] + self.activated = self.used_config["activated"] + + # Saving files + self.save_intermediate_data = self.used_config["save_intermediate_data"] + + # init orchestrator + self.orchestrator = None + + def check_conf(self, conf): + """ + Check configuration + + :param conf: configuration to check + :type conf: dict + + :return: overloaded configuration + :rtype: dict + + """ + + # init conf + if conf is not None: + overloaded_conf = conf.copy() + else: + conf = {} + overloaded_conf = {} + + # Overload conf + overloaded_conf["method"] = conf.get("method", "pandora") + overloaded_conf["connection_val"] = conf.get("connection_val", 3.0) + overloaded_conf["nb_pts_threshold"] = conf.get("nb_pts_threshold", 80) + overloaded_conf["resolution"] = conf.get("resolution", 4) + overloaded_conf["strip_margin"] = conf.get("strip_margin", 10) + overloaded_conf["disparity_margin"] = conf.get("disparity_margin", 0.02) + overloaded_conf["epipolar_error_upper_bound"] = conf.get( + "epipolar_error_upper_bound", 10.0 + ) + overloaded_conf["epipolar_error_maximum_bias"] = conf.get( + "epipolar_error_maximum_bias", 0.0 + ) + + # minimum number of matches to continue with + overloaded_conf["minimum_nb_matches"] = conf.get( + "minimum_nb_matches", 100 + ) + + overloaded_conf["elevation_delta_lower_bound"] = conf.get( + "elevation_delta_lower_bound", None + ) + overloaded_conf["elevation_delta_upper_bound"] = conf.get( + "elevation_delta_upper_bound", None + ) + + # filter params + overloaded_conf["clusters_distance_threshold"] = conf.get( + "clusters_distance_threshold", None + ) + overloaded_conf["filtered_elt_pos"] = conf.get( + "filtered_elt_pos", False + ) + overloaded_conf["matches_filter_knn"] = conf.get( + "matches_filter_knn", 25 + ) + overloaded_conf["matches_filter_dev_factor"] = conf.get( + "matches_filter_dev_factor", 3.0 + ) + + overloaded_conf["activated"] = conf.get("activated", False) + + # check loader + loader_conf = conf.get("loader_conf", None) + + # TODO modify, use loader directly + pandora_loader = PandoraLoader( + conf=loader_conf, + use_cross_validation=True, + ) + + # Get params from loader + self.loader = pandora_loader + self.corr_config = collections.OrderedDict(pandora_loader.get_conf()) + + overloaded_conf["loader_conf"] = self.corr_config + + overloaded_conf["save_intermediate_data"] = conf.get( + "save_intermediate_data", False + ) + + application_schema = { + "method": str, + "disparity_margin": float, + "epipolar_error_upper_bound": And(float, lambda x: x > 0), + "epipolar_error_maximum_bias": And(float, lambda x: x >= 0), + "minimum_nb_matches": And(int, lambda x: x > 0), + "elevation_delta_lower_bound": Or(int, float, None), + "elevation_delta_upper_bound": Or(int, float, None), + "resolution": Or(int, list), + "loader_conf": Or(dict, collections.OrderedDict, str, None), + "strip_margin": And(int, lambda x: x > 0), + "connection_val": And(float, lambda x: x > 0), + "nb_pts_threshold": And(int, lambda x: x > 0), + "clusters_distance_threshold": Or(None, float), + "filtered_elt_pos": bool, + "matches_filter_knn": int, + "matches_filter_dev_factor": Or(int, float), + "activated": bool, + "save_intermediate_data": bool, + } + + # Check conf + checker = Checker(application_schema) + checker.validate(overloaded_conf) + + # Check consistency between bounds for elevation delta + elevation_delta_lower_bound = overloaded_conf[ + "elevation_delta_lower_bound" + ] + elevation_delta_upper_bound = overloaded_conf[ + "elevation_delta_upper_bound" + ] + if None not in ( + elevation_delta_lower_bound, + elevation_delta_upper_bound, + ): + if elevation_delta_lower_bound > elevation_delta_upper_bound: + raise ValueError( + "Upper bound must be bigger than " + "lower bound for expected elevation delta" + ) + + return overloaded_conf + + def get_save_matches(self): + """ + Get save_matches parameter + + :return: true is save_matches activated + :rtype: bool + + """ + + return self.save_intermediate_data + + def get_disparity_margin(self): + """ + Get disparity margin corresponding to sparse matches + + :return: margin in percent + + """ + return self.disparity_margin + + def get_connection_val(self): + """ + Get connection_val : + distance to use to consider that two points are connected + + :return: connection_val + :rtype: + + """ + return self.connection_val + + def get_nb_pts_threshold(self): + """ + Get nb_pts_threshold : + number of points to use to identify small clusters to filter + + :return: nb_pts_threshold + + """ + return self.nb_pts_threshold + + def get_minimum_nb_matches(self): + """ + Get minimum_nb_matches : + get the minimum number of matches + + :return: minimum_nb_matches + + """ + + return self.minimum_nb_matches + + def get_matches_filter_knn(self): + """ + Get matches_filter_knn : + number of neighboors used to measure isolation of matches + + :return: matches_filter_knn + + """ + return self.matches_filter_knn + + def get_matches_filter_dev_factor(self): + """ + Get matches_filter_dev_factor : + factor of deviation in the formula + to compute threshold of outliers + + :return: matches_filter_dev_factor + + """ + return self.matches_filter_dev_factor + + def get_filtered_elt_pos(self): + """ + Get filtered_elt_pos : + if filtered_elt_pos is set to True, \ + the removed points positions in their original \ + epipolar images are returned, otherwise it is set to None + + :return: filtered_elt_pos + + """ + return self.filtered_elt_pos + + def get_clusters_dist_thresh(self): + """ + Get clusters_distance_threshold : + distance to use to consider if two points clusters \ + are far from each other or not (set to None to deactivate \ + this level of filtering) + + :return: clusters_distance_threshold + + """ + return self.clusters_distance_threshold + + def get_strip_margin(self): + """ + Get strip margin corresponding to sparse matches + + :return: margin in percent + + """ + return self.strip_margin + + def get_epipolar_error_upper_bound(self): + """ + Get epipolar error upper bound corresponding to sparse matches + + :return: margin + + """ + + return self.epipolar_error_upper_bound + + def get_epipolar_error_maximum_bias(self): + """ + Get epipolar error maximum bias corresponding to sparse matches + + :return: margin + + """ + + return self.epipolar_error_maximum_bias + + def run( + self, + epipolar_image_left, + epipolar_image_right, + orchestrator=None, + pair_folder=None, + pair_key="PAIR_0", + disp_to_alt_ratio=None, + ): + """ + Run PandoraSparseMatching application. + + Get matches using pandora in low resolution + + :param epipolar_image_left: tiled left epipolar CarsDataset contains: + :param epipolar_image_left: tiled left epipolar CarsDataset contains: + + - N x M Delayed tiles. \ + Each tile will be a future xarray Dataset containing: + + - data with keys : "im", "msk", "color" + - attrs with keys: "margins" with "disp_min" and "disp_max"\ + "transform", "crs", "valid_pixels", "no_data_mask",\ + "no_data_img" + - attributes containing: + "largest_epipolar_region","opt_epipolar_tile_size" + :type epipolar_image_left: CarsDataset + :param epipolar_image_right: tiled right epipolar CarsDataset contains: + + - N x M Delayed tiles. \ + Each tile will be a future xarray Dataset containing: + + - data with keys : "im", "msk", "color" + - attrs with keys: "margins" with "disp_min" and "disp_max" + "transform", "crs", "valid_pixels", "no_data_mask", + "no_data_img" + - attributes containing: + "largest_epipolar_region","opt_epipolar_tile_size" + :type epipolar_image_right: CarsDataset + :param orchestrator: orchestrator used + :param pair_folder: folder used for current pair + :type pair_folder: str + :param pair_key: pair id + :type pair_key: str + :param disp_to_alt_ratio: disp to alti ratio used for performance map + :type disp_to_alt_ratio: float + + :return: left matches, right matches. Each CarsDataset contains: + + - N x M Delayed tiles \ + Each tile will be a future pandas DataFrame containing: + - data : (L, 4) shape matches + - attributes containing "disp_lower_bound", "disp_upper_bound",\ + "elevation_delta_lower_bound","elevation_delta_upper_bound" + + :rtype: Tuple(CarsDataset, CarsDataset) + + """ + + # Default orchestrator + if orchestrator is None: + self.orchestrator = ocht.Orchestrator( + orchestrator_conf={"mode": "sequential"} + ) + else: + self.orchestrator = orchestrator + + if pair_folder is None: + pair_folder = os.path.join(self.orchestrator.out_dir, "tmp") + + if epipolar_image_left.dataset_type == "arrays": + # Create CarsDataset + # Epipolar_disparity + pandora_epipolar_matches = cars_dataset.CarsDataset( + "points", name="pandora_sparse_matching_" + pair_key + ) + pandora_epipolar_matches.create_empty_copy(epipolar_image_left) + + # Update attributes to get epipolar info + pandora_epipolar_matches.attributes.update( + epipolar_image_left.attributes + ) + + pandora_epipolar_disparity_map = cars_dataset.CarsDataset( + "arrays", name="pandora_sparse_matching_" + pair_key + ) + + pandora_epipolar_disparity_map.create_empty_copy( + epipolar_image_left + ) + pandora_epipolar_disparity_map.overlaps *= 0 + # Update attributes to get epipolar info + pandora_epipolar_disparity_map.attributes.update( + epipolar_image_left.attributes + ) + + # Save disparity maps + if self.save_intermediate_data: + safe_makedirs(pair_folder) + + self.orchestrator.add_to_save_lists( + os.path.join(pair_folder, "epi_disp.tif"), + cst_disp.MAP, + pandora_epipolar_disparity_map, + cars_ds_name="epi_disp", + ) + + self.orchestrator.add_to_save_lists( + os.path.join(pair_folder, "epi_pandora_matches_left"), + None, + pandora_epipolar_matches, + cars_ds_name="epi_pandora_matches_left", + ) + + # Get saving infos in order to save tiles when they are computed + [saving_info_matches] = self.orchestrator.get_saving_infos( + [pandora_epipolar_matches] + ) + + [saving_info_disparity_map] = self.orchestrator.get_saving_infos( + [pandora_epipolar_disparity_map] + ) + + # Add to replace list so tiles will be readable at the same time + self.orchestrator.add_to_replace_lists( + pandora_epipolar_matches, + cars_ds_name="epi_pandora_matches_left", + ) + + # Compute disparity range + if self.elevation_delta_lower_bound is None: + disp_upper_bound = np.inf + else: + disp_upper_bound = ( + -self.elevation_delta_lower_bound / disp_to_alt_ratio + ) + if self.elevation_delta_upper_bound is None: + disp_lower_bound = -np.inf + else: + disp_lower_bound = ( + -self.elevation_delta_upper_bound / disp_to_alt_ratio + ) + + # Generate disparity maps + for col in range(pandora_epipolar_matches.shape[1]): + for row in range(pandora_epipolar_matches.shape[0]): + # initialize list of matches + full_saving_info_matches = ocht.update_saving_infos( + saving_info_matches, row=row, col=col + ) + full_saving_info_disp_map = ocht.update_saving_infos( + saving_info_disparity_map, row=row, col=col + ) + # Compute matches + if type(None) not in ( + type(epipolar_image_left[row, col]), + type(epipolar_image_right[row, col]), + ): + ( + pandora_epipolar_matches[row, col], + pandora_epipolar_disparity_map[row, col], + ) = self.orchestrator.cluster.create_task( + compute_pandora_matches_wrapper, nout=2 + )( + epipolar_image_left[row, col], + epipolar_image_right[row, col], + self.corr_config, + disp_upper_bound=disp_upper_bound, + disp_lower_bound=disp_lower_bound, + resolution=self.resolution, + disp_to_alt_ratio=disp_to_alt_ratio, + saving_info_matches=full_saving_info_matches, + saving_info_disparity_map=full_saving_info_disp_map, + ) + + else: + logging.error( + "PandoraSparseMatching application doesn't " + "support this input data format" + ) + + return pandora_epipolar_matches, None + + +def compute_pandora_matches_wrapper( + left_image_object: xr.Dataset, + right_image_object: xr.Dataset, + corr_conf, + disp_upper_bound, + disp_lower_bound, + resolution, + disp_to_alt_ratio=None, + saving_info_matches=None, + saving_info_disparity_map=None, +) -> Dict[str, Tuple[xr.Dataset, xr.Dataset]]: + """ + Compute pandora matches from image objects. + This function will be run as a delayed task. + + User must provide saving infos to save properly created datasets + + :param left_image_object: tiled Left image dataset with : + + - cst.EPI_IMAGE + - cst.EPI_MSK (if given) + - cst.EPI_COLOR (for left, if given) + :type left_image_object: xr.Dataset with : + + - cst.EPI_IMAGE + - cst.EPI_MSK (if given) + - cst.EPI_COLOR (for left, if given) + :param right_image_object: tiled Right image + :type right_image_object: xr.Dataset + :param disp_upper_bound: upper bound of disparity range + :type disp_upper_bound: float + :param disp_lower_bound: lower bound of disparity range + :type disp_lower_bound: float + :param corr_conf: pandora conf + :type corr_conf: dict + :param resolution: resolution for downsampling + :type resolution: int or list + :param disp_to_alt_ratio: disp to alti ratio used for performance map + :type disp_to_alt_ratio: float + + + :return: Left pandora matches object,\ + Right pandora matches object (if exists) + + """ + list_matches = None + if isinstance(resolution, list): + for res in resolution: + if res == np.max(resolution): + matches, disp_map_dataset = pandora_tools.pandora_matches( + left_image_object, + right_image_object, + corr_conf, + disp_upper_bound, + disp_lower_bound, + res, + disp_to_alt_ratio, + ) + else: + matches, _ = pandora_tools.pandora_matches( + left_image_object, + right_image_object, + corr_conf, + disp_upper_bound, + disp_lower_bound, + res, + disp_to_alt_ratio, + ) + + if list_matches is None: + list_matches = matches + else: + list_matches = np.row_stack((list_matches, matches)) + else: + matches, disp_map_dataset = pandora_tools.pandora_matches( + left_image_object, + right_image_object, + corr_conf, + disp_upper_bound, + disp_lower_bound, + resolution, + disp_to_alt_ratio, + ) + + # Resample the matches in full resolution + left_pandora_matches_dataframe = pandas.DataFrame(matches) + + cars_dataset.fill_dataframe( + left_pandora_matches_dataframe, + saving_info=saving_info_matches, + attributes=None, + ) + + # Fill with attributes + cars_dataset.fill_dataset( + disp_map_dataset, + saving_info=saving_info_disparity_map, + window=cars_dataset.get_window_dataset(left_image_object), + profile=cars_dataset.get_profile_rasterio(left_image_object), + attributes=None, + overlaps=None, # overlaps are removed + ) + return left_pandora_matches_dataframe, disp_map_dataset diff --git a/cars/applications/sparse_matching/sift.py b/cars/applications/sparse_matching/sift.py index a3a86247..fae07af4 100644 --- a/cars/applications/sparse_matching/sift.py +++ b/cars/applications/sparse_matching/sift.py @@ -26,7 +26,6 @@ # Standard imports import logging -import math import os from typing import Dict, Tuple @@ -44,12 +43,11 @@ from cars.applications.sparse_matching import sparse_matching_tools from cars.applications.sparse_matching.sparse_matching import SparseMatching from cars.core import constants as cst -from cars.core.geometry.abstract_geometry import AbstractGeometry from cars.core.utils import safe_makedirs from cars.data_structures import cars_dataset -class Sift(SparseMatching, short_name="sift"): +class Sift(SparseMatching, short_name=["sift"]): """ SparseMatching """ @@ -104,8 +102,6 @@ def __init__(self, conf=None): self.sift_magnification = self.used_config["sift_magnification"] self.sift_window_size = self.used_config["sift_window_size"] self.sift_back_matching = self.used_config["sift_back_matching"] - - # sifts filter self.matches_filter_knn = self.used_config["matches_filter_knn"] self.matches_filter_dev_factor = self.used_config[ "matches_filter_dev_factor" @@ -184,7 +180,6 @@ def check_conf(self, conf): "sift_back_matching", True ) - # sifts filter params overloaded_conf["matches_filter_knn"] = conf.get( "matches_filter_knn", 25 ) @@ -265,102 +260,71 @@ def get_disparity_margin(self): """ return self.disparity_margin - def get_matches_filter_knn(self): + def get_strip_margin(self): """ - Get matches_filter_knn : - number of neighboors used to measure isolation of matches + Get strip margin corresponding to sparse matches - :return: matches_filter_knn + :return: margin in percent """ - return self.matches_filter_knn + return self.strip_margin - def get_matches_filter_dev_factor(self): + def get_epipolar_error_upper_bound(self): """ - Get matches_filter_dev_factor : - factor of deviation in the formula - to compute threshold of outliers + Get epipolar error upper bound corresponding to sparse matches - :return: matches_filter_dev_factor + :return: margin """ - return self.matches_filter_dev_factor - def get_margins_fun(self, disp_min=None, disp_max=None): - """ - Get margins function to use in resampling + return self.epipolar_error_upper_bound - :param disp_min: disp min for info - :param disp_max: disp max for info + def get_minimum_nb_matches(self): + """ + Get minimum_nb_matches : + get the minimum number of matches - :return: margins function - :rtype: function generating xr.Dataset + :return: minimum_nb_matches """ - # Compute margins - corner = ["left", "up", "right", "down"] - data = np.zeros(len(corner)) - col = np.arange(len(corner)) - margins = xr.Dataset( - {"left_margin": (["col"], data)}, coords={"col": col} - ) - margins["right_margin"] = xr.DataArray(data, dims=["col"]) + return self.minimum_nb_matches - left_margin = self.strip_margin - right_margin = left_margin + int( - math.floor( - self.epipolar_error_upper_bound - + self.epipolar_error_maximum_bias - ) - ) - - # Compute margins for left region - margins["left_margin"].data = [0, left_margin, 0, left_margin] + def get_epipolar_error_maximum_bias(self): + """ + Get epipolar error maximum bias corresponding to sparse matches - # Compute margins for right region - margins["right_margin"].data = [0, right_margin, 0, right_margin] + :return: margin - # add disp range info - margins.attrs["disp_min"] = disp_min - margins.attrs["disp_max"] = disp_max + """ - logging.info( - "Margins added to left region for matching: {}".format( - margins["left_margin"].data - ) - ) + return self.epipolar_error_maximum_bias - logging.info( - "Margins added to right region for matching: {}".format( - margins["right_margin"].data - ) - ) + def get_matches_filter_knn(self): + """ + Get matches_filter_knn : + number of neighboors used to measure isolation of matches - def margins_wrapper( # pylint: disable=unused-argument - row_min, row_max, col_min, col_max - ): - """ - Generates margins Dataset used in resampling + :return: matches_filter_knn - :param row_min: row min - :param row_max: row max - :param col_min: col min - :param col_max: col max + """ + return self.matches_filter_knn - :return: margins - :rtype: xr.Dataset - """ + def get_matches_filter_dev_factor(self): + """ + Get matches_filter_dev_factor : + factor of deviation in the formula + to compute threshold of outliers - # Constant margins for all tiles - return margins + :return: matches_filter_dev_factor - return margins_wrapper + """ + return self.matches_filter_dev_factor def run( self, - epipolar_images_left, - epipolar_images_right, + epipolar_image_left, + epipolar_image_right, disp_to_alt_ratio, orchestrator=None, pair_folder=None, @@ -371,9 +335,9 @@ def run( Create left and right CarsDataset filled with pandas.DataFrame , corresponding to epipolar 2D disparities, on the same geometry - that epipolar_images_left and epipolar_images_right. + that epipolar_image_left and epipolar_image_right. - :param epipolar_images_left: tiled left epipolar. CarsDataset contains: + :param epipolar_image_left: tiled left epipolar. CarsDataset contains: - N x M Delayed tiles \ Each tile will be a future xarray Dataset containing: @@ -384,8 +348,8 @@ def run( "no_data_img" - attributes containing: "largest_epipolar_region","opt_epipolar_tile_size" - :type epipolar_images_left: CarsDataset - :param epipolar_images_right: tiled right epipolar.CarsDataset contains: + :type epipolar_image_left: CarsDataset + :param epipolar_image_right: tiled right epipolar.CarsDataset contains: - N x M Delayed tiles \ Each tile will be a future xarray Dataset containing: @@ -396,7 +360,7 @@ def run( "no_data_img" - attributes containing:"largest_epipolar_region", \ "opt_epipolar_tile_size" - :type epipolar_images_right: CarsDataset + :type epipolar_image_right: CarsDataset :param disp_to_alt_ratio: disp to alti ratio :type disp_to_alt_ratio: float :param orchestrator: orchestrator used @@ -415,7 +379,6 @@ def run( :rtype: Tuple(CarsDataset, CarsDataset) """ - # Default orchestrator if orchestrator is None: # Create default sequential orchestrator for current application @@ -430,17 +393,17 @@ def run( if pair_folder is None: pair_folder = os.path.join(self.orchestrator.out_dir, "tmp") - if epipolar_images_left.dataset_type == "arrays": + if epipolar_image_left.dataset_type == "arrays": # Create CarsDataset # Epipolar_disparity epipolar_disparity_map_left = cars_dataset.CarsDataset( "points", name="sparse_matching_" + pair_key ) - epipolar_disparity_map_left.create_empty_copy(epipolar_images_left) + epipolar_disparity_map_left.create_empty_copy(epipolar_image_left) # Update attributes to get epipolar info epipolar_disparity_map_left.attributes.update( - epipolar_images_left.attributes + epipolar_image_left.attributes ) # check sift_peak_threshold with image type # only if sift_peak_threshold is None @@ -525,7 +488,6 @@ def run( self.orchestrator.add_to_replace_lists( epipolar_disparity_map_left, cars_ds_name="epi_matches_left" ) - # Generate disparity maps for row in range(epipolar_disparity_map_left.shape[0]): # initialize list of matches @@ -534,16 +496,16 @@ def run( ) # Compute matches if type(None) not in ( - type(epipolar_images_left[row, 0]), - type(epipolar_images_right[row, 0]), + type(epipolar_image_left[row, 0]), + type(epipolar_image_right[row, 0]), ): ( epipolar_disparity_map_left[row, 0] ) = self.orchestrator.cluster.create_task( compute_matches_wrapper, nout=1 )( - epipolar_images_left[row, 0], - epipolar_images_right[row, 0], + epipolar_image_left[row, 0], + epipolar_image_right[row, 0], matching_threshold=self.sift_matching_threshold, n_octave=self.sift_n_octave, n_scale_per_octave=self.sift_n_scale_per_octave, @@ -565,194 +527,6 @@ def run( return epipolar_disparity_map_left, None - def filter_matches( - self, - epipolar_matches_left, - grid_left, - grid_right, - orchestrator=None, - pair_key="pair_0", - pair_folder=None, - save_matches=False, - ): - """ - Transform matches CarsDataset to numpy matches, and filters matches - - :param cars_orchestrator: orchestrator - :param epipolar_matches_left: matches. CarsDataset contains: - - - N x M Delayed tiles \ - Each tile will be a future pandas DataFrame containing: - - - data : (L, 4) shape matches - - attributes containing "disp_lower_bound", "disp_upper_bound", \ - "elevation_delta_lower_bound","elevation_delta_upper_bound" - :type epipolar_matches_left: CarsDataset - :param grid_left: left epipolar grid - :type grid_left: CarsDataset - :param grid_right: right epipolar grid - :type grid_right: CarsDataset - :param save_matches: true is matches needs to be saved - :type save_matches: bool - - :return filtered matches - :rtype: np.ndarray - - """ - - # Default orchestrator - if orchestrator is None: - # Create default sequential orchestrator for current application - # be awere, no out_json will be shared between orchestrators - # No files saved - cars_orchestrator = ocht.Orchestrator( - orchestrator_conf={"mode": "sequential"} - ) - else: - cars_orchestrator = orchestrator - - if pair_folder is None: - pair_folder = os.path.join(cars_orchestrator.out_dir, "tmp") - - epipolar_error_upper_bound = self.epipolar_error_upper_bound - epipolar_error_maximum_bias = self.epipolar_error_maximum_bias - - # Compute grid correction - - # Concatenated matches - list_matches = [] - for row in range(epipolar_matches_left.shape[0]): - for col in range(epipolar_matches_left.shape[1]): - # CarsDataset containing Pandas DataFrame, not Delayed anymore - if epipolar_matches_left[row, col] is not None: - epipolar_matches = epipolar_matches_left[ - row, col - ].to_numpy() - sensor_matches = AbstractGeometry.matches_to_sensor_coords( - grid_left, - grid_right, - epipolar_matches, - cst.MATCHES_MODE, - ) - sensor_matches = np.concatenate(sensor_matches, axis=1) - matches = np.concatenate( - [ - epipolar_matches, - sensor_matches, - ], - axis=1, - ) - list_matches.append(matches) - - matches = np.concatenate(list_matches) - - raw_nb_matches = matches.shape[0] - - logging.info( - "Raw number of matches found: {} matches".format(raw_nb_matches) - ) - - # Export matches - raw_matches_array_path = None - if save_matches: - safe_makedirs(pair_folder) - - logging.info("Writing raw matches file") - raw_matches_array_path = os.path.join( - pair_folder, "raw_matches.npy" - ) - np.save(raw_matches_array_path, matches) - - # Filter matches that are out of margin - if epipolar_error_maximum_bias == 0: - epipolar_median_shift = 0 - else: - epipolar_median_shift = np.median(matches[:, 3] - matches[:, 1]) - - matches = matches[ - ((matches[:, 3] - matches[:, 1]) - epipolar_median_shift) - >= -epipolar_error_upper_bound - ] - matches = matches[ - ((matches[:, 3] - matches[:, 1]) - epipolar_median_shift) - <= epipolar_error_upper_bound - ] - - matches_discarded_message = ( - "{} matches discarded because their epipolar error " - "is greater than --epipolar_error_upper_bound = {} pix" - ).format(raw_nb_matches - matches.shape[0], epipolar_error_upper_bound) - - if epipolar_error_maximum_bias != 0: - matches_discarded_message += ( - " considering a shift of {} pix".format(epipolar_median_shift) - ) - - logging.info(matches_discarded_message) - - filtered_matches_array_path = None - if save_matches: - logging.info("Writing filtered matches file") - filtered_matches_array_path = os.path.join( - pair_folder, "filtered_matches.npy" - ) - np.save(filtered_matches_array_path, matches) - - # Retrieve number of matches - nb_matches = matches.shape[0] - - # Check if we have enough matches - # TODO: we could also make it a warning and continue - # with uncorrected grid - # and default disparity range - if nb_matches < self.minimum_nb_matches: - error_message_matches = ( - "Insufficient amount of matches found ({} < {}), " - "can not safely estimate epipolar error correction " - " and disparity range".format( - nb_matches, self.minimum_nb_matches - ) - ) - logging.error(error_message_matches) - raise ValueError(error_message_matches) - - logging.info( - "Number of matches kept for epipolar " - "error correction: {} matches".format(nb_matches) - ) - - # Compute epipolar error - epipolar_error = matches[:, 1] - matches[:, 3] - epi_error_mean = np.mean(epipolar_error) - epi_error_std = np.std(epipolar_error) - epi_error_max = np.max(np.fabs(epipolar_error)) - logging.info( - "Epipolar error before correction: mean = {:.3f} pix., " - "standard deviation = {:.3f} pix., max = {:.3f} pix.".format( - epi_error_mean, - epi_error_std, - epi_error_max, - ) - ) - - # Update orchestrator out_json - raw_matches_infos = { - application_constants.APPLICATION_TAG: { - sm_cst.MATCH_FILTERING_TAG: { - pair_key: { - sm_cst.NUMBER_MATCHES_TAG: nb_matches, - sm_cst.RAW_NUMBER_MATCHES_TAG: raw_nb_matches, - sm_cst.BEFORE_CORRECTION_EPI_ERROR_MEAN: epi_error_mean, - sm_cst.BEFORE_CORRECTION_EPI_ERROR_STD: epi_error_std, - sm_cst.BEFORE_CORRECTION_EPI_ERROR_MAX: epi_error_max, - } - } - } - } - cars_orchestrator.update_out_info(raw_matches_infos) - - return matches - def compute_matches_wrapper( left_image_object: xr.Dataset, diff --git a/cars/applications/sparse_matching/sparse_matching.py b/cars/applications/sparse_matching/sparse_matching.py index 136a954f..f311e416 100644 --- a/cars/applications/sparse_matching/sparse_matching.py +++ b/cars/applications/sparse_matching/sparse_matching.py @@ -22,11 +22,22 @@ this module contains the abstract matching application class. """ import logging +import math +import os from abc import ABCMeta, abstractmethod -from typing import Dict, List +from typing import Dict +import numpy as np +import xarray as xr + +import cars.applications.sparse_matching.sparse_matching_constants as sm_cst +import cars.orchestrator.orchestrator as ocht +from cars.applications import application_constants from cars.applications.application import Application from cars.applications.application_template import ApplicationTemplate +from cars.core import constants as cst +from cars.core.geometry.abstract_geometry import AbstractGeometry +from cars.core.utils import safe_makedirs @Application.register("sparse_matching") @@ -81,7 +92,8 @@ def __new__(cls, conf=None): # pylint: disable=W0613 def __init_subclass__(cls, short_name, **kwargs): # pylint: disable=E0302 super().__init_subclass__(**kwargs) - cls.available_applications[short_name] = cls + for name in short_name: + cls.available_applications[name] = cls def __init__(self, conf=None): """ @@ -102,6 +114,33 @@ def get_disparity_margin(self): """ + @abstractmethod + def get_strip_margin(self): + """ + Get strip margin corresponding to sparse matches + + :return: margin + + """ + + @abstractmethod + def get_epipolar_error_upper_bound(self): + """ + Get epipolar error upper bound corresponding to sparse matches + + :return: margin + + """ + + @abstractmethod + def get_epipolar_error_maximum_bias(self): + """ + Get epipolar error lower bound corresponding to sparse matches + + :return: margin + + """ + @abstractmethod def get_matches_filter_knn(self): """ @@ -116,7 +155,7 @@ def get_matches_filter_knn(self): def get_matches_filter_dev_factor(self): """ Get matches_filter_dev_factor : - factor ofdeviation in the formula + factor of deviation in the formula to compute threshold of outliers :return: matches_filter_dev_factor @@ -124,15 +163,280 @@ def get_matches_filter_dev_factor(self): """ @abstractmethod - def get_margins_fun(self): + def get_minimum_nb_matches(self): + """ + Get minimum_nb_matches : + get the minimum number of matches + + :return: minimum_nb_matches + + """ + + def get_margins_fun(self, disp_min=None, disp_max=None, method="sift"): """ Get margins function to use in resampling + :param disp_min: disp min for info + :param disp_max: disp max for info + :param method: method for the margins + :return: margins function :rtype: function generating xr.Dataset """ + # Compute margins + corner = ["left", "up", "right", "down"] + data = np.zeros(len(corner)) + col = np.arange(len(corner)) + margins = xr.Dataset( + {"left_margin": (["col"], data)}, coords={"col": col} + ) + margins["right_margin"] = xr.DataArray(data, dims=["col"]) + + left_margin = self.get_strip_margin() + + if method == "sift": + right_margin = self.get_strip_margin() + int( + math.floor( + self.get_epipolar_error_upper_bound() + + self.get_epipolar_error_maximum_bias() + ) + ) + else: + right_margin = left_margin + + # Compute margins for left region + margins["left_margin"].data = [0, left_margin, 0, left_margin] + + # Compute margins for right region + margins["right_margin"].data = [0, right_margin, 0, right_margin] + + # add disp range info + margins.attrs["disp_min"] = disp_min + margins.attrs["disp_max"] = disp_max + + logging.info( + "Margins added to left region for matching: {}".format( + margins["left_margin"].data + ) + ) + + logging.info( + "Margins added to right region for matching: {}".format( + margins["right_margin"].data + ) + ) + + def margins_wrapper( # pylint: disable=unused-argument + row_min, row_max, col_min, col_max + ): + """ + Generates margins Dataset used in resampling + + :param row_min: row min + :param row_max: row max + :param col_min: col min + :param col_max: col max + + :return: margins + :rtype: xr.Dataset + """ + + # Constant margins for all tiles + return margins + + return margins_wrapper + + def filter_matches( + self, + epipolar_matches_left, + grid_left, + grid_right, + orchestrator=None, + pair_key="pair_0", + pair_folder=None, + save_matches=False, + ): + """ + Transform matches CarsDataset to numpy matches, and filters matches + + :param cars_orchestrator: orchestrator + :param epipolar_matches_left: matches. CarsDataset contains: + + - N x M Delayed tiles \ + Each tile will be a future pandas DataFrame containing: + + - data : (L, 4) shape matches + - attributes containing "disp_lower_bound", "disp_upper_bound", \ + "elevation_delta_lower_bound","elevation_delta_upper_bound" + :type epipolar_matches_left: CarsDataset + :param grid_left: left epipolar grid + :type grid_left: CarsDataset + :param grid_right: right epipolar grid + :type grid_right: CarsDataset + :param save_matches: true is matches needs to be saved + :type save_matches: bool + + :return filtered matches + :rtype: np.ndarray + + """ + + # Default orchestrator + if orchestrator is None: + # Create default sequential orchestrator for current application + # be awere, no out_json will be shared between orchestrators + # No files saved + cars_orchestrator = ocht.Orchestrator( + orchestrator_conf={"mode": "sequential"} + ) + else: + cars_orchestrator = orchestrator + + if pair_folder is None: + pair_folder = os.path.join(cars_orchestrator.out_dir, "tmp") + + epipolar_error_upper_bound = self.get_epipolar_error_upper_bound() + epipolar_error_maximum_bias = self.get_epipolar_error_maximum_bias() + + # Compute grid correction + + # Concatenated matches + list_matches = [] + for row in range(epipolar_matches_left.shape[0]): + for col in range(epipolar_matches_left.shape[1]): + # CarsDataset containing Pandas DataFrame, not Delayed anymore + if epipolar_matches_left[row, col] is not None: + epipolar_matches = epipolar_matches_left[ + row, col + ].to_numpy() + + sensor_matches = AbstractGeometry.matches_to_sensor_coords( + grid_left, + grid_right, + epipolar_matches, + cst.MATCHES_MODE, + ) + sensor_matches = np.concatenate(sensor_matches, axis=1) + matches = np.concatenate( + [ + epipolar_matches, + sensor_matches, + ], + axis=1, + ) + list_matches.append(matches) + + matches = np.concatenate(list_matches) + + raw_nb_matches = matches.shape[0] + + logging.info( + "Raw number of matches found: {} matches".format(raw_nb_matches) + ) + + # Export matches + raw_matches_array_path = None + if save_matches: + safe_makedirs(pair_folder) + + logging.info("Writing raw matches file") + raw_matches_array_path = os.path.join( + pair_folder, "raw_matches.npy" + ) + np.save(raw_matches_array_path, matches) + + # Filter matches that are out of margin + if epipolar_error_maximum_bias == 0: + epipolar_median_shift = 0 + else: + epipolar_median_shift = np.median(matches[:, 3] - matches[:, 1]) + + matches = matches[ + ((matches[:, 3] - matches[:, 1]) - epipolar_median_shift) + >= -epipolar_error_upper_bound + ] + matches = matches[ + ((matches[:, 3] - matches[:, 1]) - epipolar_median_shift) + <= epipolar_error_upper_bound + ] + + matches_discarded_message = ( + "{} matches discarded because their epipolar error " + "is greater than --epipolar_error_upper_bound = {} pix" + ).format(raw_nb_matches - matches.shape[0], epipolar_error_upper_bound) + + if epipolar_error_maximum_bias != 0: + matches_discarded_message += ( + " considering a shift of {} pix".format(epipolar_median_shift) + ) + + logging.info(matches_discarded_message) + + filtered_matches_array_path = None + if save_matches: + logging.info("Writing filtered matches file") + filtered_matches_array_path = os.path.join( + pair_folder, "filtered_matches.npy" + ) + np.save(filtered_matches_array_path, matches) + + # Retrieve number of matches + nb_matches = matches.shape[0] + + # Check if we have enough matches + # TODO: we could also make it a warning and continue + # with uncorrected grid + # and default disparity range + if nb_matches < self.get_minimum_nb_matches(): + error_message_matches = ( + "Insufficient amount of matches found ({} < {}), " + "can not safely estimate epipolar error correction " + " and disparity range".format( + nb_matches, self.get_minimum_nb_matches() + ) + ) + logging.error(error_message_matches) + raise ValueError(error_message_matches) + + logging.info( + "Number of matches kept for epipolar " + "error correction: {} matches".format(nb_matches) + ) + + # Compute epipolar error + epipolar_error = matches[:, 1] - matches[:, 3] + epi_error_mean = np.mean(epipolar_error) + epi_error_std = np.std(epipolar_error) + epi_error_max = np.max(np.fabs(epipolar_error)) + logging.info( + "Epipolar error before correction: mean = {:.3f} pix., " + "standard deviation = {:.3f} pix., max = {:.3f} pix.".format( + epi_error_mean, + epi_error_std, + epi_error_max, + ) + ) + + # Update orchestrator out_json + raw_matches_infos = { + application_constants.APPLICATION_TAG: { + sm_cst.MATCH_FILTERING_TAG: { + pair_key: { + sm_cst.NUMBER_MATCHES_TAG: nb_matches, + sm_cst.RAW_NUMBER_MATCHES_TAG: raw_nb_matches, + sm_cst.BEFORE_CORRECTION_EPI_ERROR_MEAN: epi_error_mean, + sm_cst.BEFORE_CORRECTION_EPI_ERROR_STD: epi_error_std, + sm_cst.BEFORE_CORRECTION_EPI_ERROR_MAX: epi_error_max, + } + } + } + } + cars_orchestrator.update_out_info(raw_matches_infos) + + return matches + @abstractmethod def get_save_matches(self): """ @@ -143,17 +447,7 @@ def get_save_matches(self): """ @abstractmethod - def run( - self, - epipolar_images_left, - epipolar_images_right, - disp_to_alt_ratio, - orchestrator=None, - pair_folder=None, - pair_key="PAIR_0", - mask1_ignored_by_sift: List[int] = None, - mask2_ignored_by_sift: List[int] = None, - ): + def run(self, epipolar_image_left, epipolar_image_right, **kwargs): """ Run Matching application. @@ -161,10 +455,10 @@ def run( corresponding to epipolar 2D disparities, on the same geometry that epipolar_images_left and epipolar_images_right. - :param epipolar_images_left: tiled left epipolar - :type epipolar_images_left: CarsDataset - :param epipolar_images_right: tiled right epipolar - :type epipolar_images_right: CarsDataset + :param epipolar_image_left: tiled left epipolar + :type epipolar_image_left: CarsDataset + :param epipolar_image_right: tiled right epipolar + :type epipolar_image_right: CarsDataset :param disp_to_alt_ratio: disp to alti ratio :type disp_to_alt_ratio: float :param orchestrator: orchestrator used diff --git a/cars/applications/sparse_matching/sparse_matching_tools.py b/cars/applications/sparse_matching/sparse_matching_tools.py index 761354e8..f3389ad5 100644 --- a/cars/applications/sparse_matching/sparse_matching_tools.py +++ b/cars/applications/sparse_matching/sparse_matching_tools.py @@ -30,12 +30,20 @@ # Third party imports import numpy as np +import pandas +import xarray as xr +from scipy.ndimage import zoom from vlsift.sift.sift import sift +import cars.applications.dense_matching.dense_matching_constants as dm_cst + # CARS imports import cars.applications.sparse_matching.sparse_matching_constants as sm_cst from cars.applications import application_constants +from cars.applications.dense_matching import dense_matching_tools as dm_tools from cars.applications.point_cloud_outlier_removal import outlier_removal_tools +from cars.core import constants as cst +from cars.data_structures import cars_dataset def euclidean_matrix_distance(descr1: np.array, descr2: np.array): @@ -103,8 +111,10 @@ def compute_matches( :type window_size: int :param backmatching: also check that right vs. left gives same match :type backmatching: bool + :return: matches :rtype: numpy buffer of shape (nb_matches,4) + """ left_origin = [0, 0] if left_origin is None else left_origin right_origin = [0, 0] if right_origin is None else right_origin @@ -304,12 +314,14 @@ def dataset_matching( :type window_size: int :param backmatching: also check that right vs. left gives same match :type backmatching: bool + :return: matches :rtype: numpy buffer of shape (nb_matches,4) """ # get input data from dataset origin1 = [float(ds1.attrs["region"][0]), float(ds1.attrs["region"][1])] origin2 = [float(ds2.attrs["region"][0]), float(ds2.attrs["region"][1])] + left = ds1.im.values right = ds2.im.values left_mask = ds1.msk.values == 0 @@ -388,40 +400,6 @@ def compute_disparity_range(matches, percent=0.1): return mindisp, maxdisp -def filter_point_cloud_matches( - pd_cloud, - matches_filter_knn=25, - matches_filter_dev_factor=3, -): - """ - Filter triangulated matches - - :param pd_cloud: triangulated_matches - :type pd_cloud: pandas Dataframe - :param matches_filter_knn: number of neighboors used to measure - isolation of matches - :type matches_filter_knn: int - :param matches_filter_dev_factor: factor of deviation in the - formula to compute threshold of outliers - :type matches_filter_dev_factor: float - - :return: disp min and disp max - :rtype: float, float - """ - - # Statistical filtering - filter_cloud, _ = outlier_removal_tools.statistical_outlier_filtering( - pd_cloud, - k=matches_filter_knn, - dev_factor=matches_filter_dev_factor, - ) - - # filter nans - filter_cloud.dropna(axis=0, inplace=True) - - return filter_cloud - - def compute_disp_min_disp_max( pd_cloud, orchestrator, @@ -484,3 +462,307 @@ def compute_disp_min_disp_max( orchestrator.update_out_info(updating_infos) return dmin, dmax + + +def downsample(tab, resolution): + """ + Downsample the image dataset + + :param tab: the image dataset + :type tab: cars dataset + :param resolution: the resolution of the resampling + :type resolution: float + + :return: the downsampled image + :rtype: cars dataset + + """ + # Zoom is using round, that lead to some bugs, + # so we had to redefine the resolution + coords_row = np.ceil(resolution * tab["im"].shape[0]) + coords_col = np.ceil(resolution * tab["im"].shape[1]) + upscaled_factor = ( + coords_row / tab.im.shape[0], + coords_col / tab.im.shape[1], + ) + + # downsample + upsampled_raster = zoom(tab[cst.EPI_IMAGE], upscaled_factor, order=1) + + # Construct the new dataset + upsampled_dataset = xr.Dataset( + {cst.EPI_IMAGE: ([cst.ROW, cst.COL], upsampled_raster)}, + coords={ + cst.ROW: np.arange(0, upsampled_raster.shape[0]), + cst.COL: np.arange(0, upsampled_raster.shape[1]), + }, + attrs=tab.attrs, + ) + + cars_dataset.fill_dataset( + upsampled_dataset, + window=None, + profile=None, + overlaps=None, + ) + + if cst.EPI_MSK in tab: + upsampled_msk = zoom(tab[cst.EPI_MSK], upscaled_factor, order=0) + upsampled_dataset["msk"] = (["row", "col"], upsampled_msk) + + if cst.EPI_COLOR in tab: + upsampled_color = zoom(tab[cst.EPI_MSK], upscaled_factor, order=0) + upsampled_dataset["color"] = (["row", "col"], upsampled_color) + + # Change useful attributes + transform = tab.transform * tab.transform.scale( + (tab.im.shape[0] / upsampled_raster.shape[0]), + (tab.im.shape[1] / upsampled_raster.shape[1]), + ) + upsampled_dataset.attrs["transform"] = transform + + # roi_with_margins + roi_with_margins = np.empty(4) + roi_with_margins[0] = np.ceil(tab.roi_with_margins[0] * upscaled_factor[1]) + roi_with_margins[1] = np.ceil(tab.roi_with_margins[1] * upscaled_factor[0]) + roi_with_margins[2] = np.ceil(tab.roi_with_margins[2] * upscaled_factor[1]) + roi_with_margins[3] = np.ceil(tab.roi_with_margins[3] * upscaled_factor[0]) + upsampled_dataset.attrs["roi_with_margins"] = roi_with_margins.astype(int) + + # roi + roi = np.empty(4) + roi[0] = np.ceil(tab.roi[0] * upscaled_factor[1]) + roi[1] = np.ceil(tab.roi[1] * upscaled_factor[0]) + roi[2] = np.ceil(tab.roi[2] * upscaled_factor[1]) + roi[3] = np.ceil(tab.roi[3] * upscaled_factor[0]) + upsampled_dataset.attrs["roi"] = roi.astype(int) + + # margins + margins = np.empty(4) + margins[0] = -(roi[0] - roi_with_margins[0]) + margins[1] = -(roi[1] - roi_with_margins[1]) + margins[2] = roi_with_margins[2] - roi[2] + margins[3] = roi_with_margins[3] - roi[3] + upsampled_dataset.attrs["margins"] = margins + + return upsampled_dataset, upscaled_factor + + +def clustering_matches( + triangulated_matches, + connection_val=3.0, + nb_pts_threshold=80, + clusters_distance_threshold: float = None, + filtered_elt_pos: bool = False, +): + """ + Filter triangulated matches + + :param pd_cloud: triangulated_matches + :type pd_cloud: pandas Dataframe + :param connection_val: distance to use + to consider that two points are connected + :param nb_pts_threshold: number of points to use + to identify small clusters to filter + :param clusters_distance_threshold: distance to use + to consider if two points clusters are far from each other or not + (set to None to deactivate this level of filtering) + :param filtered_elt_pos: if filtered_elt_pos is set to True, + the removed points positions in their original + epipolar images are returned, otherwise it is set to None + + :return: filtered_matches + :rtype: pandas Dataframe + + """ + + filtered_pandora_matches, _ = ( + outlier_removal_tools.small_component_filtering( + triangulated_matches, + connection_val=connection_val, + nb_pts_threshold=nb_pts_threshold, + clusters_distance_threshold=clusters_distance_threshold, + filtered_elt_pos=filtered_elt_pos, + ) + ) + + filtered_pandora_matches_dataframe = pandas.DataFrame( + filtered_pandora_matches + ) + filtered_pandora_matches_dataframe.attrs["epsg"] = ( + triangulated_matches.attrs["epsg"] + ) + + return filtered_pandora_matches_dataframe + + +def filter_point_cloud_matches( + pd_cloud, + matches_filter_knn=25, + matches_filter_dev_factor=3, +): + """ + Filter triangulated matches + + :param pd_cloud: triangulated_matches + :type pd_cloud: pandas Dataframe + :param matches_filter_knn: number of neighboors used to measure + isolation of matches + :type matches_filter_knn: int + :param matches_filter_dev_factor: factor of deviation in the + formula to compute threshold of outliers + :type matches_filter_dev_factor: float + + :return: disp min and disp max + :rtype: float, float + """ + + # Statistical filtering + filter_cloud, _ = outlier_removal_tools.statistical_outlier_filtering( + pd_cloud, + k=matches_filter_knn, + dev_factor=matches_filter_dev_factor, + ) + + # filter nans + filter_cloud.dropna(axis=0, inplace=True) + + return filter_cloud + + +def pandora_matches( + left_image_object, + right_image_object, + corr_conf, + disp_upper_bound, + disp_lower_bound, + resolution, + disp_to_alt_ratio=None, +): + """ + Calculate the pandora matches + + :param left_image_object: the left image dataset + :type left_image_object: cars dataset + :param right_image_object: the right image dataset + :type right_image_object: cars dataset + :param corr_conf: the pandora configuration + :type corr_conf: dict + :param resolution: the resolution of the resampling + :type resolution: int + :param disp_to_alt_ratio: disp to alti ratio used for performance map + :type disp_to_alt_ratio: float + + :return: matches and disparity_map + :rtype: datasets + + """ + + # Downsample the epipolar images + epipolar_image_left_low_res, new_resolution = downsample( + left_image_object, 1 / resolution + ) + epipolar_image_right_low_res, _ = downsample( + right_image_object, 1 / resolution + ) + + # Calculate the disparity grid + roi_left = epipolar_image_left_low_res.roi_with_margins[0] + roi_top = epipolar_image_left_low_res.roi_with_margins[1] + roi_right = epipolar_image_left_low_res.roi_with_margins[2] + roi_bottom = epipolar_image_left_low_res.roi_with_margins[3] + + # dmin & dmax + dmin = disp_lower_bound / resolution + dmax = disp_upper_bound / resolution + + # Create CarsDataset + disp_range_grid = cars_dataset.CarsDataset( + "arrays", name="grid_disp_range_unknown_pair" + ) + # Only one tile + disp_range_grid.tiling_grid = np.array( + [[[roi_top, roi_bottom, roi_left, roi_right]]] + ) + + row_range = np.arange(roi_top, roi_bottom) + col_range = np.arange(roi_left, roi_right) + + grid_attributes = { + "row_range": row_range, + "col_range": col_range, + } + disp_range_grid.attributes = grid_attributes.copy() + + grid_min = np.empty((len(row_range), len(col_range))) + grid_max = np.empty((len(row_range), len(col_range))) + + grid_min[:, :] = dmin + grid_max[:, :] = dmax + + disp_range_tile = xr.Dataset( + data_vars={ + dm_cst.DISP_MIN_GRID: (["row", "col"], grid_min), + dm_cst.DISP_MAX_GRID: (["row", "col"], grid_max), + }, + coords={ + "row": np.arange(0, grid_min.shape[0]), + "col": np.arange(0, grid_min.shape[1]), + }, + ) + + disp_range_grid[0, 0] = disp_range_tile + + ( + disp_min_grid, + disp_max_grid, + ) = dm_tools.compute_disparity_grid( + disp_range_grid, epipolar_image_left_low_res + ) + + # Compute the disparity map + epipolar_disparity_map = dm_tools.compute_disparity( + epipolar_image_left_low_res, + epipolar_image_right_low_res, + corr_conf, + disp_min_grid=disp_min_grid, + disp_max_grid=disp_max_grid, + disp_to_alt_ratio=disp_to_alt_ratio, + ) + + # get values + mask = epipolar_disparity_map["disp_msk"].values + disp_map = epipolar_disparity_map["disp"].values + disp_map[mask == 0] = np.nan + + # Construct the matches using the disparity map + rows = np.arange( + epipolar_image_left_low_res.roi[1], epipolar_image_left_low_res.roi[3] + ) + cols = np.arange( + epipolar_image_left_low_res.roi[0], epipolar_image_left_low_res.roi[2] + ) + + cols_mesh, rows_mesh = np.meshgrid(cols, rows) + left_points = np.column_stack( + (cols_mesh.ravel(), rows_mesh.ravel()) + ).astype(float) + + right_points = np.copy(left_points) + + right_points[:, 0] += disp_map.ravel() + + matches = np.column_stack((left_points, right_points)) + + matches = matches[~np.isnan(matches).any(axis=1)] + matches_true_res = np.empty((len(matches), 4)) + + matches_true_res[:, 0] = matches[:, 0] * 1 / new_resolution[1] + matches_true_res[:, 1] = matches[:, 1] * 1 / new_resolution[0] + matches_true_res[:, 2] = matches[:, 2] * 1 / new_resolution[1] + matches_true_res[:, 3] = matches[:, 3] * 1 / new_resolution[0] + + order = np.argsort(matches_true_res[:, 0]) + matches_true_res = matches_true_res[order, :] + + return matches_true_res, epipolar_disparity_map diff --git a/cars/pipelines/default/default_pipeline.py b/cars/pipelines/default/default_pipeline.py index 01258e6a..86e2e6be 100644 --- a/cars/pipelines/default/default_pipeline.py +++ b/cars/pipelines/default/default_pipeline.py @@ -243,27 +243,28 @@ def infer_conditions_from_applications(self, conf): self.last_application_to_run = 0 sensor_to_depth_apps = { - "grid_generation": 1, # and 6 - "resampling": 2, # and 7 + "grid_generation": 1, # and 5 + "resampling": 2, # and 8 "hole_detection": 3, - "sparse_matching": 4, - "dem_generation": 5, - "dense_matching": 8, - "dense_match_filling.1": 9, - "dense_match_filling.2": 10, - "triangulation": 11, - "point_cloud_outlier_removal.1": 12, - "point_cloud_outlier_removal.2": 13, + "sparse_matching.sift": 4, + "sparse_matching.pandora": 6, + "dem_generation": 7, + "dense_matching": 9, + "dense_match_filling.1": 10, + "dense_match_filling.2": 11, + "triangulation": 12, + "point_cloud_outlier_removal.1": 13, + "point_cloud_outlier_removal.2": 14, } depth_merge_apps = { - "point_cloud_fusion": 14, + "point_cloud_fusion": 15, } depth_to_dsm_apps = { - "pc_denoising": 15, - "point_cloud_rasterization": 16, - "dsm_filling": 17, + "pc_denoising": 16, + "point_cloud_rasterization": 17, + "dsm_filling": 18, } self.app_values = {} @@ -408,7 +409,7 @@ def check_output(conf): """ return output_parameters.check_output_parameters(conf) - def check_applications( + def check_applications( # noqa: C901 self, conf, ): @@ -432,7 +433,8 @@ def check_applications( "hole_detection", "dense_match_filling.1", "dense_match_filling.2", - "sparse_matching", + "sparse_matching.sift", + "sparse_matching.pandora", "dense_matching", "triangulation", "dem_generation", @@ -468,6 +470,17 @@ def check_applications( # Initialize used config used_conf = {} + + for app_key in [ + "sparse_matching.pandora", + "point_cloud_outlier_removal.1", + "point_cloud_outlier_removal.2", + ]: + if conf.get(app_key) is not None: + config_app = conf.get(app_key) + if "activated" not in config_app: + conf[app_key]["activated"] = True + for app_key in needed_applications: used_conf[app_key] = conf.get(app_key, {}) used_conf[app_key]["save_intermediate_data"] = ( @@ -491,7 +504,8 @@ def check_applications( self.hole_detection_app = None self.dense_match_filling_1 = None self.dense_match_filling_2 = None - self.sparse_mtch_app = None + self.sparse_mtch_sift_app = None + self.sparse_mtch_pandora_app = None self.dense_matching_app = None self.triangulation_application = None self.dem_generation_application = None @@ -548,10 +562,23 @@ def check_applications( ) # Sparse Matching - self.sparse_mtch_app = Application( - "sparse_matching", cfg=used_conf.get("sparse_matching", {}) + self.sparse_mtch_sift_app = Application( + "sparse_matching", + cfg=used_conf.get("sparse_matching.sift", {"method": "sift"}), + ) + used_conf["sparse_matching.sift"] = ( + self.sparse_mtch_sift_app.get_conf() + ) + + # Pandora Sparse Matching + used_conf["sparse_matching.pandora"]["method"] = "pandora" + self.sparse_mtch_pandora_app = Application( + "sparse_matching", + cfg=used_conf.get("sparse_matching.pandora"), + ) + used_conf["sparse_matching.pandora"] = ( + self.sparse_mtch_pandora_app.get_conf() ) - used_conf["sparse_matching"] = self.sparse_mtch_app.get_conf() # Matching generate_performance_map = ( @@ -661,21 +688,49 @@ def check_applications_with_inputs(self, inputs_conf, application_conf): initial_elevation = ( inputs_conf[sens_cst.INITIAL_ELEVATION]["dem"] is not None ) - if self.sparse_mtch_app.elevation_delta_lower_bound is None: - self.sparse_mtch_app.used_config["elevation_delta_lower_bound"] = ( - -500 if initial_elevation else -1000 + if self.sparse_mtch_sift_app.elevation_delta_lower_bound is None: + self.sparse_mtch_sift_app.used_config[ + "elevation_delta_lower_bound" + ] = (-500 if initial_elevation else -1000) + self.sparse_mtch_sift_app.elevation_delta_lower_bound = ( + self.sparse_mtch_sift_app.used_config[ + "elevation_delta_lower_bound" + ] ) - self.sparse_mtch_app.elevation_delta_lower_bound = ( - self.sparse_mtch_app.used_config["elevation_delta_lower_bound"] + if self.sparse_mtch_sift_app.elevation_delta_upper_bound is None: + self.sparse_mtch_sift_app.used_config[ + "elevation_delta_upper_bound" + ] = (1000 if initial_elevation else 9000) + self.sparse_mtch_sift_app.elevation_delta_upper_bound = ( + self.sparse_mtch_sift_app.used_config[ + "elevation_delta_upper_bound" + ] ) - if self.sparse_mtch_app.elevation_delta_upper_bound is None: - self.sparse_mtch_app.used_config["elevation_delta_upper_bound"] = ( - 1000 if initial_elevation else 9000 + application_conf["sparse_matching.sift"] = ( + self.sparse_mtch_sift_app.get_conf() + ) + + if self.sparse_mtch_pandora_app.elevation_delta_lower_bound is None: + self.sparse_mtch_pandora_app.used_config[ + "elevation_delta_lower_bound" + ] = (-500 if initial_elevation else -1000) + self.sparse_mtch_pandora_app.elevation_delta_lower_bound = ( + self.sparse_mtch_pandora_app.used_config[ + "elevation_delta_lower_bound" + ] ) - self.sparse_mtch_app.elevation_delta_upper_bound = ( - self.sparse_mtch_app.used_config["elevation_delta_upper_bound"] + if self.sparse_mtch_pandora_app.elevation_delta_upper_bound is None: + self.sparse_mtch_pandora_app.used_config[ + "elevation_delta_upper_bound" + ] = (1000 if initial_elevation else 9000) + self.sparse_mtch_pandora_app.elevation_delta_upper_bound = ( + self.sparse_mtch_pandora_app.used_config[ + "elevation_delta_upper_bound" + ] ) - application_conf["sparse_matching"] = self.sparse_mtch_app.get_conf() + application_conf["sparse_matching.pandora"] = ( + self.sparse_mtch_pandora_app.get_conf() + ) # check classification application parameter compare # to each sensors inputs classification list @@ -714,6 +769,7 @@ def check_applications_with_inputs(self, inputs_conf, application_conf): ) for key1, key2 in inputs_conf["pairing"]: corr_cfg = self.dense_matching_app.loader.get_conf() + corr_cfg_sparse = self.sparse_mtch_pandora_app.loader.get_conf() img_left = inputs_conf["sensors"][key1]["image"] img_right = inputs_conf["sensors"][key2]["image"] classif_left = None @@ -731,6 +787,15 @@ def check_applications_with_inputs(self, inputs_conf, application_conf): classif_right, ) ) + self.sparse_mtch_pandora_app.corr_config = ( + self.sparse_mtch_pandora_app.loader.check_conf( + corr_cfg_sparse, + img_left, + img_right, + classif_left, + classif_right, + ) + ) return application_conf @@ -787,7 +852,8 @@ def sensor_to_depth_maps(self): # noqa: C901 # used in dem generation self.triangulated_matches_list = [] - save_matches = self.sparse_mtch_app.get_save_matches() + save_matches = self.sparse_mtch_sift_app.get_save_matches() + save_corrected_grid = ( self.epipolar_grid_generation_application.get_save_grids() ) @@ -892,12 +958,11 @@ def sensor_to_depth_maps(self): # noqa: C901 self.pairs[pair_key]["sensor_image_right"], self.pairs[pair_key]["grid_left"], self.pairs[pair_key]["grid_right"], - orchestrator=self.cars_orchestrator, pair_folder=os.path.join( self.dump_dir, "resampling", "initial", pair_key ), pair_key=pair_key, - margins_fun=self.sparse_mtch_app.get_margins_fun(), + margins_fun=self.sparse_mtch_sift_app.get_margins_fun(), tile_width=None, tile_height=None, add_color=False, @@ -933,7 +998,7 @@ def sensor_to_depth_maps(self): # noqa: C901 ( self.pairs[pair_key]["epipolar_matches_left"], _, - ) = self.sparse_mtch_app.run( + ) = self.sparse_mtch_sift_app.run( self.pairs[pair_key]["epipolar_image_left"], self.pairs[pair_key]["epipolar_image_right"], self.pairs[pair_key]["grid_left"].attributes[ @@ -941,7 +1006,7 @@ def sensor_to_depth_maps(self): # noqa: C901 ], orchestrator=self.cars_orchestrator, pair_folder=os.path.join( - self.dump_dir, "sparse_matching", pair_key + self.dump_dir, "sparse_matching.sift", pair_key ), pair_key=pair_key, ) @@ -954,18 +1019,21 @@ def sensor_to_depth_maps(self): # noqa: C901 # Estimate grid correction if no epipolar a priori # Filter and save matches self.pairs[pair_key]["matches_array"] = ( - self.sparse_mtch_app.filter_matches( + self.sparse_mtch_sift_app.filter_matches( self.pairs[pair_key]["epipolar_matches_left"], self.pairs[pair_key]["grid_left"], self.pairs[pair_key]["grid_right"], orchestrator=self.cars_orchestrator, pair_key=pair_key, pair_folder=os.path.join( - self.dump_dir, "sparse_matching", pair_key + self.dump_dir, "sparse_matching.sift", pair_key + ), + save_matches=( + self.sparse_mtch_sift_app.get_save_matches() ), - save_matches=(self.sparse_mtch_app.get_save_matches()), ) ) + # Compute grid correction ( self.pairs[pair_key]["grid_correction_coef"], @@ -1005,6 +1073,90 @@ def sensor_to_depth_maps(self): # noqa: C901 pair_key ]["grid_left"] + if ( + self.sparse_mtch_pandora_app.used_config.get( + "activated", False + ) + is True + ): + # Run epipolar resampling + ( + self.pairs[pair_key]["new_epipolar_image_left"], + self.pairs[pair_key]["new_epipolar_image_right"], + ) = self.resampling_application.run( + self.pairs[pair_key]["sensor_image_left"], + self.pairs[pair_key]["sensor_image_right"], + self.pairs[pair_key]["corrected_grid_left"], + self.pairs[pair_key]["corrected_grid_right"], + self.cars_orchestrator, + os.path.join( + self.dump_dir, + "resampling", + "corrected_for_pandora", + pair_key, + ), + pair_key, + self.sparse_mtch_pandora_app.get_margins_fun( + method="pandora" + ), + tile_width=None, + tile_height=None, + add_color=False, + add_classif=add_classif, + ) + + if self.quit_on_app("resampling"): + continue + + pandora_sparse_matching_pair_folder = os.path.join( + self.dump_dir, "sparse_matching.pandora", pair_key + ) + + ( + self.pairs[pair_key]["pandora_epipolar_matches_left"], + _, + ) = self.sparse_mtch_pandora_app.run( + self.pairs[pair_key]["new_epipolar_image_left"], + self.pairs[pair_key]["new_epipolar_image_right"], + orchestrator=self.cars_orchestrator, + pair_folder=pandora_sparse_matching_pair_folder, + pair_key=pair_key, + disp_to_alt_ratio=self.pairs[pair_key][ + "grid_left" + ].attributes["disp_to_alt_ratio"], + ) + + self.cars_orchestrator.breakpoint() + + self.pairs[pair_key]["pandora_matches_array"] = ( + self.sparse_mtch_pandora_app.filter_matches( + self.pairs[pair_key][ + "pandora_epipolar_matches_left" + ], + self.pairs[pair_key]["corrected_grid_left"], + self.pairs[pair_key]["corrected_grid_right"], + orchestrator=self.cars_orchestrator, + pair_key=pair_key, + pair_folder=os.path.join( + self.dump_dir, + "sparse_matching.pandora", + pair_key, + ), + save_matches=( + self.sparse_mtch_pandora_app.get_save_matches() + ), + ) + ) + + matches = np.row_stack( + ( + self.pairs[pair_key]["pandora_matches_array"], + self.pairs[pair_key]["corrected_matches_array"], + ) + ) + else: + matches = self.pairs[pair_key]["corrected_matches_array"] + # Triangulate matches self.pairs[pair_key]["triangulated_matches"] = ( dem_generation_tools.triangulate_sparse_matches( @@ -1012,21 +1164,54 @@ def sensor_to_depth_maps(self): # noqa: C901 self.pairs[pair_key]["sensor_image_right"], self.pairs[pair_key]["grid_left"], self.pairs[pair_key]["corrected_grid_right"], - self.pairs[pair_key]["corrected_matches_array"], + matches, geom_plugin, ) ) - # filter triangulated_matches + if ( + self.sparse_mtch_pandora_app.used_config.get( + "activated", False + ) + is True + ): + # filter triangulated_matches + connection_val = ( + self.sparse_mtch_pandora_app.get_connection_val() + ) + nb_pts_threshold = ( + self.sparse_mtch_pandora_app.get_nb_pts_threshold() + ) + clusters_dist_thresh = ( + self.sparse_mtch_pandora_app.get_clusters_dist_thresh() + ) + filtered_elt_pos = ( + self.sparse_mtch_pandora_app.get_filtered_elt_pos() + ) + filtered_matches = sparse_mtch_tools.clustering_matches( + self.pairs[pair_key]["triangulated_matches"], + connection_val=connection_val, + nb_pts_threshold=nb_pts_threshold, + clusters_distance_threshold=clusters_dist_thresh, + filtered_elt_pos=filtered_elt_pos, + ) + + app_sparse_matching = self.sparse_mtch_pandora_app + else: + app_sparse_matching = self.sparse_mtch_sift_app + filtered_matches = copy.copy( + self.pairs[pair_key]["triangulated_matches"] + ) + matches_filter_knn = ( - self.sparse_mtch_app.get_matches_filter_knn() + app_sparse_matching.get_matches_filter_knn() ) matches_filter_dev_factor = ( - self.sparse_mtch_app.get_matches_filter_dev_factor() + app_sparse_matching.get_matches_filter_dev_factor() ) self.pairs[pair_key]["filtered_triangulated_matches"] = ( sparse_mtch_tools.filter_point_cloud_matches( - self.pairs[pair_key]["triangulated_matches"], + filtered_matches, matches_filter_knn=matches_filter_knn, matches_filter_dev_factor=matches_filter_dev_factor, ) @@ -1036,7 +1221,9 @@ def sensor_to_depth_maps(self): # noqa: C901 self.pairs[pair_key]["filtered_triangulated_matches"] ) - if self.quit_on_app("sparse_matching"): + if self.quit_on_app("sparse_matching.sift") or self.quit_on_app( + "sparse_matching.pandora" + ): continue # keep iterating over pairs, but don't go further # Clean grids at the end of processing if required. Note that this will @@ -1058,7 +1245,8 @@ def sensor_to_depth_maps(self): # noqa: C901 self.quit_on_app("grid_generation") or self.quit_on_app("resampling") or self.quit_on_app("hole_detection") - or self.quit_on_app("sparse_matching") + or self.quit_on_app("sparse_matching.sift") + or self.quit_on_app("sparse_matching.pandora") ): return True @@ -1192,21 +1380,20 @@ def sensor_to_depth_maps(self): # noqa: C901 # Correct grids with former matches # Transform matches to new grids - new_grid_matches_array = ( AbstractGeometry.transform_matches_from_grids( - self.pairs[pair_key]["corrected_matches_array"], + matches, self.pairs[pair_key]["corrected_grid_left"], self.pairs[pair_key]["corrected_grid_right"], self.pairs[pair_key]["new_grid_left"], self.pairs[pair_key]["new_grid_right"], ) ) - save_matches = self.sparse_mtch_app.get_save_matches() + save_matches = self.sparse_mtch_sift_app.get_save_matches() # Estimate grid_correction ( self.pairs[pair_key]["grid_correction_coef"], - self.pairs[pair_key]["corrected_matches_array"], + matches, self.pairs[pair_key]["corrected_matches_cars_ds"], _, _, @@ -1251,10 +1438,10 @@ def sensor_to_depth_maps(self): # noqa: C901 # matches filter params matches_filter_knn = ( - self.sparse_mtch_app.get_matches_filter_knn() + app_sparse_matching.get_matches_filter_knn() ) matches_filter_dev_factor = ( - self.sparse_mtch_app.get_matches_filter_dev_factor() + app_sparse_matching.get_matches_filter_dev_factor() ) if use_global_disp_range: # Triangulate new matches @@ -1264,15 +1451,34 @@ def sensor_to_depth_maps(self): # noqa: C901 self.pairs[pair_key]["sensor_image_right"], self.pairs[pair_key]["corrected_grid_left"], self.pairs[pair_key]["corrected_grid_right"], - self.pairs[pair_key]["corrected_matches_array"], + matches, geometry_plugin=geom_plugin, ) ) - # filter triangulated_matches - # Filter outliers + + if ( + self.sparse_mtch_pandora_app.used_config.get( + "activated", False + ) + is True + ): + # filter triangulated_matches + # Filter outliers + filtered_matches = sparse_mtch_tools.clustering_matches( + self.pairs[pair_key]["triangulated_matches"], + connection_val=connection_val, + nb_pts_threshold=nb_pts_threshold, + clusters_distance_threshold=clusters_dist_thresh, + filtered_elt_pos=filtered_elt_pos, + ) + else: + filtered_matches = self.pairs[pair_key][ + "triangulated_matches" + ] + self.pairs[pair_key]["filtered_triangulated_matches"] = ( sparse_mtch_tools.filter_point_cloud_matches( - self.pairs[pair_key]["triangulated_matches"], + filtered_matches, matches_filter_knn=matches_filter_knn, matches_filter_dev_factor=matches_filter_dev_factor, ) @@ -1286,7 +1492,7 @@ def sensor_to_depth_maps(self): # noqa: C901 self.pairs[pair_key]["filtered_triangulated_matches"], self.cars_orchestrator, disp_margin=( - self.sparse_mtch_app.get_disparity_margin() + self.sparse_mtch_sift_app.get_disparity_margin() ), pair_key=pair_key, disp_to_alt_ratio=self.pairs[pair_key][ diff --git a/docs/source/usage/configuration.rst b/docs/source/usage/configuration.rst index 69919fe0..f8c27262 100644 --- a/docs/source/usage/configuration.rst +++ b/docs/source/usage/configuration.rst @@ -863,22 +863,39 @@ The structure follows this organization: +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | Name | Description | Type | Available value | Default value | Required | +======================================+================================================================================================+=============+========================+===============+==========+ - | method | Method for sparse matching | string | "sift" | "sift" | No | + | method | Method for sparse matching | string | "sift", "pandora" | "sift" | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | disparity_margin | Add a margin to min and max disparity as percent of the disparity range. | float | | 0.02 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | epipolar_error_upper_bound | Expected upper bound for epipolar error in pixels | float | should be > 0 | 10.0 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | epipolar_error_maximum_bias | Maximum bias for epipolar error in pixels | float | should be >= 0 | 0.0 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | sift_back_matching | Also check that right vs. left gives same match | boolean | | true | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | matches_filter_knn | Number of neighbors used to measure isolation of matches and detect isolated matches | int | should be > 0 | 25 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | matches_filter_dev_factor | Factor of deviation of isolation of matches to compute threshold of outliers | int, float | should be > 0 | 3.0 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | save_intermediate_data | Save matches in epipolar geometry (4 first columns) and sensor geometry (4 last columns) | boolean | | false | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | strip_margin | Margin to use on strip | int | should be > 0 | 10 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | elevation_delta_lower_bound | Expected lower bound for elevation delta with respect to input low resolution dem in meters | int, float | | -9000 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | elevation_delta_upper_bound | Expected upper bound for elevation delta with respect to input low resolution dem in meters | int, float | | 9000 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | epipolar_error_upper_bound | Expected upper bound for epipolar error in pixels | float | should be > 0 | 10.0 | No | + | minimum_nb_matches | Minimum number of matches that must be computed to continue pipeline | int | should be > 0 | 100 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | epipolar_error_maximum_bias | Maximum bias for epipolar error in pixels | float | should be >= 0 | 0.0 | No | + + + **Sift:** + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ + | Name | Description | Type | Available value | Default value | Required | + +======================================+================================================================================================+=============+========================+===============+==========+ | disparity_outliers_rejection_percent | Percentage of outliers to reject | float | between 0 and 1 | 0.1 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | minimum_nb_matches | Minimum number of matches that must be computed to continue pipeline | int | should be > 0 | 100 | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | sift_matching_threshold | Threshold for the ratio to nearest second match | float | should be > 0 | 0.7 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | sift_n_octave | The number of octaves of the Difference of Gaussians scale space | int | should be > 0 | 8 | No | @@ -892,39 +909,71 @@ The structure follows this organization: | sift_magnification | The descriptor magnification factor | float | should be > 0 | 7.0 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ | sift_window_size | smaller values let the center of the descriptor count more | int | should be > 0 | 2 | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | sift_back_matching | Also check that right vs. left gives same match | boolean | | true | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | matches_filter_knn | Number of neighbors used to measure isolation of matches and detect isolated matches | int | should be > 0 | 25 | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | matches_filter_dev_factor | Factor of deviation of isolation of matches to compute threshold of outliers | int, float | should be > 0 | 3.0 | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | save_intermediate_data | Save matches in epipolar geometry (4 first columns) and sensor geometry (4 last columns) | boolean | | false | No | - +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - | strip_margin | Margin to use on strip | int | should be > 0 | 10 | No | +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+---------------+----------+ - For more information about these parameters, please refer to the `VLFEAT SIFT documentation `_. + + **Pandora:** + + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | Name | Description | Type | Available value | Default value | Required | + +======================================+================================================================================================+=============+========================+=======================+==========+ + | resolution | Resolution at which the image will be downsampled for the use of pandora | int, list | should be > 0 | 4 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | loader_conf | Pandora configuration that will be used | dict | | Pandora default conf | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | connection_val | distance to use to consider that two points are connected | float | should be > 0 | 3.0 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | nb_pts_threshold |number of points to use to identify small clusters to filter | int | should be > 0 | 80 | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | filtered_elt_pos | if filtered_elt_pos is set to True, the removed points positions in their original \ | | | | | + | | epipolar images are returned, otherwise it is set to None | bool | | False | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + | clusters_distance_threshold | distance to use to consider if two points clusters are far from each other or not | float | | None | No | + +--------------------------------------+------------------------------------------------------------------------------------------------+-------------+------------------------+-----------------------+----------+ + + .. warning:: + + There is a particular case with the *sparse_matching* application because it can be called twice. + So you can configure the application twice , once for the *sift*, the other for *pandora* method. + Because it is not possible to define twice the *application_name* on your json configuration file, we have decided to configure + those two applications with : + + *sparse_matching.sift* + *sparse_matching.pandora* + + Each one is associated to a particular *sparse_matching* method* + Therefore, is it not possible to use the key *sparse_matching* and to select the method. + + **Example** .. code-block:: json "applications": { - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "disparity_margin": 0.01 + }, + "sparse_matching.pandora":{ + "method": "pandora", + "resolution": [4, 2] } }, + .. note:: + * Sift will always be used during the cars execution + * Pandora is optionnal, by default this one is not activated + * You can use both sift and pandora during your execution, the combined matches will be used + .. tab:: DEM Generation **Name**: "dem_generation" **Description** - Generates dem from sparse matches. + Generates dem from sparse matches. 3 dems are generated, with different methods: * median @@ -977,11 +1026,11 @@ The structure follows this organization: **Description** Compute the disparity map from stereo-rectified pair images - + .. list-table:: Configuration :widths: 19 19 19 19 19 19 :header-rows: 1 - + * - Name - Description - Type @@ -1003,13 +1052,13 @@ The structure follows this organization: * - loader_conf - Configuration associated with loader, dictionary or path to config - dict or str - - - - + - + - - No * - min_elevation_offset - Override minimum disparity from prepare step with this offset in meters - int - - + - - None - No * - max_elevation_offset @@ -1021,7 +1070,7 @@ The structure follows this organization: * - disp_min_threshold - Override minimum disparity when less than lower bound - int - - + - - None - No * - disp_max_threshold @@ -1045,61 +1094,61 @@ The structure follows this organization: * - epipolar_tile_margin_in_percent - Size of the margin used for dense matching (percent of tile size) - int - - + - - 60 - No * - generate_performance_map - Generate a performance map from disparity map - boolean - - + - - False - No * - generate_confidence_intervals - - Compute confidence intervals from disparity map. + - Compute confidence intervals from disparity map. - boolean - - + - - False - No * - perf_eta_max_ambiguity - Ambiguity confidence eta max used for performance map - float - - + - - 0.99 - No * - perf_eta_max_risk - Risk confidence eta max used for performance map - float - - + - - 0.25 - No * - perf_eta_step - Risk and Ambiguity confidence eta step used for performance map - float - - + - - 0.04 - No * - perf_ambiguity_threshold - Maximal ambiguity considered for performance map - float - - + - - 0.6 - No * - save_intermediate_data - Save disparity map and disparity confidence - boolean - - + - - false - No * - use_global_disp_range - If true, use global disparity range, otherwise local range estimation - boolean - - + - - false - No * - local_disp_grid_step - Step of disparity min/ max grid used to resample dense disparity range - int - - + - - 30 - No * - disp_range_propagation_filter_size @@ -1135,7 +1184,7 @@ The structure follows this organization: * When user activate the generation of performance map, this map transits until being rasterized. Performance map is managed as a confidence map. * To save the confidence, the save_intermediate_data parameter should be activated. - + .. tab:: Dense match filling **Name**: "dense_match_filling" @@ -1209,6 +1258,7 @@ The structure follows this organization: * *dense_match_filling.2* Each one is associated to a particular *dense_match_filling* method* + Therefore, is it not possible to use the key *dense_match_filling* and to select the method. **Example** @@ -1363,6 +1413,8 @@ The structure follows this organization: * *point_cloud_outlier_removal.2* Each one is associated to a particular *point_cloud_outlier_removal* method* + Therefore, is it not possible to use the key *point_cloud_outlier_removal* and to select the method. + **Example** @@ -1392,11 +1444,11 @@ The structure follows this organization: Project altitudes on regular grid. Only one simple gaussian method is available for now. - + .. list-table:: Configuration :widths: 19 19 19 19 19 19 :header-rows: 1 - + * - Name - Description - Type @@ -1404,41 +1456,41 @@ The structure follows this organization: - Default value - Required * - method - - + - - string - "simple_gaussian" - simple_gaussian - No * - dsm_radius - - + - - float, int - - + - - 1.0 - No * - sigma - - + - - float - - + - - None - No * - grid_points_division_factor - - + - - int - - + - - None - No * - dsm_no_data - - + - - int - - + - - -32768 - - + - * - color_no_data - - + - - int - - + - - 0 - - + - * - color_dtype - | By default, it's retrieved from the input color | Otherwise, specify an image type @@ -1450,13 +1502,13 @@ The structure follows this organization: * - msk_no_data - No data value for mask and classif - int - - + - - 255 - - + - * - save_intermediate_data - Save all layers from input point cloud in application `dump_dir` - boolean - - + - - false - No @@ -1478,20 +1530,20 @@ The structure follows this organization: **Description** - Fill in the missing values of the DSM by using the DEM's elevation. - This application replaces the existing dsm.tif. - + Fill in the missing values of the DSM by using the DEM's elevation. + This application replaces the existing dsm.tif. + Only one method is available for now: "bulldozer". .. note:: When ``save_intermediate_data`` is activated, the folder ``dump_dir/dsm_filling`` will contain : - + * The replaced dsm.tif, saved under ``dump_dir/dsm_filling/dsm_not_filled.tif`` * The dsm given to Bulldozer as input, saved under ``dump_dir/dsm_filling/dsm_filled_with_dem_not_smoothed.tif`` * The configuration given to Bulldozer, saved under ``dump_dir/dsm_filling/bulldozer_config.yaml`` * All the outputs generated by Bulldozer, saved under ``dump_dir/dsm_filling/bulldozer/`` - + **Configuration** @@ -1736,7 +1788,7 @@ The structure follows this organization: **Point cloud output** - If product type `point_cloud` is selected, a directory named `point_cloud` will be created with a subfolder for every pair. + If product type `point_cloud` is selected, a directory named `point_cloud` will be created with a subfolder for every pair. The point cloud output product consists of a collection of laz files, each containing a tile of the point cloud. @@ -1757,3 +1809,4 @@ The structure follows this organization: } } + diff --git a/tests/data/ref_output/classification_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/classification_end2end_ventoux_no_elevation.tif index 38446bd9..7cddaddb 100644 Binary files a/tests/data/ref_output/classification_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/classification_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/classification_end2end_ventoux_with_classif.tif b/tests/data/ref_output/classification_end2end_ventoux_with_classif.tif index 2d725b65..635d364d 100644 Binary files a/tests/data/ref_output/classification_end2end_ventoux_with_classif.tif and b/tests/data/ref_output/classification_end2end_ventoux_with_classif.tif differ diff --git a/tests/data/ref_output/clr_end2end_ventoux_split_no_merging.tif b/tests/data/ref_output/clr_end2end_ventoux_split_no_merging.tif index 3b4439fb..6fcb8f66 100644 Binary files a/tests/data/ref_output/clr_end2end_ventoux_split_no_merging.tif and b/tests/data/ref_output/clr_end2end_ventoux_split_no_merging.tif differ diff --git a/tests/data/ref_output/color_end2end_gizeh_crop_no_merging.tif b/tests/data/ref_output/color_end2end_gizeh_crop_no_merging.tif index b70e1f78..ed4b6f6a 100644 Binary files a/tests/data/ref_output/color_end2end_gizeh_crop_no_merging.tif and b/tests/data/ref_output/color_end2end_gizeh_crop_no_merging.tif differ diff --git a/tests/data/ref_output/color_end2end_gizeh_fill.tif b/tests/data/ref_output/color_end2end_gizeh_fill.tif index 189b8a3b..0131c69b 100644 Binary files a/tests/data/ref_output/color_end2end_gizeh_fill.tif and b/tests/data/ref_output/color_end2end_gizeh_fill.tif differ diff --git a/tests/data/ref_output/color_end2end_gizeh_fill_with_zero.tif b/tests/data/ref_output/color_end2end_gizeh_fill_with_zero.tif index 0191c691..1a914119 100644 Binary files a/tests/data/ref_output/color_end2end_gizeh_fill_with_zero.tif and b/tests/data/ref_output/color_end2end_gizeh_fill_with_zero.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux.tif b/tests/data/ref_output/color_end2end_ventoux.tif index 1e50cc21..5bdc7e32 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux.tif and b/tests/data/ref_output/color_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_egm96.tif b/tests/data/ref_output/color_end2end_ventoux_egm96.tif index 1e50cc21..5bdc7e32 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_egm96.tif and b/tests/data/ref_output/color_end2end_ventoux_egm96.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_egm96_custom_geoid.tif b/tests/data/ref_output/color_end2end_ventoux_egm96_custom_geoid.tif index b5959240..57016509 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_egm96_custom_geoid.tif and b/tests/data/ref_output/color_end2end_ventoux_egm96_custom_geoid.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/color_end2end_ventoux_no_elevation.tif index 1f089eee..e375090a 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/color_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/color_end2end_ventoux_no_srtm.tif index c00e1af9..c1814742 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/color_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/color_end2end_ventoux_quality_stats.tif index e6f72fe1..5496e966 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/color_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_split.tif b/tests/data/ref_output/color_end2end_ventoux_split.tif index 010ca6ac..ffae8939 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_split.tif and b/tests/data/ref_output/color_end2end_ventoux_split.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_split_4326.tif b/tests/data/ref_output/color_end2end_ventoux_split_4326.tif index f62f9be3..2d0dda98 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_split_4326.tif and b/tests/data/ref_output/color_end2end_ventoux_split_4326.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_with_color.tif b/tests/data/ref_output/color_end2end_ventoux_with_color.tif index 84695425..1976a521 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_with_color.tif and b/tests/data/ref_output/color_end2end_ventoux_with_color.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_with_roi.tif b/tests/data/ref_output/color_end2end_ventoux_with_roi.tif index 82a86480..e01b72ae 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_with_roi.tif and b/tests/data/ref_output/color_end2end_ventoux_with_roi.tif differ diff --git a/tests/data/ref_output/color_end2end_ventoux_with_snap_to_img1.tif b/tests/data/ref_output/color_end2end_ventoux_with_snap_to_img1.tif index 44fb5d34..b4ff8bc1 100644 Binary files a/tests/data/ref_output/color_end2end_ventoux_with_snap_to_img1.tif and b/tests/data/ref_output/color_end2end_ventoux_with_snap_to_img1.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split.tif b/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split.tif index 9089a1f9..59f81b94 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split.tif and b/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split_no_merging.tif b/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split_no_merging.tif index 9161f3aa..bbd16850 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split_no_merging.tif and b/tests/data/ref_output/confidence_from_ambiguity1_end2end_ventoux_split_no_merging.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split.tif b/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split.tif index 49b60cf0..c00f0410 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split.tif and b/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split_no_merging.tif b/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split_no_merging.tif index 32635698..53557826 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split_no_merging.tif and b/tests/data/ref_output/confidence_from_ambiguity2_end2end_ventoux_split_no_merging.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity_before_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_ambiguity_before_end2end_ventoux.tif index b9189598..faaca226 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity_before_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_ambiguity_before_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux.tif index 7dd5f4f0..d49eceb9 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux_no_srtm.tif index 4af40782..9b1473fb 100644 Binary files a/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/confidence_from_ambiguity_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/confidence_from_intensity_std_before_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_intensity_std_before_end2end_ventoux.tif index 985d4c56..2d149136 100644 Binary files a/tests/data/ref_output/confidence_from_intensity_std_before_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_intensity_std_before_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_intensity_std_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_intensity_std_end2end_ventoux.tif index 985d4c56..2d149136 100644 Binary files a/tests/data/ref_output/confidence_from_intensity_std_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_intensity_std_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_risk_max_before_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_risk_max_before_end2end_ventoux.tif index 4e2acf9b..8ce60c8d 100644 Binary files a/tests/data/ref_output/confidence_from_risk_max_before_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_risk_max_before_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_risk_max_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_risk_max_end2end_ventoux.tif index 405cf1c6..4ef0fcda 100644 Binary files a/tests/data/ref_output/confidence_from_risk_max_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_risk_max_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_risk_min_before_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_risk_min_before_end2end_ventoux.tif index 1ee3f124..d2d3de6e 100644 Binary files a/tests/data/ref_output/confidence_from_risk_min_before_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_risk_min_before_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/confidence_from_risk_min_end2end_ventoux.tif b/tests/data/ref_output/confidence_from_risk_min_end2end_ventoux.tif index 066cd21d..3b1912ce 100644 Binary files a/tests/data/ref_output/confidence_from_risk_min_end2end_ventoux.tif and b/tests/data/ref_output/confidence_from_risk_min_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/contributing_pair_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/contributing_pair_end2end_ventoux_no_elevation.tif index cfe63a10..761b8e69 100644 Binary files a/tests/data/ref_output/contributing_pair_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/contributing_pair_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/dem_max_end2end_ventoux.tif b/tests/data/ref_output/dem_max_end2end_ventoux.tif index 940f5547..a591e785 100644 Binary files a/tests/data/ref_output/dem_max_end2end_ventoux.tif and b/tests/data/ref_output/dem_max_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/dem_max_end2end_ventoux_8bit.tif b/tests/data/ref_output/dem_max_end2end_ventoux_8bit.tif index afd3efd5..683a7ebe 100644 Binary files a/tests/data/ref_output/dem_max_end2end_ventoux_8bit.tif and b/tests/data/ref_output/dem_max_end2end_ventoux_8bit.tif differ diff --git a/tests/data/ref_output/dem_max_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/dem_max_end2end_ventoux_no_srtm.tif index 2201483d..fb600bd2 100644 Binary files a/tests/data/ref_output/dem_max_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/dem_max_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/dem_max_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dem_max_end2end_ventoux_quality_stats.tif index bdbdc574..86d740d3 100644 Binary files a/tests/data/ref_output/dem_max_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dem_max_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dem_median_end2end_ventoux.tif b/tests/data/ref_output/dem_median_end2end_ventoux.tif index 68d63c76..991cfc19 100644 Binary files a/tests/data/ref_output/dem_median_end2end_ventoux.tif and b/tests/data/ref_output/dem_median_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/dem_median_end2end_ventoux_8bit.tif b/tests/data/ref_output/dem_median_end2end_ventoux_8bit.tif index 224601ec..b389e060 100644 Binary files a/tests/data/ref_output/dem_median_end2end_ventoux_8bit.tif and b/tests/data/ref_output/dem_median_end2end_ventoux_8bit.tif differ diff --git a/tests/data/ref_output/dem_median_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/dem_median_end2end_ventoux_no_srtm.tif index bd92606d..2365beff 100644 Binary files a/tests/data/ref_output/dem_median_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/dem_median_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/dem_median_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dem_median_end2end_ventoux_quality_stats.tif index c970581b..6e1cc5e0 100644 Binary files a/tests/data/ref_output/dem_median_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dem_median_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dem_min_end2end_ventoux.tif b/tests/data/ref_output/dem_min_end2end_ventoux.tif index 522c20d3..8d6cab8d 100644 Binary files a/tests/data/ref_output/dem_min_end2end_ventoux.tif and b/tests/data/ref_output/dem_min_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/dem_min_end2end_ventoux_8bit.tif b/tests/data/ref_output/dem_min_end2end_ventoux_8bit.tif index 6ee6627c..a124efa4 100644 Binary files a/tests/data/ref_output/dem_min_end2end_ventoux_8bit.tif and b/tests/data/ref_output/dem_min_end2end_ventoux_8bit.tif differ diff --git a/tests/data/ref_output/dem_min_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/dem_min_end2end_ventoux_no_srtm.tif index c9df123f..054bfdd6 100644 Binary files a/tests/data/ref_output/dem_min_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/dem_min_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/dem_min_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dem_min_end2end_ventoux_quality_stats.tif index c9df123f..f5b25df6 100644 Binary files a/tests/data/ref_output/dem_min_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dem_min_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dsm_end2end_gizeh_crop_no_merging.tif b/tests/data/ref_output/dsm_end2end_gizeh_crop_no_merging.tif index b8757f00..faa7254d 100644 Binary files a/tests/data/ref_output/dsm_end2end_gizeh_crop_no_merging.tif and b/tests/data/ref_output/dsm_end2end_gizeh_crop_no_merging.tif differ diff --git a/tests/data/ref_output/dsm_end2end_gizeh_fill.tif b/tests/data/ref_output/dsm_end2end_gizeh_fill.tif index aac4ad5b..55bbb900 100644 Binary files a/tests/data/ref_output/dsm_end2end_gizeh_fill.tif and b/tests/data/ref_output/dsm_end2end_gizeh_fill.tif differ diff --git a/tests/data/ref_output/dsm_end2end_gizeh_fill_with_zero.tif b/tests/data/ref_output/dsm_end2end_gizeh_fill_with_zero.tif index 9c929588..cb0a244d 100644 Binary files a/tests/data/ref_output/dsm_end2end_gizeh_fill_with_zero.tif and b/tests/data/ref_output/dsm_end2end_gizeh_fill_with_zero.tif differ diff --git a/tests/data/ref_output/dsm_end2end_paca_bulldozer.tif b/tests/data/ref_output/dsm_end2end_paca_bulldozer.tif index e7bed58c..577821b5 100644 Binary files a/tests/data/ref_output/dsm_end2end_paca_bulldozer.tif and b/tests/data/ref_output/dsm_end2end_paca_bulldozer.tif differ diff --git a/tests/data/ref_output/dsm_end2end_paca_matches_filling.tif b/tests/data/ref_output/dsm_end2end_paca_matches_filling.tif index 8a9fc6e8..fb45e2f2 100644 Binary files a/tests/data/ref_output/dsm_end2end_paca_matches_filling.tif and b/tests/data/ref_output/dsm_end2end_paca_matches_filling.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux.tif b/tests/data/ref_output/dsm_end2end_ventoux.tif index d0448588..1fd4b1ba 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux.tif and b/tests/data/ref_output/dsm_end2end_ventoux.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_egm96.tif b/tests/data/ref_output/dsm_end2end_ventoux_egm96.tif index 31039c5c..3a607016 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_egm96.tif and b/tests/data/ref_output/dsm_end2end_ventoux_egm96.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_egm96_custom_geoid.tif b/tests/data/ref_output/dsm_end2end_ventoux_egm96_custom_geoid.tif index 14588a14..fb74949b 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_egm96_custom_geoid.tif and b/tests/data/ref_output/dsm_end2end_ventoux_egm96_custom_geoid.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/dsm_end2end_ventoux_no_elevation.tif index 9515251c..90ec5b4c 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/dsm_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_no_srtm.tif b/tests/data/ref_output/dsm_end2end_ventoux_no_srtm.tif index 78ff4a9c..7a74fbec 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_no_srtm.tif and b/tests/data/ref_output/dsm_end2end_ventoux_no_srtm.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dsm_end2end_ventoux_quality_stats.tif index cf8964fd..a3aa2268 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dsm_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_split.tif b/tests/data/ref_output/dsm_end2end_ventoux_split.tif index bd95db5a..55417837 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_split.tif and b/tests/data/ref_output/dsm_end2end_ventoux_split.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_split_4326.tif b/tests/data/ref_output/dsm_end2end_ventoux_split_4326.tif index 279a19ad..682bf3b6 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_split_4326.tif and b/tests/data/ref_output/dsm_end2end_ventoux_split_4326.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_split_no_merging.tif b/tests/data/ref_output/dsm_end2end_ventoux_split_no_merging.tif index adfec8d3..8a152042 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_split_no_merging.tif and b/tests/data/ref_output/dsm_end2end_ventoux_split_no_merging.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_with_classif.tif b/tests/data/ref_output/dsm_end2end_ventoux_with_classif.tif index d0448588..1fd4b1ba 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_with_classif.tif and b/tests/data/ref_output/dsm_end2end_ventoux_with_classif.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_with_color.tif b/tests/data/ref_output/dsm_end2end_ventoux_with_color.tif index d0448588..1fd4b1ba 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_with_color.tif and b/tests/data/ref_output/dsm_end2end_ventoux_with_color.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_with_roi.tif b/tests/data/ref_output/dsm_end2end_ventoux_with_roi.tif index a9dda04f..aeb754cb 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_with_roi.tif and b/tests/data/ref_output/dsm_end2end_ventoux_with_roi.tif differ diff --git a/tests/data/ref_output/dsm_end2end_ventoux_with_snap_to_img1.tif b/tests/data/ref_output/dsm_end2end_ventoux_with_snap_to_img1.tif index 00fd91e0..1b0c7019 100644 Binary files a/tests/data/ref_output/dsm_end2end_ventoux_with_snap_to_img1.tif and b/tests/data/ref_output/dsm_end2end_ventoux_with_snap_to_img1.tif differ diff --git a/tests/data/ref_output/dsm_mean_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dsm_mean_end2end_ventoux_quality_stats.tif index fea7a221..8ca0b9c5 100644 Binary files a/tests/data/ref_output/dsm_mean_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dsm_mean_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dsm_n_pts_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dsm_n_pts_end2end_ventoux_quality_stats.tif index 0e33d65f..e63e2ddf 100644 Binary files a/tests/data/ref_output/dsm_n_pts_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dsm_n_pts_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dsm_pts_in_cell_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dsm_pts_in_cell_end2end_ventoux_quality_stats.tif index cb380d59..5cb3c6bf 100644 Binary files a/tests/data/ref_output/dsm_pts_in_cell_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dsm_pts_in_cell_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/dsm_std_end2end_ventoux_quality_stats.tif b/tests/data/ref_output/dsm_std_end2end_ventoux_quality_stats.tif index 25fc9a19..63bcff55 100644 Binary files a/tests/data/ref_output/dsm_std_end2end_ventoux_quality_stats.tif and b/tests/data/ref_output/dsm_std_end2end_ventoux_quality_stats.tif differ diff --git a/tests/data/ref_output/epi_pc_X_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/epi_pc_X_end2end_ventoux_no_elevation.tif index 2946708e..1b73f600 100644 Binary files a/tests/data/ref_output/epi_pc_X_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/epi_pc_X_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/epi_pc_Y_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/epi_pc_Y_end2end_ventoux_no_elevation.tif index f6ab18dd..3c5d75e7 100644 Binary files a/tests/data/ref_output/epi_pc_Y_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/epi_pc_Y_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/epi_pc_Z_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/epi_pc_Z_end2end_ventoux_no_elevation.tif index e3fdcb73..3e42d0c1 100644 Binary files a/tests/data/ref_output/epi_pc_Z_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/epi_pc_Z_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/epi_pc_classification_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/epi_pc_classification_end2end_ventoux_no_elevation.tif index 051e3759..aadac11f 100644 Binary files a/tests/data/ref_output/epi_pc_classification_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/epi_pc_classification_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/filling_end2end_gizeh_fill_with_zero.tif b/tests/data/ref_output/filling_end2end_gizeh_fill_with_zero.tif index a9ea545c..7b216c5b 100644 Binary files a/tests/data/ref_output/filling_end2end_gizeh_fill_with_zero.tif and b/tests/data/ref_output/filling_end2end_gizeh_fill_with_zero.tif differ diff --git a/tests/data/ref_output/filling_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/filling_end2end_ventoux_no_elevation.tif index 4a2dfef1..95e5cb73 100644 Binary files a/tests/data/ref_output/filling_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/filling_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/mask_end2end_gizeh_crop_no_merging.tif b/tests/data/ref_output/mask_end2end_gizeh_crop_no_merging.tif index 0347a247..a6d48021 100644 Binary files a/tests/data/ref_output/mask_end2end_gizeh_crop_no_merging.tif and b/tests/data/ref_output/mask_end2end_gizeh_crop_no_merging.tif differ diff --git a/tests/data/ref_output/mask_end2end_gizeh_fill_with_zero.tif b/tests/data/ref_output/mask_end2end_gizeh_fill_with_zero.tif index bb6267d3..764a88d3 100644 Binary files a/tests/data/ref_output/mask_end2end_gizeh_fill_with_zero.tif and b/tests/data/ref_output/mask_end2end_gizeh_fill_with_zero.tif differ diff --git a/tests/data/ref_output/mask_end2end_ventoux_no_elevation.tif b/tests/data/ref_output/mask_end2end_ventoux_no_elevation.tif index dd2bb154..d3b37fce 100644 Binary files a/tests/data/ref_output/mask_end2end_ventoux_no_elevation.tif and b/tests/data/ref_output/mask_end2end_ventoux_no_elevation.tif differ diff --git a/tests/data/ref_output/pandora_epi_disp.tif b/tests/data/ref_output/pandora_epi_disp.tif new file mode 100644 index 00000000..c1c80ac7 Binary files /dev/null and b/tests/data/ref_output/pandora_epi_disp.tif differ diff --git a/tests/data/ref_output/performance_map_end2end_gizeh_crop_no_merging.tif b/tests/data/ref_output/performance_map_end2end_gizeh_crop_no_merging.tif index b8a4efa0..5bbee490 100644 Binary files a/tests/data/ref_output/performance_map_end2end_gizeh_crop_no_merging.tif and b/tests/data/ref_output/performance_map_end2end_gizeh_crop_no_merging.tif differ diff --git a/tests/data/ref_output/performance_map_end2end_ventoux_split.tif b/tests/data/ref_output/performance_map_end2end_ventoux_split.tif index 29f41d31..d5db0380 100644 Binary files a/tests/data/ref_output/performance_map_end2end_ventoux_split.tif and b/tests/data/ref_output/performance_map_end2end_ventoux_split.tif differ diff --git a/tests/data/ref_output/performance_map_end2end_ventoux_split_no_merging.tif b/tests/data/ref_output/performance_map_end2end_ventoux_split_no_merging.tif index ebc79332..5a2d450f 100644 Binary files a/tests/data/ref_output/performance_map_end2end_ventoux_split_no_merging.tif and b/tests/data/ref_output/performance_map_end2end_ventoux_split_no_merging.tif differ diff --git a/tests/data/ref_output/source_pc_end2end_ventoux_split_4326.tif b/tests/data/ref_output/source_pc_end2end_ventoux_split_4326.tif index 5e566077..dc2dd096 100644 Binary files a/tests/data/ref_output/source_pc_end2end_ventoux_split_4326.tif and b/tests/data/ref_output/source_pc_end2end_ventoux_split_4326.tif differ diff --git a/tests/test_end2end.py b/tests/test_end2end.py index 4cf3f818..c727aeb5 100644 --- a/tests/test_end2end.py +++ b/tests/test_end2end.py @@ -82,6 +82,11 @@ def test_end2end_gizeh_rectangle_epi_image_performance_map(): ) dense_dsm_applications = { "grid_generation": {"method": "epipolar", "epi_step": 30}, + "sparse_matching.pandora": { + "save_intermediate_data": True, + "connection_val": 3.0, + "nb_pts_threshold": 100, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -220,7 +225,7 @@ def test_end2end_ventoux_sparse_dsm_8bits(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic"}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", # Uncomment the following line to update dsm reference data # "sift_peak_threshold":1, @@ -229,6 +234,13 @@ def test_end2end_ventoux_sparse_dsm_8bits(): "elevation_delta_upper_bound": 20.0, "save_intermediate_data": False, }, + "sparse_matching.pandora": { + "save_intermediate_data": True, + "connection_val": 3.0, + "nb_pts_threshold": 100, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -271,7 +283,7 @@ def test_end2end_ventoux_sparse_dsm_8bits(): == 612 ) assert ( - -20 + -22 < out_json["applications"]["disparity_range_computation"][ "left_right" ]["minimum_disparity"] @@ -376,7 +388,7 @@ def test_end2end_ventoux_unique(): "epi_step": 30, }, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -384,9 +396,16 @@ def test_end2end_ventoux_unique(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { # run disp min disp max in the global pipeline - "use_global_disp_range": True + "use_global_disp_range": True, + "save_intermediate_data": True, }, "dem_generation": { # save the dems in the global pipeline @@ -429,7 +448,7 @@ def test_end2end_ventoux_unique(): < out_json["applications"]["disparity_range_computation"][ "left_right" ]["minimum_disparity"] - < -19 + < -18 ) assert ( 12 @@ -441,7 +460,6 @@ def test_end2end_ventoux_unique(): # Ref output dir dependent from geometry plugin chosen ref_output_dir = "ref_output" - # Uncomment the 2 following instructions to update reference data # copy2( # os.path.join(out_dir, "dump_dir", "dem_generation", @@ -522,7 +540,9 @@ def test_end2end_ventoux_unique(): assert "sensors" in used_conf["inputs"] # check used_conf sparse_matching configuration assert ( - used_conf["applications"]["sparse_matching"]["disparity_margin"] + used_conf["applications"]["sparse_matching.sift"][ + "disparity_margin" + ] == 0.25 ) # check used_conf orchestrator conf is the same as gt @@ -921,7 +941,7 @@ def test_end2end_ventoux_unique(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -953,6 +973,12 @@ def test_end2end_ventoux_unique(): input_config_dense_dsm = input_config_sparse_dsm.copy() # update applications dense_dsm_applications = { + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_global_disp_range": False, @@ -1072,7 +1098,7 @@ def test_end2end_ventoux_unique(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -1080,6 +1106,12 @@ def test_end2end_ventoux_unique(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -1176,6 +1208,10 @@ def test_end2end_ventoux_unique_split_epsg_4326(): }, ) input_config_pc["applications"] = { + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -1339,7 +1375,7 @@ def test_end2end_ventoux_unique_split(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 200}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -1347,6 +1383,12 @@ def test_end2end_ventoux_unique_split(): "disparity_margin": 0.25, "save_intermediate_data": False, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -2008,12 +2050,18 @@ def test_end2end_use_epipolar_a_priori(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 200}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "nb_pts_threshold": 100, + "connection_val": 3.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -2058,7 +2106,7 @@ def test_end2end_use_epipolar_a_priori(): < out_json["applications"]["disparity_range_computation"][ "left_right" ]["minimum_disparity"] - < -25 + < -23 ) assert ( 23 @@ -2171,7 +2219,9 @@ def test_end2end_use_epipolar_a_priori(): assert "sensors" in used_conf["inputs"] # check used_conf sparse_matching configuration assert ( - used_conf["applications"]["sparse_matching"]["disparity_margin"] + used_conf["applications"]["sparse_matching.sift"][ + "disparity_margin" + ] == 0.25 ) # check used_conf orchestrator conf is the same as gt @@ -2350,7 +2400,7 @@ def test_prepare_ventoux_bias(): application_config = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 100}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "epipolar_error_maximum_bias": 50.0, @@ -2359,6 +2409,14 @@ def test_prepare_ventoux_bias(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "nb_pts_threshold": 150, + "connection_val": 3.0, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 120.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -2430,7 +2488,7 @@ def test_end2end_ventoux_full_output_no_elevation(): "strip_height": 80, "save_intermediate_data": True, }, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": 400.0, @@ -2438,6 +2496,12 @@ def test_end2end_ventoux_full_output_no_elevation(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": 400.0, + "elevation_delta_upper_bound": 700.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -2827,7 +2891,7 @@ def test_end2end_ventoux_with_color(): "strip_height": 80, "save_intermediate_data": True, }, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -2835,6 +2899,12 @@ def test_end2end_ventoux_with_color(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -3097,7 +3167,7 @@ def test_end2end_ventoux_with_classif(): "strip_height": 80, "save_intermediate_data": True, }, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -3105,6 +3175,12 @@ def test_end2end_ventoux_with_classif(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { # run disp min disp max in the global pipeline "use_global_disp_range": True @@ -3354,7 +3430,7 @@ def test_compute_dsm_with_roi_ventoux(): "use_cross_validation": True, "use_global_disp_range": False, }, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -1000, # -20.0, @@ -3362,6 +3438,12 @@ def test_compute_dsm_with_roi_ventoux(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -1000, # -20.0, + "elevation_delta_upper_bound": 1000, # 20.0, + }, "point_cloud_rasterization": { "method": "simple_gaussian", "dsm_radius": 3, @@ -3500,7 +3582,7 @@ def test_compute_dsm_with_snap_to_img1(): dense_dsm_applications = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -3508,6 +3590,12 @@ def test_compute_dsm_with_snap_to_img1(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -3621,11 +3709,15 @@ def test_end2end_quality_stats(): dense_dsm_applications = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "disparity_margin": 0.25, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -3680,7 +3772,7 @@ def test_end2end_quality_stats(): out_disp_compute = out_data["applications"]["dense_matching"][ "left_right" ] - assert out_disp_compute["global_disp_min"] > -33 + assert out_disp_compute["global_disp_min"] > -36 assert out_disp_compute["global_disp_min"] < -32 assert out_disp_compute["global_disp_max"] > 25 assert out_disp_compute["global_disp_max"] < 32 @@ -3904,7 +3996,7 @@ def test_end2end_ventoux_egm96_geoid(): "save_intermediate_data": True, }, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -3912,6 +4004,12 @@ def test_end2end_ventoux_egm96_geoid(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -3964,7 +4062,7 @@ def test_end2end_ventoux_egm96_geoid(): "left_right" ] # global_disp_min -21 shareloc - assert out_disp_compute["global_disp_min"] > -67 + assert out_disp_compute["global_disp_min"] > -68 assert out_disp_compute["global_disp_min"] < -66 # global max: 86 shareloc assert out_disp_compute["global_disp_max"] > 45 @@ -4027,7 +4125,7 @@ def test_end2end_ventoux_egm96_geoid(): "save_intermediate_data": True, }, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -4035,6 +4133,12 @@ def test_end2end_ventoux_egm96_geoid(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -4116,7 +4220,7 @@ def test_end2end_ventoux_egm96_geoid(): "save_intermediate_data": True, }, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -4124,6 +4228,12 @@ def test_end2end_ventoux_egm96_geoid(): "disparity_margin": 0.25, "save_intermediate_data": True, }, + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + "elevation_delta_lower_bound": -20.0, + "elevation_delta_upper_bound": 20.0, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True, @@ -4178,7 +4288,7 @@ def test_end2end_ventoux_egm96_geoid(): "left_right" ] # global_disp_min -21 shareloc - assert out_disp_compute["global_disp_min"] > -67 + assert out_disp_compute["global_disp_min"] > -68 assert out_disp_compute["global_disp_min"] < -66 # global max: 86 shareloc assert out_disp_compute["global_disp_max"] > 45 @@ -4249,7 +4359,7 @@ def test_end2end_paca_with_mask(): dense_dsm_applications = { "grid_generation": {"method": "epipolar", "epi_step": 30}, "resampling": {"method": "bicubic", "strip_height": 80}, - "sparse_matching": { + "sparse_matching.sift": { "method": "sift", "epipolar_error_upper_bound": 43.0, "elevation_delta_lower_bound": -20.0, @@ -4577,6 +4687,10 @@ def test_end2end_disparity_filling_with_zeros(): "multiprocessing", ) dense_dsm_applications = { + "sparse_matching.pandora": { + "resolution": 4, + "save_intermediate_data": True, + }, "dense_matching": { "method": "census_sgm", "use_cross_validation": True,