From 3986faa3865ba479db426046f84b657a18f933ac Mon Sep 17 00:00:00 2001 From: Emmanuel Dubois Date: Mon, 2 Oct 2023 12:05:32 +0200 Subject: [PATCH 01/18] feat: first geomodel structure to aggregate rpc and grid geomodel structure --- shareloc/geomodels/__init__.py | 8 +- shareloc/geomodels/geomodel.py | 112 ++++++++++++++++++++++++ shareloc/geomodels/geomodel_template.py | 58 ++++++++++++ shareloc/geomodels/grid.py | 8 +- shareloc/geomodels/rpc.py | 5 +- 5 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 shareloc/geomodels/geomodel.py create mode 100644 shareloc/geomodels/geomodel_template.py diff --git a/shareloc/geomodels/__init__.py b/shareloc/geomodels/__init__.py index 8bea709..00772b2 100644 --- a/shareloc/geomodels/__init__.py +++ b/shareloc/geomodels/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding: utf8 # -# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). # # This file is part of Shareloc # (see https://github.com/CNES/shareloc). @@ -20,4 +20,10 @@ # """ Shareloc geomodels module +Imports are used to simplify calls to module API Coregistration. """ +# Demcompare imports +from . import grid, rpc +from .geomodel import GeoModel + +__all__ = ["rpc", "grid", "GeoModel"] # To avoid flake8 F401 diff --git a/shareloc/geomodels/geomodel.py b/shareloc/geomodels/geomodel.py new file mode 100644 index 0000000..450167c --- /dev/null +++ b/shareloc/geomodels/geomodel.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 GeoModel class factory. +This main API GeoModel class generates an object linked +with geomodel configuration "geomodel_name" +(from registered GeoModelTemplate class) + +** Experimental and not used as API for now** +""" + +# Standard imports +import logging +from typing import Any, Dict + + +class GeoModel: + """ + GeoModel factory: + A class designed for registered all available geomodels + and instantiate them when needed. + """ + + # Dict (geomodel_name: str, class: object) containing registered geomodels + available_geomodels: Dict[str, Any] = {} + + def __new__(cls, geomodel_path: str, geomodel_type: str = "RPC"): + """ + Return a GeoModelTemplate child instance + associated with the "geomodel_name" given in the configuration + through create_geomodel local method for clarity. + + :param geomodel_path: Path of geomodel file + :type geomodel_path: string + :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance + :type geomodel_type: string + """ + return cls.create_geomodel(geomodel_path, geomodel_type) + + @classmethod + def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): + """ + Factory command to create the geomodel from geomodel_type + Return a GeoModelTemplate child instance + associated with the "geomodel_type" + + :param geomodel_path: Path of geomodel file + :type geomodel_path: string + :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance + :type geomodel_type: string + """ + + # If no type is given, the default is "rpc" + + # Create geomodel object with geomodel_path parameter from geomodel_type if exists + try: + geomodel_class = cls.available_geomodels[geomodel_type] + geomodel = geomodel_class(geomodel_path) + logging.debug("GeoModel type name: %s", geomodel_type) + except KeyError: + logging.error("Geomodel type named %s is not supported", geomodel_type) + raise + + return geomodel + + @classmethod + def print_avalaible_geomodels(cls): + """ + Print all registered applications + """ + for geomodel_type in cls.available_geomodels: + print(geomodel_type) + + @classmethod + def register(cls, geomodel_type: str): + """ + Allows to register the GeoModelTemplate subclass in the factory + with its geomodel geomodel_type through decorator + + :param geomodel_type: the subclass name to be registered + :type geomodel_type: string + """ + + def decorator(geomodel_subclass): + """ + Register the geomodel subclass in the available methods + + :param geomodel_subclass: the subclass to be registered + :type geomodel_subclass: object + """ + cls.available_geomodels[geomodel_type] = geomodel_subclass + return geomodel_subclass + + return decorator diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py new file mode 100644 index 0000000..e1c6cdf --- /dev/null +++ b/shareloc/geomodels/geomodel_template.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 coregistration class template. +It contains the structure for all coregistration methods in subclasses and +generic coregistration code to avoid duplication. +""" + +# Standard imports +from abc import ABCMeta, abstractmethod +from typing import Dict + +# Third party imports + + +# pylint: disable=too-few-public-methods +class GeoModelTemplate(metaclass=ABCMeta): + """ + Class for general specification of a geometric model + declined in rpc.py and grid.py + """ + + @abstractmethod + def __init__(self, geomodel_file: str, geomodel_params: Dict = None): + """ + Return the geomodel object associated with the geomodel_type + given in the configuration + + :param geomodel_file: path of the geomodel file to instanciate. + :type geomodel_file: string + :param geomodel_params: Geomodels parameters as dict (unused, just to fit RPC) TODO: evolve with new API + :type geomodel_params: Dict + """ + # geomodel filename path + self.filename = geomodel_file + + # geomodel_params if exists (to evolve) + self.geomodels_params = geomodel_params + + # diff --git a/shareloc/geomodels/grid.py b/shareloc/geomodels/grid.py index dec6ebf..c27be7d 100755 --- a/shareloc/geomodels/grid.py +++ b/shareloc/geomodels/grid.py @@ -30,6 +30,8 @@ import numpy as np # Shareloc imports +from shareloc.geomodels.geomodel import GeoModel +from shareloc.geomodels.geomodel_template import GeoModelTemplate from shareloc.image import Image from shareloc.math_utils import interpol_bilin, interpol_bilin_vectorized from shareloc.proj_utils import coordinates_conversion @@ -37,7 +39,8 @@ # gitlab issue #58 # pylint: disable=too-many-instance-attributes -class Grid: +@GeoModel.register("grid") +class Grid(GeoModelTemplate): """ multi H direct localization grid handling class. please refer to the main documentation grid format @@ -83,7 +86,8 @@ def __init__(self, grid_filename): :param grid_filename: grid filename (Geotiff) :type grid_filename: string """ - self.filename = grid_filename + # Instanciate GeoModelTemplate generic init with shared parameters + super().__init__(grid_filename) self.row0 = None self.col0 = None self.nbrow = None diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 02856b8..2af5cbf 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -36,6 +36,8 @@ from numba import config, njit, prange # Shareloc imports +from shareloc.geomodels.geomodel import GeoModel +from shareloc.geomodels.geomodel_template import GeoModelTemplate from shareloc.proj_utils import coordinates_conversion # Set numba type of threading layer before parallel target compilation @@ -119,7 +121,8 @@ def identify_geotiff_rpc(image_filename): return None -class RPC: +@GeoModel.register("RPC") +class RPC(GeoModelTemplate): """ RPC class including direct and inverse localization instance methods """ From 23fb25e81ab0968cbbacf5b9239ccb63198b1d79 Mon Sep 17 00:00:00 2001 From: Emmanuel Dubois Date: Wed, 18 Oct 2023 17:18:27 +0200 Subject: [PATCH 02/18] feat: align geomodels Grid and RPC API, clean RPC.from_any, update doc, clean geomodels --- docs/source/getting_started.rst | 2 +- docs/source/user_manual_conventions.rst | 6 - docs/source/user_manual_geometric_models.rst | 2 +- shareloc/geofunctions/localization.py | 3 +- shareloc/geofunctions/rectification_grid.py | 2 +- shareloc/geomodels/geomodel.py | 6 +- shareloc/geomodels/geomodel_template.py | 26 +- shareloc/geomodels/grid.py | 24 +- shareloc/geomodels/rpc.py | 355 +++++++++---------- tests/geofunctions/test_localization.py | 28 +- tests/geofunctions/test_rectification.py | 72 +--- tests/geofunctions/test_triangulation.py | 12 +- tests/geomodels/test_los.py | 4 +- tests/geomodels/test_rpc.py | 38 +- 14 files changed, 271 insertions(+), 309 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index a95ec74..57b5726 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -40,7 +40,7 @@ Quick Start >>> # Create RPC object from downloaded geometry file >>> rpc_geom_file = "left_image.geom" - >>> rpc = RPC.from_any(rpc_geom_file) + >>> rpc = RPC(rpc_geom_file) >>> # Create Localization object from created RPC >>> loc = Localization(rpc) diff --git a/docs/source/user_manual_conventions.rst b/docs/source/user_manual_conventions.rst index a91bffb..14c2f76 100644 --- a/docs/source/user_manual_conventions.rst +++ b/docs/source/user_manual_conventions.rst @@ -19,12 +19,6 @@ Geometric model convention for coordinates is, by default, [0.5,0.5] at the cent pixel convention -When dealing with shareloc RPC (``shareloc.geomodels.rpc.RPC``), the center at [0,0] can be changed by setting the ``topleftconvention`` option to ``False``. - -.. code-block:: bash - - @classmethod - def from_any(cls, primary_file, secondary_file=None, topleftconvention=True): Image convention ================ diff --git a/docs/source/user_manual_geometric_models.rst b/docs/source/user_manual_geometric_models.rst index 0caa518..fa70023 100644 --- a/docs/source/user_manual_geometric_models.rst +++ b/docs/source/user_manual_geometric_models.rst @@ -52,7 +52,7 @@ RPC class API Example $ python3 >>> from shareloc.geomodels.rpc import RPC >>> file_dimap = "RPC_PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.XML") - >>> rpc_dimap = RPC.from_any(file_dimap) + >>> rpc_dimap = RPC(file_dimap) Direct location grids diff --git a/shareloc/geofunctions/localization.py b/shareloc/geofunctions/localization.py index 6739fb0..c0f58c8 100755 --- a/shareloc/geofunctions/localization.py +++ b/shareloc/geofunctions/localization.py @@ -53,7 +53,7 @@ def __init__(self, model, elevation=None, image=None, epsg=None): :param epsg: coordinate system of world points, if None model coordiante system will be used :type epsg: int """ - self.use_rpc = model.type == "rpc" + self.use_rpc = model.type == "RPC" self.model = model self.default_elevation = 0.0 self.dtm = None @@ -137,6 +137,7 @@ def inverse(self, lon, lat, h=None, using_geotransform=False): """ if not self.use_rpc and not hasattr(self.model, "pred_ofset_scale_lon"): + # for grids only self.model.estimate_inverse_loc_predictor() if h is None: h = self.default_elevation diff --git a/shareloc/geofunctions/rectification_grid.py b/shareloc/geofunctions/rectification_grid.py index 53b02a5..c9a8aff 100755 --- a/shareloc/geofunctions/rectification_grid.py +++ b/shareloc/geofunctions/rectification_grid.py @@ -40,7 +40,7 @@ def __init__(self, grid_filename): :param grid_filename: grid filename :type filename: string """ - self.filename = grid_filename + self.grid_filename = grid_filename dataset = rio.open(grid_filename) diff --git a/shareloc/geomodels/geomodel.py b/shareloc/geomodels/geomodel.py index 450167c..e6ee48b 100644 --- a/shareloc/geomodels/geomodel.py +++ b/shareloc/geomodels/geomodel.py @@ -25,6 +25,8 @@ (from registered GeoModelTemplate class) ** Experimental and not used as API for now** +** Will be used with an automatic switcher between Grid file format and +RPC file format obj = GeoModel("file_geom") only without geomodel_type """ # Standard imports @@ -48,6 +50,8 @@ def __new__(cls, geomodel_path: str, geomodel_type: str = "RPC"): associated with the "geomodel_name" given in the configuration through create_geomodel local method for clarity. + TODO: optional geomodel_type would profit to have an automatic switcher between geomodels (RPC, grids, ...) + :param geomodel_path: Path of geomodel file :type geomodel_path: string :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance @@ -68,7 +72,7 @@ def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): :type geomodel_type: string """ - # If no type is given, the default is "rpc" + # If no type is given, the default is "RPC" # Create geomodel object with geomodel_path parameter from geomodel_type if exists try: diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index e1c6cdf..dca847c 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -26,12 +26,18 @@ # Standard imports from abc import ABCMeta, abstractmethod -from typing import Dict -# Third party imports +# Global variable for optimization mode (functions in C) +# SHARELOC_OPTIM_GEOMODEL = False +# TODO: Override functions depending on optimization or not + +# if(SHARELOC_OPTIM_GEOMODEL == True): +# GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim # pylint: disable=too-few-public-methods + + class GeoModelTemplate(metaclass=ABCMeta): """ Class for general specification of a geometric model @@ -39,20 +45,16 @@ class GeoModelTemplate(metaclass=ABCMeta): """ @abstractmethod - def __init__(self, geomodel_file: str, geomodel_params: Dict = None): + def __init__(self, geomodel_path: str): """ Return the geomodel object associated with the geomodel_type given in the configuration - :param geomodel_file: path of the geomodel file to instanciate. - :type geomodel_file: string - :param geomodel_params: Geomodels parameters as dict (unused, just to fit RPC) TODO: evolve with new API - :type geomodel_params: Dict + :param geomodel_path: path of the geomodel file to instanciate. + :type geomodel_path: string """ # geomodel filename path - self.filename = geomodel_file - - # geomodel_params if exists (to evolve) - self.geomodels_params = geomodel_params + self.geomodel_path: str = geomodel_path - # + # geomodel type. Set by the subclass + self.type: str diff --git a/shareloc/geomodels/grid.py b/shareloc/geomodels/grid.py index c27be7d..93a035d 100755 --- a/shareloc/geomodels/grid.py +++ b/shareloc/geomodels/grid.py @@ -19,9 +19,8 @@ # limitations under the License. # """ -localisation functions from multi h direct grids. +Localisation functions from multi h direct grids. """ -# pylint: disable=no-member # Standard imports import logging @@ -39,14 +38,15 @@ # gitlab issue #58 # pylint: disable=too-many-instance-attributes +# pylint: disable=no-member @GeoModel.register("grid") class Grid(GeoModelTemplate): """ multi H direct localization grid handling class. please refer to the main documentation grid format - :param filename: grid path - :type filename: str + Derives from GeoModelTemplate + :param row0: grid first pixel center along Y axis (row). :type row0: float :param col0: grid first pixel center along X axis (column). @@ -79,15 +79,18 @@ class Grid(GeoModelTemplate): :type type: str """ - def __init__(self, grid_filename): + def __init__(self, geomodel_path: str): """ Grid Constructor - :param grid_filename: grid filename (Geotiff) - :type grid_filename: string + :param geomodel_path: grid filename (Geotiff) + :type geomodel_path: string """ # Instanciate GeoModelTemplate generic init with shared parameters - super().__init__(grid_filename) + super().__init__(geomodel_path) + self.type = "multi H grid" + + # GeoModel Grid parameters definition (see documentation) self.row0 = None self.col0 = None self.nbrow = None @@ -102,8 +105,9 @@ def __init__(self, grid_filename): self.rowmax = None self.colmax = None self.epsg = 0 + + # Load function of grid parameters from grid file to grid object self.load() - self.type = "multi H grid" def load(self): """ @@ -116,7 +120,7 @@ def load(self): - lat_data : [alt,row,col] """ - grid_image = Image(self.filename, read_data=True) + grid_image = Image(self.geomodel_path, read_data=True) if grid_image.dataset.driver != "GTiff": raise TypeError( "Only Geotiff grids are accepted. Please refer to the documentation for grid supported format." diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 2af5cbf..2d99e22 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -23,11 +23,11 @@ This module contains the RPC class corresponding to the RPC models. RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. """ -# pylint: disable=no-member # Standard imports import logging from os.path import basename +from typing import Dict from xml.dom import minidom # Third party imports @@ -121,6 +121,9 @@ def identify_geotiff_rpc(image_filename): return None +# pylint: disable=no-member + + @GeoModel.register("RPC") class RPC(GeoModelTemplate): """ @@ -129,13 +132,25 @@ class RPC(GeoModelTemplate): # gitlab issue #61 # pylint: disable=too-many-instance-attributes - def __init__(self, rpc_params): + def __init__(self, geomodel_path: str): + # Instanciate GeoModelTemplate generic init with shared parameters + super().__init__(geomodel_path) + self.type = "RPC" + + # initiate epsg and datum, can overriden by rpc_params self.epsg = None self.datum = None - for key, value in rpc_params.items(): + + # RPC parameters as Dict + self.rpc_params: Dict = {} + + # RPC parameters are load from geomodel_path to rpc params + self.load() + + # set class parameters from rpc_params (epsg and datum can be overriden) + for key, value in self.rpc_params.items(): setattr(self, key, value) - self.type = "rpc" if self.epsg is None: self.epsg = 4326 if self.datum is None: @@ -143,31 +158,32 @@ def __init__(self, rpc_params): self.lim_extrapol = 1.0001 + # Monomes seems not used in shareloc code: Clean ? # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] - monomes_order = [ - [1, 0, 0, 0], - [1, 1, 0, 0], - [1, 0, 1, 0], - [1, 0, 0, 1], - [1, 1, 1, 0], - [1, 1, 0, 1], - [1, 0, 1, 1], - [1, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [1, 1, 1, 1], - [1, 3, 0, 0], - [1, 1, 2, 0], - [1, 1, 0, 2], - [1, 2, 1, 0], - [1, 0, 3, 0], - [1, 0, 1, 2], - [1, 2, 0, 1], - [1, 0, 2, 1], - [1, 0, 0, 3], - ] - - self.monomes = np.array(monomes_order) + self.monomes = np.array( + [ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 0, 1, 0], + [1, 0, 0, 1], + [1, 1, 1, 0], + [1, 1, 0, 1], + [1, 0, 1, 1], + [1, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [1, 1, 1, 1], + [1, 3, 0, 0], + [1, 1, 2, 0], + [1, 1, 0, 2], + [1, 2, 1, 0], + [1, 0, 3, 0], + [1, 0, 1, 2], + [1, 2, 0, 1], + [1, 0, 2, 1], + [1, 0, 0, 3], + ] + ) # monomial coefficients of 1st variable derivative self.monomes_deriv_1 = np.array( @@ -246,153 +262,164 @@ def __init__(self, rpc_params): self.row0 = self.offset_row - self.scale_row self.rowmax = self.offset_row + self.scale_row - @classmethod - def from_dimap(cls, dimap_filepath, topleftconvention=True): + def load(self): """ - Load from Dimap + Load from any RPC (auto identify driver) + from filename (dimap, ossim kwl, geotiff) - param dimap_filepath: dimap xml file - :type dimap_filepath: str - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - dimap_version = identify_dimap(dimap_filepath) - if identify_dimap(dimap_filepath) is not None: - if float(dimap_version) < 2.0: - return cls.from_dimap_v1(dimap_filepath, topleftconvention) - if float(dimap_version) >= 2.0: - return cls.read_dimap_coeff(dimap_filepath, topleftconvention) - else: - raise ValueError("can''t read dimap file") + TODO: topleftconvention always to True, set a standard and remove the option - return None + topleftconvention boolean: [0,0] position + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # Set topleftconvention (keeping historic option): to clean + topleftconvention = True - @classmethod - def read_dimap_coeff(cls, dimap_filepath, topleftconvention=True): + # If ends with XML --> DIMAP + if basename(self.geomodel_path.upper()).endswith("XML"): + dimap_version = identify_dimap(self.geomodel_path) + if dimap_version is not None: + if float(dimap_version) < 2.0: + self.load_dimap_v1(topleftconvention) + if float(dimap_version) >= 2.0: + self.load_dimap_coeff(topleftconvention) + else: + # If not DIMAP, identify ossim + ossim_model = identify_ossim_kwl(self.geomodel_path) + if ossim_model is not None: + self.load_ossim_kwl(topleftconvention) + else: + # Otherwise, check if RPC is in geotif + geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) + if geotiff_rpc_dict is not None: + self.load_geotiff(topleftconvention) + else: + # Raise error if no file recognized. + raise ValueError("can not read rpc file") + + def load_dimap_coeff(self, topleftconvention=True): """ Load from Dimap v2 and V3 - :param dimap_filepath: dimap xml file - :type dimap_filepath: str :param topleftconvention: [0,0] position :type topleftconvention: boolean If False : [0,0] is at the center of the Top Left pixel If True : [0,0] is at the top left of the Top Left pixel (OSSIM) """ - rpc_params = {} - if not basename(dimap_filepath).upper().endswith("XML"): + if not basename(self.geomodel_path).upper().endswith("XML"): raise ValueError("dimap must ends with .xml") - xmldoc = minidom.parse(dimap_filepath) + xmldoc = minidom.parse(self.geomodel_path) mtd = xmldoc.getElementsByTagName("Metadata_Identification") version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] - rpc_params["driver_type"] = "dimap_v" + version + self.rpc_params["driver_type"] = "dimap_v" + version global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] - rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) - rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) + self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) + self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) if float(version) >= 3: direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] - rpc_params["num_x"] = [ + self.rpc_params["num_x"] = [ float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_x"] = [ + self.rpc_params["den_x"] = [ float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["num_y"] = [ + self.rpc_params["num_y"] = [ float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_y"] = [ + self.rpc_params["den_y"] = [ float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["offset_col"] -= 0.5 - rpc_params["offset_row"] -= 0.5 + self.rpc_params["offset_col"] -= 0.5 + self.rpc_params["offset_row"] -= 0.5 else: direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] - rpc_params["num_x"] = [ + self.rpc_params["num_x"] = [ float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_x"] = [ + self.rpc_params["den_x"] = [ float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["num_y"] = [ + self.rpc_params["num_y"] = [ float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_y"] = [ + self.rpc_params["den_y"] = [ float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["offset_col"] -= 1.0 - rpc_params["offset_row"] -= 1.0 + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 - rpc_params["num_col"] = [ + self.rpc_params["num_col"] = [ float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_col"] = [ + self.rpc_params["den_col"] = [ float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["num_row"] = [ + self.rpc_params["num_row"] = [ float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["den_row"] = [ + self.rpc_params["den_row"] = [ float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) for index in range(1, 21) ] - rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) - rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) - rpc_params["offset_alt"] = float(normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data) - rpc_params["scale_alt"] = float(normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data) - rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) - rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) - rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) - rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) + self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) + self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) + self.rpc_params["offset_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data + ) + self.rpc_params["scale_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data + ) + self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) + self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) + self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) + self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) # If top left convention, 0.5 pixel shift added on col/row offsets if topleftconvention: - rpc_params["offset_col"] += 0.5 - rpc_params["offset_row"] += 0.5 - return cls(rpc_params) + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 - @classmethod - def from_dimap_v1(cls, dimap_filepath, topleftconvention=True): + def load_dimap_v1(self, topleftconvention=True): """ Load from dimap v1 - :param dimap_filepath: dimap xml file - :type dimap_filepath: str + ** Deprecated, to clean ? ** + :param topleftconvention: [0,0] position :type topleftconvention: boolean If False : [0,0] is at the center of the Top Left pixel If True : [0,0] is at the top left of the Top Left pixel (OSSIM) """ - if not basename(dimap_filepath).upper().endswith("XML"): + if not basename(self.geomodel_path).upper().endswith("XML"): raise ValueError("dimap must ends with .xml") - xmldoc = minidom.parse(dimap_filepath) + xmldoc = minidom.parse(self.geomodel_path) mtd = xmldoc.getElementsByTagName("Metadata_Identification") version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] - rpc_params = {"driver_type": "dimap_v" + version} + self.rpc_params = {"driver_type": "dimap_v" + version} global_rfm = xmldoc.getElementsByTagName("Global_RFM") rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") @@ -412,50 +439,47 @@ def from_dimap_v1(cls, dimap_filepath, topleftconvention=True): scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) - rpc_params["offset_col"] = offset_col - rpc_params["scale_col"] = scale_col - rpc_params["offset_row"] = offset_row - rpc_params["scale_row"] = scale_row - rpc_params["offset_alt"] = offset_alt - rpc_params["scale_alt"] = scale_alt - rpc_params["offset_x"] = offset_lon - rpc_params["scale_x"] = scale_lon - rpc_params["offset_y"] = offset_lat - rpc_params["scale_y"] = scale_lat - rpc_params["num_x"] = coeff_lon[0:20] - rpc_params["den_x"] = coeff_lon[20::] - rpc_params["num_y"] = coeff_lat[0:20] - rpc_params["den_y"] = coeff_lat[20::] - rpc_params["num_col"] = coeff_col[0:20] - rpc_params["den_col"] = coeff_col[20::] - rpc_params["num_row"] = coeff_lig[0:20] - rpc_params["den_row"] = coeff_lig[20::] - rpc_params["offset_col"] -= 1.0 - rpc_params["offset_row"] -= 1.0 + self.rpc_params["offset_col"] = offset_col + self.rpc_params["scale_col"] = scale_col + self.rpc_params["offset_row"] = offset_row + self.rpc_params["scale_row"] = scale_row + self.rpc_params["offset_alt"] = offset_alt + self.rpc_params["scale_alt"] = scale_alt + self.rpc_params["offset_x"] = offset_lon + self.rpc_params["scale_x"] = scale_lon + self.rpc_params["offset_y"] = offset_lat + self.rpc_params["scale_y"] = scale_lat + self.rpc_params["num_x"] = coeff_lon[0:20] + self.rpc_params["den_x"] = coeff_lon[20::] + self.rpc_params["num_y"] = coeff_lat[0:20] + self.rpc_params["den_y"] = coeff_lat[20::] + self.rpc_params["num_col"] = coeff_col[0:20] + self.rpc_params["den_col"] = coeff_col[20::] + self.rpc_params["num_row"] = coeff_lig[0:20] + self.rpc_params["den_row"] = coeff_lig[20::] + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 # If top left convention, 0.5 pixel shift added on col/row offsets if topleftconvention: - rpc_params["offset_col"] += 0.5 - rpc_params["offset_row"] += 0.5 - return cls(rpc_params) + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 - @classmethod - def from_geotiff(cls, image_filename, topleftconvention=True): + def load_geotiff(self, topleftconvention=True): """ Load from a geotiff image file - :param image_filename: image filename - :type image_filename: str + :param topleftconvention: [0,0] position :type topleftconvention: boolean If False : [0,0] is at the center of the Top Left pixel If True : [0,0] is at the top left of the Top Left pixel (OSSIM) """ - dataset = rio.open(image_filename) + dataset = rio.open(self.geomodel_path) rpc_dict = dataset.tags(ns="RPC") if not rpc_dict: - logging.error("%s does not contains RPCs ", image_filename) + logging.error("%s does not contains RPCs ", self.geomodel_path) raise ValueError - rpc_params = { + self.rpc_params = { "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), @@ -478,12 +502,10 @@ def from_geotiff(cls, image_filename, topleftconvention=True): # inverse coeff are not defined # If top left convention, 0.5 pixel shift added on col/row offsets if topleftconvention: - rpc_params["offset_col"] += 0.5 - rpc_params["offset_row"] += 0.5 - return cls(rpc_params) + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 - @classmethod - def from_ossim_kwl(cls, ossim_kwl_filename, topleftconvention=True): + def load_ossim_kwl(self, topleftconvention=True): """ Load from a geom file @@ -492,10 +514,9 @@ def from_ossim_kwl(cls, ossim_kwl_filename, topleftconvention=True): If False : [0,0] is at the center of the Top Left pixel If True : [0,0] is at the top left of the Top Left pixel (OSSIM) """ - rpc_params = {} # OSSIM keyword list - rpc_params["driver_type"] = "ossim_kwl" - with open(ossim_kwl_filename, "r", encoding="utf-8") as ossim_file: + self.rpc_params["driver_type"] = "ossim_kwl" + with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: content = ossim_file.readlines() geom_dict = {} @@ -503,71 +524,43 @@ def from_ossim_kwl(cls, ossim_kwl_filename, topleftconvention=True): (key, val) = line.split(": ") geom_dict[key] = val.rstrip() - rpc_params["den_row"] = [np.nan] * 20 - rpc_params["num_row"] = [np.nan] * 20 - rpc_params["den_col"] = [np.nan] * 20 - rpc_params["num_col"] = [np.nan] * 20 + self.rpc_params["den_row"] = [np.nan] * 20 + self.rpc_params["num_row"] = [np.nan] * 20 + self.rpc_params["den_col"] = [np.nan] * 20 + self.rpc_params["num_col"] = [np.nan] * 20 for index in range(0, 20): axis = "line" num_den = "den" key = f"{axis}_{num_den}_coeff_{index:02d}" - rpc_params["den_row"][index] = float(geom_dict[key]) + self.rpc_params["den_row"][index] = float(geom_dict[key]) num_den = "num" key = f"{axis}_{num_den}_coeff_{index:02d}" - rpc_params["num_row"][index] = float(geom_dict[key]) + self.rpc_params["num_row"][index] = float(geom_dict[key]) axis = "samp" key = f"{axis}_{num_den}_coeff_{index:02d}" - rpc_params["num_col"][index] = float(geom_dict[key]) + self.rpc_params["num_col"][index] = float(geom_dict[key]) num_den = "den" key = f"{axis}_{num_den}_coeff_{index:02d}" - rpc_params["den_col"][index] = float(geom_dict[key]) - rpc_params["offset_col"] = float(geom_dict["samp_off"]) - rpc_params["scale_col"] = float(geom_dict["samp_scale"]) - rpc_params["offset_row"] = float(geom_dict["line_off"]) - rpc_params["scale_row"] = float(geom_dict["line_scale"]) - rpc_params["offset_alt"] = float(geom_dict["height_off"]) - rpc_params["scale_alt"] = float(geom_dict["height_scale"]) - rpc_params["offset_x"] = float(geom_dict["long_off"]) - rpc_params["scale_x"] = float(geom_dict["long_scale"]) - rpc_params["offset_y"] = float(geom_dict["lat_off"]) - rpc_params["scale_y"] = float(geom_dict["lat_scale"]) + self.rpc_params["den_col"][index] = float(geom_dict[key]) + self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) + self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) + self.rpc_params["offset_row"] = float(geom_dict["line_off"]) + self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) + self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) + self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) + self.rpc_params["offset_x"] = float(geom_dict["long_off"]) + self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) + self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) + self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) # inverse coeff are not defined - rpc_params["num_x"] = None - rpc_params["den_x"] = None - rpc_params["num_y"] = None - rpc_params["den_y"] = None + self.rpc_params["num_x"] = None + self.rpc_params["den_x"] = None + self.rpc_params["num_y"] = None + self.rpc_params["den_y"] = None # If top left convention, 0.5 pixel shift added on col/row offsets if topleftconvention: - rpc_params["offset_col"] += 0.5 - rpc_params["offset_row"] += 0.5 - return cls(rpc_params) - - @classmethod - def from_any(cls, primary_file, topleftconvention=True): - """ - Load from any RPC (auto identify driver) - - :param primary_file: rpc filename (dimap, ossim kwl, geotiff) - :type primary_file: str - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - if basename(primary_file.upper()).endswith("XML"): - dimap_version = identify_dimap(primary_file) - if dimap_version is not None: - if float(dimap_version) < 2.0: - return cls.from_dimap_v1(primary_file, topleftconvention) - if float(dimap_version) >= 2.0: - return cls.read_dimap_coeff(primary_file, topleftconvention) - ossim_model = identify_ossim_kwl(primary_file) - if ossim_model is not None: - return cls.from_ossim_kwl(primary_file, topleftconvention) - geotiff_rpc_dict = identify_geotiff_rpc(primary_file) - if geotiff_rpc_dict is not None: - return cls.from_geotiff(primary_file, topleftconvention) - raise ValueError("can not read rpc file") + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 def direct_loc_h(self, row, col, alt, fill_nan=False): """ diff --git a/tests/geofunctions/test_localization.py b/tests/geofunctions/test_localization.py index f16c1ec..045b4ec 100644 --- a/tests/geofunctions/test_localization.py +++ b/tests/geofunctions/test_localization.py @@ -57,9 +57,9 @@ def test_localize_direct_rpc(): # first instanciate the RPC geometric model # data = os.path.join(data_path(), "rpc/phr_ventoux/", "left_image.geom") - # geom_model = RPC.from_any(data) + # geom_model = RPC(data) data = os.path.join(data_path(), "rpc/phr_ventoux/", "RPC_PHR1B_P_201308051042194_SEN_690908101-001.XML") - geom_model = RPC.from_any(data) + geom_model = RPC(data) # then read the Image to retrieve its geotransform image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -94,7 +94,7 @@ def test_localize_direct_grid(): # first instanciate the Grid geometric model # data = os.path.join(data_path(), "rpc/phr_ventoux/", "left_image.geom") - # geom_model_1 = RPC.from_any(data) + # geom_model_1 = RPC(data) data = os.path.join(data_path(), "grid/phr_ventoux/", "GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") geom_model = Grid(data) # then read the Image to retrieve its geotransform @@ -229,7 +229,7 @@ def test_extent(): Test extent """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model = RPC.from_any(data_left + ".geom", topleftconvention=True) + geom_model = RPC(data_left + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image_pixsize_0_5.tif") image = Image(image_filename) loc_rpc_image = Localization(geom_model, elevation=None, image=image) @@ -245,7 +245,7 @@ def test_sensor_loc_dir_dtm_geoid(col, row, valid_coord): Test direct localization using image geotransform """ data = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC.from_any(data + ".geom", topleftconvention=True) + geom_model_left = RPC(data + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -266,7 +266,7 @@ def test_sensor_loc_dir_dtm_geoid_utm(col, row, valid_coord): Test direct localization using image geotransform """ data = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC.from_any(data + ".geom", topleftconvention=True) + geom_model_left = RPC(data + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -296,7 +296,7 @@ def test_sensor_loc_dir_vs_loc_rpc(row, col, h): data_folder = data_path() fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_any(fichier_dimap) + fctrat = RPC(fichier_dimap) loc_rpc = Localization(fctrat) lonlatalt_rpc = loc_rpc.direct(row, col, h) @@ -391,7 +391,7 @@ def test_sensor_loc_inv_vs_loc_rpc(lon, lat, alt): data_folder = data_path() fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_any(fichier_dimap, topleftconvention=True) + fctrat = RPC(fichier_dimap) loc_rpc = Localization(fctrat) [row_rpc, col_rpc, __] = loc_rpc.inverse(lon, lat, alt) @@ -507,7 +507,7 @@ def test_loc_dir_loc_inv_rpc(id_scene, rpc, row, col, h): data_folder = data_path() fichier_dimap = os.path.join(data_folder, "rpc", rpc) - fctrat = RPC.from_any(fichier_dimap, topleftconvention=True) + fctrat = RPC(fichier_dimap) (inv_row, inv_col, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) assert row == pytest.approx(inv_row, abs=1e-2) assert col == pytest.approx(inv_col, abs=1e-2) @@ -559,7 +559,7 @@ def test_colocalization(col, row, alt): data_folder = data_path() id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) row_coloc, col_coloc, _ = coloc_rpc(fctrat, fctrat, row, col, alt) @@ -574,12 +574,12 @@ def test_sensor_coloc_using_geotransform(col, row, h): Test direct localization using image geotransform """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC.from_any(data_left + ".geom", topleftconvention=True) + geom_model_left = RPC(data_left + ".geom") image_filename_left = os.path.join(data_path(), "image/phr_ventoux/", "left_image_pixsize_0_5.tif") image_left = Image(image_filename_left) data_right = os.path.join(data_path(), "rectification", "right_image") - geom_model_right = RPC.from_any(data_right + ".geom", topleftconvention=True) + geom_model_right = RPC(data_right + ".geom") image_filename_right = os.path.join(data_path(), "image/phr_ventoux/", "right_image_pixsize_0_5.tif") image_right = Image(image_filename_right) @@ -609,7 +609,7 @@ def test_sensor_loc_utm(col, row): Test direct localization using image geotransform """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model = RPC.from_any(data_left + ".geom", topleftconvention=True) + geom_model = RPC(data_left + ".geom") epsg = 32631 loc_wgs = Localization(geom_model) loc_utm = Localization(geom_model, epsg=epsg) @@ -632,7 +632,7 @@ def test_sensor_loc_dir_dtm_multi_points(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model = RPC.from_any(os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True) + geom_model = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) dtm_file = os.path.join(data_path(), "dtm", "srtm_ventoux", "srtm90_non_void_filled", "N44E005.hgt") geoid_file = os.path.join(data_path(), "dtm", "geoid", "egm96_15.gtx") diff --git a/tests/geofunctions/test_rectification.py b/tests/geofunctions/test_rectification.py index a77e87a..5431215 100644 --- a/tests/geofunctions/test_rectification.py +++ b/tests/geofunctions/test_rectification.py @@ -63,12 +63,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -114,12 +110,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_alti(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -159,12 +151,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_dtm_geoid(): """ # first instantiate geometric models left and right (here RPC geometrics model) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) # read the images left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) @@ -219,12 +207,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_dtm_geoid_roi() left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) dtm_file = os.path.join(data_path(), "dtm", "srtm_ventoux", "srtm90_non_void_filled", "N44E005.hgt") geoid_file = os.path.join(data_path(), "dtm", "geoid", "egm96_15.gtx") @@ -439,12 +423,8 @@ def test_prepare_rectification(): """ left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -479,12 +459,8 @@ def test_prepare_rectification_footprint(): """ left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -510,12 +486,8 @@ def test_rectification_moving_along_line(): """ Test moving along line in epipolar geometry """ - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) current_left_coords = np.array([[5000.5, 5000.5, 0.0]], dtype=np.float64) mean_spacing = 1 @@ -539,12 +511,8 @@ def test_rectification_moving_to_next_line(): """ Test moving to next line in epipolar geometry """ - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) current_left_coords = np.array([5000.5, 5000.5, 0.0], dtype=np.float64) mean_spacing = 1 @@ -660,12 +628,8 @@ def test_rectification_grid_pos_inside_prepare_footprint_bounding_box(): # Compute shareloc epipolar footprint left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC.from_any( - os.path.join(data_path(), "rectification", "left_image.geom"), topleftconvention=True - ) - geom_model_right = RPC.from_any( - os.path.join(data_path(), "rectification", "right_image.geom"), topleftconvention=True - ) + geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 default_elev = 0.0 diff --git a/tests/geofunctions/test_triangulation.py b/tests/geofunctions/test_triangulation.py index 6772a53..76ab72e 100644 --- a/tests/geofunctions/test_triangulation.py +++ b/tests/geofunctions/test_triangulation.py @@ -156,10 +156,10 @@ def test_epi_triangulation_sift_rpc(): data_folder = data_path() id_scene = "PHR1B_P_201709281038045_SEN_PRG_FC_178608-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_left = RPC.from_any(file_geom, topleftconvention=True) + geom_model_left = RPC(file_geom) id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_right = RPC.from_any(file_geom, topleftconvention=True) + geom_model_right = RPC(file_geom) grid_left_filename = os.path.join(data_path(), "rectification_grids", "left_epipolar_grid.tif") grid_right_filename = os.path.join(data_path(), "rectification_grids", "right_epipolar_grid.tif") @@ -217,10 +217,10 @@ def test_epi_triangulation_disp_rpc(): data_folder = data_path() id_scene = "PHR1B_P_201709281038045_SEN_PRG_FC_178608-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_left = RPC.from_any(file_geom, topleftconvention=True) + geom_model_left = RPC(file_geom) id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_right = RPC.from_any(file_geom, topleftconvention=True) + geom_model_right = RPC(file_geom) # grid_left_filename = os.path.join(data_path(), "rectification_grids", # "grid_{}.tif".format(id_scene_left)) @@ -258,9 +258,9 @@ def test_epi_triangulation_disp_rpc_roi(): """ data_folder = data_path() file_geom = os.path.join(data_folder, "rpc/phr_ventoux/left_image.geom") - geom_model_left = RPC.from_any(file_geom, topleftconvention=True) + geom_model_left = RPC(file_geom) file_geom = os.path.join(data_folder, "rpc/phr_ventoux/right_image.geom") - geom_model_right = RPC.from_any(file_geom, topleftconvention=True) + geom_model_right = RPC(file_geom) grid_left_filename = os.path.join(data_path(), "rectification_grids", "left_epipolar_grid_ventoux.tif") grid_right_filename = os.path.join(data_path(), "rectification_grids", "right_epipolar_grid_ventoux.tif") diff --git a/tests/geomodels/test_los.py b/tests/geomodels/test_los.py index 666a06b..3c3288c 100644 --- a/tests/geomodels/test_los.py +++ b/tests/geomodels/test_los.py @@ -51,7 +51,7 @@ def test_los_creation_with_different_alt(alt): # Load geometrical model id_scene = "P1BP--2017092838284574CP" file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - geometrical_model = RPC.from_any(file_dimap) + geometrical_model = RPC(file_dimap) # Get alt max if alt is None: alt_min, alt_max = geometrical_model.get_alt_min_max() @@ -103,7 +103,7 @@ def test_compare_two_altitude(): # Load geometrical model id_scene = "P1BP--2017092838284574CP" file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - geometrical_model = RPC.from_any(file_dimap) + geometrical_model = RPC(file_dimap) # Create los model_los = LOS(matches_left, geometrical_model, [310, 850]) diff --git a/tests/geomodels/test_rpc.py b/tests/geomodels/test_rpc.py index 6b128db..cdb707a 100755 --- a/tests/geomodels/test_rpc.py +++ b/tests/geomodels/test_rpc.py @@ -50,25 +50,25 @@ def test_rpc_drivers(): # Test DIMAP RPC id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat_dimap = RPC.from_any(file_dimap) + fctrat_dimap = RPC(file_dimap) assert fctrat_dimap.driver_type == "dimap_v1.4" # Test OSSIM KWL GEOM RPC id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC.from_any(file_geom) + fctrat_geom = RPC(file_geom) assert fctrat_geom.driver_type == "ossim_kwl" # Test DIMAPv2 RPC id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_dimap_v2 = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat_dimap_v2 = RPC.from_any(file_dimap_v2, topleftconvention=True) + fctrat_dimap_v2 = RPC(file_dimap_v2) assert fctrat_dimap_v2.driver_type == "dimap_v2.15" # Test fake RPC fake_rpc = os.path.join(data_folder, "rpc/fake_rpc.txt") try: - RPC.from_any(fake_rpc, topleftconvention=True) # Raise ValueError->True + RPC(fake_rpc) # Raise ValueError->True raise AssertionError() # Assert false if no exception raised except ValueError: assert True @@ -123,7 +123,7 @@ def test_rpc_ossim_kwl(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC.from_any(file_geom, topleftconvention=True) + fctrat_geom = RPC(file_geom) (row, col, __) = fctrat_geom.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) @@ -165,7 +165,7 @@ def test_rpc_from_any(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC.from_any(rpc_file, topleftconvention=True) + fctrat = RPC(rpc_file) (row, col, __) = fctrat.inverse_loc(lon, lat, alt) assert fctrat.epsg == 4326 assert fctrat.datum == "ellipsoid" @@ -183,7 +183,7 @@ def test_rpc_direct_iterative(id_scene, lon, lat, alt): """ data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC.from_any(rpc_file, topleftconvention=True) + fctrat = RPC(rpc_file) (row, col, __) = fctrat.inverse_loc(lon, lat, alt) (lon2, lat2, __) = fctrat.direct_loc_inverse_iterative(row, col, alt) assert lon == pytest.approx(lon2, abs=1e-2) @@ -205,7 +205,7 @@ def test_rpc_from_geotiff_without_rpc(prod, can_read): data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", prod) try: - RPC.from_geotiff(rpc_file, topleftconvention=True) + RPC(rpc_file) assert can_read except ValueError: assert not can_read @@ -221,14 +221,14 @@ def test_rpc_dimap_v2(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat_dimap = RPC.from_dimap(file_dimap, topleftconvention=True) + fctrat_dimap = RPC(file_dimap) (row, col, __) = fctrat_dimap.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) assert row == pytest.approx(row_vt, abs=1e-2) file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC.from_any(file_geom, topleftconvention=True) + fctrat_geom = RPC(file_geom) (row, col, __) = fctrat_geom.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) assert row == pytest.approx(row_vt, abs=1e-2) @@ -243,7 +243,7 @@ def test_rpc_phrdimap(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap(file_dimap) + fctrat = RPC(file_dimap) (lonlatalt) = fctrat.direct_loc_h(row, col, alt) @@ -261,7 +261,7 @@ def test_rpc_direct_inverse_iterative_vs_direct(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) # (col,lig,alt)=(100,1000,400) lonlatalt = fctrat.direct_loc_h(row, col, alt) @@ -281,7 +281,7 @@ def test_rpc_direct_inverse_iterative_vs_direct_multiple_points(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) (col, row, alt) = (np.array([600, 610]), np.array([200, 210]), np.array([125])) p_direct = fctrat.direct_loc_h(row, col, alt) @@ -308,7 +308,7 @@ def test_rpc_direct_iterative_nan(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) (col, row, alt) = (np.array([600, np.nan]), np.array([200, 210]), np.array([125])) p_direct0 = fctrat.direct_loc_inverse_iterative(row, col, alt, fill_nan=True) @@ -327,7 +327,7 @@ def test_rpc_direct_iterative_all_nan(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) (col, row, alt) = (np.array([600, np.nan]), np.array([200, 210]), np.array([125])) direct_loc_tab = fctrat.direct_loc_inverse_iterative(row, col, alt, fill_nan=True) @@ -353,7 +353,7 @@ def test_rpc_direct_inverse_iterative(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_dimap_v1(file_dimap) + fctrat = RPC(file_dimap) lonlatalt = fctrat.direct_loc_h(row, col, alt) (row_inv, col_inv, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) @@ -374,7 +374,7 @@ def test_rpc_direct_dtm(id_scene, index_x, index_y): data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC.from_any(rpc_file, topleftconvention=True) + fctrat = RPC(rpc_file) id_scene = "P1BP--2017092838284574CP" data_folder_mnt = data_path("ellipsoide", id_scene) @@ -400,7 +400,7 @@ def test_rpc_los_extrapolation(id_scene, row, col): """ data_folder = data_path() file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat = RPC.from_dimap(file_dimap, topleftconvention=True) + fctrat = RPC(file_dimap) los_edges = fctrat.los_extrema(row, col) altmin = -10 altmax = 2000 @@ -417,7 +417,7 @@ def test_rpc_minmax(): data_folder = data_path() id_scene = "P1BP--2018122638935449CP" fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC.from_any(fichier_dimap) + fctrat = RPC(fichier_dimap) (h_min, h_max) = fctrat.get_alt_min_max() assert h_min == 532.5 assert h_max == 617.5 From b19f1a5b6bfc05a15f66deaad547af84c7ad1110 Mon Sep 17 00:00:00 2001 From: Emmanuel Dubois Date: Wed, 18 Oct 2023 17:57:41 +0200 Subject: [PATCH 03/18] feat: add geomodel template interface loc functions --- shareloc/geomodels/geomodel_template.py | 51 ++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index dca847c..5ccf790 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -35,8 +35,6 @@ # if(SHARELOC_OPTIM_GEOMODEL == True): # GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim -# pylint: disable=too-few-public-methods - class GeoModelTemplate(metaclass=ABCMeta): """ @@ -58,3 +56,52 @@ def __init__(self, geomodel_path: str): # geomodel type. Set by the subclass self.type: str + + # Define GeoModelTemplate functions interface + + @abstractmethod + def direct_loc_h(self, row, col, alt, fill_nan=False): + """ + direct localization at constant altitude + + :param row: line sensor position + :type row: float or 1D numpy.ndarray dtype=float64 + :param col: column sensor position + :type col: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :return: ground position (lon,lat,h) + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + @abstractmethod + def direct_loc_dtm(self, row, col, dtm): + """ + direct localization on dtm + + :param row: line sensor position + :type row: float + :param col: column sensor position + :type col: float + :param dtm: dtm intersection model + :type dtm: shareloc.geofunctions.dtm_intersection + :return: ground position (lon,lat,h) in dtm coordinates system + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + @abstractmethod + def inverse_loc(self, lon, lat, alt): + """ + Inverse localization + + :param lon: longitude position + :type lon: float or 1D numpy.ndarray dtype=float64 + :param lat: latitude position + :type lat: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :type alt: float + :return: sensor position (row, col, alt) + :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) + """ From a2a3882cfaf6f7348ff8c274a022db27286a84f8 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Fri, 10 Nov 2023 09:25:34 +0100 Subject: [PATCH 04/18] 1 step with pybind --- pyproject.toml | 4 ++-- setup.cfg | 3 ++- setup.py | 17 +++++++++++++++-- shareloc/draft_pybind/bind_helloworld.cpp | 15 +++++++++++++++ shareloc/draft_pybind/example.cpp | 17 +++++++++++++++++ shareloc/draft_pybind/example_pybind.py | 10 ++++++++++ shareloc/draft_pybind/hello_world.cpp | 15 +++++++++++++++ shareloc/draft_pybind/hello_world.h | 10 ++++++++++ tests/geomodels/test_draft_pybind.py | 9 +++++++++ 9 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 shareloc/draft_pybind/bind_helloworld.cpp create mode 100644 shareloc/draft_pybind/example.cpp create mode 100644 shareloc/draft_pybind/example_pybind.py create mode 100644 shareloc/draft_pybind/hello_world.cpp create mode 100644 shareloc/draft_pybind/hello_world.h create mode 100644 tests/geomodels/test_draft_pybind.py diff --git a/pyproject.toml b/pyproject.toml index e142fa2..bbb38b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ # pyproject.toml [build-system] -requires = ["setuptools>=65.5", "wheel", "setuptools_scm[toml]>=6.2"] +requires = ["setuptools>=65.5", "wheel", "setuptools_scm[toml]>=6.2","pybind11"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] @@ -30,4 +30,4 @@ disable_error_code = 'attr-defined' module = [ 'setuptools', ] -ignore_missing_imports = true \ No newline at end of file +ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg index 5697fe0..b45ff22 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,7 +61,8 @@ install_requires = rasterio xarray numba - + pybind11 + package_dir = . = shareloc packages = find: diff --git a/setup.py b/setup.py index 2398ac3..4b28d6a 100755 --- a/setup.py +++ b/setup.py @@ -20,8 +20,21 @@ Shareloc Setup.py kept for compatibility and setuptools_scm configuration. Main part is in setup.cfg file. """ +import os +from setuptools import setup, Extension +#from setuptools.command.build_ext import build_ext +from pybind11.setup_helpers import Pybind11Extension, build_ext, intree_extensions -from setuptools import setup + +#create libs folder to contain .so files (if it doesn't exist) +if not os.path.exists("libs"): + os.makedirs("libs") # Main setup with setup.cfg file. -setup(use_scm_version=True) +extensions = [ + Pybind11Extension("libs.pbhelloworld", ["shareloc/draft_pybind/bind_helloworld.cpp"])#"lib.pbhelloworld" +] + +#extensions = intree_extensions("pbhelloworld", ["shareloc/draft_pybind/hello_world.cpp"]) + +setup(use_scm_version=True,cmdclass={"build_ext": build_ext},ext_modules=extensions) \ No newline at end of file diff --git a/shareloc/draft_pybind/bind_helloworld.cpp b/shareloc/draft_pybind/bind_helloworld.cpp new file mode 100644 index 0000000..926aeb7 --- /dev/null +++ b/shareloc/draft_pybind/bind_helloworld.cpp @@ -0,0 +1,15 @@ +#include +#include "hello_world.cpp" + +namespace py = pybind11; + + +PYBIND11_MODULE(pbhelloworld, m) { + py::class_(m, "HW") + .def(py::init<>()) + .def("hellow_world", &HW::hellow_world); + /*m.doc() = "Pybind hello world"; // optional module docstring + m.def("main", &main, "Print hello world str");*/ +} + +//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp -o pbhelloworld$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/draft_pybind/example.cpp b/shareloc/draft_pybind/example.cpp new file mode 100644 index 0000000..75df225 --- /dev/null +++ b/shareloc/draft_pybind/example.cpp @@ -0,0 +1,17 @@ +#include +namespace py = pybind11; + +int add(int i, int j) { + return i + j; +} + +PYBIND11_MODULE(example, m) { + m.doc() = "pybind11 example plugin"; // optional module docstring + + m.def("add", &add, "A function which adds two numbers", + py::arg("i"), py::arg("j")); +} + + + +// c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/draft_pybind/example_pybind.py b/shareloc/draft_pybind/example_pybind.py new file mode 100644 index 0000000..82d2d7e --- /dev/null +++ b/shareloc/draft_pybind/example_pybind.py @@ -0,0 +1,10 @@ +import sys +sys.path.append(".") +import libs.pbhelloworld as HWmodule + + +HW_object = HWmodule.HW() +print(HW_object.hellow_world()) + + + diff --git a/shareloc/draft_pybind/hello_world.cpp b/shareloc/draft_pybind/hello_world.cpp new file mode 100644 index 0000000..9056b18 --- /dev/null +++ b/shareloc/draft_pybind/hello_world.cpp @@ -0,0 +1,15 @@ +#include +#include "hello_world.h" + +HW::HW() {} + +HW::~HW() {} + +std::string HW::hellow_world() const { + return "Hello world !"; +} + +/*int main(){ + HW HelloWorld; + std::cout< +#include + +class HW { +public: + HW(); + ~HW(); + + std::string hellow_world() const; +}; diff --git a/tests/geomodels/test_draft_pybind.py b/tests/geomodels/test_draft_pybind.py new file mode 100644 index 0000000..63e4810 --- /dev/null +++ b/tests/geomodels/test_draft_pybind.py @@ -0,0 +1,9 @@ +import sys +sys.path.append(".") +import libs.pbhelloworld as HWmodule + + +def test_helloworld(): + HWobject = HWmodule.HW() + + assert "Hello world !" == HWobject.hellow_world() \ No newline at end of file From 7f3755cc908db67f90d3418816002d25c65ef093 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Fri, 10 Nov 2023 11:13:30 +0100 Subject: [PATCH 05/18] pre commit check 1step --- .pylintrc | 2 ++ shareloc/draft_pybind/example_pybind.py | 10 ++++++---- tests/geomodels/test_draft_pybind.py | 12 +++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.pylintrc b/.pylintrc index 16d5e65..6908d22 100644 --- a/.pylintrc +++ b/.pylintrc @@ -29,6 +29,8 @@ suggestion-mode=yes # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no +# List of cpp allowed extension +extension-pkg-whitelist= libs.pbhelloworld [MESSAGES CONTROL] diff --git a/shareloc/draft_pybind/example_pybind.py b/shareloc/draft_pybind/example_pybind.py index 82d2d7e..94579ad 100644 --- a/shareloc/draft_pybind/example_pybind.py +++ b/shareloc/draft_pybind/example_pybind.py @@ -1,10 +1,12 @@ +"""Module sys to set python path to root""" import sys + sys.path.append(".") -import libs.pbhelloworld as HWmodule +# pylint: disable=wrong-import-position +import libs.pbhelloworld as HWmodule # noqa: E402 + +# pylint: enable=wrong-import-position HW_object = HWmodule.HW() print(HW_object.hellow_world()) - - - diff --git a/tests/geomodels/test_draft_pybind.py b/tests/geomodels/test_draft_pybind.py index 63e4810..0461958 100644 --- a/tests/geomodels/test_draft_pybind.py +++ b/tests/geomodels/test_draft_pybind.py @@ -1,9 +1,15 @@ +"""Module sys to set python path to root""" import sys + sys.path.append(".") -import libs.pbhelloworld as HWmodule + +# pylint: disable=wrong-import-position +import libs.pbhelloworld as HWmodule # noqa: E402 + +# pylint: enable=wrong-import-position def test_helloworld(): - HWobject = HWmodule.HW() + hw_object = HWmodule.HW() - assert "Hello world !" == HWobject.hellow_world() \ No newline at end of file + assert "Hello world !" == hw_object.hellow_world() From 424db6b269730ee4b907e2f495aad54cae9a91d8 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Tue, 14 Nov 2023 18:10:01 +0100 Subject: [PATCH 06/18] traduction en cpp du squelette de GeoModelTemplate et RPC (sauf les fct load) + RAF test + aucun respect pour la CI --- setup.py | 16 +- .../draft_pybind/221_python_files/geomodel.py | 116 ++ .../221_python_files/geomodel_template.py | 107 ++ .../draft_pybind/221_python_files/helpers.py | 42 + shareloc/draft_pybind/221_python_files/rpc.py | 1227 +++++++++++++++++ .../draft_pybind/221_python_files/test_rpc.py | 28 + shareloc/draft_pybind/CMakeLists.txt | 9 + shareloc/draft_pybind/GeoModelTemplate.cpp | 29 + shareloc/draft_pybind/GeoModelTemplate.hpp | 22 + shareloc/draft_pybind/bind_helloworld.cpp | 37 +- shareloc/draft_pybind/example_pybind.py | 30 +- shareloc/draft_pybind/hello_world.cpp | 15 - shareloc/draft_pybind/hello_world.h | 10 - shareloc/draft_pybind/rpc.cpp | 110 ++ shareloc/draft_pybind/rpc.hpp | 63 + tests/geomodels/test_draft_pybind.py | 8 +- 16 files changed, 1820 insertions(+), 49 deletions(-) create mode 100644 shareloc/draft_pybind/221_python_files/geomodel.py create mode 100644 shareloc/draft_pybind/221_python_files/geomodel_template.py create mode 100644 shareloc/draft_pybind/221_python_files/helpers.py create mode 100644 shareloc/draft_pybind/221_python_files/rpc.py create mode 100644 shareloc/draft_pybind/221_python_files/test_rpc.py create mode 100644 shareloc/draft_pybind/CMakeLists.txt create mode 100644 shareloc/draft_pybind/GeoModelTemplate.cpp create mode 100644 shareloc/draft_pybind/GeoModelTemplate.hpp delete mode 100644 shareloc/draft_pybind/hello_world.cpp delete mode 100644 shareloc/draft_pybind/hello_world.h create mode 100644 shareloc/draft_pybind/rpc.cpp create mode 100644 shareloc/draft_pybind/rpc.hpp diff --git a/setup.py b/setup.py index 4b28d6a..486540c 100755 --- a/setup.py +++ b/setup.py @@ -21,20 +21,18 @@ Main part is in setup.cfg file. """ import os -from setuptools import setup, Extension -#from setuptools.command.build_ext import build_ext -from pybind11.setup_helpers import Pybind11Extension, build_ext, intree_extensions +# from setuptools.command.build_ext import build_ext +from pybind11.setup_helpers import Pybind11Extension, build_ext, intree_extensions +from setuptools import Extension, setup -#create libs folder to contain .so files (if it doesn't exist) +# create libs folder to contain .so files (if it doesn't exist) if not os.path.exists("libs"): os.makedirs("libs") # Main setup with setup.cfg file. -extensions = [ - Pybind11Extension("libs.pbhelloworld", ["shareloc/draft_pybind/bind_helloworld.cpp"])#"lib.pbhelloworld" -] +extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/draft_pybind/bind_helloworld.cpp"])] # "lib.pbhelloworld" -#extensions = intree_extensions("pbhelloworld", ["shareloc/draft_pybind/hello_world.cpp"]) +# extensions = intree_extensions("pbrpc", ["shareloc/draft_pybind/hello_world.cpp"]) -setup(use_scm_version=True,cmdclass={"build_ext": build_ext},ext_modules=extensions) \ No newline at end of file +setup(use_scm_version=True, cmdclass={"build_ext": build_ext}, ext_modules=extensions) diff --git a/shareloc/draft_pybind/221_python_files/geomodel.py b/shareloc/draft_pybind/221_python_files/geomodel.py new file mode 100644 index 0000000..e6ee48b --- /dev/null +++ b/shareloc/draft_pybind/221_python_files/geomodel.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 GeoModel class factory. +This main API GeoModel class generates an object linked +with geomodel configuration "geomodel_name" +(from registered GeoModelTemplate class) + +** Experimental and not used as API for now** +** Will be used with an automatic switcher between Grid file format and +RPC file format obj = GeoModel("file_geom") only without geomodel_type +""" + +# Standard imports +import logging +from typing import Any, Dict + + +class GeoModel: + """ + GeoModel factory: + A class designed for registered all available geomodels + and instantiate them when needed. + """ + + # Dict (geomodel_name: str, class: object) containing registered geomodels + available_geomodels: Dict[str, Any] = {} + + def __new__(cls, geomodel_path: str, geomodel_type: str = "RPC"): + """ + Return a GeoModelTemplate child instance + associated with the "geomodel_name" given in the configuration + through create_geomodel local method for clarity. + + TODO: optional geomodel_type would profit to have an automatic switcher between geomodels (RPC, grids, ...) + + :param geomodel_path: Path of geomodel file + :type geomodel_path: string + :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance + :type geomodel_type: string + """ + return cls.create_geomodel(geomodel_path, geomodel_type) + + @classmethod + def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): + """ + Factory command to create the geomodel from geomodel_type + Return a GeoModelTemplate child instance + associated with the "geomodel_type" + + :param geomodel_path: Path of geomodel file + :type geomodel_path: string + :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance + :type geomodel_type: string + """ + + # If no type is given, the default is "RPC" + + # Create geomodel object with geomodel_path parameter from geomodel_type if exists + try: + geomodel_class = cls.available_geomodels[geomodel_type] + geomodel = geomodel_class(geomodel_path) + logging.debug("GeoModel type name: %s", geomodel_type) + except KeyError: + logging.error("Geomodel type named %s is not supported", geomodel_type) + raise + + return geomodel + + @classmethod + def print_avalaible_geomodels(cls): + """ + Print all registered applications + """ + for geomodel_type in cls.available_geomodels: + print(geomodel_type) + + @classmethod + def register(cls, geomodel_type: str): + """ + Allows to register the GeoModelTemplate subclass in the factory + with its geomodel geomodel_type through decorator + + :param geomodel_type: the subclass name to be registered + :type geomodel_type: string + """ + + def decorator(geomodel_subclass): + """ + Register the geomodel subclass in the available methods + + :param geomodel_subclass: the subclass to be registered + :type geomodel_subclass: object + """ + cls.available_geomodels[geomodel_type] = geomodel_subclass + return geomodel_subclass + + return decorator diff --git a/shareloc/draft_pybind/221_python_files/geomodel_template.py b/shareloc/draft_pybind/221_python_files/geomodel_template.py new file mode 100644 index 0000000..5ccf790 --- /dev/null +++ b/shareloc/draft_pybind/221_python_files/geomodel_template.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 coregistration class template. +It contains the structure for all coregistration methods in subclasses and +generic coregistration code to avoid duplication. +""" + +# Standard imports +from abc import ABCMeta, abstractmethod + +# Global variable for optimization mode (functions in C) +# SHARELOC_OPTIM_GEOMODEL = False + +# TODO: Override functions depending on optimization or not + +# if(SHARELOC_OPTIM_GEOMODEL == True): +# GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim + + +class GeoModelTemplate(metaclass=ABCMeta): + """ + Class for general specification of a geometric model + declined in rpc.py and grid.py + """ + + @abstractmethod + def __init__(self, geomodel_path: str): + """ + Return the geomodel object associated with the geomodel_type + given in the configuration + + :param geomodel_path: path of the geomodel file to instanciate. + :type geomodel_path: string + """ + # geomodel filename path + self.geomodel_path: str = geomodel_path + + # geomodel type. Set by the subclass + self.type: str + + # Define GeoModelTemplate functions interface + + @abstractmethod + def direct_loc_h(self, row, col, alt, fill_nan=False): + """ + direct localization at constant altitude + + :param row: line sensor position + :type row: float or 1D numpy.ndarray dtype=float64 + :param col: column sensor position + :type col: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :return: ground position (lon,lat,h) + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + @abstractmethod + def direct_loc_dtm(self, row, col, dtm): + """ + direct localization on dtm + + :param row: line sensor position + :type row: float + :param col: column sensor position + :type col: float + :param dtm: dtm intersection model + :type dtm: shareloc.geofunctions.dtm_intersection + :return: ground position (lon,lat,h) in dtm coordinates system + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + @abstractmethod + def inverse_loc(self, lon, lat, alt): + """ + Inverse localization + + :param lon: longitude position + :type lon: float or 1D numpy.ndarray dtype=float64 + :param lat: latitude position + :type lat: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :type alt: float + :return: sensor position (row, col, alt) + :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) + """ diff --git a/shareloc/draft_pybind/221_python_files/helpers.py b/shareloc/draft_pybind/221_python_files/helpers.py new file mode 100644 index 0000000..60d01d4 --- /dev/null +++ b/shareloc/draft_pybind/221_python_files/helpers.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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. +# +""" +Helpers shared testing generic module: +contains global shared generic functions for tests/*.py +""" + +# Standard imports +import os + + +def data_path(alti="", scene_id=""): + """ + return the data path, when used without any argument data_path() returns data directory + :param alti: first sub dir corresponding to datum ("ellipsoide" or "geoid") + :type alti: str + :param scene_id: second sub dir corresponding to the scene id + :type scene_id: str + :return: data path. + :rtype: str + """ + data_root_folder = os.path.join(os.path.dirname(__file__), "data") + sub_folder = os.path.join(alti, scene_id) + return os.path.join(data_root_folder, sub_folder) diff --git a/shareloc/draft_pybind/221_python_files/rpc.py b/shareloc/draft_pybind/221_python_files/rpc.py new file mode 100644 index 0000000..65f8ce1 --- /dev/null +++ b/shareloc/draft_pybind/221_python_files/rpc.py @@ -0,0 +1,1227 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# Copyright (c) 2023 CS GROUP - France, https://csgroup.eu +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 RPC class corresponding to the RPC models. +RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. +""" + +# Standard imports +import logging +from os.path import basename +from typing import Dict +from xml.dom import minidom + +# Third party imports +import numpy as np +import rasterio as rio +from numba import config, njit, prange + +import shareloc.draft_pybind.pbrpc as pbrpc + +# Shareloc imports +from shareloc.draft_pybind.geomodel import GeoModel +from shareloc.draft_pybind.geomodel_template import GeoModelTemplate +from shareloc.proj_utils import coordinates_conversion + +# Set numba type of threading layer before parallel target compilation +config.THREADING_LAYER = "omp" + + +def parse_coeff_line(coeff_str): + """ + split str coef to float list + + :param coeff_str: line coef + :type coeff_str: str + :return: coeff list + :rtype: list() + """ + return [float(el) for el in coeff_str.split()] + + +def identify_dimap(xml_file): + """ + parse xml file to identify dimap and its version + + :param xml_file: dimap rpc file + :type xml_file: str + :return: dimap info : dimap_version and None if not an dimap file + :rtype: str + """ + try: + xmldoc = minidom.parse(xml_file) + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data + if mtd_format == "DIMAP_PHR": + version_tag = "METADATA_PROFILE" + else: + version_tag = "METADATA_FORMAT" + version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] + except Exception: # pylint: disable=broad-except + return None + + return version + + +def identify_ossim_kwl(ossim_kwl_file): + """ + parse geom file to identify if it is an ossim model + + :param ossim_kwl_fil : ossim keyword list file + :type ossim_kwl_file: str + :return: ossimmodel or None if not an ossim kwl file + :rtype: str + """ + try: + with open(ossim_kwl_file, encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + if "type" in geom_dict: + if geom_dict["type"].strip().startswith("ossim"): + return geom_dict["type"].strip() + return None + except Exception: # pylint: disable=broad-except + return None + + +def identify_geotiff_rpc(image_filename): + """ + read image file to identify if it is a geotiff which contains RPCs + + :param image_filename: image_filename + :type image_filename: str + :return: rpc info, rpc dict or None if not a geotiff with rpc + :rtype: str + """ + try: + dataset = rio.open(image_filename) + rpc_dict = dataset.tags(ns="RPC") + return rpc_dict + except Exception: # pylint: disable=broad-except + return None + + +# pylint: disable=no-member + + +@GeoModel.register("RPC") +class RPC(GeoModelTemplate): + """ + RPC class including direct and inverse localization instance methods + """ + + # gitlab issue #61 + # pylint: disable=too-many-instance-attributes + def __init__(self, geomodel_path: str): + # Instanciate GeoModelTemplate generic init with shared parameters + super().__init__(geomodel_path) + self.type = "RPC" + + self.RPC_cpp = pbrpc.RPC() + + # initiate epsg and datum, can overriden by rpc_params + self.epsg = None + self.datum = None + + # RPC parameters as Dict + self.rpc_params: Dict = {} + + # RPC parameters are load from geomodel_path to rpc params + self.load() + + # set class parameters from rpc_params (epsg and datum can be overriden) + for key, value in self.rpc_params.items(): + setattr(self, key, value) + + if self.epsg is None: + self.epsg = 4326 + if self.datum is None: + self.datum = "ellipsoid" + + self.lim_extrapol = 1.0001 + + # Monomes seems not used in shareloc code: Clean ? + # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] + self.monomes = np.array( + [ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 0, 1, 0], + [1, 0, 0, 1], + [1, 1, 1, 0], + [1, 1, 0, 1], + [1, 0, 1, 1], + [1, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [1, 1, 1, 1], + [1, 3, 0, 0], + [1, 1, 2, 0], + [1, 1, 0, 2], + [1, 2, 1, 0], + [1, 0, 3, 0], + [1, 0, 1, 2], + [1, 2, 0, 1], + [1, 0, 2, 1], + [1, 0, 0, 3], + ] + ) + + # monomial coefficients of 1st variable derivative + self.monomes_deriv_1 = np.array( + [ + [0, 0, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 1, 0], + [1, 0, 0, 1], + [0, 0, 1, 1], + [2, 1, 0, 0], + [0, 0, 2, 0], + [0, 0, 0, 2], + [1, 0, 1, 1], + [3, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [2, 1, 1, 0], + [0, 0, 3, 0], + [0, 0, 1, 2], + [2, 1, 0, 1], + [0, 0, 2, 1], + [0, 0, 0, 3], + ] + ) + + # monomial coefficients of 2nd variable derivative + self.monomes_deriv_2 = np.array( + [ + [0, 0, 0, 0], + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 1], + [1, 1, 0, 0], + [0, 1, 0, 1], + [1, 0, 0, 1], + [0, 2, 0, 0], + [2, 0, 1, 0], + [0, 0, 0, 2], + [1, 1, 0, 1], + [0, 3, 0, 0], + [2, 1, 1, 0], + [0, 1, 0, 2], + [1, 2, 0, 0], + [3, 0, 2, 0], + [1, 0, 0, 2], + [0, 2, 0, 1], + [2, 0, 1, 1], + [0, 0, 0, 3], + ] + ) + + self.inverse_coefficient = False + self.direct_coefficient = False + + # pylint: disable=access-member-before-definition + if self.num_col: + self.inverse_coefficient = True + self.num_col = np.array(self.num_col) + self.den_col = np.array(self.den_col) + self.num_row = np.array(self.num_row) + self.den_row = np.array(self.den_row) + + # pylint: disable=access-member-before-definition + if self.num_x: + self.direct_coefficient = True + self.num_x = np.array(self.num_x) + self.den_x = np.array(self.den_x) + self.num_y = np.array(self.num_y) + self.den_y = np.array(self.den_y) + + self.alt_minmax = [self.offset_alt - self.scale_alt, self.offset_alt + self.scale_alt] + self.col0 = self.offset_col - self.scale_col + self.colmax = self.offset_col + self.scale_col + self.row0 = self.offset_row - self.scale_row + self.rowmax = self.offset_row + self.scale_row + + def load(self): + """ + Load from any RPC (auto identify driver) + from filename (dimap, ossim kwl, geotiff) + + TODO: topleftconvention always to True, set a standard and remove the option + + topleftconvention boolean: [0,0] position + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # Set topleftconvention (keeping historic option): to clean + topleftconvention = True + + # If ends with XML --> DIMAP + if basename(self.geomodel_path.upper()).endswith("XML"): + dimap_version = identify_dimap(self.geomodel_path) + if dimap_version is not None: + if float(dimap_version) < 2.0: + self.load_dimap_v1(topleftconvention) + if float(dimap_version) >= 2.0: + self.load_dimap_coeff(topleftconvention) + else: + # If not DIMAP, identify ossim + ossim_model = identify_ossim_kwl(self.geomodel_path) + if ossim_model is not None: + self.load_ossim_kwl(topleftconvention) + else: + # Otherwise, check if RPC is in geotif + geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) + if geotiff_rpc_dict is not None: + self.load_geotiff(topleftconvention) + else: + # Raise error if no file recognized. + raise ValueError("can not read rpc file") + + def load_dimap_coeff(self, topleftconvention=True): + """ + Load from Dimap v2 and V3 + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + + if not basename(self.geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(self.geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] + self.rpc_params["driver_type"] = "dimap_v" + version + global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] + normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] + self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) + self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) + if float(version) >= 3: + direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] + inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] + + self.rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["offset_col"] -= 0.5 + self.rpc_params["offset_row"] -= 0.5 + + else: + direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] + inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] + + self.rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 + + self.rpc_params["num_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + + self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) + self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) + self.rpc_params["offset_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data + ) + self.rpc_params["scale_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data + ) + self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) + self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) + self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) + self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_dimap_v1(self, topleftconvention=True): + """ + Load from dimap v1 + + ** Deprecated, to clean ? ** + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + + if not basename(self.geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(self.geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] + self.rpc_params = {"driver_type": "dimap_v" + version} + + global_rfm = xmldoc.getElementsByTagName("Global_RFM") + rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") + coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] + coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] + coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] + coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] + + scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) + scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) + scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) + offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) + scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) + offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) + scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) + offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) + + self.rpc_params["offset_col"] = offset_col + self.rpc_params["scale_col"] = scale_col + self.rpc_params["offset_row"] = offset_row + self.rpc_params["scale_row"] = scale_row + self.rpc_params["offset_alt"] = offset_alt + self.rpc_params["scale_alt"] = scale_alt + self.rpc_params["offset_x"] = offset_lon + self.rpc_params["scale_x"] = scale_lon + self.rpc_params["offset_y"] = offset_lat + self.rpc_params["scale_y"] = scale_lat + self.rpc_params["num_x"] = coeff_lon[0:20] + self.rpc_params["den_x"] = coeff_lon[20::] + self.rpc_params["num_y"] = coeff_lat[0:20] + self.rpc_params["den_y"] = coeff_lat[20::] + self.rpc_params["num_col"] = coeff_col[0:20] + self.rpc_params["den_col"] = coeff_col[20::] + self.rpc_params["num_row"] = coeff_lig[0:20] + self.rpc_params["den_row"] = coeff_lig[20::] + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_geotiff(self, topleftconvention=True): + """ + Load from a geotiff image file + + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + dataset = rio.open(self.geomodel_path) + rpc_dict = dataset.tags(ns="RPC") + if not rpc_dict: + logging.error("%s does not contains RPCs ", self.geomodel_path) + raise ValueError + self.rpc_params = { + "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), + "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), + "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), + "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), + "offset_col": float(rpc_dict["SAMP_OFF"]), + "scale_col": float(rpc_dict["SAMP_SCALE"]), + "offset_row": float(rpc_dict["LINE_OFF"]), + "scale_row": float(rpc_dict["LINE_SCALE"]), + "offset_alt": float(rpc_dict["HEIGHT_OFF"]), + "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), + "offset_x": float(rpc_dict["LONG_OFF"]), + "scale_x": float(rpc_dict["LONG_SCALE"]), + "offset_y": float(rpc_dict["LAT_OFF"]), + "scale_y": float(rpc_dict["LAT_SCALE"]), + "num_x": None, + "den_x": None, + "num_y": None, + "den_y": None, + } + # inverse coeff are not defined + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_ossim_kwl(self, topleftconvention=True): + """ + Load from a geom file + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # OSSIM keyword list + self.rpc_params["driver_type"] = "ossim_kwl" + with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + + self.rpc_params["den_row"] = [np.nan] * 20 + self.rpc_params["num_row"] = [np.nan] * 20 + self.rpc_params["den_col"] = [np.nan] * 20 + self.rpc_params["num_col"] = [np.nan] * 20 + for index in range(0, 20): + axis = "line" + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["den_row"][index] = float(geom_dict[key]) + num_den = "num" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["num_row"][index] = float(geom_dict[key]) + axis = "samp" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["num_col"][index] = float(geom_dict[key]) + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["den_col"][index] = float(geom_dict[key]) + self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) + self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) + self.rpc_params["offset_row"] = float(geom_dict["line_off"]) + self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) + self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) + self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) + self.rpc_params["offset_x"] = float(geom_dict["long_off"]) + self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) + self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) + self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) + # inverse coeff are not defined + self.rpc_params["num_x"] = None + self.rpc_params["den_x"] = None + self.rpc_params["num_y"] = None + self.rpc_params["den_y"] = None + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def direct_loc_h(self, row, col, alt, fill_nan=False): + """ + direct localization at constant altitude + + :param row: line sensor position + :type row: float or 1D numpy.ndarray dtype=float64 + :param col: column sensor position + :type col: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :return: ground position (lon,lat,h) + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + zero = self.RPC_cpp.direct_loc_h_rpc() + + if not isinstance(col, (list, np.ndarray)): + col = np.array([col]) + row = np.array([row]) + + if not isinstance(alt, (list, np.ndarray)): + alt = np.array([alt]) + + if alt.shape[0] != col.shape[0]: + alt = np.full(col.shape[0], fill_value=alt[0]) + + points = np.zeros((col.size, 3)) + filter_nan, points[:, 0], points[:, 1] = self.filter_coordinates(row, col, fill_nan) + row = row[filter_nan] + col = col[filter_nan] + + # Direct localization using direct RPC + if self.direct_coefficient: + # ground position + col_norm = (col - self.offset_col) / self.scale_col + row_norm = (row - self.offset_row) / self.scale_row + alt_norm = (alt - self.offset_alt) / self.scale_alt + + if np.sum(abs(col_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! column extrapolation in direct localization ") + if np.sum(abs(row_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! row extrapolation in direct localization ") + if np.sum(abs(alt_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! alt extrapolation in direct localization ") + + points[filter_nan, 1], points[filter_nan, 0] = compute_rational_function_polynomial( + col_norm, + row_norm, + alt_norm, + self.num_x, + self.den_x, + self.num_y, + self.den_y, + self.scale_x, + self.offset_x, + self.scale_y, + self.offset_y, + ) + + # Direct localization using inverse RPC + else: + logging.debug("direct localisation from inverse iterative") + (points[filter_nan, 0], points[filter_nan, 1], points[filter_nan, 2]) = self.direct_loc_inverse_iterative( + row, col, alt, 10, fill_nan + ) + points[:, 2] = alt + return points + + def direct_loc_grid_h(self, row0, col0, steprow, stepcol, nbrow, nbcol, alt): + """ + calculates a direct loc grid (lat, lon) from the direct RPCs at constant altitude + TODO: not tested. + + :param row0: grid origin (row) + :type row0: int + :param col0: grid origin (col) + :type col0: int + :param steprow: grid step (row) + :type steprow: int + :param stepcol: grid step (col) + :type stepcol: int + :param nbrow: grid nb row + :type nbrow: int + :param nbcol: grid nb col + :type nbcol: int + :param alt: altitude of the grid + :type alt: float + :return: direct localization grid longitude and latitude + :rtype: Tuple(numpy.array, numpy.array) + """ + gri_lon = np.zeros((nbrow, nbcol)) + gri_lat = np.zeros((nbrow, nbcol)) + for column in range(int(nbcol)): + col = col0 + stepcol * column + for line in range(int(nbrow)): + row = row0 + steprow * line + (gri_lon[line, column], gri_lat[line, column], __) = self.direct_loc_h(row, col, alt) + return (gri_lon, gri_lat) + + def direct_loc_dtm(self, row, col, dtm): + """ + direct localization on dtm + + :param row: line sensor position + :type row: float + :param col: column sensor position + :type col: float + :param dtm: dtm intersection model + :type dtm: shareloc.geofunctions.dtm_intersection + :return: ground position (lon,lat,h) in dtm coordinates system + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + if isinstance(col, (list, np.ndarray)): + points_nb = len(col) + else: + points_nb = 1 + row = np.array([row]) + col = np.array([col]) + direct_dtm = np.zeros((points_nb, 3)) + + diff_alti_min, diff_alti_max = dtm.get_alt_offset(self.epsg) + # print("min {} max {}".format(dtm.Zmin,dtm.Zmax)) + (min_dtm, max_dtm) = (dtm.alt_min - 1.0 + diff_alti_min, dtm.alt_max + 1.0 + diff_alti_max) + if min_dtm < self.offset_alt - self.scale_alt: + logging.debug("minimum dtm value is outside RPC validity domain, extrapolation will be done") + if max_dtm > self.offset_alt + self.scale_alt: + logging.debug("maximum dtm value is outside RPC validity domain, extrapolation will be done") + los = self.los_extrema(row, col, min_dtm, max_dtm, epsg=dtm.epsg) + for i in range(points_nb): + los_i = los[2 * i : 2 * i + 2, :] + (__, __, position_cube, alti) = dtm.intersect_dtm_cube(los_i) + if position_cube is not None: + (__, __, position) = dtm.intersection(los_i, position_cube, alti) + direct_dtm[i, :] = position + else: + position = np.full(3, fill_value=np.nan) + direct_dtm[i, :] = position + return direct_dtm + + def inverse_loc(self, lon, lat, alt): + """ + Inverse localization + + :param lon: longitude position + :type lon: float or 1D numpy.ndarray dtype=float64 + :param lat: latitude position + :type lat: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :type alt: float + :return: sensor position (row, col, alt) + :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) + """ + if self.inverse_coefficient: + if not isinstance(lon, (list, np.ndarray)): + lon = np.array([lon]) + lat = np.array([lat]) + if not isinstance(alt, (list, np.ndarray)): + alt = np.array([alt]) + + if alt.shape[0] != lon.shape[0]: + alt = np.full(lon.shape[0], fill_value=alt[0]) + + lon_norm = (lon - self.offset_x) / self.scale_x + lat_norm = (lat - self.offset_y) / self.scale_y + alt_norm = (alt - self.offset_alt) / self.scale_alt + + if np.sum(abs(lon_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! longitude extrapolation in inverse localization ") + if np.sum(abs(lat_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! row extrapolation in inverse localization ") + if np.sum(abs(alt_norm) > self.lim_extrapol) > 0: + logging.debug("!!!!! alt extrapolation in inverse localization ") + + row_out, col_out = compute_rational_function_polynomial( + lon_norm, + lat_norm, + alt_norm, + self.num_col, + self.den_col, + self.num_row, + self.den_row, + self.scale_col, + self.offset_col, + self.scale_row, + self.offset_row, + ) + else: + logging.warning("inverse localisation can't be performed, inverse coefficients have not been defined") + (col_out, row_out) = (None, None) + return row_out, col_out, alt + + def filter_coordinates(self, first_coord, second_coord, fill_nan=False, direction="direct"): + """ + Filter nan input values + + :param first_coord: first coordinate + :type first_coord: 1D numpy.ndarray dtype=float64 + :param second_coord: second coordinate + :type second_coord: 1D numpy.ndarray dtype=float64 + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :param direction: direct or inverse localisation + :type direction: str in ('direct', 'inverse') + :return: filtered coordinates + :rtype: list of numpy.array (index of nan, first filtered, second filtered) + """ + filter_nan = np.logical_not(np.logical_or(np.isnan(first_coord), np.isnan(second_coord))) + + if fill_nan: + if direction == "direct": + out_x_nan_value = self.offset_x + out_y_nan_value = self.offset_y + else: + out_x_nan_value = self.offset_col + out_y_nan_value = self.offset_row + else: + out_x_nan_value = np.nan + out_y_nan_value = np.nan + + x_out = np.full(len(second_coord), out_x_nan_value) + y_out = np.full(len(second_coord), out_y_nan_value) + + return filter_nan, x_out, y_out + + def compute_loc_inverse_derivates(self, lon, lat, alt): + """ + Inverse loc partial derivatives analytical compute + + :param lon: longitude coordinate + :param lat: latitude coordinate + :param alt: altitude coordinate + :return: partials derivatives of inverse localization + :rtype: Tuple(dcol_dlon np.array, dcol_dlat np.array, drow_dlon np.array, drow_dlat np.array) + """ + if not isinstance(alt, (list, np.ndarray)): + alt = np.array([alt]) + + if alt.shape[0] != lon.shape[0]: + alt = np.full(lon.shape[0], fill_value=alt[0]) + + lon_norm = (lon - self.offset_x) / self.scale_x + lat_norm = (lat - self.offset_y) / self.scale_y + alt_norm = (alt - self.offset_alt) / self.scale_alt + + dcol_dlon, dcol_dlat, drow_dlon, drow_dlat = compute_loc_inverse_derivates_numba( + lon_norm, + lat_norm, + alt_norm, + self.num_col, + self.den_col, + self.num_row, + self.den_row, + self.scale_col, + self.scale_x, + self.scale_row, + self.scale_y, + ) + + return (dcol_dlon, dcol_dlat, drow_dlon, drow_dlat) + + def direct_loc_inverse_iterative(self, row, col, alt, nb_iter_max=10, fill_nan=False): + """ + Iterative direct localization using inverse RPC + + :param row: line sensor position + :type row: float or 1D numpy.ndarray dtype=float64 + :param col: column sensor position + :type col: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :type alt: float + :param nb_iter_max: max number of iteration + :type alt: int + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :return: ground position (lon,lat,h) + :rtype: list of numpy.array + """ + if self.inverse_coefficient: + if not isinstance(row, (list, np.ndarray)): + col = np.array([col]) + row = np.array([row]) + + if not isinstance(alt, (list, np.ndarray)): + alt = np.array([alt]) + + if alt.shape[0] != col.shape[0]: + alt = np.full(col.shape[0], fill_value=alt[0]) + + filter_nan, long_out, lat_out = self.filter_coordinates(row, col, fill_nan) + row = row[filter_nan] + col = col[filter_nan] + alt = alt[filter_nan] + + # if all coord contains Nan then return + if not np.any(filter_nan): + return long_out, lat_out, alt + + # inverse localization starting from the center of the scene + lon = np.array([self.offset_x]) + lat = np.array([self.offset_y]) + (row_start, col_start, __) = self.inverse_loc(lon, lat, alt) + + # desired precision in pixels + eps = 1e-6 + + iteration = 0 + # computing the residue between the sensor positions and those estimated by the inverse localization + delta_col = col - col_start + delta_row = row - row_start + + # ground coordinates (latitude and longitude) of each point + lon = np.repeat(lon, delta_col.size) + lat = np.repeat(lat, delta_col.size) + # while the required precision is not achieved + while (np.max(abs(delta_col)) > eps or np.max(abs(delta_row)) > eps) and iteration < nb_iter_max: + # list of points that require another iteration + iter_ = np.where((abs(delta_col) > eps) | (abs(delta_row) > eps))[0] + + # partial derivatives + (dcol_dlon, dcol_dlat, drow_dlon, drow_dlat) = self.compute_loc_inverse_derivates( + lon[iter_], lat[iter_], alt[iter_] + ) + det = dcol_dlon * drow_dlat - drow_dlon * dcol_dlat + + delta_lon = (drow_dlat * delta_col[iter_] - dcol_dlat * delta_row[iter_]) / det + delta_lat = (-drow_dlon * delta_col[iter_] + dcol_dlon * delta_row[iter_]) / det + + # update ground coordinates + lon[iter_] += delta_lon + lat[iter_] += delta_lat + + # inverse localization + (row_estim, col_estim, __) = self.inverse_loc(lon[iter_], lat[iter_], alt[iter_]) + + # updating the residue between the sensor positions and those estimated by the inverse localization + delta_col[iter_] = col[iter_] - col_estim + delta_row[iter_] = row[iter_] - row_estim + iteration += 1 + + long_out[filter_nan] = lon + lat_out[filter_nan] = lat + + else: + logging.warning("inverse localisation can't be performed, inverse coefficients have not been defined") + (long_out, lat_out) = (None, None) + + return long_out, lat_out, alt + + def get_alt_min_max(self): + """ + returns altitudes min and max layers + + :return: alt_min,lat_max + :rtype: list + """ + return [self.offset_alt - self.scale_alt / 2.0, self.offset_alt + self.scale_alt / 2.0] + + def los_extrema(self, row, col, alt_min=None, alt_max=None, fill_nan=False, epsg=None): + """ + compute los extrema + + :param row: line sensor position + :type row: float + :param col: column sensor position + :type col: float + :param alt_min: los alt min + :type alt_min: float + :param alt_max: los alt max + :type alt_max: float + :param epsg: epsg code of the dtm + :type epsg: int + :return: los extrema + :rtype: numpy.array (2x3) + """ + extrapolate = False + if alt_min is None or alt_max is None: + [los_alt_min, los_alt_max] = self.get_alt_min_max() + elif alt_min >= self.alt_minmax[0] and alt_max <= self.alt_minmax[1]: + los_alt_min = alt_min + los_alt_max = alt_max + else: + extrapolate = True + [los_alt_min, los_alt_max] = self.get_alt_min_max() + + # + if isinstance(row, (np.ndarray)): + los_nb = row.shape[0] + row_array = np.full([los_nb * 2], fill_value=0.0) + col_array = np.full([los_nb * 2], fill_value=0.0) + alt_array = np.full([los_nb * 2], fill_value=0.0) + row_array[0::2] = row + row_array[1::2] = row + col_array[0::2] = col + col_array[1::2] = col + alt_array[0::2] = los_alt_max + alt_array[1::2] = los_alt_min + else: + los_nb = 1 + row_array = np.array([row, row]) + col_array = np.array([col, col]) + alt_array = np.array([los_alt_max, los_alt_min]) + los_edges = self.direct_loc_h(row_array, col_array, alt_array, fill_nan) + if extrapolate: + diff = los_edges[0::2, :] - los_edges[1::2, :] + delta_alt = diff[:, 2] + coeff_alt_max = (alt_max - los_edges[1::2, 2]) / delta_alt + coeff_alt_max = np.tile(coeff_alt_max[:, np.newaxis], (1, 3)) + coeff_alt_min = (alt_min - los_edges[1::2, 2]) / delta_alt + coeff_alt_min = np.tile(coeff_alt_min[:, np.newaxis], (1, 3)) + los_edges[0::2, :] = los_edges[1::2, :] + diff * coeff_alt_max + los_edges[1::2, :] = los_edges[1::2, :] + diff * coeff_alt_min + if epsg is not None and epsg != self.epsg: + los_edges = coordinates_conversion(los_edges, self.epsg, epsg) + return los_edges + + +@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) +def polynomial_equation(xnorm, ynorm, znorm, coeff): + """ + Compute polynomial equation + + :param xnorm: Normalized longitude (for inverse) or column (for direct) position + :type xnorm: float 64 + :param ynorm: Normalized latitude (for inverse) or line (for direct) position + :type ynorm: float 64 + :param znorm: Normalized altitude position + :type znorm: float 64 + :param coeff: coefficients + :type coeff: 1D np.array dtype np.float 64 + :return: rational + :rtype: float 64 + """ + rational = ( + coeff[0] + + coeff[1] * xnorm + + coeff[2] * ynorm + + coeff[3] * znorm + + coeff[4] * xnorm * ynorm + + coeff[5] * xnorm * znorm + + coeff[6] * ynorm * znorm + + coeff[7] * xnorm**2 + + coeff[8] * ynorm**2 + + coeff[9] * znorm**2 + + coeff[10] * xnorm * ynorm * znorm + + coeff[11] * xnorm**3 + + coeff[12] * xnorm * ynorm**2 + + coeff[13] * xnorm * znorm**2 + + coeff[14] * xnorm**2 * ynorm + + coeff[15] * ynorm**3 + + coeff[16] * ynorm * znorm**2 + + coeff[17] * xnorm**2 * znorm + + coeff[18] * ynorm**2 * znorm + + coeff[19] * znorm**3 + ) + + return rational + + +# pylint: disable=too-many-arguments +@njit( + "Tuple((f8[:], f8[:]))(f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8, f8, f8, f8)", + parallel=True, + cache=True, + fastmath=True, +) +def compute_rational_function_polynomial( + lon_col_norm, + lat_row_norm, + alt_norm, + num_col, + den_col, + num_lin, + den_lin, + scale_col, + offset_col, + scale_lin, + offset_lin, +): + """ + Compute rational function polynomial using numba to reduce calculation time on multiple points. + useful to compute direct and inverse localization using direct or inverse RPC. + + :param lon_col_norm: Normalized longitude (for inverse) or column (for direct) position + :type lon_col_norm: 1D np.array dtype np.float 64 + :param lat_row_norm: Normalized latitude (for inverse) or line (for direct) position + :type lat_row_norm: 1D np.array dtype np.float 64 + :param alt_norm: Normalized altitude position + :type alt_norm: 1D np.array dtype np.float 64 + :param num_col: Column numerator coefficients + :type num_col: 1D np.array dtype np.float 64 + :param den_col: Column denominator coefficients + :type den_col: 1D np.array dtype np.float 64 + :param num_lin: Line numerator coefficients + :type num_lin: 1D np.array dtype np.float 64 + :param den_lin: Line denominator coefficients + :type den_lin: 1D np.array dtype np.float 64 + :param scale_col: Column scale + :type scale_col: float 64 + :param offset_col: Column offset + :type offset_col: float 64 + :param scale_lin: Line scale + :type scale_lin: float 64 + :param offset_lin: Line offset + :type offset_lin: float 64 + :return: for inverse localization : sensor position (row, col). for direct localization : ground position (lon, lat) + :rtype: Tuple(np.ndarray, np.ndarray) + """ + assert lon_col_norm.shape == alt_norm.shape + + col_lat_out = np.zeros((lon_col_norm.shape[0]), dtype=np.float64) + row_lon_out = np.zeros((lon_col_norm.shape[0]), dtype=np.float64) + + # pylint: disable=not-an-iterable + for i in prange(lon_col_norm.shape[0]): + poly_num_col = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], num_col) + poly_den_col = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], den_col) + poly_num_lin = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], num_lin) + poly_den_lin = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], den_lin) + col_lat_out[i] = poly_num_col / poly_den_col * scale_col + offset_col + row_lon_out[i] = poly_num_lin / poly_den_lin * scale_lin + offset_lin + + return row_lon_out, col_lat_out + + +@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) +def derivative_polynomial_latitude(lon_norm, lat_norm, alt_norm, coeff): + """ + Compute latitude derivative polynomial equation + + :param lon_norm: Normalized longitude position + :type lon_norm: float 64 + :param lat_norm: Normalized latitude position + :type lat_norm: float 64 + :param alt_norm: Normalized altitude position + :type alt_norm: float 64 + :param coeff: coefficients + :type coeff: 1D np.array dtype np.float 64 + :return: rational derivative + :rtype: float 64 + """ + derivate = ( + coeff[2] + + coeff[4] * lon_norm + + coeff[6] * alt_norm + + 2 * coeff[8] * lat_norm + + coeff[10] * lon_norm * alt_norm + + 2 * coeff[12] * lon_norm * lat_norm + + coeff[14] * lon_norm**2 + + 3 * coeff[15] * lat_norm**2 + + coeff[16] * alt_norm**2 + + 2 * coeff[18] * lat_norm * alt_norm + ) + + return derivate + + +@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) +def derivative_polynomial_longitude(lon_norm, lat_norm, alt_norm, coeff): + """ + Compute longitude derivative polynomial equation + + :param lon_norm: Normalized longitude position + :type lon_norm: float 64 + :param lat_norm: Normalized latitude position + :type lat_norm: float 64 + :param alt_norm: Normalized altitude position + :type alt_norm: float 64 + :param coeff: coefficients + :type coeff: 1D np.array dtype np.float 64 + :return: rational derivative + :rtype: float 64 + """ + derivate = ( + coeff[1] + + coeff[4] * lat_norm + + coeff[5] * alt_norm + + 2 * coeff[7] * lon_norm + + coeff[10] * lat_norm * alt_norm + + 3 * coeff[11] * lon_norm**2 + + coeff[12] * lat_norm**2 + + coeff[13] * alt_norm**2 + + 2 * coeff[14] * lat_norm * lon_norm + + 2 * coeff[17] * lon_norm * alt_norm + ) + + return derivate + + +# pylint: disable=too-many-arguments +@njit( + "Tuple((f8[:], f8[:], f8[:], f8[:]))(f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8, f8, f8, f8)", + parallel=True, + cache=True, + fastmath=True, +) +def compute_loc_inverse_derivates_numba( + lon_norm, lat_norm, alt_norm, num_col, den_col, num_lin, den_lin, scale_col, scale_lon, scale_lin, scale_lat +): + """ + Analytically compute the partials derivatives of inverse localization using numba to reduce calculation time on + multiple points + + :param lon_norm: Normalized longitude position + :type lon_norm: 1D np.array dtype np.float 64 + :param lat_norm: Normalized latitude position + :type lat_norm: 1D np.array dtype np.float 64 + :param alt_norm: Normalized altitude position + :type alt_norm: 1D np.array dtype np.float 64 + :param num_col: Column numerator coefficients + :type num_col: 1D np.array dtype np.float 64 + :param den_col: Column denominator coefficients + :type den_col: 1D np.array dtype np.float 64 + :param num_lin: Line numerator coefficients + :type num_lin: 1D np.array dtype np.float 64 + :param den_lin: Line denominator coefficients + :type den_lin: 1D np.array dtype np.float 64 + :param scale_col: Column scale + :type scale_col: float 64 + :param scale_lon: Geodetic longitude scale + :type scale_lon: float 64 + :param scale_lin: Line scale + :type scale_lin: float 64 + :param scale_lat: Geodetic latitude scale + :type scale_lat: float 64 + :return: partials derivatives of inverse localization + :rtype: Tuples(dcol_dlon np.array, dcol_dlat np.array, drow_dlon np.array, drow_dlat np.array) + """ + dcol_dlon = np.zeros((lon_norm.shape[0]), dtype=np.float64) + dcol_dlat = np.zeros((lon_norm.shape[0]), dtype=np.float64) + drow_dlon = np.zeros((lon_norm.shape[0]), dtype=np.float64) + drow_dlat = np.zeros((lon_norm.shape[0]), dtype=np.float64) + + # pylint: disable=not-an-iterable + for i in prange(lon_norm.shape[0]): + num_dcol = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], num_col) + den_dcol = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], den_col) + num_drow = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) + den_drow = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) + + num_dcol_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], num_col) + den_dcol_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], den_col) + num_drow_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) + den_drow_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) + + num_dcol_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], num_col) + den_dcol_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], den_col) + num_drow_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) + den_drow_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) + + dcol_dlon[i] = scale_col / scale_lon * (num_dcol_dlon * den_dcol - den_dcol_dlon * num_dcol) / den_dcol**2 + dcol_dlat[i] = scale_col / scale_lat * (num_dcol_dlat * den_dcol - den_dcol_dlat * num_dcol) / den_dcol**2 + drow_dlon[i] = scale_lin / scale_lon * (num_drow_dlon * den_drow - den_drow_dlon * num_drow) / den_drow**2 + drow_dlat[i] = scale_lin / scale_lat * (num_drow_dlat * den_drow - den_drow_dlat * num_drow) / den_drow**2 + + return dcol_dlon, dcol_dlat, drow_dlon, drow_dlat diff --git a/shareloc/draft_pybind/221_python_files/test_rpc.py b/shareloc/draft_pybind/221_python_files/test_rpc.py new file mode 100644 index 0000000..23145e2 --- /dev/null +++ b/shareloc/draft_pybind/221_python_files/test_rpc.py @@ -0,0 +1,28 @@ +# Standard imports +import os + +# Third party imports +import numpy as np +import pytest +import rasterio + +# Shareloc imports +from shareloc.draft_pybind.rpc import RPC + + +@pytest.mark.parametrize("col,row,alt", [(600, 200, 125)]) +def test_rpc_phrdimap(col, row, alt): + """ + test the sequence of a inverse localization followed by a direct localization using dimap file + """ + + file_dimap = "/home/adevaux/Bureau/sharloc_231/shareloc/tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML" + + fctrat = RPC(file_dimap) + + (lonlatalt) = fctrat.direct_loc_h(row, col, alt) + + (row_ar, col_ar, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) + assert col_ar == pytest.approx(col, abs=2e-2) + assert row_ar == pytest.approx(row, abs=2e-2) + assert False diff --git a/shareloc/draft_pybind/CMakeLists.txt b/shareloc/draft_pybind/CMakeLists.txt new file mode 100644 index 0000000..53b7a05 --- /dev/null +++ b/shareloc/draft_pybind/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.22) +project(pbrpc) + +add_library(GeoModelTemplate SHARED GeoModelTemplate.cpp) +target_link_libraries(pbrpc GeoModelTemplate) +#add_executable(main main.cpp Student.cpp) + +find_package(pybind11 REQUIRED) +pybind11_add_module(pbrpc bind_helloworld.cpp) diff --git a/shareloc/draft_pybind/GeoModelTemplate.cpp b/shareloc/draft_pybind/GeoModelTemplate.cpp new file mode 100644 index 0000000..371cb91 --- /dev/null +++ b/shareloc/draft_pybind/GeoModelTemplate.cpp @@ -0,0 +1,29 @@ + + + +GeoModelTemplate::GeoModelTemplate(string geomodel_path_arg) { + cout<<"GeoModelTemplate : constructor"<> GeoModelTemplate::direct_loc_h(vector row, vector col, double alt, bool fill_nan){ + cout<<"GeoModelTemplate : direct_loc_h"<> vect; + return vect; +} + +vector> GeoModelTemplate::direct_loc_dtm(vector row, vector col, string dtm){ + cout<<"GeoModelTemplate : direct_loc_dtm"<> vect; + return vect; +} + +tuple,vector,vector> GeoModelTemplate::inverse_loc(vector lon, vector lat, double alt){ + cout<<"GeoModelTemplate : inverse_loc"<,vector,vector> res; + return res; +} \ No newline at end of file diff --git a/shareloc/draft_pybind/GeoModelTemplate.hpp b/shareloc/draft_pybind/GeoModelTemplate.hpp new file mode 100644 index 0000000..decac55 --- /dev/null +++ b/shareloc/draft_pybind/GeoModelTemplate.hpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +using std::cout; +using std::endl; +using std::vector; +using std::string; +using std::tuple; + +class GeoModelTemplate { +private: + string geomodel_path; + string type; +public: + GeoModelTemplate(string geomodel_path_arg); + ~GeoModelTemplate(); + vector> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); + vector> direct_loc_dtm(vector row, vector col, string dtm); + tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); +}; diff --git a/shareloc/draft_pybind/bind_helloworld.cpp b/shareloc/draft_pybind/bind_helloworld.cpp index 926aeb7..8693df0 100644 --- a/shareloc/draft_pybind/bind_helloworld.cpp +++ b/shareloc/draft_pybind/bind_helloworld.cpp @@ -1,15 +1,36 @@ #include -#include "hello_world.cpp" +#include +#include "rpc.cpp" namespace py = pybind11; -PYBIND11_MODULE(pbhelloworld, m) { - py::class_(m, "HW") - .def(py::init<>()) - .def("hellow_world", &HW::hellow_world); - /*m.doc() = "Pybind hello world"; // optional module docstring - m.def("main", &main, "Print hello world str");*/ +PYBIND11_MODULE(pbrpc, m) { + + py::class_(m, "GeoModelTemplate") + .def(py::init()) + .def("direct_loc_h", &GeoModelTemplate::direct_loc_h) + .def("direct_loc_dtm", &GeoModelTemplate::direct_loc_dtm) + .def("inverse_loc", &GeoModelTemplate::inverse_loc); + + py::class_(m, "RPC") + .def(py::init()) + .def("direct_loc_h", &RPC::direct_loc_h) + .def("direct_loc_grid_h", &RPC::direct_loc_grid_h) + .def("direct_loc_dtm", &RPC::direct_loc_dtm) + .def("filter_coordinates", &RPC::filter_coordinates) + .def("compute_loc_inverse_derivates", &RPC::compute_loc_inverse_derivates) + .def("direct_loc_inverse_iterative", &RPC::direct_loc_inverse_iterative) + .def("get_alt_min_max", &RPC::get_alt_min_max) + .def("los_extrema", &RPC::los_extrema); + + //m.doc() = "Pybind hello world"; // optional module docstring + m.def("parse_coeff_line", &parse_coeff_line, "TODO: doc"); + m.def("polynomial_equation", &polynomial_equation, "TODO: doc"); + m.def("compute_rational_function_polynomial", &compute_rational_function_polynomial, "TODO: doc"); + m.def("derivative_polynomial_latitude", &derivative_polynomial_latitude, "TODO: doc"); + m.def("derivative_polynomial_longitude", &derivative_polynomial_longitude, "TODO: doc"); + m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); } -//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp -o pbhelloworld$(python3-config --extension-suffix) \ No newline at end of file +//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/draft_pybind/example_pybind.py b/shareloc/draft_pybind/example_pybind.py index 94579ad..5301d1d 100644 --- a/shareloc/draft_pybind/example_pybind.py +++ b/shareloc/draft_pybind/example_pybind.py @@ -4,9 +4,33 @@ sys.path.append(".") # pylint: disable=wrong-import-position -import libs.pbhelloworld as HWmodule # noqa: E402 +import libs.pbrpc as pbrpc # noqa: E402 # pylint: enable=wrong-import-position -HW_object = HWmodule.HW() -print(HW_object.hellow_world()) + +row = [1.0, 1.0, 1.0] +col = [1.0, 1.0, 1.0] +alt = 1.0 + +GM_object = pbrpc.GeoModelTemplate("geomdel_path") +lonlatalt1 = GM_object.direct_loc_h(row, col, alt, False) +lonlatalt1 = GM_object.inverse_loc(row, col, alt) +del GM_object +# lonlatalt2 = GM_object.direct_loc_dtm(row,col,"test") +# lonlatalt3 = GM_object.inverse_loc(row,col,alt,) +# print(lonlatalt1) +# print(lonlatalt2) +# print(lonlatalt3) + + +# class RPCOptim(pbrpc.RPC): +# def hello_python(self): +# return "hello python" + +# rpc_optim = RPCOptim() +# arg = [[1.,1.,1.],[1.,1.,1.]] +# zero = rpc_optim.direct_loc_h(arg) + +# print(type(zero)) +# print(zero) diff --git a/shareloc/draft_pybind/hello_world.cpp b/shareloc/draft_pybind/hello_world.cpp deleted file mode 100644 index 9056b18..0000000 --- a/shareloc/draft_pybind/hello_world.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "hello_world.h" - -HW::HW() {} - -HW::~HW() {} - -std::string HW::hellow_world() const { - return "Hello world !"; -} - -/*int main(){ - HW HelloWorld; - std::cout< -#include - -class HW { -public: - HW(); - ~HW(); - - std::string hellow_world() const; -}; diff --git a/shareloc/draft_pybind/rpc.cpp b/shareloc/draft_pybind/rpc.cpp new file mode 100644 index 0000000..858d4ef --- /dev/null +++ b/shareloc/draft_pybind/rpc.cpp @@ -0,0 +1,110 @@ +//#include + +#include "rpc.hpp" + +//---- RPC methodes ----// + +vector> RPC::direct_loc_h(vector row, vector col, double alt, bool fill_nan){ + vector> vect; + return vect; +} + +tuple>,vector>> RPC::direct_loc_grid_h(int row0, int col0, int steprow, int stepcol, int nbrow, int nbcol, double alt){ + tuple>,vector>> res; + return res; +} + +vector> RPC::direct_loc_dtm(double row, double col, string dtm){//////////////////////////////////////////////////////// dtm intersection model ->python class + vector> vect; + return vect; +} + +tuple,vector,vector> RPC::inverse_loc(vector lon, vector lat, double alt){} + + +vector> RPC::filter_coordinates(vector first_coord, vector second_coord, bool fill_nan, string direction){ + vector> vect; + return vect; +} + +tuple,vector,vector,vector> RPC::compute_loc_inverse_derivates(vector lon, vector lat, vector alt){ + tuple,vector,vector,vector> res; + return res; +} + +vector> RPC::direct_loc_inverse_iterative(vector row, vector col, float alt, int nb_iter_max, bool fill_nan){ + vector> vect; + return vect; +} + +vector RPC::get_alt_min_max(){ + vector vect; + return vect; +} + +vector> RPC::los_extrema(double row, double col, double alt_min, double alt_max, bool fill_nan, int epsg){ + vector> vect; + return vect; +} + + +//---- Functions ----// + +vector parse_coeff_line(string coeff_str){ + vector vect; + return vect; +} + + +double polynomial_equation(double xnorm, double ynorm, double znorm, vector coeff){ + double res; + return res; +} + + +tuple,vector> compute_rational_function_polynomial( + vector lon_col_norm, + vector lat_row_norm, + vector alt_norm, + vector num_col, + vector den_col, + vector num_lin, + vector den_lin, + double scale_col, + double offset_col, + double scale_lin, + double offset_lin +){ + tuple,vector> res; + return res; +} + + +double derivative_polynomial_latitude(double lon_norm, double lat_norm, double alt_norm, vector coeff){ + double res; + return res; +} + + +double derivative_polynomial_longitude(double lon_norm, double lat_norm, double alt_norm, vector coeff){ + double res; + return res; +} + + +tuple,vector,vector,vector> compute_loc_inverse_derivates_numba( + vector lon_norm, + vector lat_norm, + vector alt_norm, + vector num_col, + vector den_col, + vector num_lin, + vector den_lin, + double scale_col, + double scale_lon, + double scale_lin, + double scale_lat +){ + tuple,vector,vector,vector> res; + return res; +} \ No newline at end of file diff --git a/shareloc/draft_pybind/rpc.hpp b/shareloc/draft_pybind/rpc.hpp new file mode 100644 index 0000000..7da1e79 --- /dev/null +++ b/shareloc/draft_pybind/rpc.hpp @@ -0,0 +1,63 @@ +#include "GeoModelTemplate.hpp" +#include "GeoModelTemplate.cpp" +#include + +using std::map; + +/* +Squelette de la classe RPC.py de l'issue 221 + +*/ + +class RPC : public GeoModelTemplate{ +private: + string type; + int epsg; + string datum; + map rpc_params; //map is a simple dict -> maybe inappropiate here + double lim_extrapol; + + vector> monomes; + vector> monomes_deriv_1; + vector> monomes_deriv_2; + + bool inverse_coefficient; + bool direct_coefficient; + + vector num_col; + vector den_col; + vector num_row; + vector den_row; + + vector num_x; + vector den_x; + vector num_y; + vector den_y; + + vector alt_minmax; + + double col0; + double colmax; + double row0; + double rowmax; + + double offset_row; + double scale_row; + double offset_col; + double scale_col; + double offset_alt; + double scale_alt; + +public: + using GeoModelTemplate::GeoModelTemplate; + + vector> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); + tuple>,vector>> direct_loc_grid_h(int row0, int col0, int steprow, int stepcol, int nbrow, int nbcol, double alt); + vector> direct_loc_dtm(double row, double col, string dtm);// dtm is a python class not a string + tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); + vector> filter_coordinates(vector first_coord, vector second_coord, bool fill_nan=false, string direction="direct"); + tuple,vector,vector,vector> compute_loc_inverse_derivates(vector lon, vector lat, vector alt); + vector> direct_loc_inverse_iterative(vector row, vector col, float alt, int nb_iter_max=10, bool fill_nan=false); + vector get_alt_min_max(); + vector> los_extrema(double row, double col, double alt_min, double alt_max, bool fill_nan=false, int epsg=4326); +}; \ No newline at end of file diff --git a/tests/geomodels/test_draft_pybind.py b/tests/geomodels/test_draft_pybind.py index 0461958..967cabf 100644 --- a/tests/geomodels/test_draft_pybind.py +++ b/tests/geomodels/test_draft_pybind.py @@ -4,12 +4,12 @@ sys.path.append(".") # pylint: disable=wrong-import-position -import libs.pbhelloworld as HWmodule # noqa: E402 +import libs.pbrpc as pbrpc # noqa: E402 # pylint: enable=wrong-import-position -def test_helloworld(): - hw_object = HWmodule.HW() +def test_pbrpc(): + # TODO - assert "Hello world !" == hw_object.hellow_world() + assert True From adb77b244f26ee5aebd7913cf6fd932b881b5ca9 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Wed, 15 Nov 2023 10:34:42 +0100 Subject: [PATCH 07/18] suppression de l'argument du constructor GeomodelTemplate --- shareloc/draft_pybind/GeoModelTemplate.cpp | 4 +--- shareloc/draft_pybind/GeoModelTemplate.hpp | 2 +- shareloc/draft_pybind/bind_helloworld.cpp | 4 ++-- shareloc/draft_pybind/example_pybind.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/shareloc/draft_pybind/GeoModelTemplate.cpp b/shareloc/draft_pybind/GeoModelTemplate.cpp index 371cb91..4a4fb36 100644 --- a/shareloc/draft_pybind/GeoModelTemplate.cpp +++ b/shareloc/draft_pybind/GeoModelTemplate.cpp @@ -1,10 +1,8 @@ -GeoModelTemplate::GeoModelTemplate(string geomodel_path_arg) { +GeoModelTemplate::GeoModelTemplate() { cout<<"GeoModelTemplate : constructor"<> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); vector> direct_loc_dtm(vector row, vector col, string dtm); diff --git a/shareloc/draft_pybind/bind_helloworld.cpp b/shareloc/draft_pybind/bind_helloworld.cpp index 8693df0..3538cff 100644 --- a/shareloc/draft_pybind/bind_helloworld.cpp +++ b/shareloc/draft_pybind/bind_helloworld.cpp @@ -8,13 +8,13 @@ namespace py = pybind11; PYBIND11_MODULE(pbrpc, m) { py::class_(m, "GeoModelTemplate") - .def(py::init()) + .def(py::init<>()) .def("direct_loc_h", &GeoModelTemplate::direct_loc_h) .def("direct_loc_dtm", &GeoModelTemplate::direct_loc_dtm) .def("inverse_loc", &GeoModelTemplate::inverse_loc); py::class_(m, "RPC") - .def(py::init()) + .def(py::init<>()) .def("direct_loc_h", &RPC::direct_loc_h) .def("direct_loc_grid_h", &RPC::direct_loc_grid_h) .def("direct_loc_dtm", &RPC::direct_loc_dtm) diff --git a/shareloc/draft_pybind/example_pybind.py b/shareloc/draft_pybind/example_pybind.py index 5301d1d..769d813 100644 --- a/shareloc/draft_pybind/example_pybind.py +++ b/shareloc/draft_pybind/example_pybind.py @@ -13,7 +13,7 @@ col = [1.0, 1.0, 1.0] alt = 1.0 -GM_object = pbrpc.GeoModelTemplate("geomdel_path") +GM_object = pbrpc.GeoModelTemplate() lonlatalt1 = GM_object.direct_loc_h(row, col, alt, False) lonlatalt1 = GM_object.inverse_loc(row, col, alt) del GM_object From d67b7cf3530d4e203bcae69156e73fc43a34a258 Mon Sep 17 00:00:00 2001 From: Emmanuel Dubois Date: Thu, 16 Nov 2023 13:45:16 +0100 Subject: [PATCH 08/18] feat: replace RPC and Grid by GeoModel --- .pylintrc | 2 +- shareloc/geomodels/geomodel.py | 2 +- tests/geofunctions/test_localization.py | 36 +++++++++--------- tests/geofunctions/test_rectification.py | 47 ++++++++++++------------ tests/geofunctions/test_triangulation.py | 17 ++++----- tests/geomodels/test_grid.py | 4 +- tests/geomodels/test_los.py | 6 +-- tests/geomodels/test_rpc.py | 41 +++++++++++---------- 8 files changed, 77 insertions(+), 78 deletions(-) diff --git a/.pylintrc b/.pylintrc index 16d5e65..73c1b42 100644 --- a/.pylintrc +++ b/.pylintrc @@ -250,7 +250,7 @@ ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local +ignored-classes=optparse.Values,thread._local,_thread._local, GeoModel # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime diff --git a/shareloc/geomodels/geomodel.py b/shareloc/geomodels/geomodel.py index e6ee48b..36dcb81 100644 --- a/shareloc/geomodels/geomodel.py +++ b/shareloc/geomodels/geomodel.py @@ -77,7 +77,7 @@ def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): # Create geomodel object with geomodel_path parameter from geomodel_type if exists try: geomodel_class = cls.available_geomodels[geomodel_type] - geomodel = geomodel_class(geomodel_path) + geomodel = geomodel_class(geomodel_path).load() logging.debug("GeoModel type name: %s", geomodel_type) except KeyError: logging.error("Geomodel type named %s is not supported", geomodel_type) diff --git a/tests/geofunctions/test_localization.py b/tests/geofunctions/test_localization.py index 045b4ec..ac0893a 100644 --- a/tests/geofunctions/test_localization.py +++ b/tests/geofunctions/test_localization.py @@ -34,10 +34,10 @@ from shareloc.geofunctions.dtm_intersection import DTMIntersection from shareloc.geofunctions.localization import Localization from shareloc.geofunctions.localization import coloc as coloc_rpc +from shareloc.geomodels import GeoModel # Shareloc imports -from shareloc.geomodels.grid import Grid, coloc -from shareloc.geomodels.rpc import RPC +from shareloc.geomodels.grid import coloc from shareloc.image import Image from shareloc.proj_utils import coordinates_conversion @@ -57,9 +57,9 @@ def test_localize_direct_rpc(): # first instanciate the RPC geometric model # data = os.path.join(data_path(), "rpc/phr_ventoux/", "left_image.geom") - # geom_model = RPC(data) + # geom_model = GeoModel(data) data = os.path.join(data_path(), "rpc/phr_ventoux/", "RPC_PHR1B_P_201308051042194_SEN_690908101-001.XML") - geom_model = RPC(data) + geom_model = GeoModel(data) # then read the Image to retrieve its geotransform image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -94,9 +94,9 @@ def test_localize_direct_grid(): # first instanciate the Grid geometric model # data = os.path.join(data_path(), "rpc/phr_ventoux/", "left_image.geom") - # geom_model_1 = RPC(data) + # geom_model_1 = GeoModel(data) data = os.path.join(data_path(), "grid/phr_ventoux/", "GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") - geom_model = Grid(data) + geom_model = GeoModel(data) # then read the Image to retrieve its geotransform image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -136,7 +136,7 @@ def prepare_loc(alti="geoide", id_scene="P1BP--2017030824934340CP"): dtm = DTMIntersection(fic) # load grid model gld = os.path.join(data_folder, grid_name) - gri = Grid(gld) + gri = GeoModel(gld) return dtm, gri @@ -229,7 +229,7 @@ def test_extent(): Test extent """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model = RPC(data_left + ".geom") + geom_model = GeoModel(data_left + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image_pixsize_0_5.tif") image = Image(image_filename) loc_rpc_image = Localization(geom_model, elevation=None, image=image) @@ -245,7 +245,7 @@ def test_sensor_loc_dir_dtm_geoid(col, row, valid_coord): Test direct localization using image geotransform """ data = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC(data + ".geom") + geom_model_left = GeoModel(data + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -266,7 +266,7 @@ def test_sensor_loc_dir_dtm_geoid_utm(col, row, valid_coord): Test direct localization using image geotransform """ data = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC(data + ".geom") + geom_model_left = GeoModel(data + ".geom") image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -296,7 +296,7 @@ def test_sensor_loc_dir_vs_loc_rpc(row, col, h): data_folder = data_path() fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(fichier_dimap) + fctrat = GeoModel(fichier_dimap) loc_rpc = Localization(fctrat) lonlatalt_rpc = loc_rpc.direct(row, col, h) @@ -391,7 +391,7 @@ def test_sensor_loc_inv_vs_loc_rpc(lon, lat, alt): data_folder = data_path() fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(fichier_dimap) + fctrat = GeoModel(fichier_dimap) loc_rpc = Localization(fctrat) [row_rpc, col_rpc, __] = loc_rpc.inverse(lon, lat, alt) @@ -507,7 +507,7 @@ def test_loc_dir_loc_inv_rpc(id_scene, rpc, row, col, h): data_folder = data_path() fichier_dimap = os.path.join(data_folder, "rpc", rpc) - fctrat = RPC(fichier_dimap) + fctrat = GeoModel(fichier_dimap) (inv_row, inv_col, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) assert row == pytest.approx(inv_row, abs=1e-2) assert col == pytest.approx(inv_col, abs=1e-2) @@ -559,7 +559,7 @@ def test_colocalization(col, row, alt): data_folder = data_path() id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) row_coloc, col_coloc, _ = coloc_rpc(fctrat, fctrat, row, col, alt) @@ -574,12 +574,12 @@ def test_sensor_coloc_using_geotransform(col, row, h): Test direct localization using image geotransform """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model_left = RPC(data_left + ".geom") + geom_model_left = GeoModel(data_left + ".geom") image_filename_left = os.path.join(data_path(), "image/phr_ventoux/", "left_image_pixsize_0_5.tif") image_left = Image(image_filename_left) data_right = os.path.join(data_path(), "rectification", "right_image") - geom_model_right = RPC(data_right + ".geom") + geom_model_right = GeoModel(data_right + ".geom") image_filename_right = os.path.join(data_path(), "image/phr_ventoux/", "right_image_pixsize_0_5.tif") image_right = Image(image_filename_right) @@ -609,7 +609,7 @@ def test_sensor_loc_utm(col, row): Test direct localization using image geotransform """ data_left = os.path.join(data_path(), "rectification", "left_image") - geom_model = RPC(data_left + ".geom") + geom_model = GeoModel(data_left + ".geom") epsg = 32631 loc_wgs = Localization(geom_model) loc_utm = Localization(geom_model, epsg=epsg) @@ -632,7 +632,7 @@ def test_sensor_loc_dir_dtm_multi_points(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) dtm_file = os.path.join(data_path(), "dtm", "srtm_ventoux", "srtm90_non_void_filled", "N44E005.hgt") geoid_file = os.path.join(data_path(), "dtm", "geoid", "egm96_15.gtx") diff --git a/tests/geofunctions/test_rectification.py b/tests/geofunctions/test_rectification.py index 5431215..4e2842f 100644 --- a/tests/geofunctions/test_rectification.py +++ b/tests/geofunctions/test_rectification.py @@ -44,8 +44,7 @@ prepare_rectification, ) from shareloc.geofunctions.rectification_grid import RectificationGrid -from shareloc.geomodels.grid import Grid -from shareloc.geomodels.rpc import RPC +from shareloc.geomodels import GeoModel from shareloc.image import Image # Shareloc test imports @@ -63,8 +62,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -110,8 +109,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_alti(): left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -151,8 +150,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_dtm_geoid(): """ # first instantiate geometric models left and right (here RPC geometrics model) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) # read the images left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) @@ -207,8 +206,8 @@ def test_compute_stereorectification_epipolar_grids_geomodel_rpc_dtm_geoid_roi() left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) right_im = Image(os.path.join(data_path(), "rectification", "right_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) dtm_file = os.path.join(data_path(), "dtm", "srtm_ventoux", "srtm90_non_void_filled", "N44E005.hgt") geoid_file = os.path.join(data_path(), "dtm", "geoid", "egm96_15.gtx") @@ -257,10 +256,10 @@ def test_compute_stereorectification_epipolar_grids_geomodel_grid(): """ # first instantiate geometric models left and right (here Grid geometric model) - geom_model_left = Grid( + geom_model_left = GeoModel( os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") ) - geom_model_right = Grid( + geom_model_right = GeoModel( os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif") ) @@ -313,10 +312,10 @@ def test_compute_stereorectification_epipolar_grids_geomodel_grid_dtm_geoid(): """ # first instantiate geometric models left and right (here Grid geometrics model) - geom_model_left = Grid( + geom_model_left = GeoModel( os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") ) - geom_model_right = Grid( + geom_model_right = GeoModel( os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif") ) @@ -423,8 +422,8 @@ def test_prepare_rectification(): """ left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -459,8 +458,8 @@ def test_prepare_rectification_footprint(): """ left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 @@ -486,8 +485,8 @@ def test_rectification_moving_along_line(): """ Test moving along line in epipolar geometry """ - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) current_left_coords = np.array([[5000.5, 5000.5, 0.0]], dtype=np.float64) mean_spacing = 1 @@ -511,8 +510,8 @@ def test_rectification_moving_to_next_line(): """ Test moving to next line in epipolar geometry """ - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) current_left_coords = np.array([5000.5, 5000.5, 0.0], dtype=np.float64) mean_spacing = 1 @@ -628,8 +627,8 @@ def test_rectification_grid_pos_inside_prepare_footprint_bounding_box(): # Compute shareloc epipolar footprint left_im = Image(os.path.join(data_path(), "rectification", "left_image.tif")) - geom_model_left = RPC(os.path.join(data_path(), "rectification", "left_image.geom")) - geom_model_right = RPC(os.path.join(data_path(), "rectification", "right_image.geom")) + geom_model_left = GeoModel(os.path.join(data_path(), "rectification", "left_image.geom")) + geom_model_right = GeoModel(os.path.join(data_path(), "rectification", "right_image.geom")) epi_step = 30 elevation_offset = 50 default_elev = 0.0 diff --git a/tests/geofunctions/test_triangulation.py b/tests/geofunctions/test_triangulation.py index 76ab72e..3006e22 100644 --- a/tests/geofunctions/test_triangulation.py +++ b/tests/geofunctions/test_triangulation.py @@ -36,8 +36,7 @@ # Shareloc imports from shareloc.geofunctions.triangulation import distance_point_los, epipolar_triangulation, sensor_triangulation -from shareloc.geomodels.grid import Grid -from shareloc.geomodels.rpc import RPC +from shareloc.geomodels import GeoModel # Shareloc test imports from ..helpers import data_path @@ -86,7 +85,7 @@ def prepare_loc(alti="geoide", id_scene="P1BP--2017030824934340CP"): data_folder = data_path(alti, id_scene) # load grid gld = os.path.join(data_folder, f"GRID_{id_scene}.tif") - gri = Grid(gld) + gri = GeoModel(gld) return gri @@ -156,10 +155,10 @@ def test_epi_triangulation_sift_rpc(): data_folder = data_path() id_scene = "PHR1B_P_201709281038045_SEN_PRG_FC_178608-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_left = RPC(file_geom) + geom_model_left = GeoModel(file_geom) id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_right = RPC(file_geom) + geom_model_right = GeoModel(file_geom) grid_left_filename = os.path.join(data_path(), "rectification_grids", "left_epipolar_grid.tif") grid_right_filename = os.path.join(data_path(), "rectification_grids", "right_epipolar_grid.tif") @@ -217,10 +216,10 @@ def test_epi_triangulation_disp_rpc(): data_folder = data_path() id_scene = "PHR1B_P_201709281038045_SEN_PRG_FC_178608-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_left = RPC(file_geom) + geom_model_left = GeoModel(file_geom) id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - geom_model_right = RPC(file_geom) + geom_model_right = GeoModel(file_geom) # grid_left_filename = os.path.join(data_path(), "rectification_grids", # "grid_{}.tif".format(id_scene_left)) @@ -258,9 +257,9 @@ def test_epi_triangulation_disp_rpc_roi(): """ data_folder = data_path() file_geom = os.path.join(data_folder, "rpc/phr_ventoux/left_image.geom") - geom_model_left = RPC(file_geom) + geom_model_left = GeoModel(file_geom) file_geom = os.path.join(data_folder, "rpc/phr_ventoux/right_image.geom") - geom_model_right = RPC(file_geom) + geom_model_right = GeoModel(file_geom) grid_left_filename = os.path.join(data_path(), "rectification_grids", "left_epipolar_grid_ventoux.tif") grid_right_filename = os.path.join(data_path(), "rectification_grids", "right_epipolar_grid_ventoux.tif") diff --git a/tests/geomodels/test_grid.py b/tests/geomodels/test_grid.py index 90ecac1..4ab9cb6 100755 --- a/tests/geomodels/test_grid.py +++ b/tests/geomodels/test_grid.py @@ -28,7 +28,7 @@ import scipy # Shareloc imports -from shareloc.geomodels.grid import Grid +from shareloc.geomodels import GeoModel from shareloc.image import Image # Shareloc test imports @@ -41,7 +41,7 @@ def fixture_get_geotiff_grid(): get grid and associated path """ geotiff_grid_path = data_path("ellipsoide", "loc_direct_grid_PHR_2013072139303958CP.tif") - gri_geotiff = Grid(geotiff_grid_path) + gri_geotiff = GeoModel(geotiff_grid_path) grid_image = Image(geotiff_grid_path, read_data=True) return gri_geotiff, grid_image diff --git a/tests/geomodels/test_los.py b/tests/geomodels/test_los.py index 3c3288c..e85b7d4 100644 --- a/tests/geomodels/test_los.py +++ b/tests/geomodels/test_los.py @@ -28,8 +28,8 @@ import pytest # Shareloc imports +from shareloc.geomodels import GeoModel from shareloc.geomodels.los import LOS -from shareloc.geomodels.rpc import RPC from shareloc.proj_utils import coordinates_conversion # Shareloc test imports @@ -51,7 +51,7 @@ def test_los_creation_with_different_alt(alt): # Load geometrical model id_scene = "P1BP--2017092838284574CP" file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - geometrical_model = RPC(file_dimap) + geometrical_model = GeoModel(file_dimap) # Get alt max if alt is None: alt_min, alt_max = geometrical_model.get_alt_min_max() @@ -103,7 +103,7 @@ def test_compare_two_altitude(): # Load geometrical model id_scene = "P1BP--2017092838284574CP" file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - geometrical_model = RPC(file_dimap) + geometrical_model = GeoModel(file_dimap) # Create los model_los = LOS(matches_left, geometrical_model, [310, 850]) diff --git a/tests/geomodels/test_rpc.py b/tests/geomodels/test_rpc.py index cdb707a..42ae96c 100755 --- a/tests/geomodels/test_rpc.py +++ b/tests/geomodels/test_rpc.py @@ -35,7 +35,8 @@ # Shareloc imports from shareloc.geofunctions.dtm_intersection import DTMIntersection -from shareloc.geomodels.rpc import RPC, identify_dimap, identify_ossim_kwl +from shareloc.geomodels import GeoModel +from shareloc.geomodels.rpc import identify_dimap, identify_ossim_kwl # Shareloc test imports from ..helpers import data_path @@ -50,25 +51,25 @@ def test_rpc_drivers(): # Test DIMAP RPC id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat_dimap = RPC(file_dimap) + fctrat_dimap = GeoModel(file_dimap) assert fctrat_dimap.driver_type == "dimap_v1.4" # Test OSSIM KWL GEOM RPC id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC(file_geom) + fctrat_geom = GeoModel(file_geom) assert fctrat_geom.driver_type == "ossim_kwl" # Test DIMAPv2 RPC id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" file_dimap_v2 = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat_dimap_v2 = RPC(file_dimap_v2) + fctrat_dimap_v2 = GeoModel(file_dimap_v2) assert fctrat_dimap_v2.driver_type == "dimap_v2.15" # Test fake RPC fake_rpc = os.path.join(data_folder, "rpc/fake_rpc.txt") try: - RPC(fake_rpc) # Raise ValueError->True + GeoModel(fake_rpc) # Raise ValueError->True raise AssertionError() # Assert false if no exception raised except ValueError: assert True @@ -123,7 +124,7 @@ def test_rpc_ossim_kwl(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC(file_geom) + fctrat_geom = GeoModel(file_geom) (row, col, __) = fctrat_geom.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) @@ -165,7 +166,7 @@ def test_rpc_from_any(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC(rpc_file) + fctrat = GeoModel(rpc_file) (row, col, __) = fctrat.inverse_loc(lon, lat, alt) assert fctrat.epsg == 4326 assert fctrat.datum == "ellipsoid" @@ -183,7 +184,7 @@ def test_rpc_direct_iterative(id_scene, lon, lat, alt): """ data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC(rpc_file) + fctrat = GeoModel(rpc_file) (row, col, __) = fctrat.inverse_loc(lon, lat, alt) (lon2, lat2, __) = fctrat.direct_loc_inverse_iterative(row, col, alt) assert lon == pytest.approx(lon2, abs=1e-2) @@ -205,7 +206,7 @@ def test_rpc_from_geotiff_without_rpc(prod, can_read): data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", prod) try: - RPC(rpc_file) + GeoModel(rpc_file) assert can_read except ValueError: assert not can_read @@ -221,14 +222,14 @@ def test_rpc_dimap_v2(id_scene, lon, lat, alt, row_vt, col_vt): """ data_folder = data_path() file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat_dimap = RPC(file_dimap) + fctrat_dimap = GeoModel(file_dimap) (row, col, __) = fctrat_dimap.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) assert row == pytest.approx(row_vt, abs=1e-2) file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = RPC(file_geom) + fctrat_geom = GeoModel(file_geom) (row, col, __) = fctrat_geom.inverse_loc(lon, lat, alt) assert col == pytest.approx(col_vt, abs=1e-2) assert row == pytest.approx(row_vt, abs=1e-2) @@ -243,7 +244,7 @@ def test_rpc_phrdimap(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) (lonlatalt) = fctrat.direct_loc_h(row, col, alt) @@ -261,7 +262,7 @@ def test_rpc_direct_inverse_iterative_vs_direct(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) # (col,lig,alt)=(100,1000,400) lonlatalt = fctrat.direct_loc_h(row, col, alt) @@ -281,7 +282,7 @@ def test_rpc_direct_inverse_iterative_vs_direct_multiple_points(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) (col, row, alt) = (np.array([600, 610]), np.array([200, 210]), np.array([125])) p_direct = fctrat.direct_loc_h(row, col, alt) @@ -308,7 +309,7 @@ def test_rpc_direct_iterative_nan(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) (col, row, alt) = (np.array([600, np.nan]), np.array([200, 210]), np.array([125])) p_direct0 = fctrat.direct_loc_inverse_iterative(row, col, alt, fill_nan=True) @@ -327,7 +328,7 @@ def test_rpc_direct_iterative_all_nan(): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) (col, row, alt) = (np.array([600, np.nan]), np.array([200, 210]), np.array([125])) direct_loc_tab = fctrat.direct_loc_inverse_iterative(row, col, alt, fill_nan=True) @@ -353,7 +354,7 @@ def test_rpc_direct_inverse_iterative(col, row, alt): id_scene = "P1BP--2018122638935449CP" file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) lonlatalt = fctrat.direct_loc_h(row, col, alt) (row_inv, col_inv, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) @@ -374,7 +375,7 @@ def test_rpc_direct_dtm(id_scene, index_x, index_y): data_folder = data_path() rpc_file = os.path.join(data_folder, "rpc", id_scene) - fctrat = RPC(rpc_file) + fctrat = GeoModel(rpc_file) id_scene = "P1BP--2017092838284574CP" data_folder_mnt = data_path("ellipsoide", id_scene) @@ -400,7 +401,7 @@ def test_rpc_los_extrapolation(id_scene, row, col): """ data_folder = data_path() file_dimap = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat = RPC(file_dimap) + fctrat = GeoModel(file_dimap) los_edges = fctrat.los_extrema(row, col) altmin = -10 altmax = 2000 @@ -417,7 +418,7 @@ def test_rpc_minmax(): data_folder = data_path() id_scene = "P1BP--2018122638935449CP" fichier_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat = RPC(fichier_dimap) + fctrat = GeoModel(fichier_dimap) (h_min, h_max) = fctrat.get_alt_min_max() assert h_min == 532.5 assert h_max == 617.5 From 89f19a623d864fffadbada42dbe7dbdce3ed7d3a Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Thu, 16 Nov 2023 17:53:14 +0100 Subject: [PATCH 09/18] class rpc_optim fille de GeomodelTemple.py et rpc.cpp + test + RAF CI --- setup.py | 2 +- .../GeoModelTemplate.cpp | 0 .../GeoModelTemplate.hpp | 0 .../bind_helloworld.cpp | 0 shareloc/{draft_pybind => bindings}/rpc.cpp | 2 +- shareloc/{draft_pybind => bindings}/rpc.hpp | 2 +- .../draft_pybind/221_python_files/geomodel.py | 116 -- .../221_python_files/geomodel_template.py | 107 -- .../draft_pybind/221_python_files/helpers.py | 42 - shareloc/draft_pybind/221_python_files/rpc.py | 1227 ----------------- .../draft_pybind/221_python_files/test_rpc.py | 28 - shareloc/draft_pybind/CMakeLists.txt | 9 - shareloc/draft_pybind/example.cpp | 17 - shareloc/draft_pybind/example_pybind.py | 36 - shareloc/geomodels/geomodel_template.py | 432 +++++- shareloc/geomodels/rpc.py | 381 +---- shareloc/geomodels/rpc_optim.py | 69 + tests/data/geomodel_template/PHRDIMAP.json | 189 +++ tests/data/geomodel_template/RPC_P1BP.json | 189 +++ tests/data/geomodel_template/RPC_PHR1B.json | 189 +++ tests/data/geomodel_template/geom.json | 105 ++ tests/data/geomodel_template/tif.json | 104 ++ tests/geomodels/test_geomodel_template.py | 152 ++ tests/geomodels/test_rpc.py | 3 +- tests/geomodels/test_rpc_optim.py | 96 ++ 25 files changed, 1532 insertions(+), 1965 deletions(-) rename shareloc/{draft_pybind => bindings}/GeoModelTemplate.cpp (100%) rename shareloc/{draft_pybind => bindings}/GeoModelTemplate.hpp (100%) rename shareloc/{draft_pybind => bindings}/bind_helloworld.cpp (100%) rename shareloc/{draft_pybind => bindings}/rpc.cpp (97%) rename shareloc/{draft_pybind => bindings}/rpc.hpp (96%) delete mode 100644 shareloc/draft_pybind/221_python_files/geomodel.py delete mode 100644 shareloc/draft_pybind/221_python_files/geomodel_template.py delete mode 100644 shareloc/draft_pybind/221_python_files/helpers.py delete mode 100644 shareloc/draft_pybind/221_python_files/rpc.py delete mode 100644 shareloc/draft_pybind/221_python_files/test_rpc.py delete mode 100644 shareloc/draft_pybind/CMakeLists.txt delete mode 100644 shareloc/draft_pybind/example.cpp delete mode 100644 shareloc/draft_pybind/example_pybind.py create mode 100755 shareloc/geomodels/rpc_optim.py create mode 100644 tests/data/geomodel_template/PHRDIMAP.json create mode 100644 tests/data/geomodel_template/RPC_P1BP.json create mode 100644 tests/data/geomodel_template/RPC_PHR1B.json create mode 100644 tests/data/geomodel_template/geom.json create mode 100644 tests/data/geomodel_template/tif.json create mode 100644 tests/geomodels/test_geomodel_template.py create mode 100644 tests/geomodels/test_rpc_optim.py diff --git a/setup.py b/setup.py index 486540c..f7b11ec 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ os.makedirs("libs") # Main setup with setup.cfg file. -extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/draft_pybind/bind_helloworld.cpp"])] # "lib.pbhelloworld" +extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/bindings/bind_helloworld.cpp"])] # extensions = intree_extensions("pbrpc", ["shareloc/draft_pybind/hello_world.cpp"]) diff --git a/shareloc/draft_pybind/GeoModelTemplate.cpp b/shareloc/bindings/GeoModelTemplate.cpp similarity index 100% rename from shareloc/draft_pybind/GeoModelTemplate.cpp rename to shareloc/bindings/GeoModelTemplate.cpp diff --git a/shareloc/draft_pybind/GeoModelTemplate.hpp b/shareloc/bindings/GeoModelTemplate.hpp similarity index 100% rename from shareloc/draft_pybind/GeoModelTemplate.hpp rename to shareloc/bindings/GeoModelTemplate.hpp diff --git a/shareloc/draft_pybind/bind_helloworld.cpp b/shareloc/bindings/bind_helloworld.cpp similarity index 100% rename from shareloc/draft_pybind/bind_helloworld.cpp rename to shareloc/bindings/bind_helloworld.cpp diff --git a/shareloc/draft_pybind/rpc.cpp b/shareloc/bindings/rpc.cpp similarity index 97% rename from shareloc/draft_pybind/rpc.cpp rename to shareloc/bindings/rpc.cpp index 858d4ef..a692f29 100644 --- a/shareloc/draft_pybind/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -32,7 +32,7 @@ tuple,vector,vector,vector> RPC::compute_ return res; } -vector> RPC::direct_loc_inverse_iterative(vector row, vector col, float alt, int nb_iter_max, bool fill_nan){ +vector> RPC::direct_loc_inverse_iterative(vector row, vector col, double alt, int nb_iter_max, bool fill_nan){ vector> vect; return vect; } diff --git a/shareloc/draft_pybind/rpc.hpp b/shareloc/bindings/rpc.hpp similarity index 96% rename from shareloc/draft_pybind/rpc.hpp rename to shareloc/bindings/rpc.hpp index 7da1e79..208815d 100644 --- a/shareloc/draft_pybind/rpc.hpp +++ b/shareloc/bindings/rpc.hpp @@ -57,7 +57,7 @@ class RPC : public GeoModelTemplate{ tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); vector> filter_coordinates(vector first_coord, vector second_coord, bool fill_nan=false, string direction="direct"); tuple,vector,vector,vector> compute_loc_inverse_derivates(vector lon, vector lat, vector alt); - vector> direct_loc_inverse_iterative(vector row, vector col, float alt, int nb_iter_max=10, bool fill_nan=false); + vector> direct_loc_inverse_iterative(vector row, vector col, double alt, int nb_iter_max=10, bool fill_nan=false); vector get_alt_min_max(); vector> los_extrema(double row, double col, double alt_min, double alt_max, bool fill_nan=false, int epsg=4326); }; \ No newline at end of file diff --git a/shareloc/draft_pybind/221_python_files/geomodel.py b/shareloc/draft_pybind/221_python_files/geomodel.py deleted file mode 100644 index e6ee48b..0000000 --- a/shareloc/draft_pybind/221_python_files/geomodel.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# coding: utf8 -# -# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). -# -# This file is part of shareloc -# (see https://github.com/CNES/shareloc). -# -# 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 GeoModel class factory. -This main API GeoModel class generates an object linked -with geomodel configuration "geomodel_name" -(from registered GeoModelTemplate class) - -** Experimental and not used as API for now** -** Will be used with an automatic switcher between Grid file format and -RPC file format obj = GeoModel("file_geom") only without geomodel_type -""" - -# Standard imports -import logging -from typing import Any, Dict - - -class GeoModel: - """ - GeoModel factory: - A class designed for registered all available geomodels - and instantiate them when needed. - """ - - # Dict (geomodel_name: str, class: object) containing registered geomodels - available_geomodels: Dict[str, Any] = {} - - def __new__(cls, geomodel_path: str, geomodel_type: str = "RPC"): - """ - Return a GeoModelTemplate child instance - associated with the "geomodel_name" given in the configuration - through create_geomodel local method for clarity. - - TODO: optional geomodel_type would profit to have an automatic switcher between geomodels (RPC, grids, ...) - - :param geomodel_path: Path of geomodel file - :type geomodel_path: string - :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance - :type geomodel_type: string - """ - return cls.create_geomodel(geomodel_path, geomodel_type) - - @classmethod - def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): - """ - Factory command to create the geomodel from geomodel_type - Return a GeoModelTemplate child instance - associated with the "geomodel_type" - - :param geomodel_path: Path of geomodel file - :type geomodel_path: string - :param geomodel_type: Type of the geomodel, default "rpc", used as key for specific geomodel subclass instance - :type geomodel_type: string - """ - - # If no type is given, the default is "RPC" - - # Create geomodel object with geomodel_path parameter from geomodel_type if exists - try: - geomodel_class = cls.available_geomodels[geomodel_type] - geomodel = geomodel_class(geomodel_path) - logging.debug("GeoModel type name: %s", geomodel_type) - except KeyError: - logging.error("Geomodel type named %s is not supported", geomodel_type) - raise - - return geomodel - - @classmethod - def print_avalaible_geomodels(cls): - """ - Print all registered applications - """ - for geomodel_type in cls.available_geomodels: - print(geomodel_type) - - @classmethod - def register(cls, geomodel_type: str): - """ - Allows to register the GeoModelTemplate subclass in the factory - with its geomodel geomodel_type through decorator - - :param geomodel_type: the subclass name to be registered - :type geomodel_type: string - """ - - def decorator(geomodel_subclass): - """ - Register the geomodel subclass in the available methods - - :param geomodel_subclass: the subclass to be registered - :type geomodel_subclass: object - """ - cls.available_geomodels[geomodel_type] = geomodel_subclass - return geomodel_subclass - - return decorator diff --git a/shareloc/draft_pybind/221_python_files/geomodel_template.py b/shareloc/draft_pybind/221_python_files/geomodel_template.py deleted file mode 100644 index 5ccf790..0000000 --- a/shareloc/draft_pybind/221_python_files/geomodel_template.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# coding: utf8 -# -# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). -# -# This file is part of shareloc -# (see https://github.com/CNES/shareloc). -# -# 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 coregistration class template. -It contains the structure for all coregistration methods in subclasses and -generic coregistration code to avoid duplication. -""" - -# Standard imports -from abc import ABCMeta, abstractmethod - -# Global variable for optimization mode (functions in C) -# SHARELOC_OPTIM_GEOMODEL = False - -# TODO: Override functions depending on optimization or not - -# if(SHARELOC_OPTIM_GEOMODEL == True): -# GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim - - -class GeoModelTemplate(metaclass=ABCMeta): - """ - Class for general specification of a geometric model - declined in rpc.py and grid.py - """ - - @abstractmethod - def __init__(self, geomodel_path: str): - """ - Return the geomodel object associated with the geomodel_type - given in the configuration - - :param geomodel_path: path of the geomodel file to instanciate. - :type geomodel_path: string - """ - # geomodel filename path - self.geomodel_path: str = geomodel_path - - # geomodel type. Set by the subclass - self.type: str - - # Define GeoModelTemplate functions interface - - @abstractmethod - def direct_loc_h(self, row, col, alt, fill_nan=False): - """ - direct localization at constant altitude - - :param row: line sensor position - :type row: float or 1D numpy.ndarray dtype=float64 - :param col: column sensor position - :type col: float or 1D numpy.ndarray dtype=float64 - :param alt: altitude - :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned - otherwise - :type fill_nan: boolean - :return: ground position (lon,lat,h) - :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates - """ - - @abstractmethod - def direct_loc_dtm(self, row, col, dtm): - """ - direct localization on dtm - - :param row: line sensor position - :type row: float - :param col: column sensor position - :type col: float - :param dtm: dtm intersection model - :type dtm: shareloc.geofunctions.dtm_intersection - :return: ground position (lon,lat,h) in dtm coordinates system - :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates - """ - - @abstractmethod - def inverse_loc(self, lon, lat, alt): - """ - Inverse localization - - :param lon: longitude position - :type lon: float or 1D numpy.ndarray dtype=float64 - :param lat: latitude position - :type lat: float or 1D numpy.ndarray dtype=float64 - :param alt: altitude - :type alt: float - :return: sensor position (row, col, alt) - :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) - """ diff --git a/shareloc/draft_pybind/221_python_files/helpers.py b/shareloc/draft_pybind/221_python_files/helpers.py deleted file mode 100644 index 60d01d4..0000000 --- a/shareloc/draft_pybind/221_python_files/helpers.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# coding: utf8 -# -# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). -# -# This file is part of Shareloc -# (see https://github.com/CNES/shareloc). -# -# 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. -# -""" -Helpers shared testing generic module: -contains global shared generic functions for tests/*.py -""" - -# Standard imports -import os - - -def data_path(alti="", scene_id=""): - """ - return the data path, when used without any argument data_path() returns data directory - :param alti: first sub dir corresponding to datum ("ellipsoide" or "geoid") - :type alti: str - :param scene_id: second sub dir corresponding to the scene id - :type scene_id: str - :return: data path. - :rtype: str - """ - data_root_folder = os.path.join(os.path.dirname(__file__), "data") - sub_folder = os.path.join(alti, scene_id) - return os.path.join(data_root_folder, sub_folder) diff --git a/shareloc/draft_pybind/221_python_files/rpc.py b/shareloc/draft_pybind/221_python_files/rpc.py deleted file mode 100644 index 65f8ce1..0000000 --- a/shareloc/draft_pybind/221_python_files/rpc.py +++ /dev/null @@ -1,1227 +0,0 @@ -#!/usr/bin/env python -# coding: utf8 -# -# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). -# Copyright (c) 2023 CS GROUP - France, https://csgroup.eu -# -# This file is part of Shareloc -# (see https://github.com/CNES/shareloc). -# -# 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 RPC class corresponding to the RPC models. -RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. -""" - -# Standard imports -import logging -from os.path import basename -from typing import Dict -from xml.dom import minidom - -# Third party imports -import numpy as np -import rasterio as rio -from numba import config, njit, prange - -import shareloc.draft_pybind.pbrpc as pbrpc - -# Shareloc imports -from shareloc.draft_pybind.geomodel import GeoModel -from shareloc.draft_pybind.geomodel_template import GeoModelTemplate -from shareloc.proj_utils import coordinates_conversion - -# Set numba type of threading layer before parallel target compilation -config.THREADING_LAYER = "omp" - - -def parse_coeff_line(coeff_str): - """ - split str coef to float list - - :param coeff_str: line coef - :type coeff_str: str - :return: coeff list - :rtype: list() - """ - return [float(el) for el in coeff_str.split()] - - -def identify_dimap(xml_file): - """ - parse xml file to identify dimap and its version - - :param xml_file: dimap rpc file - :type xml_file: str - :return: dimap info : dimap_version and None if not an dimap file - :rtype: str - """ - try: - xmldoc = minidom.parse(xml_file) - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data - if mtd_format == "DIMAP_PHR": - version_tag = "METADATA_PROFILE" - else: - version_tag = "METADATA_FORMAT" - version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] - except Exception: # pylint: disable=broad-except - return None - - return version - - -def identify_ossim_kwl(ossim_kwl_file): - """ - parse geom file to identify if it is an ossim model - - :param ossim_kwl_fil : ossim keyword list file - :type ossim_kwl_file: str - :return: ossimmodel or None if not an ossim kwl file - :rtype: str - """ - try: - with open(ossim_kwl_file, encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - if "type" in geom_dict: - if geom_dict["type"].strip().startswith("ossim"): - return geom_dict["type"].strip() - return None - except Exception: # pylint: disable=broad-except - return None - - -def identify_geotiff_rpc(image_filename): - """ - read image file to identify if it is a geotiff which contains RPCs - - :param image_filename: image_filename - :type image_filename: str - :return: rpc info, rpc dict or None if not a geotiff with rpc - :rtype: str - """ - try: - dataset = rio.open(image_filename) - rpc_dict = dataset.tags(ns="RPC") - return rpc_dict - except Exception: # pylint: disable=broad-except - return None - - -# pylint: disable=no-member - - -@GeoModel.register("RPC") -class RPC(GeoModelTemplate): - """ - RPC class including direct and inverse localization instance methods - """ - - # gitlab issue #61 - # pylint: disable=too-many-instance-attributes - def __init__(self, geomodel_path: str): - # Instanciate GeoModelTemplate generic init with shared parameters - super().__init__(geomodel_path) - self.type = "RPC" - - self.RPC_cpp = pbrpc.RPC() - - # initiate epsg and datum, can overriden by rpc_params - self.epsg = None - self.datum = None - - # RPC parameters as Dict - self.rpc_params: Dict = {} - - # RPC parameters are load from geomodel_path to rpc params - self.load() - - # set class parameters from rpc_params (epsg and datum can be overriden) - for key, value in self.rpc_params.items(): - setattr(self, key, value) - - if self.epsg is None: - self.epsg = 4326 - if self.datum is None: - self.datum = "ellipsoid" - - self.lim_extrapol = 1.0001 - - # Monomes seems not used in shareloc code: Clean ? - # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] - self.monomes = np.array( - [ - [1, 0, 0, 0], - [1, 1, 0, 0], - [1, 0, 1, 0], - [1, 0, 0, 1], - [1, 1, 1, 0], - [1, 1, 0, 1], - [1, 0, 1, 1], - [1, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [1, 1, 1, 1], - [1, 3, 0, 0], - [1, 1, 2, 0], - [1, 1, 0, 2], - [1, 2, 1, 0], - [1, 0, 3, 0], - [1, 0, 1, 2], - [1, 2, 0, 1], - [1, 0, 2, 1], - [1, 0, 0, 3], - ] - ) - - # monomial coefficients of 1st variable derivative - self.monomes_deriv_1 = np.array( - [ - [0, 0, 0, 0], - [1, 0, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - [1, 0, 1, 0], - [1, 0, 0, 1], - [0, 0, 1, 1], - [2, 1, 0, 0], - [0, 0, 2, 0], - [0, 0, 0, 2], - [1, 0, 1, 1], - [3, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [2, 1, 1, 0], - [0, 0, 3, 0], - [0, 0, 1, 2], - [2, 1, 0, 1], - [0, 0, 2, 1], - [0, 0, 0, 3], - ] - ) - - # monomial coefficients of 2nd variable derivative - self.monomes_deriv_2 = np.array( - [ - [0, 0, 0, 0], - [0, 1, 0, 0], - [1, 0, 0, 0], - [0, 0, 0, 1], - [1, 1, 0, 0], - [0, 1, 0, 1], - [1, 0, 0, 1], - [0, 2, 0, 0], - [2, 0, 1, 0], - [0, 0, 0, 2], - [1, 1, 0, 1], - [0, 3, 0, 0], - [2, 1, 1, 0], - [0, 1, 0, 2], - [1, 2, 0, 0], - [3, 0, 2, 0], - [1, 0, 0, 2], - [0, 2, 0, 1], - [2, 0, 1, 1], - [0, 0, 0, 3], - ] - ) - - self.inverse_coefficient = False - self.direct_coefficient = False - - # pylint: disable=access-member-before-definition - if self.num_col: - self.inverse_coefficient = True - self.num_col = np.array(self.num_col) - self.den_col = np.array(self.den_col) - self.num_row = np.array(self.num_row) - self.den_row = np.array(self.den_row) - - # pylint: disable=access-member-before-definition - if self.num_x: - self.direct_coefficient = True - self.num_x = np.array(self.num_x) - self.den_x = np.array(self.den_x) - self.num_y = np.array(self.num_y) - self.den_y = np.array(self.den_y) - - self.alt_minmax = [self.offset_alt - self.scale_alt, self.offset_alt + self.scale_alt] - self.col0 = self.offset_col - self.scale_col - self.colmax = self.offset_col + self.scale_col - self.row0 = self.offset_row - self.scale_row - self.rowmax = self.offset_row + self.scale_row - - def load(self): - """ - Load from any RPC (auto identify driver) - from filename (dimap, ossim kwl, geotiff) - - TODO: topleftconvention always to True, set a standard and remove the option - - topleftconvention boolean: [0,0] position - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - # Set topleftconvention (keeping historic option): to clean - topleftconvention = True - - # If ends with XML --> DIMAP - if basename(self.geomodel_path.upper()).endswith("XML"): - dimap_version = identify_dimap(self.geomodel_path) - if dimap_version is not None: - if float(dimap_version) < 2.0: - self.load_dimap_v1(topleftconvention) - if float(dimap_version) >= 2.0: - self.load_dimap_coeff(topleftconvention) - else: - # If not DIMAP, identify ossim - ossim_model = identify_ossim_kwl(self.geomodel_path) - if ossim_model is not None: - self.load_ossim_kwl(topleftconvention) - else: - # Otherwise, check if RPC is in geotif - geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) - if geotiff_rpc_dict is not None: - self.load_geotiff(topleftconvention) - else: - # Raise error if no file recognized. - raise ValueError("can not read rpc file") - - def load_dimap_coeff(self, topleftconvention=True): - """ - Load from Dimap v2 and V3 - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] - self.rpc_params["driver_type"] = "dimap_v" + version - global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] - normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] - self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) - self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) - if float(version) >= 3: - direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] - inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 0.5 - self.rpc_params["offset_row"] -= 0.5 - - else: - direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] - inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - - self.rpc_params["num_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - - self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) - self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) - self.rpc_params["offset_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data - ) - self.rpc_params["scale_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data - ) - self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) - self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) - self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) - self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_dimap_v1(self, topleftconvention=True): - """ - Load from dimap v1 - - ** Deprecated, to clean ? ** - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] - self.rpc_params = {"driver_type": "dimap_v" + version} - - global_rfm = xmldoc.getElementsByTagName("Global_RFM") - rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") - coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] - coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] - coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] - coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] - - scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) - scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) - scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) - offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) - scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) - offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) - scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) - offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) - - self.rpc_params["offset_col"] = offset_col - self.rpc_params["scale_col"] = scale_col - self.rpc_params["offset_row"] = offset_row - self.rpc_params["scale_row"] = scale_row - self.rpc_params["offset_alt"] = offset_alt - self.rpc_params["scale_alt"] = scale_alt - self.rpc_params["offset_x"] = offset_lon - self.rpc_params["scale_x"] = scale_lon - self.rpc_params["offset_y"] = offset_lat - self.rpc_params["scale_y"] = scale_lat - self.rpc_params["num_x"] = coeff_lon[0:20] - self.rpc_params["den_x"] = coeff_lon[20::] - self.rpc_params["num_y"] = coeff_lat[0:20] - self.rpc_params["den_y"] = coeff_lat[20::] - self.rpc_params["num_col"] = coeff_col[0:20] - self.rpc_params["den_col"] = coeff_col[20::] - self.rpc_params["num_row"] = coeff_lig[0:20] - self.rpc_params["den_row"] = coeff_lig[20::] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_geotiff(self, topleftconvention=True): - """ - Load from a geotiff image file - - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - dataset = rio.open(self.geomodel_path) - rpc_dict = dataset.tags(ns="RPC") - if not rpc_dict: - logging.error("%s does not contains RPCs ", self.geomodel_path) - raise ValueError - self.rpc_params = { - "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), - "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), - "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), - "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), - "offset_col": float(rpc_dict["SAMP_OFF"]), - "scale_col": float(rpc_dict["SAMP_SCALE"]), - "offset_row": float(rpc_dict["LINE_OFF"]), - "scale_row": float(rpc_dict["LINE_SCALE"]), - "offset_alt": float(rpc_dict["HEIGHT_OFF"]), - "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), - "offset_x": float(rpc_dict["LONG_OFF"]), - "scale_x": float(rpc_dict["LONG_SCALE"]), - "offset_y": float(rpc_dict["LAT_OFF"]), - "scale_y": float(rpc_dict["LAT_SCALE"]), - "num_x": None, - "den_x": None, - "num_y": None, - "den_y": None, - } - # inverse coeff are not defined - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_ossim_kwl(self, topleftconvention=True): - """ - Load from a geom file - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - # OSSIM keyword list - self.rpc_params["driver_type"] = "ossim_kwl" - with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - - self.rpc_params["den_row"] = [np.nan] * 20 - self.rpc_params["num_row"] = [np.nan] * 20 - self.rpc_params["den_col"] = [np.nan] * 20 - self.rpc_params["num_col"] = [np.nan] * 20 - for index in range(0, 20): - axis = "line" - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_row"][index] = float(geom_dict[key]) - num_den = "num" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_row"][index] = float(geom_dict[key]) - axis = "samp" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_col"][index] = float(geom_dict[key]) - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_col"][index] = float(geom_dict[key]) - self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) - self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) - self.rpc_params["offset_row"] = float(geom_dict["line_off"]) - self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) - self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) - self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) - self.rpc_params["offset_x"] = float(geom_dict["long_off"]) - self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) - self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) - self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) - # inverse coeff are not defined - self.rpc_params["num_x"] = None - self.rpc_params["den_x"] = None - self.rpc_params["num_y"] = None - self.rpc_params["den_y"] = None - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def direct_loc_h(self, row, col, alt, fill_nan=False): - """ - direct localization at constant altitude - - :param row: line sensor position - :type row: float or 1D numpy.ndarray dtype=float64 - :param col: column sensor position - :type col: float or 1D numpy.ndarray dtype=float64 - :param alt: altitude - :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned - otherwise - :type fill_nan: boolean - :return: ground position (lon,lat,h) - :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates - """ - - zero = self.RPC_cpp.direct_loc_h_rpc() - - if not isinstance(col, (list, np.ndarray)): - col = np.array([col]) - row = np.array([row]) - - if not isinstance(alt, (list, np.ndarray)): - alt = np.array([alt]) - - if alt.shape[0] != col.shape[0]: - alt = np.full(col.shape[0], fill_value=alt[0]) - - points = np.zeros((col.size, 3)) - filter_nan, points[:, 0], points[:, 1] = self.filter_coordinates(row, col, fill_nan) - row = row[filter_nan] - col = col[filter_nan] - - # Direct localization using direct RPC - if self.direct_coefficient: - # ground position - col_norm = (col - self.offset_col) / self.scale_col - row_norm = (row - self.offset_row) / self.scale_row - alt_norm = (alt - self.offset_alt) / self.scale_alt - - if np.sum(abs(col_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! column extrapolation in direct localization ") - if np.sum(abs(row_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! row extrapolation in direct localization ") - if np.sum(abs(alt_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! alt extrapolation in direct localization ") - - points[filter_nan, 1], points[filter_nan, 0] = compute_rational_function_polynomial( - col_norm, - row_norm, - alt_norm, - self.num_x, - self.den_x, - self.num_y, - self.den_y, - self.scale_x, - self.offset_x, - self.scale_y, - self.offset_y, - ) - - # Direct localization using inverse RPC - else: - logging.debug("direct localisation from inverse iterative") - (points[filter_nan, 0], points[filter_nan, 1], points[filter_nan, 2]) = self.direct_loc_inverse_iterative( - row, col, alt, 10, fill_nan - ) - points[:, 2] = alt - return points - - def direct_loc_grid_h(self, row0, col0, steprow, stepcol, nbrow, nbcol, alt): - """ - calculates a direct loc grid (lat, lon) from the direct RPCs at constant altitude - TODO: not tested. - - :param row0: grid origin (row) - :type row0: int - :param col0: grid origin (col) - :type col0: int - :param steprow: grid step (row) - :type steprow: int - :param stepcol: grid step (col) - :type stepcol: int - :param nbrow: grid nb row - :type nbrow: int - :param nbcol: grid nb col - :type nbcol: int - :param alt: altitude of the grid - :type alt: float - :return: direct localization grid longitude and latitude - :rtype: Tuple(numpy.array, numpy.array) - """ - gri_lon = np.zeros((nbrow, nbcol)) - gri_lat = np.zeros((nbrow, nbcol)) - for column in range(int(nbcol)): - col = col0 + stepcol * column - for line in range(int(nbrow)): - row = row0 + steprow * line - (gri_lon[line, column], gri_lat[line, column], __) = self.direct_loc_h(row, col, alt) - return (gri_lon, gri_lat) - - def direct_loc_dtm(self, row, col, dtm): - """ - direct localization on dtm - - :param row: line sensor position - :type row: float - :param col: column sensor position - :type col: float - :param dtm: dtm intersection model - :type dtm: shareloc.geofunctions.dtm_intersection - :return: ground position (lon,lat,h) in dtm coordinates system - :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates - """ - if isinstance(col, (list, np.ndarray)): - points_nb = len(col) - else: - points_nb = 1 - row = np.array([row]) - col = np.array([col]) - direct_dtm = np.zeros((points_nb, 3)) - - diff_alti_min, diff_alti_max = dtm.get_alt_offset(self.epsg) - # print("min {} max {}".format(dtm.Zmin,dtm.Zmax)) - (min_dtm, max_dtm) = (dtm.alt_min - 1.0 + diff_alti_min, dtm.alt_max + 1.0 + diff_alti_max) - if min_dtm < self.offset_alt - self.scale_alt: - logging.debug("minimum dtm value is outside RPC validity domain, extrapolation will be done") - if max_dtm > self.offset_alt + self.scale_alt: - logging.debug("maximum dtm value is outside RPC validity domain, extrapolation will be done") - los = self.los_extrema(row, col, min_dtm, max_dtm, epsg=dtm.epsg) - for i in range(points_nb): - los_i = los[2 * i : 2 * i + 2, :] - (__, __, position_cube, alti) = dtm.intersect_dtm_cube(los_i) - if position_cube is not None: - (__, __, position) = dtm.intersection(los_i, position_cube, alti) - direct_dtm[i, :] = position - else: - position = np.full(3, fill_value=np.nan) - direct_dtm[i, :] = position - return direct_dtm - - def inverse_loc(self, lon, lat, alt): - """ - Inverse localization - - :param lon: longitude position - :type lon: float or 1D numpy.ndarray dtype=float64 - :param lat: latitude position - :type lat: float or 1D numpy.ndarray dtype=float64 - :param alt: altitude - :type alt: float - :return: sensor position (row, col, alt) - :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) - """ - if self.inverse_coefficient: - if not isinstance(lon, (list, np.ndarray)): - lon = np.array([lon]) - lat = np.array([lat]) - if not isinstance(alt, (list, np.ndarray)): - alt = np.array([alt]) - - if alt.shape[0] != lon.shape[0]: - alt = np.full(lon.shape[0], fill_value=alt[0]) - - lon_norm = (lon - self.offset_x) / self.scale_x - lat_norm = (lat - self.offset_y) / self.scale_y - alt_norm = (alt - self.offset_alt) / self.scale_alt - - if np.sum(abs(lon_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! longitude extrapolation in inverse localization ") - if np.sum(abs(lat_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! row extrapolation in inverse localization ") - if np.sum(abs(alt_norm) > self.lim_extrapol) > 0: - logging.debug("!!!!! alt extrapolation in inverse localization ") - - row_out, col_out = compute_rational_function_polynomial( - lon_norm, - lat_norm, - alt_norm, - self.num_col, - self.den_col, - self.num_row, - self.den_row, - self.scale_col, - self.offset_col, - self.scale_row, - self.offset_row, - ) - else: - logging.warning("inverse localisation can't be performed, inverse coefficients have not been defined") - (col_out, row_out) = (None, None) - return row_out, col_out, alt - - def filter_coordinates(self, first_coord, second_coord, fill_nan=False, direction="direct"): - """ - Filter nan input values - - :param first_coord: first coordinate - :type first_coord: 1D numpy.ndarray dtype=float64 - :param second_coord: second coordinate - :type second_coord: 1D numpy.ndarray dtype=float64 - :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned - otherwise - :type fill_nan: boolean - :param direction: direct or inverse localisation - :type direction: str in ('direct', 'inverse') - :return: filtered coordinates - :rtype: list of numpy.array (index of nan, first filtered, second filtered) - """ - filter_nan = np.logical_not(np.logical_or(np.isnan(first_coord), np.isnan(second_coord))) - - if fill_nan: - if direction == "direct": - out_x_nan_value = self.offset_x - out_y_nan_value = self.offset_y - else: - out_x_nan_value = self.offset_col - out_y_nan_value = self.offset_row - else: - out_x_nan_value = np.nan - out_y_nan_value = np.nan - - x_out = np.full(len(second_coord), out_x_nan_value) - y_out = np.full(len(second_coord), out_y_nan_value) - - return filter_nan, x_out, y_out - - def compute_loc_inverse_derivates(self, lon, lat, alt): - """ - Inverse loc partial derivatives analytical compute - - :param lon: longitude coordinate - :param lat: latitude coordinate - :param alt: altitude coordinate - :return: partials derivatives of inverse localization - :rtype: Tuple(dcol_dlon np.array, dcol_dlat np.array, drow_dlon np.array, drow_dlat np.array) - """ - if not isinstance(alt, (list, np.ndarray)): - alt = np.array([alt]) - - if alt.shape[0] != lon.shape[0]: - alt = np.full(lon.shape[0], fill_value=alt[0]) - - lon_norm = (lon - self.offset_x) / self.scale_x - lat_norm = (lat - self.offset_y) / self.scale_y - alt_norm = (alt - self.offset_alt) / self.scale_alt - - dcol_dlon, dcol_dlat, drow_dlon, drow_dlat = compute_loc_inverse_derivates_numba( - lon_norm, - lat_norm, - alt_norm, - self.num_col, - self.den_col, - self.num_row, - self.den_row, - self.scale_col, - self.scale_x, - self.scale_row, - self.scale_y, - ) - - return (dcol_dlon, dcol_dlat, drow_dlon, drow_dlat) - - def direct_loc_inverse_iterative(self, row, col, alt, nb_iter_max=10, fill_nan=False): - """ - Iterative direct localization using inverse RPC - - :param row: line sensor position - :type row: float or 1D numpy.ndarray dtype=float64 - :param col: column sensor position - :type col: float or 1D numpy.ndarray dtype=float64 - :param alt: altitude - :type alt: float - :param nb_iter_max: max number of iteration - :type alt: int - :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned - otherwise - :type fill_nan: boolean - :return: ground position (lon,lat,h) - :rtype: list of numpy.array - """ - if self.inverse_coefficient: - if not isinstance(row, (list, np.ndarray)): - col = np.array([col]) - row = np.array([row]) - - if not isinstance(alt, (list, np.ndarray)): - alt = np.array([alt]) - - if alt.shape[0] != col.shape[0]: - alt = np.full(col.shape[0], fill_value=alt[0]) - - filter_nan, long_out, lat_out = self.filter_coordinates(row, col, fill_nan) - row = row[filter_nan] - col = col[filter_nan] - alt = alt[filter_nan] - - # if all coord contains Nan then return - if not np.any(filter_nan): - return long_out, lat_out, alt - - # inverse localization starting from the center of the scene - lon = np.array([self.offset_x]) - lat = np.array([self.offset_y]) - (row_start, col_start, __) = self.inverse_loc(lon, lat, alt) - - # desired precision in pixels - eps = 1e-6 - - iteration = 0 - # computing the residue between the sensor positions and those estimated by the inverse localization - delta_col = col - col_start - delta_row = row - row_start - - # ground coordinates (latitude and longitude) of each point - lon = np.repeat(lon, delta_col.size) - lat = np.repeat(lat, delta_col.size) - # while the required precision is not achieved - while (np.max(abs(delta_col)) > eps or np.max(abs(delta_row)) > eps) and iteration < nb_iter_max: - # list of points that require another iteration - iter_ = np.where((abs(delta_col) > eps) | (abs(delta_row) > eps))[0] - - # partial derivatives - (dcol_dlon, dcol_dlat, drow_dlon, drow_dlat) = self.compute_loc_inverse_derivates( - lon[iter_], lat[iter_], alt[iter_] - ) - det = dcol_dlon * drow_dlat - drow_dlon * dcol_dlat - - delta_lon = (drow_dlat * delta_col[iter_] - dcol_dlat * delta_row[iter_]) / det - delta_lat = (-drow_dlon * delta_col[iter_] + dcol_dlon * delta_row[iter_]) / det - - # update ground coordinates - lon[iter_] += delta_lon - lat[iter_] += delta_lat - - # inverse localization - (row_estim, col_estim, __) = self.inverse_loc(lon[iter_], lat[iter_], alt[iter_]) - - # updating the residue between the sensor positions and those estimated by the inverse localization - delta_col[iter_] = col[iter_] - col_estim - delta_row[iter_] = row[iter_] - row_estim - iteration += 1 - - long_out[filter_nan] = lon - lat_out[filter_nan] = lat - - else: - logging.warning("inverse localisation can't be performed, inverse coefficients have not been defined") - (long_out, lat_out) = (None, None) - - return long_out, lat_out, alt - - def get_alt_min_max(self): - """ - returns altitudes min and max layers - - :return: alt_min,lat_max - :rtype: list - """ - return [self.offset_alt - self.scale_alt / 2.0, self.offset_alt + self.scale_alt / 2.0] - - def los_extrema(self, row, col, alt_min=None, alt_max=None, fill_nan=False, epsg=None): - """ - compute los extrema - - :param row: line sensor position - :type row: float - :param col: column sensor position - :type col: float - :param alt_min: los alt min - :type alt_min: float - :param alt_max: los alt max - :type alt_max: float - :param epsg: epsg code of the dtm - :type epsg: int - :return: los extrema - :rtype: numpy.array (2x3) - """ - extrapolate = False - if alt_min is None or alt_max is None: - [los_alt_min, los_alt_max] = self.get_alt_min_max() - elif alt_min >= self.alt_minmax[0] and alt_max <= self.alt_minmax[1]: - los_alt_min = alt_min - los_alt_max = alt_max - else: - extrapolate = True - [los_alt_min, los_alt_max] = self.get_alt_min_max() - - # - if isinstance(row, (np.ndarray)): - los_nb = row.shape[0] - row_array = np.full([los_nb * 2], fill_value=0.0) - col_array = np.full([los_nb * 2], fill_value=0.0) - alt_array = np.full([los_nb * 2], fill_value=0.0) - row_array[0::2] = row - row_array[1::2] = row - col_array[0::2] = col - col_array[1::2] = col - alt_array[0::2] = los_alt_max - alt_array[1::2] = los_alt_min - else: - los_nb = 1 - row_array = np.array([row, row]) - col_array = np.array([col, col]) - alt_array = np.array([los_alt_max, los_alt_min]) - los_edges = self.direct_loc_h(row_array, col_array, alt_array, fill_nan) - if extrapolate: - diff = los_edges[0::2, :] - los_edges[1::2, :] - delta_alt = diff[:, 2] - coeff_alt_max = (alt_max - los_edges[1::2, 2]) / delta_alt - coeff_alt_max = np.tile(coeff_alt_max[:, np.newaxis], (1, 3)) - coeff_alt_min = (alt_min - los_edges[1::2, 2]) / delta_alt - coeff_alt_min = np.tile(coeff_alt_min[:, np.newaxis], (1, 3)) - los_edges[0::2, :] = los_edges[1::2, :] + diff * coeff_alt_max - los_edges[1::2, :] = los_edges[1::2, :] + diff * coeff_alt_min - if epsg is not None and epsg != self.epsg: - los_edges = coordinates_conversion(los_edges, self.epsg, epsg) - return los_edges - - -@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) -def polynomial_equation(xnorm, ynorm, znorm, coeff): - """ - Compute polynomial equation - - :param xnorm: Normalized longitude (for inverse) or column (for direct) position - :type xnorm: float 64 - :param ynorm: Normalized latitude (for inverse) or line (for direct) position - :type ynorm: float 64 - :param znorm: Normalized altitude position - :type znorm: float 64 - :param coeff: coefficients - :type coeff: 1D np.array dtype np.float 64 - :return: rational - :rtype: float 64 - """ - rational = ( - coeff[0] - + coeff[1] * xnorm - + coeff[2] * ynorm - + coeff[3] * znorm - + coeff[4] * xnorm * ynorm - + coeff[5] * xnorm * znorm - + coeff[6] * ynorm * znorm - + coeff[7] * xnorm**2 - + coeff[8] * ynorm**2 - + coeff[9] * znorm**2 - + coeff[10] * xnorm * ynorm * znorm - + coeff[11] * xnorm**3 - + coeff[12] * xnorm * ynorm**2 - + coeff[13] * xnorm * znorm**2 - + coeff[14] * xnorm**2 * ynorm - + coeff[15] * ynorm**3 - + coeff[16] * ynorm * znorm**2 - + coeff[17] * xnorm**2 * znorm - + coeff[18] * ynorm**2 * znorm - + coeff[19] * znorm**3 - ) - - return rational - - -# pylint: disable=too-many-arguments -@njit( - "Tuple((f8[:], f8[:]))(f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8, f8, f8, f8)", - parallel=True, - cache=True, - fastmath=True, -) -def compute_rational_function_polynomial( - lon_col_norm, - lat_row_norm, - alt_norm, - num_col, - den_col, - num_lin, - den_lin, - scale_col, - offset_col, - scale_lin, - offset_lin, -): - """ - Compute rational function polynomial using numba to reduce calculation time on multiple points. - useful to compute direct and inverse localization using direct or inverse RPC. - - :param lon_col_norm: Normalized longitude (for inverse) or column (for direct) position - :type lon_col_norm: 1D np.array dtype np.float 64 - :param lat_row_norm: Normalized latitude (for inverse) or line (for direct) position - :type lat_row_norm: 1D np.array dtype np.float 64 - :param alt_norm: Normalized altitude position - :type alt_norm: 1D np.array dtype np.float 64 - :param num_col: Column numerator coefficients - :type num_col: 1D np.array dtype np.float 64 - :param den_col: Column denominator coefficients - :type den_col: 1D np.array dtype np.float 64 - :param num_lin: Line numerator coefficients - :type num_lin: 1D np.array dtype np.float 64 - :param den_lin: Line denominator coefficients - :type den_lin: 1D np.array dtype np.float 64 - :param scale_col: Column scale - :type scale_col: float 64 - :param offset_col: Column offset - :type offset_col: float 64 - :param scale_lin: Line scale - :type scale_lin: float 64 - :param offset_lin: Line offset - :type offset_lin: float 64 - :return: for inverse localization : sensor position (row, col). for direct localization : ground position (lon, lat) - :rtype: Tuple(np.ndarray, np.ndarray) - """ - assert lon_col_norm.shape == alt_norm.shape - - col_lat_out = np.zeros((lon_col_norm.shape[0]), dtype=np.float64) - row_lon_out = np.zeros((lon_col_norm.shape[0]), dtype=np.float64) - - # pylint: disable=not-an-iterable - for i in prange(lon_col_norm.shape[0]): - poly_num_col = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], num_col) - poly_den_col = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], den_col) - poly_num_lin = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], num_lin) - poly_den_lin = polynomial_equation(lon_col_norm[i], lat_row_norm[i], alt_norm[i], den_lin) - col_lat_out[i] = poly_num_col / poly_den_col * scale_col + offset_col - row_lon_out[i] = poly_num_lin / poly_den_lin * scale_lin + offset_lin - - return row_lon_out, col_lat_out - - -@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) -def derivative_polynomial_latitude(lon_norm, lat_norm, alt_norm, coeff): - """ - Compute latitude derivative polynomial equation - - :param lon_norm: Normalized longitude position - :type lon_norm: float 64 - :param lat_norm: Normalized latitude position - :type lat_norm: float 64 - :param alt_norm: Normalized altitude position - :type alt_norm: float 64 - :param coeff: coefficients - :type coeff: 1D np.array dtype np.float 64 - :return: rational derivative - :rtype: float 64 - """ - derivate = ( - coeff[2] - + coeff[4] * lon_norm - + coeff[6] * alt_norm - + 2 * coeff[8] * lat_norm - + coeff[10] * lon_norm * alt_norm - + 2 * coeff[12] * lon_norm * lat_norm - + coeff[14] * lon_norm**2 - + 3 * coeff[15] * lat_norm**2 - + coeff[16] * alt_norm**2 - + 2 * coeff[18] * lat_norm * alt_norm - ) - - return derivate - - -@njit("f8(f8, f8, f8, f8[:])", cache=True, fastmath=True) -def derivative_polynomial_longitude(lon_norm, lat_norm, alt_norm, coeff): - """ - Compute longitude derivative polynomial equation - - :param lon_norm: Normalized longitude position - :type lon_norm: float 64 - :param lat_norm: Normalized latitude position - :type lat_norm: float 64 - :param alt_norm: Normalized altitude position - :type alt_norm: float 64 - :param coeff: coefficients - :type coeff: 1D np.array dtype np.float 64 - :return: rational derivative - :rtype: float 64 - """ - derivate = ( - coeff[1] - + coeff[4] * lat_norm - + coeff[5] * alt_norm - + 2 * coeff[7] * lon_norm - + coeff[10] * lat_norm * alt_norm - + 3 * coeff[11] * lon_norm**2 - + coeff[12] * lat_norm**2 - + coeff[13] * alt_norm**2 - + 2 * coeff[14] * lat_norm * lon_norm - + 2 * coeff[17] * lon_norm * alt_norm - ) - - return derivate - - -# pylint: disable=too-many-arguments -@njit( - "Tuple((f8[:], f8[:], f8[:], f8[:]))(f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8, f8, f8, f8)", - parallel=True, - cache=True, - fastmath=True, -) -def compute_loc_inverse_derivates_numba( - lon_norm, lat_norm, alt_norm, num_col, den_col, num_lin, den_lin, scale_col, scale_lon, scale_lin, scale_lat -): - """ - Analytically compute the partials derivatives of inverse localization using numba to reduce calculation time on - multiple points - - :param lon_norm: Normalized longitude position - :type lon_norm: 1D np.array dtype np.float 64 - :param lat_norm: Normalized latitude position - :type lat_norm: 1D np.array dtype np.float 64 - :param alt_norm: Normalized altitude position - :type alt_norm: 1D np.array dtype np.float 64 - :param num_col: Column numerator coefficients - :type num_col: 1D np.array dtype np.float 64 - :param den_col: Column denominator coefficients - :type den_col: 1D np.array dtype np.float 64 - :param num_lin: Line numerator coefficients - :type num_lin: 1D np.array dtype np.float 64 - :param den_lin: Line denominator coefficients - :type den_lin: 1D np.array dtype np.float 64 - :param scale_col: Column scale - :type scale_col: float 64 - :param scale_lon: Geodetic longitude scale - :type scale_lon: float 64 - :param scale_lin: Line scale - :type scale_lin: float 64 - :param scale_lat: Geodetic latitude scale - :type scale_lat: float 64 - :return: partials derivatives of inverse localization - :rtype: Tuples(dcol_dlon np.array, dcol_dlat np.array, drow_dlon np.array, drow_dlat np.array) - """ - dcol_dlon = np.zeros((lon_norm.shape[0]), dtype=np.float64) - dcol_dlat = np.zeros((lon_norm.shape[0]), dtype=np.float64) - drow_dlon = np.zeros((lon_norm.shape[0]), dtype=np.float64) - drow_dlat = np.zeros((lon_norm.shape[0]), dtype=np.float64) - - # pylint: disable=not-an-iterable - for i in prange(lon_norm.shape[0]): - num_dcol = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], num_col) - den_dcol = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], den_col) - num_drow = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) - den_drow = polynomial_equation(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) - - num_dcol_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], num_col) - den_dcol_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], den_col) - num_drow_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) - den_drow_dlon = derivative_polynomial_longitude(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) - - num_dcol_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], num_col) - den_dcol_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], den_col) - num_drow_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], num_lin) - den_drow_dlat = derivative_polynomial_latitude(lon_norm[i], lat_norm[i], alt_norm[i], den_lin) - - dcol_dlon[i] = scale_col / scale_lon * (num_dcol_dlon * den_dcol - den_dcol_dlon * num_dcol) / den_dcol**2 - dcol_dlat[i] = scale_col / scale_lat * (num_dcol_dlat * den_dcol - den_dcol_dlat * num_dcol) / den_dcol**2 - drow_dlon[i] = scale_lin / scale_lon * (num_drow_dlon * den_drow - den_drow_dlon * num_drow) / den_drow**2 - drow_dlat[i] = scale_lin / scale_lat * (num_drow_dlat * den_drow - den_drow_dlat * num_drow) / den_drow**2 - - return dcol_dlon, dcol_dlat, drow_dlon, drow_dlat diff --git a/shareloc/draft_pybind/221_python_files/test_rpc.py b/shareloc/draft_pybind/221_python_files/test_rpc.py deleted file mode 100644 index 23145e2..0000000 --- a/shareloc/draft_pybind/221_python_files/test_rpc.py +++ /dev/null @@ -1,28 +0,0 @@ -# Standard imports -import os - -# Third party imports -import numpy as np -import pytest -import rasterio - -# Shareloc imports -from shareloc.draft_pybind.rpc import RPC - - -@pytest.mark.parametrize("col,row,alt", [(600, 200, 125)]) -def test_rpc_phrdimap(col, row, alt): - """ - test the sequence of a inverse localization followed by a direct localization using dimap file - """ - - file_dimap = "/home/adevaux/Bureau/sharloc_231/shareloc/tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML" - - fctrat = RPC(file_dimap) - - (lonlatalt) = fctrat.direct_loc_h(row, col, alt) - - (row_ar, col_ar, __) = fctrat.inverse_loc(lonlatalt[0][0], lonlatalt[0][1], lonlatalt[0][2]) - assert col_ar == pytest.approx(col, abs=2e-2) - assert row_ar == pytest.approx(row, abs=2e-2) - assert False diff --git a/shareloc/draft_pybind/CMakeLists.txt b/shareloc/draft_pybind/CMakeLists.txt deleted file mode 100644 index 53b7a05..0000000 --- a/shareloc/draft_pybind/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.22) -project(pbrpc) - -add_library(GeoModelTemplate SHARED GeoModelTemplate.cpp) -target_link_libraries(pbrpc GeoModelTemplate) -#add_executable(main main.cpp Student.cpp) - -find_package(pybind11 REQUIRED) -pybind11_add_module(pbrpc bind_helloworld.cpp) diff --git a/shareloc/draft_pybind/example.cpp b/shareloc/draft_pybind/example.cpp deleted file mode 100644 index 75df225..0000000 --- a/shareloc/draft_pybind/example.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -namespace py = pybind11; - -int add(int i, int j) { - return i + j; -} - -PYBIND11_MODULE(example, m) { - m.doc() = "pybind11 example plugin"; // optional module docstring - - m.def("add", &add, "A function which adds two numbers", - py::arg("i"), py::arg("j")); -} - - - -// c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/draft_pybind/example_pybind.py b/shareloc/draft_pybind/example_pybind.py deleted file mode 100644 index 769d813..0000000 --- a/shareloc/draft_pybind/example_pybind.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Module sys to set python path to root""" -import sys - -sys.path.append(".") - -# pylint: disable=wrong-import-position -import libs.pbrpc as pbrpc # noqa: E402 - -# pylint: enable=wrong-import-position - - -row = [1.0, 1.0, 1.0] -col = [1.0, 1.0, 1.0] -alt = 1.0 - -GM_object = pbrpc.GeoModelTemplate() -lonlatalt1 = GM_object.direct_loc_h(row, col, alt, False) -lonlatalt1 = GM_object.inverse_loc(row, col, alt) -del GM_object -# lonlatalt2 = GM_object.direct_loc_dtm(row,col,"test") -# lonlatalt3 = GM_object.inverse_loc(row,col,alt,) -# print(lonlatalt1) -# print(lonlatalt2) -# print(lonlatalt3) - - -# class RPCOptim(pbrpc.RPC): -# def hello_python(self): -# return "hello python" - -# rpc_optim = RPCOptim() -# arg = [[1.,1.,1.],[1.,1.,1.]] -# zero = rpc_optim.direct_loc_h(arg) - -# print(type(zero)) -# print(zero) diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 5ccf790..e0b9e9c 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -36,7 +36,118 @@ # GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim -class GeoModelTemplate(metaclass=ABCMeta): +# Standard imports +import logging +from os.path import basename +from typing import Dict +from xml.dom import minidom + +# Third party imports +import numpy as np +import rasterio as rio +from numba import config, njit, prange + +from shareloc.proj_utils import coordinates_conversion + + + + + + + + + +def parse_coeff_line(coeff_str): + """ + split str coef to float list + + :param coeff_str: line coef + :type coeff_str: str + :return: coeff list + :rtype: list() + """ + return [float(el) for el in coeff_str.split()] + + +def identify_dimap(xml_file): + """ + parse xml file to identify dimap and its version + + :param xml_file: dimap rpc file + :type xml_file: str + :return: dimap info : dimap_version and None if not an dimap file + :rtype: str + """ + try: + xmldoc = minidom.parse(xml_file) + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data + if mtd_format == "DIMAP_PHR": + version_tag = "METADATA_PROFILE" + else: + version_tag = "METADATA_FORMAT" + version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] + except Exception: # pylint: disable=broad-except + return None + + return version + + +def identify_ossim_kwl(ossim_kwl_file): + """ + parse geom file to identify if it is an ossim model + + :param ossim_kwl_fil : ossim keyword list file + :type ossim_kwl_file: str + :return: ossimmodel or None if not an ossim kwl file + :rtype: str + """ + try: + with open(ossim_kwl_file, encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + if "type" in geom_dict: + if geom_dict["type"].strip().startswith("ossim"): + return geom_dict["type"].strip() + return None + except Exception: # pylint: disable=broad-except + return None + + +def identify_geotiff_rpc(image_filename): + """ + read image file to identify if it is a geotiff which contains RPCs + + :param image_filename: image_filename + :type image_filename: str + :return: rpc info, rpc dict or None if not a geotiff with rpc + :rtype: str + """ + try: + dataset = rio.open(image_filename) + rpc_dict = dataset.tags(ns="RPC") + return rpc_dict + except Exception: # pylint: disable=broad-except + return None + + +# pylint: disable=no-member + + + + + + + + + + + + +class GeoModelTemplate(object):#metaclass=ABCMeta): """ Class for general specification of a geometric model declined in rpc.py and grid.py @@ -44,6 +155,7 @@ class GeoModelTemplate(metaclass=ABCMeta): @abstractmethod def __init__(self, geomodel_path: str): + """ Return the geomodel object associated with the geomodel_type given in the configuration @@ -57,6 +169,11 @@ def __init__(self, geomodel_path: str): # geomodel type. Set by the subclass self.type: str + # geomodel filename path + self.rpc_params: Dict = {} + + self.load() + # Define GeoModelTemplate functions interface @abstractmethod @@ -105,3 +222,316 @@ def inverse_loc(self, lon, lat, alt): :return: sensor position (row, col, alt) :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) """ + + + + + + + + + + + + + + def load(self): + """ + Load from any RPC (auto identify driver) + from filename (dimap, ossim kwl, geotiff) + + TODO: topleftconvention always to True, set a standard and remove the option + + topleftconvention boolean: [0,0] position + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # Set topleftconvention (keeping historic option): to clean + topleftconvention = True + + # If ends with XML --> DIMAP + if basename(self.geomodel_path.upper()).endswith("XML"): + dimap_version = identify_dimap(self.geomodel_path) + if dimap_version is not None: + if float(dimap_version) < 2.0: + self.load_dimap_v1(topleftconvention) + if float(dimap_version) >= 2.0: + self.load_dimap_coeff(topleftconvention) + else: + # If not DIMAP, identify ossim + ossim_model = identify_ossim_kwl(self.geomodel_path) + if ossim_model is not None: + self.load_ossim_kwl(topleftconvention) + else: + # Otherwise, check if RPC is in geotif + geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) + if geotiff_rpc_dict is not None: + self.load_geotiff(topleftconvention) + else: + # Raise error if no file recognized. + raise ValueError("can not read rpc file") + + def load_dimap_coeff(self, topleftconvention=True): + """ + Load from Dimap v2 and V3 + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + + if not basename(self.geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(self.geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] + self.rpc_params["driver_type"] = "dimap_v" + version + global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] + normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] + self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) + self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) + if float(version) >= 3: + direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] + inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] + + self.rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["offset_col"] -= 0.5 + self.rpc_params["offset_row"] -= 0.5 + + else: + direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] + inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] + + self.rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 + + self.rpc_params["num_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["num_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + self.rpc_params["den_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + + self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) + self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) + self.rpc_params["offset_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data + ) + self.rpc_params["scale_alt"] = float( + normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data + ) + self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) + self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) + self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) + self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_dimap_v1(self, topleftconvention=True): + """ + Load from dimap v1 + + ** Deprecated, to clean ? ** + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + + if not basename(self.geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(self.geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] + self.rpc_params = {"driver_type": "dimap_v" + version} + + global_rfm = xmldoc.getElementsByTagName("Global_RFM") + rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") + coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] + coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] + coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] + coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] + + scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) + scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) + scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) + offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) + scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) + offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) + scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) + offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) + + self.rpc_params["offset_col"] = offset_col + self.rpc_params["scale_col"] = scale_col + self.rpc_params["offset_row"] = offset_row + self.rpc_params["scale_row"] = scale_row + self.rpc_params["offset_alt"] = offset_alt + self.rpc_params["scale_alt"] = scale_alt + self.rpc_params["offset_x"] = offset_lon + self.rpc_params["scale_x"] = scale_lon + self.rpc_params["offset_y"] = offset_lat + self.rpc_params["scale_y"] = scale_lat + self.rpc_params["num_x"] = coeff_lon[0:20] + self.rpc_params["den_x"] = coeff_lon[20::] + self.rpc_params["num_y"] = coeff_lat[0:20] + self.rpc_params["den_y"] = coeff_lat[20::] + self.rpc_params["num_col"] = coeff_col[0:20] + self.rpc_params["den_col"] = coeff_col[20::] + self.rpc_params["num_row"] = coeff_lig[0:20] + self.rpc_params["den_row"] = coeff_lig[20::] + self.rpc_params["offset_col"] -= 1.0 + self.rpc_params["offset_row"] -= 1.0 + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_geotiff(self, topleftconvention=True): + """ + Load from a geotiff image file + + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + dataset = rio.open(self.geomodel_path) + rpc_dict = dataset.tags(ns="RPC") + if not rpc_dict: + logging.error("%s does not contains RPCs ", self.geomodel_path) + raise ValueError + self.rpc_params = { + "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), + "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), + "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), + "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), + "offset_col": float(rpc_dict["SAMP_OFF"]), + "scale_col": float(rpc_dict["SAMP_SCALE"]), + "offset_row": float(rpc_dict["LINE_OFF"]), + "scale_row": float(rpc_dict["LINE_SCALE"]), + "offset_alt": float(rpc_dict["HEIGHT_OFF"]), + "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), + "offset_x": float(rpc_dict["LONG_OFF"]), + "scale_x": float(rpc_dict["LONG_SCALE"]), + "offset_y": float(rpc_dict["LAT_OFF"]), + "scale_y": float(rpc_dict["LAT_SCALE"]), + "num_x": None, + "den_x": None, + "num_y": None, + "den_y": None, + } + # inverse coeff are not defined + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + + def load_ossim_kwl(self, topleftconvention=True): + """ + Load from a geom file + + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # OSSIM keyword list + self.rpc_params["driver_type"] = "ossim_kwl" + with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + + self.rpc_params["den_row"] = [np.nan] * 20 + self.rpc_params["num_row"] = [np.nan] * 20 + self.rpc_params["den_col"] = [np.nan] * 20 + self.rpc_params["num_col"] = [np.nan] * 20 + for index in range(0, 20): + axis = "line" + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["den_row"][index] = float(geom_dict[key]) + num_den = "num" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["num_row"][index] = float(geom_dict[key]) + axis = "samp" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["num_col"][index] = float(geom_dict[key]) + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + self.rpc_params["den_col"][index] = float(geom_dict[key]) + self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) + self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) + self.rpc_params["offset_row"] = float(geom_dict["line_off"]) + self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) + self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) + self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) + self.rpc_params["offset_x"] = float(geom_dict["long_off"]) + self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) + self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) + self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) + # inverse coeff are not defined + self.rpc_params["num_x"] = None + self.rpc_params["den_x"] = None + self.rpc_params["num_y"] = None + self.rpc_params["den_y"] = None + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + self.rpc_params["offset_col"] += 0.5 + self.rpc_params["offset_row"] += 0.5 + diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 2d99e22..0c84377 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -44,84 +44,6 @@ config.THREADING_LAYER = "omp" -def parse_coeff_line(coeff_str): - """ - split str coef to float list - - :param coeff_str: line coef - :type coeff_str: str - :return: coeff list - :rtype: list() - """ - return [float(el) for el in coeff_str.split()] - - -def identify_dimap(xml_file): - """ - parse xml file to identify dimap and its version - - :param xml_file: dimap rpc file - :type xml_file: str - :return: dimap info : dimap_version and None if not an dimap file - :rtype: str - """ - try: - xmldoc = minidom.parse(xml_file) - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data - if mtd_format == "DIMAP_PHR": - version_tag = "METADATA_PROFILE" - else: - version_tag = "METADATA_FORMAT" - version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] - except Exception: # pylint: disable=broad-except - return None - - return version - - -def identify_ossim_kwl(ossim_kwl_file): - """ - parse geom file to identify if it is an ossim model - - :param ossim_kwl_fil : ossim keyword list file - :type ossim_kwl_file: str - :return: ossimmodel or None if not an ossim kwl file - :rtype: str - """ - try: - with open(ossim_kwl_file, encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - if "type" in geom_dict: - if geom_dict["type"].strip().startswith("ossim"): - return geom_dict["type"].strip() - return None - except Exception: # pylint: disable=broad-except - return None - - -def identify_geotiff_rpc(image_filename): - """ - read image file to identify if it is a geotiff which contains RPCs - - :param image_filename: image_filename - :type image_filename: str - :return: rpc info, rpc dict or None if not a geotiff with rpc - :rtype: str - """ - try: - dataset = rio.open(image_filename) - rpc_dict = dataset.tags(ns="RPC") - return rpc_dict - except Exception: # pylint: disable=broad-except - return None - - -# pylint: disable=no-member @GeoModel.register("RPC") @@ -142,7 +64,7 @@ def __init__(self, geomodel_path: str): self.datum = None # RPC parameters as Dict - self.rpc_params: Dict = {} + #self.rpc_params: Dict = {} # RPC parameters are load from geomodel_path to rpc params self.load() @@ -262,305 +184,7 @@ def __init__(self, geomodel_path: str): self.row0 = self.offset_row - self.scale_row self.rowmax = self.offset_row + self.scale_row - def load(self): - """ - Load from any RPC (auto identify driver) - from filename (dimap, ossim kwl, geotiff) - - TODO: topleftconvention always to True, set a standard and remove the option - - topleftconvention boolean: [0,0] position - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - # Set topleftconvention (keeping historic option): to clean - topleftconvention = True - - # If ends with XML --> DIMAP - if basename(self.geomodel_path.upper()).endswith("XML"): - dimap_version = identify_dimap(self.geomodel_path) - if dimap_version is not None: - if float(dimap_version) < 2.0: - self.load_dimap_v1(topleftconvention) - if float(dimap_version) >= 2.0: - self.load_dimap_coeff(topleftconvention) - else: - # If not DIMAP, identify ossim - ossim_model = identify_ossim_kwl(self.geomodel_path) - if ossim_model is not None: - self.load_ossim_kwl(topleftconvention) - else: - # Otherwise, check if RPC is in geotif - geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) - if geotiff_rpc_dict is not None: - self.load_geotiff(topleftconvention) - else: - # Raise error if no file recognized. - raise ValueError("can not read rpc file") - - def load_dimap_coeff(self, topleftconvention=True): - """ - Load from Dimap v2 and V3 - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] - self.rpc_params["driver_type"] = "dimap_v" + version - global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] - normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] - self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) - self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) - if float(version) >= 3: - direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] - inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 0.5 - self.rpc_params["offset_row"] -= 0.5 - - else: - direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] - inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - - self.rpc_params["num_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - - self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) - self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) - self.rpc_params["offset_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data - ) - self.rpc_params["scale_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data - ) - self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) - self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) - self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) - self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_dimap_v1(self, topleftconvention=True): - """ - Load from dimap v1 - - ** Deprecated, to clean ? ** - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] - self.rpc_params = {"driver_type": "dimap_v" + version} - - global_rfm = xmldoc.getElementsByTagName("Global_RFM") - rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") - coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] - coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] - coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] - coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] - - scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) - scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) - scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) - offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) - scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) - offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) - scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) - offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) - - self.rpc_params["offset_col"] = offset_col - self.rpc_params["scale_col"] = scale_col - self.rpc_params["offset_row"] = offset_row - self.rpc_params["scale_row"] = scale_row - self.rpc_params["offset_alt"] = offset_alt - self.rpc_params["scale_alt"] = scale_alt - self.rpc_params["offset_x"] = offset_lon - self.rpc_params["scale_x"] = scale_lon - self.rpc_params["offset_y"] = offset_lat - self.rpc_params["scale_y"] = scale_lat - self.rpc_params["num_x"] = coeff_lon[0:20] - self.rpc_params["den_x"] = coeff_lon[20::] - self.rpc_params["num_y"] = coeff_lat[0:20] - self.rpc_params["den_y"] = coeff_lat[20::] - self.rpc_params["num_col"] = coeff_col[0:20] - self.rpc_params["den_col"] = coeff_col[20::] - self.rpc_params["num_row"] = coeff_lig[0:20] - self.rpc_params["den_row"] = coeff_lig[20::] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_geotiff(self, topleftconvention=True): - """ - Load from a geotiff image file - - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - dataset = rio.open(self.geomodel_path) - rpc_dict = dataset.tags(ns="RPC") - if not rpc_dict: - logging.error("%s does not contains RPCs ", self.geomodel_path) - raise ValueError - self.rpc_params = { - "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), - "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), - "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), - "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), - "offset_col": float(rpc_dict["SAMP_OFF"]), - "scale_col": float(rpc_dict["SAMP_SCALE"]), - "offset_row": float(rpc_dict["LINE_OFF"]), - "scale_row": float(rpc_dict["LINE_SCALE"]), - "offset_alt": float(rpc_dict["HEIGHT_OFF"]), - "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), - "offset_x": float(rpc_dict["LONG_OFF"]), - "scale_x": float(rpc_dict["LONG_SCALE"]), - "offset_y": float(rpc_dict["LAT_OFF"]), - "scale_y": float(rpc_dict["LAT_SCALE"]), - "num_x": None, - "den_x": None, - "num_y": None, - "den_y": None, - } - # inverse coeff are not defined - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_ossim_kwl(self, topleftconvention=True): - """ - Load from a geom file - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - # OSSIM keyword list - self.rpc_params["driver_type"] = "ossim_kwl" - with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - - self.rpc_params["den_row"] = [np.nan] * 20 - self.rpc_params["num_row"] = [np.nan] * 20 - self.rpc_params["den_col"] = [np.nan] * 20 - self.rpc_params["num_col"] = [np.nan] * 20 - for index in range(0, 20): - axis = "line" - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_row"][index] = float(geom_dict[key]) - num_den = "num" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_row"][index] = float(geom_dict[key]) - axis = "samp" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_col"][index] = float(geom_dict[key]) - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_col"][index] = float(geom_dict[key]) - self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) - self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) - self.rpc_params["offset_row"] = float(geom_dict["line_off"]) - self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) - self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) - self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) - self.rpc_params["offset_x"] = float(geom_dict["long_off"]) - self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) - self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) - self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) - # inverse coeff are not defined - self.rpc_params["num_x"] = None - self.rpc_params["den_x"] = None - self.rpc_params["num_y"] = None - self.rpc_params["den_y"] = None - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 + def direct_loc_h(self, row, col, alt, fill_nan=False): """ @@ -1218,3 +842,4 @@ def compute_loc_inverse_derivates_numba( drow_dlat[i] = scale_lin / scale_lat * (num_drow_dlat * den_drow - den_drow_dlat * num_drow) / den_drow**2 return dcol_dlon, dcol_dlat, drow_dlon, drow_dlat + diff --git a/shareloc/geomodels/rpc_optim.py b/shareloc/geomodels/rpc_optim.py new file mode 100755 index 0000000..8be1ce1 --- /dev/null +++ b/shareloc/geomodels/rpc_optim.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# Copyright (c) 2023 CS GROUP - France, https://csgroup.eu +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 RPC class corresponding to the RPC models. +RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. +""" + +# Standard imports +import logging +from os.path import basename +from typing import Dict +from xml.dom import minidom + +# Third party imports +import numpy as np +import rasterio as rio +from numba import config, njit, prange + +# Shareloc imports +from shareloc.geomodels.geomodel import GeoModel +from shareloc.geomodels.geomodel_template import GeoModelTemplate +from shareloc.proj_utils import coordinates_conversion + + +import sys +sys.path.append(".") +import libs.pbrpc as bind + + +# Set numba type of threading layer before parallel target compilation +config.THREADING_LAYER = "omp" + + + +@GeoModel.register("RPC_optim") +class RPC_optim(bind.RPC,GeoModelTemplate): + """ + RPC optimizes with cpp bindings class including direct and inverse localization instance methods + """ + + # pylint: disable=too-many-instance-attributes + def __init__(self,geomodel_path: str): + + bind.RPC.__init__(self) + GeoModelTemplate.__init__(self,geomodel_path) + + self.type = "RPC_optim" + + # RPC parameters are load from geomodel_path to rpc params + self.load() \ No newline at end of file diff --git a/tests/data/geomodel_template/PHRDIMAP.json b/tests/data/geomodel_template/PHRDIMAP.json new file mode 100644 index 0000000..2b38345 --- /dev/null +++ b/tests/data/geomodel_template/PHRDIMAP.json @@ -0,0 +1,189 @@ +{ + "driver_type": "dimap_v1.4", + "offset_col": 19975.5, + "scale_col": 19975.0, + "offset_row": 24913.0, + "scale_row": 24912.5, + "offset_alt": 200.0, + "scale_alt": 40.0, + "offset_x": 57.35073017142889, + "scale_x": 0.1342591901858867, + "offset_y": 22.0291099403746, + "scale_y": 0.1084766764916285, + "num_x": [ + 0.000703893395985555, + 0.122905857488065, + 0.876559376408557, + -8.323090227052e-05, + -0.000462721782091184, + -3.67424468481082e-06, + -3.8056161110986e-05, + -3.41631587645708e-06, + -0.000294051117854886, + 3.07042607251168e-08, + -2.7604425475108e-08, + 2.94762646473875e-06, + 0.000123433363098949, + 5.59948772965393e-06, + 4.94979878825677e-05, + 0.000143095763157488, + 3.99185456713098e-05, + -6.54044851160337e-09, + 1.94781598972765e-09, + -3.79162665683283e-09 + ], + "den_x": [ + 1.0, + 0.00031914114902447, + -0.000302428047982454, + 3.38714069203248e-05, + -6.82917109014923e-05, + 5.61440872175384e-09, + 1.28777740954338e-08, + 1.15471708343759e-05, + -7.57635535266372e-05, + 4.55262727382826e-05, + 2.74194884037076e-09, + 6.5602753668795e-08, + 2.16056853784428e-06, + 3.61425004935014e-08, + -5.62131392044912e-08, + 4.30246706778879e-07, + 1.65604538554398e-09, + 2.77076883157817e-09, + 4.05008391149373e-09, + 3.51431468137533e-09 + ], + "num_y": [ + -0.000626661725164599, + 0.821061936711914, + -0.178369587507222, + 0.000280064982137338, + -0.0011420682239078, + -3.58899959862579e-05, + -5.65033199724358e-05, + -0.000430306162614323, + -0.000209182658650528, + -2.97747533959253e-08, + 7.1133290540898e-08, + 6.5302309614961e-05, + 0.000296244103385013, + 3.41578935239657e-05, + 0.000177439335375207, + -5.61547794560041e-05, + -7.41867052771127e-06, + 2.02795975956178e-08, + -5.26377311109581e-08, + 1.16486571878642e-08 + ], + "den_y": [ + 1.0, + 0.000664075249885829, + 0.000787737867482079, + 1.95553161999737e-05, + -6.34290181409911e-05, + -2.09111441196011e-08, + 7.49685233119868e-08, + 6.71956927888057e-05, + -0.000178783971165721, + 4.15945672120287e-05, + 1.73143045687708e-08, + 1.21572890629599e-07, + 9.30480789936818e-07, + 4.83476163789087e-08, + 6.81771409422775e-07, + -5.5091769191373e-07, + 9.11177226198066e-08, + 5.61598915283857e-09, + 1.71705095405644e-08, + 1.95349869420542e-09 + ], + "num_col": [ + 0.000571359522062809, + 0.240508664651831, + 1.18193080365322, + -0.000311064822796779, + 0.0011907097906364, + 9.3092386457989e-05, + 1.48841873767377e-05, + 0.000355736784630572, + 2.92315689106095e-05, + -3.76232470317424e-08, + 1.41044509499268e-07, + -6.12947435489968e-06, + -7.599237865126e-05, + -1.71868702694503e-05, + -0.000436833970938307, + -3.2912938937577e-05, + -8.452801852614e-05, + 2.02007111132787e-07, + -1.2179993498224e-07, + 2.22400068039184e-08 + ], + "den_col": [ + 1.0, + -0.0016244385994237, + -0.000997031309129848, + -3.97228163357384e-05, + 0.000112920335905623, + -4.53635700800615e-08, + -6.28923613522465e-08, + 0.000430777708756067, + -5.35461512153272e-05, + -7.15317202374209e-05, + 8.12427721842654e-09, + 2.4551497439837e-06, + -7.9946798540747e-08, + 1.89748428282841e-07, + 1.16725274418257e-06, + -1.81162688986827e-08, + 7.29532238815964e-08, + 1.38399789354239e-07, + -8.99001868967127e-10, + 4.53722631549632e-09 + ], + "num_row": [ + -0.000883094041871494, + 1.10710077979811, + -0.165723516965738, + 0.000138560840098775, + 0.000137508147048548, + 3.25602290695629e-05, + -4.7309525091896e-06, + 0.000566524889959542, + -0.000203002898954291, + 5.21759223015684e-08, + -6.20283272095123e-09, + -0.000213280129222989, + -1.47542827666434e-05, + -5.85043197640585e-05, + -4.61507967191095e-05, + 3.60644875907175e-06, + 8.76284470829693e-06, + 8.62895031760393e-08, + -1.1020819346039e-08, + -7.31873488716101e-09 + ], + "den_row": [ + 1.0, + 0.000373545825560489, + -0.000331911915364305, + -3.67246650834077e-05, + 2.35757725297648e-05, + -1.27063477972314e-08, + -2.81157466813385e-08, + 0.000147021287682039, + -2.68475707109499e-05, + -5.28675857733775e-05, + 8.97727334556582e-09, + -1.21664983676548e-06, + 7.53378407017969e-07, + 7.76919686543327e-09, + -1.18926235693016e-06, + -9.03411866073685e-08, + 3.11151318274824e-08, + 5.66070673008579e-08, + 3.62219975482947e-10, + 3.47727317976582e-09 + ] +} \ No newline at end of file diff --git a/tests/data/geomodel_template/RPC_P1BP.json b/tests/data/geomodel_template/RPC_P1BP.json new file mode 100644 index 0000000..58e5549 --- /dev/null +++ b/tests/data/geomodel_template/RPC_P1BP.json @@ -0,0 +1,189 @@ +{ + "driver_type": "dimap_v2.15", + "offset_row": 11470.0, + "offset_col": 20000.0, + "num_x": [ + -0.00216267283740478, + 0.99476665096815, + 0.000114508866718507, + -0.0049023114890601, + -0.000215872716989672, + -0.000427274205517627, + -0.000119324243929391, + 0.00176888708185964, + -7.10364364111257e-05, + -1.67556643958707e-06, + -8.21204863921981e-08, + 1.07953107491161e-05, + 4.33858654803222e-06, + 2.4122158664569e-06, + 1.29557975835698e-05, + -0.000163221649629262, + -3.96368605635385e-08, + -8.1740661263476e-07, + 8.83130834515652e-08, + -1.33102181603001e-08 + ], + "den_x": [ + 1.0, + 0.000389649825530527, + 0.000214414500978675, + 0.000414499991232059, + -2.15290472531396e-05, + 2.41883580329737e-07, + -9.39982059291604e-08, + -2.67948645657637e-06, + -1.28611779653202e-06, + 2.70471813553735e-06, + -2.08739202324809e-10, + -5.2382918663479e-08, + -1.05653210185694e-08, + -3.36900338956884e-09, + -9.26884750902506e-08, + 3.69838456996589e-07, + 9.62773384568123e-10, + 1.26279970387044e-08, + -1.01482191379087e-09, + 2.28629478655804e-09 + ], + "num_y": [ + 0.00307427158137086, + -0.022754942567019, + -0.96351278038229, + 0.0132362349117686, + -0.000706575591579423, + 2.5384922825267e-05, + 4.83624536885611e-05, + -0.00149711245999419, + -0.00157284574156877, + -8.74685979277811e-07, + 8.61790800612903e-07, + -4.76753362870271e-06, + -9.50051432525276e-06, + -1.92748026345547e-06, + -5.78364059603347e-05, + -0.000308607295681512, + -8.13683278156748e-05, + 3.26915962818304e-06, + -8.82623801296555e-07, + 1.11769058518809e-06 + ], + "den_y": [ + 1.0, + -0.000645474544263166, + 0.00142954980810177, + 6.37447918276721e-06, + -3.66414241388746e-06, + 6.18450147768807e-07, + -1.12467770044304e-06, + 5.66121722032651e-05, + -0.000165524734584013, + 8.43923345197855e-05, + 8.20947876246614e-09, + -2.10446563367918e-07, + 5.17036007137007e-07, + -1.16011177285474e-07, + 1.61367306414579e-07, + -6.85418419451155e-07, + -1.1695435701395e-08, + 1.24103963063179e-08, + 3.94561254140935e-07, + 3.02299222687414e-09 + ], + "num_col": [ + 0.0021737009210787, + 1.00525714360974, + 0.000118927446099487, + 0.00492690455761077, + -0.000222210312924215, + 0.000409443129672268, + -0.000126528243647543, + -0.00179664040198962, + 7.84401031360425e-05, + 3.32549534336851e-06, + 6.70072581370697e-07, + -5.1135734936954e-06, + -1.14831480750326e-05, + -3.30594287170937e-06, + 1.52770173261329e-05, + -0.000183142028762822, + -1.37304096538768e-07, + -1.81212336503884e-06, + 7.10787251036085e-06, + -1.51214721736946e-08 + ], + "den_col": [ + 1.0, + -0.000381194594013202, + 0.000223622059297665, + -0.000422217570203641, + -2.26923954441157e-05, + 9.09927741240968e-07, + -5.78227612356115e-07, + 3.33927467696316e-06, + 7.72196957128252e-06, + -3.5613972104774e-06, + -4.58827275412478e-08, + -8.00430673035027e-08, + 7.23817067866213e-07, + -3.12936105231232e-09, + 1.69531827355129e-07, + -1.21829187013636e-06, + -2.31845365171365e-09, + 3.10557859100329e-09, + 4.76013222107883e-08, + 2.64663990813134e-09 + ], + "num_row": [ + 0.00313924819508418, + -0.0237474392716818, + -1.03785778307581, + 0.0136210350918835, + 0.000639692627029046, + -8.64730452249582e-06, + 2.00901312432567e-05, + -0.00153692020192914, + -0.00174305854917321, + -1.37351384062187e-06, + -5.44105824083825e-07, + 2.42451766635437e-06, + 5.75334811075023e-06, + 2.23736523071738e-06, + 6.11972731515432e-05, + 0.000329532793653865, + 9.75201543451441e-05, + -8.32354417806354e-07, + -7.37135079205433e-06, + -1.27853489593406e-06 + ], + "den_row": [ + 1.0, + 0.000702383699016933, + 0.00146370955530911, + -2.19470964651431e-05, + 1.15302715971345e-05, + 3.83105014821457e-07, + -1.09889574285476e-05, + -6.21236421847309e-05, + 0.000208501769588198, + -9.38714133905457e-05, + 1.59572795228268e-08, + -8.35364688710469e-08, + -1.63371188530178e-06, + -1.27542061938798e-07, + 2.04455528496785e-06, + 1.01440529175472e-06, + 3.28201024292344e-08, + -1.67524390757524e-08, + -4.33595844874304e-07, + 2.23152082232837e-09 + ], + "scale_col": 19999.5, + "scale_row": 11469.5, + "offset_alt": 580.0, + "scale_alt": 540.0, + "offset_x": 7.178141415466419, + "scale_x": 0.1269157277506023, + "offset_y": 43.67753428488081, + "scale_y": 0.05436212948903929 +} \ No newline at end of file diff --git a/tests/data/geomodel_template/RPC_PHR1B.json b/tests/data/geomodel_template/RPC_PHR1B.json new file mode 100644 index 0000000..58e5549 --- /dev/null +++ b/tests/data/geomodel_template/RPC_PHR1B.json @@ -0,0 +1,189 @@ +{ + "driver_type": "dimap_v2.15", + "offset_row": 11470.0, + "offset_col": 20000.0, + "num_x": [ + -0.00216267283740478, + 0.99476665096815, + 0.000114508866718507, + -0.0049023114890601, + -0.000215872716989672, + -0.000427274205517627, + -0.000119324243929391, + 0.00176888708185964, + -7.10364364111257e-05, + -1.67556643958707e-06, + -8.21204863921981e-08, + 1.07953107491161e-05, + 4.33858654803222e-06, + 2.4122158664569e-06, + 1.29557975835698e-05, + -0.000163221649629262, + -3.96368605635385e-08, + -8.1740661263476e-07, + 8.83130834515652e-08, + -1.33102181603001e-08 + ], + "den_x": [ + 1.0, + 0.000389649825530527, + 0.000214414500978675, + 0.000414499991232059, + -2.15290472531396e-05, + 2.41883580329737e-07, + -9.39982059291604e-08, + -2.67948645657637e-06, + -1.28611779653202e-06, + 2.70471813553735e-06, + -2.08739202324809e-10, + -5.2382918663479e-08, + -1.05653210185694e-08, + -3.36900338956884e-09, + -9.26884750902506e-08, + 3.69838456996589e-07, + 9.62773384568123e-10, + 1.26279970387044e-08, + -1.01482191379087e-09, + 2.28629478655804e-09 + ], + "num_y": [ + 0.00307427158137086, + -0.022754942567019, + -0.96351278038229, + 0.0132362349117686, + -0.000706575591579423, + 2.5384922825267e-05, + 4.83624536885611e-05, + -0.00149711245999419, + -0.00157284574156877, + -8.74685979277811e-07, + 8.61790800612903e-07, + -4.76753362870271e-06, + -9.50051432525276e-06, + -1.92748026345547e-06, + -5.78364059603347e-05, + -0.000308607295681512, + -8.13683278156748e-05, + 3.26915962818304e-06, + -8.82623801296555e-07, + 1.11769058518809e-06 + ], + "den_y": [ + 1.0, + -0.000645474544263166, + 0.00142954980810177, + 6.37447918276721e-06, + -3.66414241388746e-06, + 6.18450147768807e-07, + -1.12467770044304e-06, + 5.66121722032651e-05, + -0.000165524734584013, + 8.43923345197855e-05, + 8.20947876246614e-09, + -2.10446563367918e-07, + 5.17036007137007e-07, + -1.16011177285474e-07, + 1.61367306414579e-07, + -6.85418419451155e-07, + -1.1695435701395e-08, + 1.24103963063179e-08, + 3.94561254140935e-07, + 3.02299222687414e-09 + ], + "num_col": [ + 0.0021737009210787, + 1.00525714360974, + 0.000118927446099487, + 0.00492690455761077, + -0.000222210312924215, + 0.000409443129672268, + -0.000126528243647543, + -0.00179664040198962, + 7.84401031360425e-05, + 3.32549534336851e-06, + 6.70072581370697e-07, + -5.1135734936954e-06, + -1.14831480750326e-05, + -3.30594287170937e-06, + 1.52770173261329e-05, + -0.000183142028762822, + -1.37304096538768e-07, + -1.81212336503884e-06, + 7.10787251036085e-06, + -1.51214721736946e-08 + ], + "den_col": [ + 1.0, + -0.000381194594013202, + 0.000223622059297665, + -0.000422217570203641, + -2.26923954441157e-05, + 9.09927741240968e-07, + -5.78227612356115e-07, + 3.33927467696316e-06, + 7.72196957128252e-06, + -3.5613972104774e-06, + -4.58827275412478e-08, + -8.00430673035027e-08, + 7.23817067866213e-07, + -3.12936105231232e-09, + 1.69531827355129e-07, + -1.21829187013636e-06, + -2.31845365171365e-09, + 3.10557859100329e-09, + 4.76013222107883e-08, + 2.64663990813134e-09 + ], + "num_row": [ + 0.00313924819508418, + -0.0237474392716818, + -1.03785778307581, + 0.0136210350918835, + 0.000639692627029046, + -8.64730452249582e-06, + 2.00901312432567e-05, + -0.00153692020192914, + -0.00174305854917321, + -1.37351384062187e-06, + -5.44105824083825e-07, + 2.42451766635437e-06, + 5.75334811075023e-06, + 2.23736523071738e-06, + 6.11972731515432e-05, + 0.000329532793653865, + 9.75201543451441e-05, + -8.32354417806354e-07, + -7.37135079205433e-06, + -1.27853489593406e-06 + ], + "den_row": [ + 1.0, + 0.000702383699016933, + 0.00146370955530911, + -2.19470964651431e-05, + 1.15302715971345e-05, + 3.83105014821457e-07, + -1.09889574285476e-05, + -6.21236421847309e-05, + 0.000208501769588198, + -9.38714133905457e-05, + 1.59572795228268e-08, + -8.35364688710469e-08, + -1.63371188530178e-06, + -1.27542061938798e-07, + 2.04455528496785e-06, + 1.01440529175472e-06, + 3.28201024292344e-08, + -1.67524390757524e-08, + -4.33595844874304e-07, + 2.23152082232837e-09 + ], + "scale_col": 19999.5, + "scale_row": 11469.5, + "offset_alt": 580.0, + "scale_alt": 540.0, + "offset_x": 7.178141415466419, + "scale_x": 0.1269157277506023, + "offset_y": 43.67753428488081, + "scale_y": 0.05436212948903929 +} \ No newline at end of file diff --git a/tests/data/geomodel_template/geom.json b/tests/data/geomodel_template/geom.json new file mode 100644 index 0000000..7d5d9cd --- /dev/null +++ b/tests/data/geomodel_template/geom.json @@ -0,0 +1,105 @@ +{ + "driver_type": "ossim_kwl", + "den_row": [ + 1.0, + 0.000702383699016933, + 0.00146370955530911, + -2.19470964651431e-05, + 1.15302715971345e-05, + 3.83105014821457e-07, + -1.09889574285476e-05, + -6.21236421847309e-05, + 0.000208501769588198, + -9.38714133905457e-05, + 1.59572795228268e-08, + -8.35364688710469e-08, + -1.63371188530178e-06, + -1.27542061938798e-07, + 2.04455528496785e-06, + 1.01440529175472e-06, + 3.28201024292344e-08, + -1.67524390757524e-08, + -4.33595844874304e-07, + 2.23152082232837e-09 + ], + "num_row": [ + 0.00313924819508418, + -0.0237474392716818, + -1.03785778307581, + 0.0136210350918835, + 0.000639692627029046, + -8.64730452249582e-06, + 2.00901312432567e-05, + -0.00153692020192914, + -0.00174305854917321, + -1.37351384062187e-06, + -5.44105824083825e-07, + 2.42451766635437e-06, + 5.75334811075023e-06, + 2.23736523071738e-06, + 6.11972731515432e-05, + 0.000329532793653865, + 9.75201543451441e-05, + -8.32354417806354e-07, + -7.37135079205433e-06, + -1.27853489593406e-06 + ], + "den_col": [ + 1.0, + -0.000381194594013202, + 0.000223622059297665, + -0.000422217570203641, + -2.26923954441157e-05, + 9.09927741240968e-07, + -5.78227612356115e-07, + 3.33927467696316e-06, + 7.72196957128252e-06, + -3.5613972104774e-06, + -4.58827275412478e-08, + -8.00430673035027e-08, + 7.23817067866213e-07, + -3.12936105231232e-09, + 1.69531827355129e-07, + -1.21829187013636e-06, + -2.31845365171365e-09, + 3.10557859100329e-09, + 4.76013222107883e-08, + 2.64663990813134e-09 + ], + "num_col": [ + 0.0021737009210787, + 1.00525714360974, + 0.000118927446099487, + 0.00492690455761077, + -0.000222210312924215, + 0.000409443129672268, + -0.000126528243647543, + -0.00179664040198962, + 7.84401031360425e-05, + 3.32549534336851e-06, + 6.70072581370697e-07, + -5.1135734936954e-06, + -1.14831480750326e-05, + -3.30594287170937e-06, + 1.52770173261329e-05, + -0.000183142028762822, + -1.37304096538768e-07, + -1.81212336503884e-06, + 7.10787251036085e-06, + -1.51214721736946e-08 + ], + "offset_col": 20000.0, + "scale_col": 19999.5, + "offset_row": 11470.0, + "scale_row": 11469.5, + "offset_alt": 580.0, + "scale_alt": 540.0, + "offset_x": 7.17814141546642, + "scale_x": 0.126915727750602, + "offset_y": 43.6775342848808, + "scale_y": 0.0543621294890393, + "num_x": null, + "den_x": null, + "num_y": null, + "den_y": null +} \ No newline at end of file diff --git a/tests/data/geomodel_template/tif.json b/tests/data/geomodel_template/tif.json new file mode 100644 index 0000000..142c2b7 --- /dev/null +++ b/tests/data/geomodel_template/tif.json @@ -0,0 +1,104 @@ +{ + "den_row": [ + 1.0, + 0.00153702966600767, + -0.000850255148536953, + -3.31305939685797e-05, + -9.55977505058452e-05, + -3.45865692724229e-07, + 2.87774922284592e-05, + -9.71752015557712e-05, + 0.000393303600277223, + -0.000150584568710015, + 1.1205565753101e-08, + -4.19615731849281e-07, + -2.91756165031344e-06, + -4.08302077735705e-07, + 2.71755153784986e-06, + -2.06604788106092e-06, + -6.59870613549866e-08, + 4.48992839909244e-08, + -8.21670158409646e-07, + 6.70815592387116e-09 + ], + "num_row": [ + -0.00185020904067451, + 0.0698104447407509, + -1.09235324117618, + -0.021814249276077, + 0.00121171425410876, + 2.76638772076096e-05, + 8.0980462299178e-06, + -0.00119093530484733, + 0.00114897060816587, + 2.24620568639661e-06, + -3.60161922714853e-06, + -5.5151765856299e-06, + -7.83514754712989e-05, + -1.05772130483304e-05, + 0.000111691142895374, + 0.000548333220077419, + 0.000164910424389974, + 2.26464059524339e-06, + 1.78029600933359e-05, + 3.28894996895612e-06 + ], + "num_col": [ + 0.00374321456500487, + 1.01161201549305, + -0.000108650176255135, + 0.0113280713091793, + -0.000158400348205981, + 0.000456165294729941, + -0.000124246258872532, + -0.00339052319644368, + 0.00049260287439426, + 1.39382163458586e-06, + 1.52184293078925e-06, + 2.69142726729643e-06, + 1.25282679825342e-05, + -7.43627570471524e-07, + 1.25591796202999e-05, + -0.000192693282645298, + -3.13481994353245e-07, + -2.93387312507297e-06, + -1.2042569869777e-05, + -1.08970866389161e-08 + ], + "den_col": [ + 1.0, + -0.000362013732739162, + 0.000161936463848829, + -0.000503790573335675, + -1.96459240941631e-05, + 1.21810025788615e-06, + -1.46361989236423e-06, + 7.55579559413767e-06, + -1.58450537412824e-05, + -1.12284475568369e-06, + -1.2839719516335e-08, + -7.61965838022596e-08, + 2.24057677786235e-07, + 9.24695826346757e-11, + 1.77152806817512e-07, + 7.08760218732476e-07, + 1.07574931499556e-10, + -2.53815234783449e-09, + 5.83684624878976e-08, + 5.92090135139572e-10 + ], + "offset_col": 20000.0, + "scale_col": 19999.5, + "offset_row": 11470.0, + "scale_row": 11469.5, + "offset_alt": 670.0, + "scale_alt": 630.0, + "offset_x": 7.17744850367561, + "scale_x": 0.132212619668901, + "offset_y": 43.6772638723064, + "scale_y": 0.059094070033936, + "num_x": null, + "den_x": null, + "num_y": null, + "den_y": null +} \ No newline at end of file diff --git a/tests/geomodels/test_geomodel_template.py b/tests/geomodels/test_geomodel_template.py new file mode 100644 index 0000000..b219a45 --- /dev/null +++ b/tests/geomodels/test_geomodel_template.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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. +# +""" +Module to test geomodel_template class loading capabilities +""" +# TODO: refactor to disable no-member in RPC class +# pylint: disable=no-member + + +# Standard imports +import os + +# Third party imports +import pytest +import rasterio +import json + +# Shareloc imports +from shareloc.geomodels.geomodel_template import GeoModelTemplate, identify_dimap, identify_ossim_kwl + +# Shareloc test imports +from ..helpers import data_path + + +def test_rpc_drivers(): + """ + test GeoModelTemplate driver identification + """ + data_folder = data_path() + + # Test DIMAP GeoModelTemplate + id_scene = "P1BP--2018122638935449CP" + file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") + fctrat_dimap = GeoModelTemplate(file_dimap) + assert fctrat_dimap.rpc_params["driver_type"] == "dimap_v1.4" + + # Test OSSIM KWL GEOM GeoModelTemplate + id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" + file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") + fctrat_geom = GeoModelTemplate(file_geom) + assert fctrat_geom.rpc_params["driver_type"] == "ossim_kwl" + + # Test DIMAPv2 GeoModelTemplate + id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" + file_dimap_v2 = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") + fctrat_dimap_v2 = GeoModelTemplate(file_dimap_v2) + assert fctrat_dimap_v2.rpc_params["driver_type"] == "dimap_v2.15" + + # Test fake GeoModelTemplate + fake_rpc = os.path.join(data_folder, "rpc/fake_rpc.txt") + try: + GeoModelTemplate(fake_rpc) # Raise ValueError->True + raise AssertionError() # Assert false if no exception raised + except ValueError: + assert True + + +def test_identify_ossim_kwl(): + """ + test ossim file identification + """ + data_folder = data_path() + id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" + file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") + ossim_model = identify_ossim_kwl(file_geom) + assert ossim_model == "ossimPleiadesModel" + + +def test_identify_dimap(): + """ + test dimap file identification + """ + data_folder = data_path() + id_scene = "P1BP--2018122638935449CP" + file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") + dimap_version = identify_dimap(file_dimap) + assert dimap_version == "1.4" + + + +@pytest.mark.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning) +@pytest.mark.parametrize( + "prod, can_read", + [ + ("PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", True), + ("PHR1B_P_201709281038393_SEN_PRG_FC_178609-001_nogeo.tif", False), + ], +) +def test_GeoModelTemplate_from_geotiff_without_rpc(prod, can_read): + """ + test geotiff file without rpc + """ + data_folder = data_path() + rpc_file = os.path.join(data_folder, "rpc", prod) + try: + GeoModelTemplate(rpc_file) + assert can_read + except ValueError: + assert not can_read + + +def test_load_rpc_params(): + """ + test loading of rpc_params dict + """ + geom = GeoModelTemplate("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom").rpc_params + tif = GeoModelTemplate("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif").rpc_params + PHRDIMAP = GeoModelTemplate("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML").rpc_params + RPC_P1BP = GeoModelTemplate("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML").rpc_params + RPC_PHR1B = GeoModelTemplate("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML").rpc_params + + + a = open("tests/data/geomodel_template/geom.json") + geom_ref = json.load(a) + b = open("tests/data/geomodel_template/tif.json") + tif_ref = json.load(b) + c = open("tests/data/geomodel_template/PHRDIMAP.json") + PHRDIMAP_ref = json.load(c) + d = open("tests/data/geomodel_template/RPC_P1BP.json") + RPC_P1BP_ref = json.load(d) + e = open("tests/data/geomodel_template/RPC_PHR1B.json") + RPC_PHR1B_ref = json.load(e) + + assert geom == geom_ref + assert tif == tif_ref + assert PHRDIMAP == PHRDIMAP_ref + assert RPC_P1BP == RPC_P1BP_ref + assert RPC_PHR1B == RPC_PHR1B_ref + + a.close() + b.close() + c.close() + d.close() + e.close() \ No newline at end of file diff --git a/tests/geomodels/test_rpc.py b/tests/geomodels/test_rpc.py index cdb707a..dcb9d43 100755 --- a/tests/geomodels/test_rpc.py +++ b/tests/geomodels/test_rpc.py @@ -35,7 +35,8 @@ # Shareloc imports from shareloc.geofunctions.dtm_intersection import DTMIntersection -from shareloc.geomodels.rpc import RPC, identify_dimap, identify_ossim_kwl +from shareloc.geomodels.rpc import RPC +from shareloc.geomodels.geomodel_template import GeoModelTemplate, identify_dimap, identify_ossim_kwl # Shareloc test imports from ..helpers import data_path diff --git a/tests/geomodels/test_rpc_optim.py b/tests/geomodels/test_rpc_optim.py new file mode 100644 index 0000000..c0d4672 --- /dev/null +++ b/tests/geomodels/test_rpc_optim.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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. +# +""" +Module to test RPC_optim class +""" +# pylint: disable=no-member + + +# Third party imports +import json + +# Shareloc imports +from shareloc.geomodels.rpc_optim import RPC_optim + + + + +def test_load_rpc_params(): + """ + test loading of rpc_params dict + """ + geom = RPC_optim("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom").rpc_params + tif = RPC_optim("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif").rpc_params + PHRDIMAP = RPC_optim("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML").rpc_params + RPC_P1BP = RPC_optim("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML").rpc_params + RPC_PHR1B = RPC_optim("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML").rpc_params + + + a = open("tests/data/geomodel_template/geom.json") + geom_ref = json.load(a) + b = open("tests/data/geomodel_template/tif.json") + tif_ref = json.load(b) + c = open("tests/data/geomodel_template/PHRDIMAP.json") + PHRDIMAP_ref = json.load(c) + d = open("tests/data/geomodel_template/RPC_P1BP.json") + RPC_P1BP_ref = json.load(d) + e = open("tests/data/geomodel_template/RPC_PHR1B.json") + RPC_PHR1B_ref = json.load(e) + + assert geom == geom_ref + assert tif == tif_ref + assert PHRDIMAP == PHRDIMAP_ref + assert RPC_P1BP == RPC_P1BP_ref + assert RPC_PHR1B == RPC_PHR1B_ref + + a.close() + b.close() + c.close() + d.close() + e.close() + +def test_function_rpc_cpp(): + """ + Test call to methode parent class rpc in cpp + """ + + rpc = RPC_optim("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom") + + vector_double = [1.,1.,1.] + double = 1. + int = 1 + string = "peuimporte" + + try: + rpc.direct_loc_h(vector_double,vector_double, double, False) + rpc.direct_loc_grid_h(int, int, int, int, int, int, double ) + rpc.direct_loc_dtm(double, double, string) + rpc.inverse_loc(vector_double, vector_double, double) + rpc.filter_coordinates(vector_double, vector_double, False, string ) + rpc.compute_loc_inverse_derivates(vector_double, vector_double, vector_double) + rpc.direct_loc_inverse_iterative(vector_double, vector_double, double, int, False) + rpc.get_alt_min_max() + rpc.los_extrema(double, double, double , double, False, int) + + except: + assert False + + assert True \ No newline at end of file From 6c1846fbceb592689844b1769896b6e6b055e36a Mon Sep 17 00:00:00 2001 From: GUINET Jonathan Date: Tue, 21 Nov 2023 09:42:27 +0000 Subject: [PATCH 10/18] update rpc and grid readers --- shareloc/geomodels/geomodel.py | 2 +- shareloc/geomodels/geomodel_template.py | 15 +- shareloc/geomodels/grid.py | 22 +- shareloc/geomodels/rpc.py | 445 ++--------------------- shareloc/geomodels/rpc_readers.py | 416 +++++++++++++++++++++ tests/geofunctions/test_localization.py | 4 +- tests/geofunctions/test_rectification.py | 8 +- tests/geofunctions/test_triangulation.py | 2 +- tests/geomodels/test_grid.py | 2 +- tests/geomodels/test_rpc.py | 2 +- 10 files changed, 485 insertions(+), 433 deletions(-) create mode 100755 shareloc/geomodels/rpc_readers.py diff --git a/shareloc/geomodels/geomodel.py b/shareloc/geomodels/geomodel.py index 36dcb81..9bb9f8a 100644 --- a/shareloc/geomodels/geomodel.py +++ b/shareloc/geomodels/geomodel.py @@ -77,7 +77,7 @@ def create_geomodel(cls, geomodel_path: str, geomodel_type: str = "RPC"): # Create geomodel object with geomodel_path parameter from geomodel_type if exists try: geomodel_class = cls.available_geomodels[geomodel_type] - geomodel = geomodel_class(geomodel_path).load() + geomodel = geomodel_class.load(geomodel_path) logging.debug("GeoModel type name: %s", geomodel_type) except KeyError: logging.error("Geomodel type named %s is not supported", geomodel_type) diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 5ccf790..9804ae7 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -43,17 +43,11 @@ class GeoModelTemplate(metaclass=ABCMeta): """ @abstractmethod - def __init__(self, geomodel_path: str): + def __init__(self): """ Return the geomodel object associated with the geomodel_type given in the configuration - - :param geomodel_path: path of the geomodel file to instanciate. - :type geomodel_path: string """ - # geomodel filename path - self.geomodel_path: str = geomodel_path - # geomodel type. Set by the subclass self.type: str @@ -105,3 +99,10 @@ def inverse_loc(self, lon, lat, alt): :return: sensor position (row, col, alt) :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) """ + + @classmethod + @abstractmethod + def load(cls, geomodel_path): + """ + load function with class specific args + """ diff --git a/shareloc/geomodels/grid.py b/shareloc/geomodels/grid.py index 93a035d..c1ce7e6 100755 --- a/shareloc/geomodels/grid.py +++ b/shareloc/geomodels/grid.py @@ -87,9 +87,9 @@ def __init__(self, geomodel_path: str): :type geomodel_path: string """ # Instanciate GeoModelTemplate generic init with shared parameters - super().__init__(geomodel_path) + super().__init__() self.type = "multi H grid" - + self.geomodel_path = geomodel_path # GeoModel Grid parameters definition (see documentation) self.row0 = None self.col0 = None @@ -105,11 +105,10 @@ def __init__(self, geomodel_path: str): self.rowmax = None self.colmax = None self.epsg = 0 + self.read() - # Load function of grid parameters from grid file to grid object - self.load() - - def load(self): + @classmethod + def load(cls, geomodel_path): """ Load grid and fill Class attributes. @@ -119,7 +118,18 @@ def load(self): - lon_data : [alt,row,col] - lat_data : [alt,row,col] """ + return cls(geomodel_path) + + def read(self): + """ + Load grid and fill Class attributes. + The grid is read as an shareloc.image. Image and class attributes are filled. + Shareloc geotiff grids are stored by increasing altitude H0 ... Hx + 2 data cubes are defined: + - lon_data : [alt,row,col] + - lat_data : [alt,row,col] + """ grid_image = Image(self.geomodel_path, read_data=True) if grid_image.dataset.driver != "GTiff": raise TypeError( diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 2d99e22..9bed46a 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -26,101 +26,20 @@ # Standard imports import logging -from os.path import basename -from typing import Dict -from xml.dom import minidom # Third party imports import numpy as np -import rasterio as rio from numba import config, njit, prange # Shareloc imports from shareloc.geomodels.geomodel import GeoModel from shareloc.geomodels.geomodel_template import GeoModelTemplate +from shareloc.geomodels.rpc_readers import rpc_reader from shareloc.proj_utils import coordinates_conversion # Set numba type of threading layer before parallel target compilation config.THREADING_LAYER = "omp" - -def parse_coeff_line(coeff_str): - """ - split str coef to float list - - :param coeff_str: line coef - :type coeff_str: str - :return: coeff list - :rtype: list() - """ - return [float(el) for el in coeff_str.split()] - - -def identify_dimap(xml_file): - """ - parse xml file to identify dimap and its version - - :param xml_file: dimap rpc file - :type xml_file: str - :return: dimap info : dimap_version and None if not an dimap file - :rtype: str - """ - try: - xmldoc = minidom.parse(xml_file) - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data - if mtd_format == "DIMAP_PHR": - version_tag = "METADATA_PROFILE" - else: - version_tag = "METADATA_FORMAT" - version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] - except Exception: # pylint: disable=broad-except - return None - - return version - - -def identify_ossim_kwl(ossim_kwl_file): - """ - parse geom file to identify if it is an ossim model - - :param ossim_kwl_fil : ossim keyword list file - :type ossim_kwl_file: str - :return: ossimmodel or None if not an ossim kwl file - :rtype: str - """ - try: - with open(ossim_kwl_file, encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - if "type" in geom_dict: - if geom_dict["type"].strip().startswith("ossim"): - return geom_dict["type"].strip() - return None - except Exception: # pylint: disable=broad-except - return None - - -def identify_geotiff_rpc(image_filename): - """ - read image file to identify if it is a geotiff which contains RPCs - - :param image_filename: image_filename - :type image_filename: str - :return: rpc info, rpc dict or None if not a geotiff with rpc - :rtype: str - """ - try: - dataset = rio.open(image_filename) - rpc_dict = dataset.tags(ns="RPC") - return rpc_dict - except Exception: # pylint: disable=broad-except - return None - - # pylint: disable=no-member @@ -132,25 +51,16 @@ class RPC(GeoModelTemplate): # gitlab issue #61 # pylint: disable=too-many-instance-attributes - def __init__(self, geomodel_path: str): - # Instanciate GeoModelTemplate generic init with shared parameters - super().__init__(geomodel_path) - self.type = "RPC" - - # initiate epsg and datum, can overriden by rpc_params + # gitlab issue #61 + # pylint: disable=too-many-instance-attributes + def __init__(self, rpc_params): + super().__init__() self.epsg = None self.datum = None - - # RPC parameters as Dict - self.rpc_params: Dict = {} - - # RPC parameters are load from geomodel_path to rpc params - self.load() - - # set class parameters from rpc_params (epsg and datum can be overriden) - for key, value in self.rpc_params.items(): + for key, value in rpc_params.items(): setattr(self, key, value) + self.type = "RPC" if self.epsg is None: self.epsg = 4326 if self.datum is None: @@ -158,32 +68,31 @@ def __init__(self, geomodel_path: str): self.lim_extrapol = 1.0001 - # Monomes seems not used in shareloc code: Clean ? # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] - self.monomes = np.array( - [ - [1, 0, 0, 0], - [1, 1, 0, 0], - [1, 0, 1, 0], - [1, 0, 0, 1], - [1, 1, 1, 0], - [1, 1, 0, 1], - [1, 0, 1, 1], - [1, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [1, 1, 1, 1], - [1, 3, 0, 0], - [1, 1, 2, 0], - [1, 1, 0, 2], - [1, 2, 1, 0], - [1, 0, 3, 0], - [1, 0, 1, 2], - [1, 2, 0, 1], - [1, 0, 2, 1], - [1, 0, 0, 3], - ] - ) + monomes_order = [ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 0, 1, 0], + [1, 0, 0, 1], + [1, 1, 1, 0], + [1, 1, 0, 1], + [1, 0, 1, 1], + [1, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [1, 1, 1, 1], + [1, 3, 0, 0], + [1, 1, 2, 0], + [1, 1, 0, 2], + [1, 2, 1, 0], + [1, 0, 3, 0], + [1, 0, 1, 2], + [1, 2, 0, 1], + [1, 0, 2, 1], + [1, 0, 0, 3], + ] + + self.monomes = np.array(monomes_order) # monomial coefficients of 1st variable derivative self.monomes_deriv_1 = np.array( @@ -262,7 +171,8 @@ def __init__(self, geomodel_path: str): self.row0 = self.offset_row - self.scale_row self.rowmax = self.offset_row + self.scale_row - def load(self): + @classmethod + def load(cls, geomodel_path, topleftconvention=True): """ Load from any RPC (auto identify driver) from filename (dimap, ossim kwl, geotiff) @@ -274,293 +184,8 @@ def load(self): If True : [0,0] is at the top left of the Top Left pixel (OSSIM) """ # Set topleftconvention (keeping historic option): to clean - topleftconvention = True - - # If ends with XML --> DIMAP - if basename(self.geomodel_path.upper()).endswith("XML"): - dimap_version = identify_dimap(self.geomodel_path) - if dimap_version is not None: - if float(dimap_version) < 2.0: - self.load_dimap_v1(topleftconvention) - if float(dimap_version) >= 2.0: - self.load_dimap_coeff(topleftconvention) - else: - # If not DIMAP, identify ossim - ossim_model = identify_ossim_kwl(self.geomodel_path) - if ossim_model is not None: - self.load_ossim_kwl(topleftconvention) - else: - # Otherwise, check if RPC is in geotif - geotiff_rpc_dict = identify_geotiff_rpc(self.geomodel_path) - if geotiff_rpc_dict is not None: - self.load_geotiff(topleftconvention) - else: - # Raise error if no file recognized. - raise ValueError("can not read rpc file") - - def load_dimap_coeff(self, topleftconvention=True): - """ - Load from Dimap v2 and V3 - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] - self.rpc_params["driver_type"] = "dimap_v" + version - global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] - normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] - self.rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) - self.rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) - if float(version) >= 3: - direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] - inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 0.5 - self.rpc_params["offset_row"] -= 0.5 - - else: - direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] - inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] - - self.rpc_params["num_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_x"] = [ - float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_y"] = [ - float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - - self.rpc_params["num_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_col"] = [ - float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["num_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - self.rpc_params["den_row"] = [ - float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) - for index in range(1, 21) - ] - - self.rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) - self.rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) - self.rpc_params["offset_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data - ) - self.rpc_params["scale_alt"] = float( - normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data - ) - self.rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) - self.rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) - self.rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) - self.rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_dimap_v1(self, topleftconvention=True): - """ - Load from dimap v1 - - ** Deprecated, to clean ? ** - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - - if not basename(self.geomodel_path).upper().endswith("XML"): - raise ValueError("dimap must ends with .xml") - - xmldoc = minidom.parse(self.geomodel_path) - - mtd = xmldoc.getElementsByTagName("Metadata_Identification") - version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] - self.rpc_params = {"driver_type": "dimap_v" + version} - - global_rfm = xmldoc.getElementsByTagName("Global_RFM") - rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") - coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] - coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] - coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] - coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] - - scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) - scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) - offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) - scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) - offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) - scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) - offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) - scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) - offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) - - self.rpc_params["offset_col"] = offset_col - self.rpc_params["scale_col"] = scale_col - self.rpc_params["offset_row"] = offset_row - self.rpc_params["scale_row"] = scale_row - self.rpc_params["offset_alt"] = offset_alt - self.rpc_params["scale_alt"] = scale_alt - self.rpc_params["offset_x"] = offset_lon - self.rpc_params["scale_x"] = scale_lon - self.rpc_params["offset_y"] = offset_lat - self.rpc_params["scale_y"] = scale_lat - self.rpc_params["num_x"] = coeff_lon[0:20] - self.rpc_params["den_x"] = coeff_lon[20::] - self.rpc_params["num_y"] = coeff_lat[0:20] - self.rpc_params["den_y"] = coeff_lat[20::] - self.rpc_params["num_col"] = coeff_col[0:20] - self.rpc_params["den_col"] = coeff_col[20::] - self.rpc_params["num_row"] = coeff_lig[0:20] - self.rpc_params["den_row"] = coeff_lig[20::] - self.rpc_params["offset_col"] -= 1.0 - self.rpc_params["offset_row"] -= 1.0 - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_geotiff(self, topleftconvention=True): - """ - Load from a geotiff image file - - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - dataset = rio.open(self.geomodel_path) - rpc_dict = dataset.tags(ns="RPC") - if not rpc_dict: - logging.error("%s does not contains RPCs ", self.geomodel_path) - raise ValueError - self.rpc_params = { - "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), - "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), - "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), - "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), - "offset_col": float(rpc_dict["SAMP_OFF"]), - "scale_col": float(rpc_dict["SAMP_SCALE"]), - "offset_row": float(rpc_dict["LINE_OFF"]), - "scale_row": float(rpc_dict["LINE_SCALE"]), - "offset_alt": float(rpc_dict["HEIGHT_OFF"]), - "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), - "offset_x": float(rpc_dict["LONG_OFF"]), - "scale_x": float(rpc_dict["LONG_SCALE"]), - "offset_y": float(rpc_dict["LAT_OFF"]), - "scale_y": float(rpc_dict["LAT_SCALE"]), - "num_x": None, - "den_x": None, - "num_y": None, - "den_y": None, - } - # inverse coeff are not defined - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 - - def load_ossim_kwl(self, topleftconvention=True): - """ - Load from a geom file - - :param topleftconvention: [0,0] position - :type topleftconvention: boolean - If False : [0,0] is at the center of the Top Left pixel - If True : [0,0] is at the top left of the Top Left pixel (OSSIM) - """ - # OSSIM keyword list - self.rpc_params["driver_type"] = "ossim_kwl" - with open(self.geomodel_path, "r", encoding="utf-8") as ossim_file: - content = ossim_file.readlines() - - geom_dict = {} - for line in content: - (key, val) = line.split(": ") - geom_dict[key] = val.rstrip() - - self.rpc_params["den_row"] = [np.nan] * 20 - self.rpc_params["num_row"] = [np.nan] * 20 - self.rpc_params["den_col"] = [np.nan] * 20 - self.rpc_params["num_col"] = [np.nan] * 20 - for index in range(0, 20): - axis = "line" - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_row"][index] = float(geom_dict[key]) - num_den = "num" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_row"][index] = float(geom_dict[key]) - axis = "samp" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["num_col"][index] = float(geom_dict[key]) - num_den = "den" - key = f"{axis}_{num_den}_coeff_{index:02d}" - self.rpc_params["den_col"][index] = float(geom_dict[key]) - self.rpc_params["offset_col"] = float(geom_dict["samp_off"]) - self.rpc_params["scale_col"] = float(geom_dict["samp_scale"]) - self.rpc_params["offset_row"] = float(geom_dict["line_off"]) - self.rpc_params["scale_row"] = float(geom_dict["line_scale"]) - self.rpc_params["offset_alt"] = float(geom_dict["height_off"]) - self.rpc_params["scale_alt"] = float(geom_dict["height_scale"]) - self.rpc_params["offset_x"] = float(geom_dict["long_off"]) - self.rpc_params["scale_x"] = float(geom_dict["long_scale"]) - self.rpc_params["offset_y"] = float(geom_dict["lat_off"]) - self.rpc_params["scale_y"] = float(geom_dict["lat_scale"]) - # inverse coeff are not defined - self.rpc_params["num_x"] = None - self.rpc_params["den_x"] = None - self.rpc_params["num_y"] = None - self.rpc_params["den_y"] = None - # If top left convention, 0.5 pixel shift added on col/row offsets - if topleftconvention: - self.rpc_params["offset_col"] += 0.5 - self.rpc_params["offset_row"] += 0.5 + cls.geomodel_path = geomodel_path + return cls(rpc_reader(geomodel_path, topleftconvention)) def direct_loc_h(self, row, col, alt, fill_nan=False): """ diff --git a/shareloc/geomodels/rpc_readers.py b/shareloc/geomodels/rpc_readers.py new file mode 100755 index 0000000..d7bac09 --- /dev/null +++ b/shareloc/geomodels/rpc_readers.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python +# coding: utf8 +# +# Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of Shareloc +# (see https://github.com/CNES/shareloc). +# +# 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 RPC formats handling to instantiate RPC models. +RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. +""" + +# Standard imports +import logging +from os.path import basename +from typing import Dict +from xml.dom import minidom + +# Third party imports +import numpy as np +import rasterio as rio + + +def rpc_reader(geomodel_path: str, topleftconvention: bool = True) -> Dict: + """ + Load from any RPC (auto identify driver) + from filename (dimap, ossim kwl, geotiff) + + TODO: topleftconvention always to True, set a standard and remove the option + + topleftconvention boolean: [0,0] position + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + + :param geomodel_path geomodel filename + :return rpc dict filled with parameters + """ + + # If ends with XML --> DIMAP + if basename(geomodel_path.upper()).endswith("XML"): + dimap_version = identify_dimap(geomodel_path) + if dimap_version is not None: + if float(dimap_version) < 2.0: + return rpc_reader_dimap_v1(geomodel_path, topleftconvention) + if float(dimap_version) >= 2.0: + return rpc_reader_dimap_v23(geomodel_path, topleftconvention) + ossim_model = identify_ossim_kwl(geomodel_path) + if ossim_model is not None: + return rpc_reader_ossim_kwl(geomodel_path, topleftconvention) + geotiff_rpc_dict = identify_geotiff_rpc(geomodel_path) + if geotiff_rpc_dict is not None: + return rpc_reader_geotiff(geomodel_path, topleftconvention) + raise ValueError("can not read rpc file") + + +def rpc_reader_dimap_v23(geomodel_path: str, topleftconvention: bool = True) -> Dict: + """ + Load from Dimap v2 and V3 + + :param geomodel_path: path to geomodel + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + + :returns rpc_dict + """ + + if not basename(geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].attributes.items()[0][1] + rpc_params = {"driver_type": "dimap_v" + version} + global_rfm = xmldoc.getElementsByTagName("Global_RFM")[0] + normalisation_coeffs = global_rfm.getElementsByTagName("RFM_Validity")[0] + rpc_params["offset_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_OFF")[0].firstChild.data) + rpc_params["offset_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_OFF")[0].firstChild.data) + if float(version) >= 3: + direct_coeffs = global_rfm.getElementsByTagName("ImagetoGround_Values")[0] + inverse_coeffs = global_rfm.getElementsByTagName("GroundtoImage_Values")[0] + + rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"LON_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LAT_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["offset_col"] -= 0.5 + rpc_params["offset_row"] -= 0.5 + + else: + direct_coeffs = global_rfm.getElementsByTagName("Direct_Model")[0] + inverse_coeffs = global_rfm.getElementsByTagName("Inverse_Model")[0] + + rpc_params["num_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_x"] = [ + float(direct_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["num_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_y"] = [ + float(direct_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["offset_col"] -= 1.0 + rpc_params["offset_row"] -= 1.0 + + rpc_params["num_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_col"] = [ + float(inverse_coeffs.getElementsByTagName(f"SAMP_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["num_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_NUM_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + rpc_params["den_row"] = [ + float(inverse_coeffs.getElementsByTagName(f"LINE_DEN_COEFF_{index}")[0].firstChild.data) + for index in range(1, 21) + ] + + rpc_params["scale_col"] = float(normalisation_coeffs.getElementsByTagName("SAMP_SCALE")[0].firstChild.data) + rpc_params["scale_row"] = float(normalisation_coeffs.getElementsByTagName("LINE_SCALE")[0].firstChild.data) + rpc_params["offset_alt"] = float(normalisation_coeffs.getElementsByTagName("HEIGHT_OFF")[0].firstChild.data) + rpc_params["scale_alt"] = float(normalisation_coeffs.getElementsByTagName("HEIGHT_SCALE")[0].firstChild.data) + rpc_params["offset_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_OFF")[0].firstChild.data) + rpc_params["scale_x"] = float(normalisation_coeffs.getElementsByTagName("LONG_SCALE")[0].firstChild.data) + rpc_params["offset_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_OFF")[0].firstChild.data) + rpc_params["scale_y"] = float(normalisation_coeffs.getElementsByTagName("LAT_SCALE")[0].firstChild.data) + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + rpc_params["offset_col"] += 0.5 + rpc_params["offset_row"] += 0.5 + return rpc_params + + +def rpc_reader_dimap_v1(geomodel_path: str, topleftconvention: bool = True) -> Dict: + """ + Load from dimap v1 + + ** Deprecated, to clean ? ** + + :param geomodel_path: path to geomodel + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + + if not basename(geomodel_path).upper().endswith("XML"): + raise ValueError("dimap must ends with .xml") + + xmldoc = minidom.parse(geomodel_path) + + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + version = mtd[0].getElementsByTagName("METADATA_PROFILE")[0].attributes.items()[0][1] + rpc_params = {"driver_type": "dimap_v" + version} + + global_rfm = xmldoc.getElementsByTagName("Global_RFM") + rfm_validity = xmldoc.getElementsByTagName("RFM_Validity") + coeff_lon = [float(el) for el in global_rfm[0].getElementsByTagName("F_LON")[0].firstChild.data.split()] + coeff_lat = [float(el) for el in global_rfm[0].getElementsByTagName("F_LAT")[0].firstChild.data.split()] + coeff_col = [float(el) for el in global_rfm[0].getElementsByTagName("F_COL")[0].firstChild.data.split()] + coeff_lig = [float(el) for el in global_rfm[0].getElementsByTagName("F_ROW")[0].firstChild.data.split()] + + scale_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lon = float(rfm_validity[0].getElementsByTagName("Lon")[0].getElementsByTagName("B")[0].firstChild.data) + scale_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("A")[0].firstChild.data) + offset_lat = float(rfm_validity[0].getElementsByTagName("Lat")[0].getElementsByTagName("B")[0].firstChild.data) + scale_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("A")[0].firstChild.data) + offset_alt = float(rfm_validity[0].getElementsByTagName("Alt")[0].getElementsByTagName("B")[0].firstChild.data) + scale_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("A")[0].firstChild.data) + offset_col = float(rfm_validity[0].getElementsByTagName("Col")[0].getElementsByTagName("B")[0].firstChild.data) + scale_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("A")[0].firstChild.data) + offset_row = float(rfm_validity[0].getElementsByTagName("Row")[0].getElementsByTagName("B")[0].firstChild.data) + + rpc_params["offset_col"] = offset_col + rpc_params["scale_col"] = scale_col + rpc_params["offset_row"] = offset_row + rpc_params["scale_row"] = scale_row + rpc_params["offset_alt"] = offset_alt + rpc_params["scale_alt"] = scale_alt + rpc_params["offset_x"] = offset_lon + rpc_params["scale_x"] = scale_lon + rpc_params["offset_y"] = offset_lat + rpc_params["scale_y"] = scale_lat + rpc_params["num_x"] = coeff_lon[0:20] + rpc_params["den_x"] = coeff_lon[20::] + rpc_params["num_y"] = coeff_lat[0:20] + rpc_params["den_y"] = coeff_lat[20::] + rpc_params["num_col"] = coeff_col[0:20] + rpc_params["den_col"] = coeff_col[20::] + rpc_params["num_row"] = coeff_lig[0:20] + rpc_params["den_row"] = coeff_lig[20::] + rpc_params["offset_col"] -= 1.0 + rpc_params["offset_row"] -= 1.0 + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + rpc_params["offset_col"] += 0.5 + rpc_params["offset_row"] += 0.5 + return rpc_params + + +def rpc_reader_geotiff(geomodel_path, topleftconvention=True) -> Dict: + """ + Load from a geotiff image file + + :param geomodel_path: path to geomodel + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + dataset = rio.open(geomodel_path) + rpc_dict = dataset.tags(ns="RPC") + if not rpc_dict: + logging.error("%s does not contains RPCs ", geomodel_path) + raise ValueError + rpc_params = { + "den_row": parse_coeff_line(rpc_dict["LINE_DEN_COEFF"]), + "num_row": parse_coeff_line(rpc_dict["LINE_NUM_COEFF"]), + "num_col": parse_coeff_line(rpc_dict["SAMP_NUM_COEFF"]), + "den_col": parse_coeff_line(rpc_dict["SAMP_DEN_COEFF"]), + "offset_col": float(rpc_dict["SAMP_OFF"]), + "scale_col": float(rpc_dict["SAMP_SCALE"]), + "offset_row": float(rpc_dict["LINE_OFF"]), + "scale_row": float(rpc_dict["LINE_SCALE"]), + "offset_alt": float(rpc_dict["HEIGHT_OFF"]), + "scale_alt": float(rpc_dict["HEIGHT_SCALE"]), + "offset_x": float(rpc_dict["LONG_OFF"]), + "scale_x": float(rpc_dict["LONG_SCALE"]), + "offset_y": float(rpc_dict["LAT_OFF"]), + "scale_y": float(rpc_dict["LAT_SCALE"]), + "num_x": None, + "den_x": None, + "num_y": None, + "den_y": None, + } + # inverse coeff are not defined + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + rpc_params["offset_col"] += 0.5 + rpc_params["offset_row"] += 0.5 + return rpc_params + + +def rpc_reader_ossim_kwl(geomodel_path: str, topleftconvention: bool = True) -> Dict: + """ + Load from a geom file + + :param geomodel_path: path to geomodel + :param topleftconvention: [0,0] position + :type topleftconvention: boolean + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # OSSIM keyword list + rpc_params = {"driver_type": "ossim_kwl"} + with open(geomodel_path, "r", encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + + rpc_params["den_row"] = [np.nan] * 20 + rpc_params["num_row"] = [np.nan] * 20 + rpc_params["den_col"] = [np.nan] * 20 + rpc_params["num_col"] = [np.nan] * 20 + for index in range(0, 20): + axis = "line" + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + rpc_params["den_row"][index] = float(geom_dict[key]) + num_den = "num" + key = f"{axis}_{num_den}_coeff_{index:02d}" + rpc_params["num_row"][index] = float(geom_dict[key]) + axis = "samp" + key = f"{axis}_{num_den}_coeff_{index:02d}" + rpc_params["num_col"][index] = float(geom_dict[key]) + num_den = "den" + key = f"{axis}_{num_den}_coeff_{index:02d}" + rpc_params["den_col"][index] = float(geom_dict[key]) + rpc_params["offset_col"] = float(geom_dict["samp_off"]) + rpc_params["scale_col"] = float(geom_dict["samp_scale"]) + rpc_params["offset_row"] = float(geom_dict["line_off"]) + rpc_params["scale_row"] = float(geom_dict["line_scale"]) + rpc_params["offset_alt"] = float(geom_dict["height_off"]) + rpc_params["scale_alt"] = float(geom_dict["height_scale"]) + rpc_params["offset_x"] = float(geom_dict["long_off"]) + rpc_params["scale_x"] = float(geom_dict["long_scale"]) + rpc_params["offset_y"] = float(geom_dict["lat_off"]) + rpc_params["scale_y"] = float(geom_dict["lat_scale"]) + # inverse coeff are not defined + rpc_params["num_x"] = None + rpc_params["den_x"] = None + rpc_params["num_y"] = None + rpc_params["den_y"] = None + # If top left convention, 0.5 pixel shift added on col/row offsets + if topleftconvention: + rpc_params["offset_col"] += 0.5 + rpc_params["offset_row"] += 0.5 + return rpc_params + + +def identify_dimap(xml_file): + """ + parse xml file to identify dimap and its version + + :param xml_file: dimap rpc file + :type xml_file: str + :return: dimap info : dimap_version and None if not an dimap file + :rtype: str + """ + try: + xmldoc = minidom.parse(xml_file) + mtd = xmldoc.getElementsByTagName("Metadata_Identification") + mtd_format = mtd[0].getElementsByTagName("METADATA_FORMAT")[0].firstChild.data + if mtd_format == "DIMAP_PHR": + version_tag = "METADATA_PROFILE" + else: + version_tag = "METADATA_FORMAT" + version = mtd[0].getElementsByTagName(version_tag)[0].attributes.items()[0][1] + except Exception: # pylint: disable=broad-except + return None + + return version + + +def parse_coeff_line(coeff_str): + """ + split str coef to float list + + :param coeff_str: line coef + :type coeff_str: str + :return: coeff list + :rtype: list() + """ + return [float(el) for el in coeff_str.split()] + + +def identify_geotiff_rpc(image_filename): + """ + read image file to identify if it is a geotiff which contains RPCs + + :param image_filename: image_filename + :type image_filename: str + :return: rpc info, rpc dict or None if not a geotiff with rpc + :rtype: str + """ + try: + dataset = rio.open(image_filename) + rpc_dict = dataset.tags(ns="RPC") + return rpc_dict + except Exception: # pylint: disable=broad-except + return None + + +def identify_ossim_kwl(ossim_kwl_file): + """ + parse geom file to identify if it is an ossim model + + :param ossim_kwl_fil : ossim keyword list file + :type ossim_kwl_file: str + :return: ossimmodel or None if not an ossim kwl file + :rtype: str + """ + try: + with open(ossim_kwl_file, encoding="utf-8") as ossim_file: + content = ossim_file.readlines() + geom_dict = {} + for line in content: + (key, val) = line.split(": ") + geom_dict[key] = val.rstrip() + if "type" in geom_dict: + if geom_dict["type"].strip().startswith("ossim"): + return geom_dict["type"].strip() + return None + except Exception: # pylint: disable=broad-except + return None diff --git a/tests/geofunctions/test_localization.py b/tests/geofunctions/test_localization.py index ac0893a..7ad3ad3 100644 --- a/tests/geofunctions/test_localization.py +++ b/tests/geofunctions/test_localization.py @@ -96,7 +96,7 @@ def test_localize_direct_grid(): # data = os.path.join(data_path(), "rpc/phr_ventoux/", "left_image.geom") # geom_model_1 = GeoModel(data) data = os.path.join(data_path(), "grid/phr_ventoux/", "GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") - geom_model = GeoModel(data) + geom_model = GeoModel(data, "grid") # then read the Image to retrieve its geotransform image_filename = os.path.join(data_path(), "image/phr_ventoux/", "left_image.tif") image_left = Image(image_filename) @@ -136,7 +136,7 @@ def prepare_loc(alti="geoide", id_scene="P1BP--2017030824934340CP"): dtm = DTMIntersection(fic) # load grid model gld = os.path.join(data_folder, grid_name) - gri = GeoModel(gld) + gri = GeoModel(gld, "grid") return dtm, gri diff --git a/tests/geofunctions/test_rectification.py b/tests/geofunctions/test_rectification.py index 4e2842f..a24bb33 100644 --- a/tests/geofunctions/test_rectification.py +++ b/tests/geofunctions/test_rectification.py @@ -257,10 +257,10 @@ def test_compute_stereorectification_epipolar_grids_geomodel_grid(): # first instantiate geometric models left and right (here Grid geometric model) geom_model_left = GeoModel( - os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") + os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif"), "grid" ) geom_model_right = GeoModel( - os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif") + os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif"), "grid" ) # read the images @@ -313,10 +313,10 @@ def test_compute_stereorectification_epipolar_grids_geomodel_grid_dtm_geoid(): # first instantiate geometric models left and right (here Grid geometrics model) geom_model_left = GeoModel( - os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif") + os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042194_SEN_690908101-001.tif"), "grid" ) geom_model_right = GeoModel( - os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif") + os.path.join(data_path(), "grid/phr_ventoux/GRID_PHR1B_P_201308051042523_SEN_690908101-002.tif"), "grid" ) # read the images diff --git a/tests/geofunctions/test_triangulation.py b/tests/geofunctions/test_triangulation.py index 3006e22..d4f3863 100644 --- a/tests/geofunctions/test_triangulation.py +++ b/tests/geofunctions/test_triangulation.py @@ -85,7 +85,7 @@ def prepare_loc(alti="geoide", id_scene="P1BP--2017030824934340CP"): data_folder = data_path(alti, id_scene) # load grid gld = os.path.join(data_folder, f"GRID_{id_scene}.tif") - gri = GeoModel(gld) + gri = GeoModel(gld, "grid") return gri diff --git a/tests/geomodels/test_grid.py b/tests/geomodels/test_grid.py index 4ab9cb6..f40e58d 100755 --- a/tests/geomodels/test_grid.py +++ b/tests/geomodels/test_grid.py @@ -41,7 +41,7 @@ def fixture_get_geotiff_grid(): get grid and associated path """ geotiff_grid_path = data_path("ellipsoide", "loc_direct_grid_PHR_2013072139303958CP.tif") - gri_geotiff = GeoModel(geotiff_grid_path) + gri_geotiff = GeoModel(geotiff_grid_path, "grid") grid_image = Image(geotiff_grid_path, read_data=True) return gri_geotiff, grid_image diff --git a/tests/geomodels/test_rpc.py b/tests/geomodels/test_rpc.py index 42ae96c..1829e4b 100755 --- a/tests/geomodels/test_rpc.py +++ b/tests/geomodels/test_rpc.py @@ -36,7 +36,7 @@ # Shareloc imports from shareloc.geofunctions.dtm_intersection import DTMIntersection from shareloc.geomodels import GeoModel -from shareloc.geomodels.rpc import identify_dimap, identify_ossim_kwl +from shareloc.geomodels.rpc_readers import identify_dimap, identify_ossim_kwl # Shareloc test imports from ..helpers import data_path From b51831e6f4b3e81d2509bfb46ff5ba6a3deb819c Mon Sep 17 00:00:00 2001 From: GUINET Jonathan Date: Tue, 21 Nov 2023 09:43:15 +0000 Subject: [PATCH 11/18] update rpc and grid readers --- shareloc/geomodels/geomodel_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 9804ae7..388c242 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -102,7 +102,7 @@ def inverse_loc(self, lon, lat, alt): @classmethod @abstractmethod - def load(cls, geomodel_path): + def load(cls, geomodel_path, **kwargs): """ load function with class specific args """ From 6da210b7e4762f06c04a1ba0e9f4e136a9c28d11 Mon Sep 17 00:00:00 2001 From: GUINET Jonathan Date: Tue, 21 Nov 2023 12:29:08 +0000 Subject: [PATCH 12/18] update load() interface --- shareloc/geomodels/geomodel_template.py | 2 +- shareloc/geomodels/rpc.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 388c242..9804ae7 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -102,7 +102,7 @@ def inverse_loc(self, lon, lat, alt): @classmethod @abstractmethod - def load(cls, geomodel_path, **kwargs): + def load(cls, geomodel_path): """ load function with class specific args """ diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 9bed46a..86fc021 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -172,7 +172,7 @@ def __init__(self, rpc_params): self.rowmax = self.offset_row + self.scale_row @classmethod - def load(cls, geomodel_path, topleftconvention=True): + def load(cls, geomodel_path): """ Load from any RPC (auto identify driver) from filename (dimap, ossim kwl, geotiff) @@ -185,7 +185,7 @@ def load(cls, geomodel_path, topleftconvention=True): """ # Set topleftconvention (keeping historic option): to clean cls.geomodel_path = geomodel_path - return cls(rpc_reader(geomodel_path, topleftconvention)) + return cls(rpc_reader(geomodel_path, topleftconvention=True)) def direct_loc_h(self, row, col, alt, fill_nan=False): """ From e42692003f357e7594a1d1915fa7cc2de3a519a9 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Thu, 23 Nov 2023 17:07:24 +0100 Subject: [PATCH 13/18] feat: class rpc_optim + test class rpc_optim + test CI: pass quality code conforming to code quality as pointed in threads API doc on classes Ci CI: pass quality code conforming to code quality as pointed in threads API doc on classes ci --- .pylintrc | 2 +- Makefile | 4 +- ascii_art.cpp | 17 ++ setup.py | 8 +- shareloc/bindings/GeoModelTemplate.cpp | 40 ++++- shareloc/bindings/GeoModelTemplate.hpp | 38 +++++ .../{bind_helloworld.cpp => bind.cpp} | 34 +++- shareloc/bindings/rpc.cpp | 95 +++++++++-- shareloc/bindings/rpc.hpp | 101 ++++++++++-- shareloc/geomodels/geomodel_template.py | 15 +- shareloc/geomodels/rpc.py | 2 - shareloc/geomodels/rpc_optim.py | 55 ++++--- tests/geomodels/test_draft_pybind.py | 15 -- tests/geomodels/test_geomodel_template.py | 152 ------------------ tests/geomodels/test_rpc_optim.py | 86 +++++----- 15 files changed, 379 insertions(+), 285 deletions(-) create mode 100644 ascii_art.cpp rename shareloc/bindings/{bind_helloworld.cpp => bind.cpp} (60%) delete mode 100644 tests/geomodels/test_draft_pybind.py delete mode 100644 tests/geomodels/test_geomodel_template.py diff --git a/.pylintrc b/.pylintrc index ee0de2c..8a7193a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -30,7 +30,7 @@ suggestion-mode=yes unsafe-load-any-extension=no # List of cpp allowed extension -extension-pkg-whitelist= libs.pbhelloworld +extension-pkg-whitelist= shareloc.geomodels.pbrpc [MESSAGES CONTROL] diff --git a/Makefile b/Makefile index 4e5dd91..e581fa3 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,9 @@ install: venv ## install the package in dev mode in virtualenv [ "${CHECK_SHARELOC}" ] || ${VENV}/bin/python -m pip install -e .[dev,docs,notebook] @test -f .git/hooks/pre-commit || echo "Install pre-commit" @test -f .git/hooks/pre-commit || ${VENV}/bin/pre-commit install -t pre-commit - @test -f .git/hooks/pre-push || ${VENV}/bin/pre-commit install -t pre-push + @test -f .git/hooks/pre-push || ${VENV}/bin/pre-commit install -t pre-push + @g++ ascii_art.cpp -o ascii_art + @./ascii_art @echo "Shareloc ${VERSION} installed in dev mode in virtualenv ${VENV} with documentation" @echo " virtualenv usage : source ${VENV}/bin/activate; python3 -c 'import shareloc'" diff --git a/ascii_art.cpp b/ascii_art.cpp new file mode 100644 index 0000000..e6d26f6 --- /dev/null +++ b/ascii_art.cpp @@ -0,0 +1,17 @@ +#include + +int main() { + std::cout << "\n\n______________________________________________________________________________________________________________________ " << std::endl; + std::cout << " _____/\\\\\\\\\\\\\\\\\\\\\\____/\\\\\\______________________________________________________/\\\\\\\\\\\\________________________________ " << std::endl; + std::cout << " ___/\\\\\\/////////\\\\\\_\\/\\\\\\_____________________________________________________\\////\\\\\\________________________________ " << std::endl; + std::cout << " __\\//\\\\\\______\\///__\\/\\\\\\________________________________________________________\\/\\\\\\________________________________ " << std::endl; + std::cout << " ___\\////\\\\\\_________\\/\\\\\\__________/\\\\\\\\\\\\\\\\\\_____/\\\\/\\\\\\\\\\\\\\______/\\\\\\\\\\\\\\\\_____\\/\\\\\\________/\\\\\\\\\\________/\\\\\\\\\\\\\\\\_ " << std::endl; + std::cout << " ______\\////\\\\\\______\\/\\\\\\\\\\\\\\\\\\\\__\\////////\\\\\\___\\/\\\\\\/////\\\\\\___/\\\\\\/////\\\\\\____\\/\\\\\\______/\\\\\\///\\\\\\____/\\\\\\//////__ " << std::endl; + std::cout << " _________\\////\\\\\\___\\/\\\\\\/////\\\\\\___/\\\\\\\\\\\\\\\\\\\\__\\/\\\\\\___\\///___/\\\\\\\\\\\\\\\\\\\\\\_____\\/\\\\\\_____/\\\\\\__\\//\\\\\\__/\\\\\\_________ " << std::endl; + std::cout << " __/\\\\\\______\\//\\\\\\__\\/\\\\\\___\\/\\\\\\__/\\\\\\/////\\\\\\__\\/\\\\\\_________\\//\\\\///////______\\/\\\\\\____\\//\\\\\\__/\\\\\\__\\//\\\\\\________ " << std::endl; + std::cout << " _\\///\\\\\\\\\\\\\\\\\\\\\\/___\\/\\\\\\___\\/\\\\\\_\\//\\\\\\\\\\\\\\\\/\\\\_\\/\\\\\\__________\\//\\\\\\\\\\\\\\\\\\\\__/\\\\\\\\\\\\\\\\\\__\\///\\\\\\\\\\/____\\///\\\\\\\\\\\\\\\\_ " << std::endl; + std::cout << " ___\\///////////_____\\///____\\///___\\////////\\//__\\///____________\\//////////__\\/////////_____\\/////________\\////////__ " << std::endl; + std::cout << " ______________________________________________________________________________________________________________________\n\n" << std::endl; + return 0; +} + diff --git a/setup.py b/setup.py index f7b11ec..786d6de 100755 --- a/setup.py +++ b/setup.py @@ -23,16 +23,14 @@ import os # from setuptools.command.build_ext import build_ext -from pybind11.setup_helpers import Pybind11Extension, build_ext, intree_extensions -from setuptools import Extension, setup +from pybind11.setup_helpers import Pybind11Extension, build_ext +from setuptools import setup # create libs folder to contain .so files (if it doesn't exist) if not os.path.exists("libs"): os.makedirs("libs") # Main setup with setup.cfg file. -extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/bindings/bind_helloworld.cpp"])] - -# extensions = intree_extensions("pbrpc", ["shareloc/draft_pybind/hello_world.cpp"]) +extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/bindings/bind.cpp"])] setup(use_scm_version=True, cmdclass={"build_ext": build_ext}, ext_modules=extensions) diff --git a/shareloc/bindings/GeoModelTemplate.cpp b/shareloc/bindings/GeoModelTemplate.cpp index 4a4fb36..f38953f 100644 --- a/shareloc/bindings/GeoModelTemplate.cpp +++ b/shareloc/bindings/GeoModelTemplate.cpp @@ -1,4 +1,28 @@ +/* +!/usr/bin/env python +coding: utf8 +Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). + +This file is part of shareloc +(see https://github.com/CNES/shareloc). + +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. +*/ + +/** +Cpp copy of GeoModelTemplate.py +*/ GeoModelTemplate::GeoModelTemplate() { @@ -8,19 +32,29 @@ GeoModelTemplate::~GeoModelTemplate() { cout<<"GeoModelTemplate : destructor"<> GeoModelTemplate::direct_loc_h(vector row, vector col, double alt, bool fill_nan){ +vector> GeoModelTemplate::direct_loc_h( + vector row, + vector col, + double alt, + bool fill_nan){ cout<<"GeoModelTemplate : direct_loc_h"<> vect; return vect; } -vector> GeoModelTemplate::direct_loc_dtm(vector row, vector col, string dtm){ +vector> GeoModelTemplate::direct_loc_dtm( + vector row, + vector col, + string dtm){ cout<<"GeoModelTemplate : direct_loc_dtm"<> vect; return vect; } -tuple,vector,vector> GeoModelTemplate::inverse_loc(vector lon, vector lat, double alt){ +tuple,vector,vector> GeoModelTemplate::inverse_loc( + vector lon, + vector lat, + double alt){ cout<<"GeoModelTemplate : inverse_loc"<,vector,vector> res; return res; diff --git a/shareloc/bindings/GeoModelTemplate.hpp b/shareloc/bindings/GeoModelTemplate.hpp index f0da080..bc9be34 100644 --- a/shareloc/bindings/GeoModelTemplate.hpp +++ b/shareloc/bindings/GeoModelTemplate.hpp @@ -1,3 +1,31 @@ +/* +!/usr/bin/env python +coding: utf8 + +Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). + +This file is part of shareloc +(see https://github.com/CNES/shareloc). + +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. +*/ + +/** +Cpp copy of GeoModelTemplate.py +Abstract class GeoModelTemplate. + Child class: RPC +*/ + #include #include #include @@ -9,14 +37,24 @@ using std::vector; using std::string; using std::tuple; +/** +Abstract class GeoModelTemplate: + Child: RPC +*/ class GeoModelTemplate { + private: string geomodel_path; string type; public: + /**Constructor*/ GeoModelTemplate(); + /**Destructor*/ ~GeoModelTemplate(); + /**direct_loc_h*/ vector> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); + /**direct_loc_dtm*/ vector> direct_loc_dtm(vector row, vector col, string dtm); + /**inverse_loc*/ tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); }; diff --git a/shareloc/bindings/bind_helloworld.cpp b/shareloc/bindings/bind.cpp similarity index 60% rename from shareloc/bindings/bind_helloworld.cpp rename to shareloc/bindings/bind.cpp index 3538cff..c4af558 100644 --- a/shareloc/bindings/bind_helloworld.cpp +++ b/shareloc/bindings/bind.cpp @@ -1,3 +1,31 @@ +/* +!/usr/bin/env python +coding: utf8 + +Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). + +This file is part of shareloc +(see https://github.com/CNES/shareloc). + +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. +*/ + +/** +Thes purpose of this module is only to "bind" cpp code. +It gives to the compiler the instructions to compile the usefull cpp code into an .so file +which is callable in a python code as a python module. +*/ + #include #include #include "rpc.cpp" @@ -27,10 +55,12 @@ PYBIND11_MODULE(pbrpc, m) { //m.doc() = "Pybind hello world"; // optional module docstring m.def("parse_coeff_line", &parse_coeff_line, "TODO: doc"); m.def("polynomial_equation", &polynomial_equation, "TODO: doc"); - m.def("compute_rational_function_polynomial", &compute_rational_function_polynomial, "TODO: doc"); + m.def("compute_rational_function_polynomial", &compute_rational_function_polynomial, + "TODO: doc"); m.def("derivative_polynomial_latitude", &derivative_polynomial_latitude, "TODO: doc"); m.def("derivative_polynomial_longitude", &derivative_polynomial_longitude, "TODO: doc"); m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); } -//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file +//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp +//-o pbrpc$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index a692f29..810aa56 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -1,38 +1,91 @@ -//#include +/* +!/usr/bin/env python +coding: utf8 + +Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). + +This file is part of shareloc +(see https://github.com/CNES/shareloc). + +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. +*/ + +/** +Cpp copy of rpc.py +*/ #include "rpc.hpp" //---- RPC methodes ----// -vector> RPC::direct_loc_h(vector row, vector col, double alt, bool fill_nan){ +vector> RPC::direct_loc_h( + vector row, + vector col, + double alt, + bool fill_nan){ vector> vect; return vect; } -tuple>,vector>> RPC::direct_loc_grid_h(int row0, int col0, int steprow, int stepcol, int nbrow, int nbcol, double alt){ +tuple>,vector>> RPC::direct_loc_grid_h( + int row0, + int col0, + int steprow, + int stepcol, + int nbrow, + int nbcol, + double alt){ tuple>,vector>> res; return res; } -vector> RPC::direct_loc_dtm(double row, double col, string dtm){//////////////////////////////////////////////////////// dtm intersection model ->python class +vector> RPC::direct_loc_dtm( + double row, + double col, + string dtm){// dtm intersection model ->python class vector> vect; return vect; } -tuple,vector,vector> RPC::inverse_loc(vector lon, vector lat, double alt){} +tuple,vector,vector> RPC::inverse_loc( + vector lon, + vector lat, + double alt){} -vector> RPC::filter_coordinates(vector first_coord, vector second_coord, bool fill_nan, string direction){ +vector> RPC::filter_coordinates( + vector first_coord, + vector second_coord, + bool fill_nan, + string direction){ vector> vect; return vect; } -tuple,vector,vector,vector> RPC::compute_loc_inverse_derivates(vector lon, vector lat, vector alt){ +tuple,vector,vector,vector> RPC::compute_loc_inverse_derivates( + vector lon, + vector lat, + vector alt){ tuple,vector,vector,vector> res; return res; } -vector> RPC::direct_loc_inverse_iterative(vector row, vector col, double alt, int nb_iter_max, bool fill_nan){ +vector> RPC::direct_loc_inverse_iterative( + vector row, + vector col, + double alt, + int nb_iter_max, + bool fill_nan){ vector> vect; return vect; } @@ -42,7 +95,13 @@ vector RPC::get_alt_min_max(){ return vect; } -vector> RPC::los_extrema(double row, double col, double alt_min, double alt_max, bool fill_nan, int epsg){ +vector> RPC::los_extrema( + double row, + double col, + double alt_min, + double alt_max, + bool fill_nan, + int epsg){ vector> vect; return vect; } @@ -56,7 +115,11 @@ vector parse_coeff_line(string coeff_str){ } -double polynomial_equation(double xnorm, double ynorm, double znorm, vector coeff){ +double polynomial_equation( + double xnorm, + double ynorm, + double znorm, + vector coeff){ double res; return res; } @@ -80,13 +143,21 @@ tuple,vector> compute_rational_function_polynomial( } -double derivative_polynomial_latitude(double lon_norm, double lat_norm, double alt_norm, vector coeff){ +double derivative_polynomial_latitude( + double lon_norm, + double lat_norm, + double alt_norm, + vector coeff){ double res; return res; } -double derivative_polynomial_longitude(double lon_norm, double lat_norm, double alt_norm, vector coeff){ +double derivative_polynomial_longitude( + double lon_norm, + double lat_norm, + double alt_norm, + vector coeff){ double res; return res; } diff --git a/shareloc/bindings/rpc.hpp b/shareloc/bindings/rpc.hpp index 208815d..5f0018b 100644 --- a/shareloc/bindings/rpc.hpp +++ b/shareloc/bindings/rpc.hpp @@ -1,20 +1,48 @@ +/* +!/usr/bin/env python +coding: utf8 + +Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). + +This file is part of shareloc +(see https://github.com/CNES/shareloc). + +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. +*/ + +/** +Cpp copy of rpc.py +*/ + + #include "GeoModelTemplate.hpp" #include "GeoModelTemplate.cpp" #include using std::map; -/* +/** +Class RPC Squelette de la classe RPC.py de l'issue 221 - */ class RPC : public GeoModelTemplate{ + private: string type; int epsg; string datum; - map rpc_params; //map is a simple dict -> maybe inappropiate here + map rpc_params;//map is a simple dict -> maybe inappropiate here double lim_extrapol; vector> monomes; @@ -50,14 +78,65 @@ class RPC : public GeoModelTemplate{ public: using GeoModelTemplate::GeoModelTemplate; + /**direct_loc_h*/ + vector> direct_loc_h( + vector row, + vector col, + double alt, + bool fill_nan=false); + + /**direct_loc_grid_h*/ + tuple>,vector>> direct_loc_grid_h( + int row0, + int col0, + int steprow, + int stepcol, + int nbrow, + int nbcol, + double alt); + + /**direct_loc_dtm*/ + vector> direct_loc_dtm( + double row, + double col, + string dtm);// dtm is a python class not a string - vector> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); - tuple>,vector>> direct_loc_grid_h(int row0, int col0, int steprow, int stepcol, int nbrow, int nbcol, double alt); - vector> direct_loc_dtm(double row, double col, string dtm);// dtm is a python class not a string - tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); - vector> filter_coordinates(vector first_coord, vector second_coord, bool fill_nan=false, string direction="direct"); - tuple,vector,vector,vector> compute_loc_inverse_derivates(vector lon, vector lat, vector alt); - vector> direct_loc_inverse_iterative(vector row, vector col, double alt, int nb_iter_max=10, bool fill_nan=false); + /**inverse_loc*/ + tuple,vector,vector> inverse_loc( + vector lon, + vector lat, + double alt); + + /**filter_coordinates*/ + vector> filter_coordinates( + vector first_coord, + vector second_coord, + bool fill_nan=false, + string direction="direct"); + + /**compute_loc_inverse_derivates*/ + tuple,vector,vector,vector> compute_loc_inverse_derivates( + vector lon, + vector lat, + vector alt); + + /**direct_loc_inverse_iterative*/ + vector> direct_loc_inverse_iterative( + vector row, + vector col, + double alt, + int nb_iter_max=10, + bool fill_nan=false); + + /**get_alt_min_max*/ vector get_alt_min_max(); - vector> los_extrema(double row, double col, double alt_min, double alt_max, bool fill_nan=false, int epsg=4326); + + /**los_extrema*/ + vector> los_extrema( + double row, + double col, + double alt_min, + double alt_max, + bool fill_nan=false, + int epsg=4326); }; \ No newline at end of file diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 9804ae7..ecec9a2 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -23,23 +23,18 @@ It contains the structure for all coregistration methods in subclasses and generic coregistration code to avoid duplication. """ - -# Standard imports -from abc import ABCMeta, abstractmethod - # Global variable for optimization mode (functions in C) # SHARELOC_OPTIM_GEOMODEL = False # TODO: Override functions depending on optimization or not -# if(SHARELOC_OPTIM_GEOMODEL == True): -# GeoModelTemplate.direct_loc_dtm = GeoModelTemplate.direct_loc_dtm_optim +from abc import abstractmethod -class GeoModelTemplate(metaclass=ABCMeta): +class GeoModelTemplate: """ Class for general specification of a geometric model - declined in rpc.py and grid.py + declined in rpc.py and grid.py and rpc_optim.py """ @abstractmethod @@ -63,8 +58,8 @@ def direct_loc_h(self, row, col, alt, fill_nan=False): :param col: column sensor position :type col: float or 1D numpy.ndarray dtype=float64 :param alt: altitude - :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned - otherwise + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM) + nan is returned otherwise :type fill_nan: boolean :return: ground position (lon,lat,h) :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index 86fc021..a19ae82 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -49,8 +49,6 @@ class RPC(GeoModelTemplate): RPC class including direct and inverse localization instance methods """ - # gitlab issue #61 - # pylint: disable=too-many-instance-attributes # gitlab issue #61 # pylint: disable=too-many-instance-attributes def __init__(self, rpc_params): diff --git a/shareloc/geomodels/rpc_optim.py b/shareloc/geomodels/rpc_optim.py index 8be1ce1..68e20be 100755 --- a/shareloc/geomodels/rpc_optim.py +++ b/shareloc/geomodels/rpc_optim.py @@ -20,50 +20,57 @@ # limitations under the License. # """ -This module contains the RPC class corresponding to the RPC models. +This module contains the optimized (with cpp bindings) RPC class corresponding to the RPC models. RPC models covered are : DIMAP V1, DIMAP V2, DIMAP V3, ossim (geom file), geotiff. """ +# pylint: disable=abstract-method, c-extension-no-member -# Standard imports -import logging -from os.path import basename -from typing import Dict -from xml.dom import minidom + +# Cpp bidings imports +import sys # Third party imports -import numpy as np -import rasterio as rio -from numba import config, njit, prange +from numba import config # Shareloc imports from shareloc.geomodels.geomodel import GeoModel from shareloc.geomodels.geomodel_template import GeoModelTemplate -from shareloc.proj_utils import coordinates_conversion - +from shareloc.geomodels.rpc_readers import rpc_reader -import sys sys.path.append(".") -import libs.pbrpc as bind - +import libs.pbrpc as bind # noqa: E402 # pylint: disable=wrong-import-position # Set numba type of threading layer before parallel target compilation config.THREADING_LAYER = "omp" - -@GeoModel.register("RPC_optim") -class RPC_optim(bind.RPC,GeoModelTemplate): +@GeoModel.register("RpcOptim") +class RpcOptim(bind.RPC, GeoModelTemplate): """ RPC optimizes with cpp bindings class including direct and inverse localization instance methods """ - # pylint: disable=too-many-instance-attributes - def __init__(self,geomodel_path: str): - + def __init__(self, rpc_params): bind.RPC.__init__(self) - GeoModelTemplate.__init__(self,geomodel_path) + GeoModelTemplate.__init__(self) + + self.type = "RpcOptim" + + for key, value in rpc_params.items(): + setattr(self, key, value) + + @classmethod + def load(cls, geomodel_path): + """ + Load from any RPC (auto identify driver) + from filename (dimap, ossim kwl, geotiff) - self.type = "RPC_optim" + TODO: topleftconvention always to True, set a standard and remove the option - # RPC parameters are load from geomodel_path to rpc params - self.load() \ No newline at end of file + topleftconvention boolean: [0,0] position + If False : [0,0] is at the center of the Top Left pixel + If True : [0,0] is at the top left of the Top Left pixel (OSSIM) + """ + # Set topleftconvention (keeping historic option): to clean + cls.geomodel_path = geomodel_path + return cls(rpc_reader(geomodel_path, topleftconvention=True)) diff --git a/tests/geomodels/test_draft_pybind.py b/tests/geomodels/test_draft_pybind.py deleted file mode 100644 index 967cabf..0000000 --- a/tests/geomodels/test_draft_pybind.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Module sys to set python path to root""" -import sys - -sys.path.append(".") - -# pylint: disable=wrong-import-position -import libs.pbrpc as pbrpc # noqa: E402 - -# pylint: enable=wrong-import-position - - -def test_pbrpc(): - # TODO - - assert True diff --git a/tests/geomodels/test_geomodel_template.py b/tests/geomodels/test_geomodel_template.py deleted file mode 100644 index b219a45..0000000 --- a/tests/geomodels/test_geomodel_template.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python -# coding: utf8 -# -# Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). -# -# This file is part of Shareloc -# (see https://github.com/CNES/shareloc). -# -# 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. -# -""" -Module to test geomodel_template class loading capabilities -""" -# TODO: refactor to disable no-member in RPC class -# pylint: disable=no-member - - -# Standard imports -import os - -# Third party imports -import pytest -import rasterio -import json - -# Shareloc imports -from shareloc.geomodels.geomodel_template import GeoModelTemplate, identify_dimap, identify_ossim_kwl - -# Shareloc test imports -from ..helpers import data_path - - -def test_rpc_drivers(): - """ - test GeoModelTemplate driver identification - """ - data_folder = data_path() - - # Test DIMAP GeoModelTemplate - id_scene = "P1BP--2018122638935449CP" - file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - fctrat_dimap = GeoModelTemplate(file_dimap) - assert fctrat_dimap.rpc_params["driver_type"] == "dimap_v1.4" - - # Test OSSIM KWL GEOM GeoModelTemplate - id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" - file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - fctrat_geom = GeoModelTemplate(file_geom) - assert fctrat_geom.rpc_params["driver_type"] == "ossim_kwl" - - # Test DIMAPv2 GeoModelTemplate - id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" - file_dimap_v2 = os.path.join(data_folder, f"rpc/RPC_{id_scene}.XML") - fctrat_dimap_v2 = GeoModelTemplate(file_dimap_v2) - assert fctrat_dimap_v2.rpc_params["driver_type"] == "dimap_v2.15" - - # Test fake GeoModelTemplate - fake_rpc = os.path.join(data_folder, "rpc/fake_rpc.txt") - try: - GeoModelTemplate(fake_rpc) # Raise ValueError->True - raise AssertionError() # Assert false if no exception raised - except ValueError: - assert True - - -def test_identify_ossim_kwl(): - """ - test ossim file identification - """ - data_folder = data_path() - id_scene = "PHR1B_P_201709281038393_SEN_PRG_FC_178609-001" - file_geom = os.path.join(data_folder, f"rpc/{id_scene}.geom") - ossim_model = identify_ossim_kwl(file_geom) - assert ossim_model == "ossimPleiadesModel" - - -def test_identify_dimap(): - """ - test dimap file identification - """ - data_folder = data_path() - id_scene = "P1BP--2018122638935449CP" - file_dimap = os.path.join(data_folder, f"rpc/PHRDIMAP_{id_scene}.XML") - dimap_version = identify_dimap(file_dimap) - assert dimap_version == "1.4" - - - -@pytest.mark.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning) -@pytest.mark.parametrize( - "prod, can_read", - [ - ("PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", True), - ("PHR1B_P_201709281038393_SEN_PRG_FC_178609-001_nogeo.tif", False), - ], -) -def test_GeoModelTemplate_from_geotiff_without_rpc(prod, can_read): - """ - test geotiff file without rpc - """ - data_folder = data_path() - rpc_file = os.path.join(data_folder, "rpc", prod) - try: - GeoModelTemplate(rpc_file) - assert can_read - except ValueError: - assert not can_read - - -def test_load_rpc_params(): - """ - test loading of rpc_params dict - """ - geom = GeoModelTemplate("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom").rpc_params - tif = GeoModelTemplate("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif").rpc_params - PHRDIMAP = GeoModelTemplate("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML").rpc_params - RPC_P1BP = GeoModelTemplate("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML").rpc_params - RPC_PHR1B = GeoModelTemplate("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML").rpc_params - - - a = open("tests/data/geomodel_template/geom.json") - geom_ref = json.load(a) - b = open("tests/data/geomodel_template/tif.json") - tif_ref = json.load(b) - c = open("tests/data/geomodel_template/PHRDIMAP.json") - PHRDIMAP_ref = json.load(c) - d = open("tests/data/geomodel_template/RPC_P1BP.json") - RPC_P1BP_ref = json.load(d) - e = open("tests/data/geomodel_template/RPC_PHR1B.json") - RPC_PHR1B_ref = json.load(e) - - assert geom == geom_ref - assert tif == tif_ref - assert PHRDIMAP == PHRDIMAP_ref - assert RPC_P1BP == RPC_P1BP_ref - assert RPC_PHR1B == RPC_PHR1B_ref - - a.close() - b.close() - c.close() - d.close() - e.close() \ No newline at end of file diff --git a/tests/geomodels/test_rpc_optim.py b/tests/geomodels/test_rpc_optim.py index c0d4672..a040ea4 100644 --- a/tests/geomodels/test_rpc_optim.py +++ b/tests/geomodels/test_rpc_optim.py @@ -19,41 +19,44 @@ # limitations under the License. # """ -Module to test RPC_optim class +Module to test RpcOptim class """ -# pylint: disable=no-member +# pylint: disable=no-member, invalid-name # Third party imports import json -# Shareloc imports -from shareloc.geomodels.rpc_optim import RPC_optim - - +from shareloc.geomodels import GeoModel +from shareloc.geomodels.rpc_optim import RpcOptim # noqa : F401 # pylint: disable=unused-import def test_load_rpc_params(): """ test loading of rpc_params dict """ - geom = RPC_optim("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom").rpc_params - tif = RPC_optim("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif").rpc_params - PHRDIMAP = RPC_optim("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML").rpc_params - RPC_P1BP = RPC_optim("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML").rpc_params - RPC_PHR1B = RPC_optim("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML").rpc_params - - - a = open("tests/data/geomodel_template/geom.json") - geom_ref = json.load(a) - b = open("tests/data/geomodel_template/tif.json") - tif_ref = json.load(b) - c = open("tests/data/geomodel_template/PHRDIMAP.json") - PHRDIMAP_ref = json.load(c) - d = open("tests/data/geomodel_template/RPC_P1BP.json") - RPC_P1BP_ref = json.load(d) - e = open("tests/data/geomodel_template/RPC_PHR1B.json") - RPC_PHR1B_ref = json.load(e) + geom = GeoModel("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", "RpcOptim").__dict__ + tif = GeoModel("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", "RpcOptim").__dict__ + PHRDIMAP = GeoModel("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML", "RpcOptim").__dict__ + RPC_P1BP = GeoModel("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML", "RpcOptim").__dict__ + RPC_PHR1B = GeoModel("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", "RpcOptim").__dict__ + + del geom["type"] + del tif["type"] + del PHRDIMAP["type"] + del RPC_P1BP["type"] + del RPC_PHR1B["type"] + + with open("tests/data/geomodel_template/geom.json", "r", encoding="utf-8") as f: + geom_ref = json.load(f) + with open("tests/data/geomodel_template/tif.json", "r", encoding="utf-8") as f: + tif_ref = json.load(f) + with open("tests/data/geomodel_template/PHRDIMAP.json", "r", encoding="utf-8") as f: + PHRDIMAP_ref = json.load(f) + with open("tests/data/geomodel_template/RPC_P1BP.json", "r", encoding="utf-8") as f: + RPC_P1BP_ref = json.load(f) + with open("tests/data/geomodel_template/RPC_PHR1B.json", "r", encoding="utf-8") as f: + RPC_PHR1B_ref = json.load(f) assert geom == geom_ref assert tif == tif_ref @@ -61,36 +64,25 @@ def test_load_rpc_params(): assert RPC_P1BP == RPC_P1BP_ref assert RPC_PHR1B == RPC_PHR1B_ref - a.close() - b.close() - c.close() - d.close() - e.close() def test_function_rpc_cpp(): """ Test call to methode parent class rpc in cpp """ - rpc = RPC_optim("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom") + rpc = GeoModel("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", "RpcOptim") - vector_double = [1.,1.,1.] - double = 1. - int = 1 + vector_double = [1.0, 1.0, 1.0] + double = 1.0 + integer = 1 string = "peuimporte" - try: - rpc.direct_loc_h(vector_double,vector_double, double, False) - rpc.direct_loc_grid_h(int, int, int, int, int, int, double ) - rpc.direct_loc_dtm(double, double, string) - rpc.inverse_loc(vector_double, vector_double, double) - rpc.filter_coordinates(vector_double, vector_double, False, string ) - rpc.compute_loc_inverse_derivates(vector_double, vector_double, vector_double) - rpc.direct_loc_inverse_iterative(vector_double, vector_double, double, int, False) - rpc.get_alt_min_max() - rpc.los_extrema(double, double, double , double, False, int) - - except: - assert False - - assert True \ No newline at end of file + rpc.direct_loc_h(vector_double, vector_double, double, False) + rpc.direct_loc_grid_h(integer, integer, integer, integer, integer, integer, double) + rpc.direct_loc_dtm(double, double, string) + rpc.inverse_loc(vector_double, vector_double, double) + rpc.filter_coordinates(vector_double, vector_double, False, string) + rpc.compute_loc_inverse_derivates(vector_double, vector_double, vector_double) + rpc.direct_loc_inverse_iterative(vector_double, vector_double, double, integer, False) + rpc.get_alt_min_max() + rpc.los_extrema(double, double, double, double, False, integer) From 3ad171986c9b9a03724307184c2a7d67d534469c Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Tue, 5 Dec 2023 10:49:50 +0100 Subject: [PATCH 14/18] refactor: move .so to root & rename it --- .pylintrc | 2 +- Makefile | 2 -- ascii_art.cpp | 17 -------------- setup.cfg | 2 ++ setup.py | 9 +------- shareloc/bindings/bind.cpp | 9 ++++---- shareloc/bindings/rpc.cpp | 38 +++++++++++++++++++++++++++++-- shareloc/bindings/rpc.hpp | 7 +++++- shareloc/geomodels/__init__.py | 10 ++++---- shareloc/geomodels/rpc_optim.py | 11 +++------ tests/geomodels/test_rpc_optim.py | 28 +++++++++++------------ 11 files changed, 73 insertions(+), 62 deletions(-) delete mode 100644 ascii_art.cpp diff --git a/.pylintrc b/.pylintrc index 8a7193a..9584de3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -30,7 +30,7 @@ suggestion-mode=yes unsafe-load-any-extension=no # List of cpp allowed extension -extension-pkg-whitelist= shareloc.geomodels.pbrpc +extension-pkg-whitelist= rpc_c [MESSAGES CONTROL] diff --git a/Makefile b/Makefile index e581fa3..4bc05d4 100644 --- a/Makefile +++ b/Makefile @@ -86,8 +86,6 @@ install: venv ## install the package in dev mode in virtualenv @test -f .git/hooks/pre-commit || echo "Install pre-commit" @test -f .git/hooks/pre-commit || ${VENV}/bin/pre-commit install -t pre-commit @test -f .git/hooks/pre-push || ${VENV}/bin/pre-commit install -t pre-push - @g++ ascii_art.cpp -o ascii_art - @./ascii_art @echo "Shareloc ${VERSION} installed in dev mode in virtualenv ${VENV} with documentation" @echo " virtualenv usage : source ${VENV}/bin/activate; python3 -c 'import shareloc'" diff --git a/ascii_art.cpp b/ascii_art.cpp deleted file mode 100644 index e6d26f6..0000000 --- a/ascii_art.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - -int main() { - std::cout << "\n\n______________________________________________________________________________________________________________________ " << std::endl; - std::cout << " _____/\\\\\\\\\\\\\\\\\\\\\\____/\\\\\\______________________________________________________/\\\\\\\\\\\\________________________________ " << std::endl; - std::cout << " ___/\\\\\\/////////\\\\\\_\\/\\\\\\_____________________________________________________\\////\\\\\\________________________________ " << std::endl; - std::cout << " __\\//\\\\\\______\\///__\\/\\\\\\________________________________________________________\\/\\\\\\________________________________ " << std::endl; - std::cout << " ___\\////\\\\\\_________\\/\\\\\\__________/\\\\\\\\\\\\\\\\\\_____/\\\\/\\\\\\\\\\\\\\______/\\\\\\\\\\\\\\\\_____\\/\\\\\\________/\\\\\\\\\\________/\\\\\\\\\\\\\\\\_ " << std::endl; - std::cout << " ______\\////\\\\\\______\\/\\\\\\\\\\\\\\\\\\\\__\\////////\\\\\\___\\/\\\\\\/////\\\\\\___/\\\\\\/////\\\\\\____\\/\\\\\\______/\\\\\\///\\\\\\____/\\\\\\//////__ " << std::endl; - std::cout << " _________\\////\\\\\\___\\/\\\\\\/////\\\\\\___/\\\\\\\\\\\\\\\\\\\\__\\/\\\\\\___\\///___/\\\\\\\\\\\\\\\\\\\\\\_____\\/\\\\\\_____/\\\\\\__\\//\\\\\\__/\\\\\\_________ " << std::endl; - std::cout << " __/\\\\\\______\\//\\\\\\__\\/\\\\\\___\\/\\\\\\__/\\\\\\/////\\\\\\__\\/\\\\\\_________\\//\\\\///////______\\/\\\\\\____\\//\\\\\\__/\\\\\\__\\//\\\\\\________ " << std::endl; - std::cout << " _\\///\\\\\\\\\\\\\\\\\\\\\\/___\\/\\\\\\___\\/\\\\\\_\\//\\\\\\\\\\\\\\\\/\\\\_\\/\\\\\\__________\\//\\\\\\\\\\\\\\\\\\\\__/\\\\\\\\\\\\\\\\\\__\\///\\\\\\\\\\/____\\///\\\\\\\\\\\\\\\\_ " << std::endl; - std::cout << " ___\\///////////_____\\///____\\///___\\////////\\//__\\///____________\\//////////__\\/////////_____\\/////________\\////////__ " << std::endl; - std::cout << " ______________________________________________________________________________________________________________________\n\n" << std::endl; - return 0; -} - diff --git a/setup.cfg b/setup.cfg index b45ff22..7ee55d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,6 +53,7 @@ setup_requires = setuptools>=65.5 setuptools_scm[toml]>=6.2 # Following https://pypi.org/project/setuptools-scm/ wheel + pybind11 # shareloc packages dependencies install_requires = @@ -87,6 +88,7 @@ dev = pytest-sugar tox mypy + pybind11 # [doc] mode dependencies docs = diff --git a/setup.py b/setup.py index 786d6de..86efd13 100755 --- a/setup.py +++ b/setup.py @@ -20,17 +20,10 @@ Shareloc Setup.py kept for compatibility and setuptools_scm configuration. Main part is in setup.cfg file. """ -import os -# from setuptools.command.build_ext import build_ext from pybind11.setup_helpers import Pybind11Extension, build_ext from setuptools import setup -# create libs folder to contain .so files (if it doesn't exist) -if not os.path.exists("libs"): - os.makedirs("libs") - -# Main setup with setup.cfg file. -extensions = [Pybind11Extension("libs.pbrpc", ["shareloc/bindings/bind.cpp"])] +extensions = [Pybind11Extension("rpc_c", ["shareloc/bindings/bind.cpp"])] setup(use_scm_version=True, cmdclass={"build_ext": build_ext}, ext_modules=extensions) diff --git a/shareloc/bindings/bind.cpp b/shareloc/bindings/bind.cpp index c4af558..13e3142 100644 --- a/shareloc/bindings/bind.cpp +++ b/shareloc/bindings/bind.cpp @@ -33,7 +33,7 @@ which is callable in a python code as a python module. namespace py = pybind11; -PYBIND11_MODULE(pbrpc, m) { +PYBIND11_MODULE(rpc_c, m) { py::class_(m, "GeoModelTemplate") .def(py::init<>()) @@ -50,7 +50,8 @@ PYBIND11_MODULE(pbrpc, m) { .def("compute_loc_inverse_derivates", &RPC::compute_loc_inverse_derivates) .def("direct_loc_inverse_iterative", &RPC::direct_loc_inverse_iterative) .def("get_alt_min_max", &RPC::get_alt_min_max) - .def("los_extrema", &RPC::los_extrema); + .def("los_extrema", &RPC::los_extrema) + .def("method_arg_cpp", &RPC::method_arg_cpp); //m.doc() = "Pybind hello world"; // optional module docstring m.def("parse_coeff_line", &parse_coeff_line, "TODO: doc"); @@ -60,7 +61,7 @@ PYBIND11_MODULE(pbrpc, m) { m.def("derivative_polynomial_latitude", &derivative_polynomial_latitude, "TODO: doc"); m.def("derivative_polynomial_longitude", &derivative_polynomial_longitude, "TODO: doc"); m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); + m.def("FuncArgClassCpp", &FuncArgClassCpp, "TODO: doc"); } -//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind_helloworld.cpp -//-o pbrpc$(python3-config --extension-suffix) \ No newline at end of file +//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index 810aa56..98a6ff2 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -60,7 +60,10 @@ vector> RPC::direct_loc_dtm( tuple,vector,vector> RPC::inverse_loc( vector lon, vector lat, - double alt){} + double alt){ + tuple,vector,vector> vect; + return vect; + } vector> RPC::filter_coordinates( @@ -106,6 +109,22 @@ vector> RPC::los_extrema( return vect; } +//---- Demo ----// + +GeoModelTemplate RPC::method_arg_cpp( + GeoModelTemplate geomt +){ + cout<<"--- DEBUT method_arg_cpp ---\n"<Cette méthode de la classe RPC C++ prend un argument un objet de la classe C++ GeoModelTemplate, et appel sa méthode direct_loc_h"<> vect; + vector row; + vector col; + double alt; + vect = geomt.direct_loc_h(row,col,alt,false); + cout<<"\n--- FIN method_arg_cpp ---\n"<,vector,vector,vector> compute_loc_i ){ tuple,vector,vector,vector> res; return res; -} \ No newline at end of file +} + + +void FuncArgClassCpp(GeoModelTemplate class_cpp){ + + cout<<"\n--- Début de FuncArgClassCpp ---\n"< row; + vector col; + double alt; + vector> res; + + res = class_cpp.direct_loc_h(row, col, alt, false); + cout<<"\n--- Fin de FuncArgClassCpp ---\n"< Date: Tue, 5 Dec 2023 12:00:54 +0100 Subject: [PATCH 15/18] refactor: remove demo function/method --- shareloc/bindings/bind.cpp | 4 +--- shareloc/bindings/rpc.cpp | 34 +--------------------------------- shareloc/bindings/rpc.hpp | 4 ---- 3 files changed, 2 insertions(+), 40 deletions(-) diff --git a/shareloc/bindings/bind.cpp b/shareloc/bindings/bind.cpp index 13e3142..9ab5264 100644 --- a/shareloc/bindings/bind.cpp +++ b/shareloc/bindings/bind.cpp @@ -50,8 +50,7 @@ PYBIND11_MODULE(rpc_c, m) { .def("compute_loc_inverse_derivates", &RPC::compute_loc_inverse_derivates) .def("direct_loc_inverse_iterative", &RPC::direct_loc_inverse_iterative) .def("get_alt_min_max", &RPC::get_alt_min_max) - .def("los_extrema", &RPC::los_extrema) - .def("method_arg_cpp", &RPC::method_arg_cpp); + .def("los_extrema", &RPC::los_extrema); //m.doc() = "Pybind hello world"; // optional module docstring m.def("parse_coeff_line", &parse_coeff_line, "TODO: doc"); @@ -61,7 +60,6 @@ PYBIND11_MODULE(rpc_c, m) { m.def("derivative_polynomial_latitude", &derivative_polynomial_latitude, "TODO: doc"); m.def("derivative_polynomial_longitude", &derivative_polynomial_longitude, "TODO: doc"); m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); - m.def("FuncArgClassCpp", &FuncArgClassCpp, "TODO: doc"); } //c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index 98a6ff2..60ba84b 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -109,23 +109,6 @@ vector> RPC::los_extrema( return vect; } -//---- Demo ----// - -GeoModelTemplate RPC::method_arg_cpp( - GeoModelTemplate geomt -){ - cout<<"--- DEBUT method_arg_cpp ---\n"<Cette méthode de la classe RPC C++ prend un argument un objet de la classe C++ GeoModelTemplate, et appel sa méthode direct_loc_h"<> vect; - vector row; - vector col; - double alt; - vect = geomt.direct_loc_h(row,col,alt,false); - cout<<"\n--- FIN method_arg_cpp ---\n"< parse_coeff_line(string coeff_str){ @@ -197,19 +180,4 @@ tuple,vector,vector,vector> compute_loc_i ){ tuple,vector,vector,vector> res; return res; -} - - -void FuncArgClassCpp(GeoModelTemplate class_cpp){ - - cout<<"\n--- Début de FuncArgClassCpp ---\n"< row; - vector col; - double alt; - vector> res; - - res = class_cpp.direct_loc_h(row, col, alt, false); - cout<<"\n--- Fin de FuncArgClassCpp ---\n"< Date: Wed, 6 Dec 2023 15:50:13 +0100 Subject: [PATCH 16/18] refactor: update to new master + improve test coverage Refactor:update to new master + improve test coverage --- shareloc/bindings/GeoModelTemplate.cpp | 6 +- shareloc/bindings/GeoModelTemplate.hpp | 18 +- shareloc/bindings/bind.cpp | 4 +- shareloc/bindings/rpc.cpp | 16 +- shareloc/bindings/rpc.hpp | 12 +- shareloc/geomodels/__init__.py | 10 +- shareloc/geomodels/geomodel_template.py | 10 +- shareloc/geomodels/rpc_optim.py | 127 ++++++++++++- tests/data/geomodel_template/PHRDIMAP.json | 189 -------------------- tests/data/geomodel_template/RPC_P1BP.json | 189 -------------------- tests/data/geomodel_template/RPC_PHR1B.json | 189 -------------------- tests/data/geomodel_template/geom.json | 105 ----------- tests/data/geomodel_template/tif.json | 104 ----------- tests/geomodels/test_rpc_optim.py | 101 +++++++---- 14 files changed, 241 insertions(+), 839 deletions(-) delete mode 100644 tests/data/geomodel_template/PHRDIMAP.json delete mode 100644 tests/data/geomodel_template/RPC_P1BP.json delete mode 100644 tests/data/geomodel_template/RPC_PHR1B.json delete mode 100644 tests/data/geomodel_template/geom.json delete mode 100644 tests/data/geomodel_template/tif.json diff --git a/shareloc/bindings/GeoModelTemplate.cpp b/shareloc/bindings/GeoModelTemplate.cpp index f38953f..e2e0d3e 100644 --- a/shareloc/bindings/GeoModelTemplate.cpp +++ b/shareloc/bindings/GeoModelTemplate.cpp @@ -26,10 +26,9 @@ Cpp copy of GeoModelTemplate.py GeoModelTemplate::GeoModelTemplate() { - cout<<"GeoModelTemplate : constructor"<> GeoModelTemplate::direct_loc_h( @@ -37,7 +36,6 @@ vector> GeoModelTemplate::direct_loc_h( vector col, double alt, bool fill_nan){ - cout<<"GeoModelTemplate : direct_loc_h"<> vect; return vect; } @@ -46,7 +44,6 @@ vector> GeoModelTemplate::direct_loc_dtm( vector row, vector col, string dtm){ - cout<<"GeoModelTemplate : direct_loc_dtm"<> vect; return vect; } @@ -55,7 +52,6 @@ tuple,vector,vector> GeoModelTemplate::inverse_lo vector lon, vector lat, double alt){ - cout<<"GeoModelTemplate : inverse_loc"<,vector,vector> res; return res; } \ No newline at end of file diff --git a/shareloc/bindings/GeoModelTemplate.hpp b/shareloc/bindings/GeoModelTemplate.hpp index bc9be34..c642d95 100644 --- a/shareloc/bindings/GeoModelTemplate.hpp +++ b/shareloc/bindings/GeoModelTemplate.hpp @@ -44,17 +44,27 @@ Abstract class GeoModelTemplate: class GeoModelTemplate { private: - string geomodel_path; string type; + int epsg; public: /**Constructor*/ GeoModelTemplate(); /**Destructor*/ ~GeoModelTemplate(); /**direct_loc_h*/ - vector> direct_loc_h(vector row, vector col, double alt, bool fill_nan=false); + vector> direct_loc_h( + vector row, + vector col, + double alt, + bool fill_nan=false); /**direct_loc_dtm*/ - vector> direct_loc_dtm(vector row, vector col, string dtm); + vector> direct_loc_dtm( + vector row, + vector col, + string dtm); /**inverse_loc*/ - tuple,vector,vector> inverse_loc(vector lon, vector lat, double alt); + tuple,vector,vector> inverse_loc( + vector lon, + vector lat, + double alt); }; diff --git a/shareloc/bindings/bind.cpp b/shareloc/bindings/bind.cpp index 9ab5264..ffdf5a2 100644 --- a/shareloc/bindings/bind.cpp +++ b/shareloc/bindings/bind.cpp @@ -53,7 +53,6 @@ PYBIND11_MODULE(rpc_c, m) { .def("los_extrema", &RPC::los_extrema); //m.doc() = "Pybind hello world"; // optional module docstring - m.def("parse_coeff_line", &parse_coeff_line, "TODO: doc"); m.def("polynomial_equation", &polynomial_equation, "TODO: doc"); m.def("compute_rational_function_polynomial", &compute_rational_function_polynomial, "TODO: doc"); @@ -62,4 +61,5 @@ PYBIND11_MODULE(rpc_c, m) { m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); } -//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) bind.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file +//c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) +//bind.cpp -o pbrpc$(python3-config --extension-suffix) \ No newline at end of file diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index 60ba84b..d979819 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -75,7 +75,10 @@ vector> RPC::filter_coordinates( return vect; } -tuple,vector,vector,vector> RPC::compute_loc_inverse_derivates( +tuple, +vector, +vector, +vector> RPC::compute_loc_inverse_derivates( vector lon, vector lat, vector alt){ @@ -111,12 +114,6 @@ vector> RPC::los_extrema( //---- Functions ----// -vector parse_coeff_line(string coeff_str){ - vector vect; - return vect; -} - - double polynomial_equation( double xnorm, double ynorm, @@ -165,7 +162,10 @@ double derivative_polynomial_longitude( } -tuple,vector,vector,vector> compute_loc_inverse_derivates_numba( +tuple, +vector, +vector, +vector> compute_loc_inverse_derivates_numba( vector lon_norm, vector lat_norm, vector alt_norm, diff --git a/shareloc/bindings/rpc.hpp b/shareloc/bindings/rpc.hpp index 758cb7d..5b8b24d 100644 --- a/shareloc/bindings/rpc.hpp +++ b/shareloc/bindings/rpc.hpp @@ -39,6 +39,7 @@ Squelette de la classe RPC.py de l'issue 221 class RPC : public GeoModelTemplate{ private: + string type; int epsg; string datum; @@ -75,7 +76,11 @@ class RPC : public GeoModelTemplate{ double scale_col; double offset_alt; double scale_alt; - + double offset_x; + double scale_x; + double offset_y; + double scale_y; + public: using GeoModelTemplate::GeoModelTemplate; /**direct_loc_h*/ @@ -115,7 +120,10 @@ class RPC : public GeoModelTemplate{ string direction="direct"); /**compute_loc_inverse_derivates*/ - tuple,vector,vector,vector> compute_loc_inverse_derivates( + tuple, + vector, + vector, + vector> compute_loc_inverse_derivates( vector lon, vector lat, vector alt); diff --git a/shareloc/geomodels/__init__.py b/shareloc/geomodels/__init__.py index 00772b2..577d60a 100644 --- a/shareloc/geomodels/__init__.py +++ b/shareloc/geomodels/__init__.py @@ -20,10 +20,12 @@ # """ Shareloc geomodels module -Imports are used to simplify calls to module API Coregistration. +Imports are used to simplify calls to module API. """ -# Demcompare imports -from . import grid, rpc + +import rpc_c + +from . import grid, rpc, rpc_optim from .geomodel import GeoModel -__all__ = ["rpc", "grid", "GeoModel"] # To avoid flake8 F401 +__all__ = ["rpc", "grid", "GeoModel", "rpc_c", "rpc_optim"] # To avoid flake8 F401 diff --git a/shareloc/geomodels/geomodel_template.py b/shareloc/geomodels/geomodel_template.py index 93e4e7c..440abe3 100644 --- a/shareloc/geomodels/geomodel_template.py +++ b/shareloc/geomodels/geomodel_template.py @@ -19,19 +19,17 @@ # limitations under the License. # """ -This module contains the coregistration class template. -It contains the structure for all coregistration methods in subclasses and -generic coregistration code to avoid duplication. +This module contains the GeoModel abstract class """ # Standard imports -from abc import ABCMeta, abstractmethod +from abc import abstractmethod -class GeoModelTemplate(metaclass=ABCMeta): +class GeoModelTemplate: """ Class for general specification of a geometric model - declined in rpc.py and grid.py + declined in rpc.py and grid.py and rpc_optim.py """ @abstractmethod diff --git a/shareloc/geomodels/rpc_optim.py b/shareloc/geomodels/rpc_optim.py index a794a35..42a089d 100755 --- a/shareloc/geomodels/rpc_optim.py +++ b/shareloc/geomodels/rpc_optim.py @@ -25,6 +25,8 @@ """ +import numpy as np + # Third party imports from numba import config @@ -39,21 +41,144 @@ config.THREADING_LAYER = "omp" +# pylint: disable=duplicate-code @GeoModel.register("RpcOptim") class RpcOptim(bind.RPC, GeoModelTemplate): """ RPC optimized with cpp bindings class including direct and inverse localization instance methods """ + # pylint: disable=too-many-instance-attributes def __init__(self, rpc_params): bind.RPC.__init__(self) GeoModelTemplate.__init__(self) - self.type = "RpcOptim" + self.offset_alt = None + self.scale_alt = None + self.offset_col = None + self.scale_col = None + self.offset_row = None + self.scale_row = None + self.offset_x = None + self.scale_x = None + self.offset_y = None + self.scale_y = None + self.datum = None for key, value in rpc_params.items(): setattr(self, key, value) + self.type = "RpcOptim" + if self.epsg is None: + self.epsg = 4326 + if self.datum is None: + self.datum = "ellipsoid" + + self.lim_extrapol = 1.0001 + + # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] + monomes_order = [ + [1, 0, 0, 0], + [1, 1, 0, 0], + [1, 0, 1, 0], + [1, 0, 0, 1], + [1, 1, 1, 0], + [1, 1, 0, 1], + [1, 0, 1, 1], + [1, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [1, 1, 1, 1], + [1, 3, 0, 0], + [1, 1, 2, 0], + [1, 1, 0, 2], + [1, 2, 1, 0], + [1, 0, 3, 0], + [1, 0, 1, 2], + [1, 2, 0, 1], + [1, 0, 2, 1], + [1, 0, 0, 3], + ] + + self.monomes = np.array(monomes_order) + + # monomial coefficients of 1st variable derivative + self.monomes_deriv_1 = np.array( + [ + [0, 0, 0, 0], + [1, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 1, 0], + [1, 0, 0, 1], + [0, 0, 1, 1], + [2, 1, 0, 0], + [0, 0, 2, 0], + [0, 0, 0, 2], + [1, 0, 1, 1], + [3, 2, 0, 0], + [1, 0, 2, 0], + [1, 0, 0, 2], + [2, 1, 1, 0], + [0, 0, 3, 0], + [0, 0, 1, 2], + [2, 1, 0, 1], + [0, 0, 2, 1], + [0, 0, 0, 3], + ] + ) + + # monomial coefficients of 2nd variable derivative + self.monomes_deriv_2 = np.array( + [ + [0, 0, 0, 0], + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 1], + [1, 1, 0, 0], + [0, 1, 0, 1], + [1, 0, 0, 1], + [0, 2, 0, 0], + [2, 0, 1, 0], + [0, 0, 0, 2], + [1, 1, 0, 1], + [0, 3, 0, 0], + [2, 1, 1, 0], + [0, 1, 0, 2], + [1, 2, 0, 0], + [3, 0, 2, 0], + [1, 0, 0, 2], + [0, 2, 0, 1], + [2, 0, 1, 1], + [0, 0, 0, 3], + ] + ) + + self.inverse_coefficient = False + self.direct_coefficient = False + + # pylint: disable=access-member-before-definition + if self.num_col: + self.inverse_coefficient = True + self.num_col = np.array(self.num_col) + self.den_col = np.array(self.den_col) + self.num_row = np.array(self.num_row) + self.den_row = np.array(self.den_row) + + # pylint: disable=access-member-before-definition + if self.num_x: + self.direct_coefficient = True + self.num_x = np.array(self.num_x) + self.den_x = np.array(self.den_x) + self.num_y = np.array(self.num_y) + self.den_y = np.array(self.den_y) + + self.alt_minmax = [self.offset_alt - self.scale_alt, self.offset_alt + self.scale_alt] + self.col0 = self.offset_col - self.scale_col + self.colmax = self.offset_col + self.scale_col + self.row0 = self.offset_row - self.scale_row + self.rowmax = self.offset_row + self.scale_row + @classmethod def load(cls, geomodel_path): """ diff --git a/tests/data/geomodel_template/PHRDIMAP.json b/tests/data/geomodel_template/PHRDIMAP.json deleted file mode 100644 index 2b38345..0000000 --- a/tests/data/geomodel_template/PHRDIMAP.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "driver_type": "dimap_v1.4", - "offset_col": 19975.5, - "scale_col": 19975.0, - "offset_row": 24913.0, - "scale_row": 24912.5, - "offset_alt": 200.0, - "scale_alt": 40.0, - "offset_x": 57.35073017142889, - "scale_x": 0.1342591901858867, - "offset_y": 22.0291099403746, - "scale_y": 0.1084766764916285, - "num_x": [ - 0.000703893395985555, - 0.122905857488065, - 0.876559376408557, - -8.323090227052e-05, - -0.000462721782091184, - -3.67424468481082e-06, - -3.8056161110986e-05, - -3.41631587645708e-06, - -0.000294051117854886, - 3.07042607251168e-08, - -2.7604425475108e-08, - 2.94762646473875e-06, - 0.000123433363098949, - 5.59948772965393e-06, - 4.94979878825677e-05, - 0.000143095763157488, - 3.99185456713098e-05, - -6.54044851160337e-09, - 1.94781598972765e-09, - -3.79162665683283e-09 - ], - "den_x": [ - 1.0, - 0.00031914114902447, - -0.000302428047982454, - 3.38714069203248e-05, - -6.82917109014923e-05, - 5.61440872175384e-09, - 1.28777740954338e-08, - 1.15471708343759e-05, - -7.57635535266372e-05, - 4.55262727382826e-05, - 2.74194884037076e-09, - 6.5602753668795e-08, - 2.16056853784428e-06, - 3.61425004935014e-08, - -5.62131392044912e-08, - 4.30246706778879e-07, - 1.65604538554398e-09, - 2.77076883157817e-09, - 4.05008391149373e-09, - 3.51431468137533e-09 - ], - "num_y": [ - -0.000626661725164599, - 0.821061936711914, - -0.178369587507222, - 0.000280064982137338, - -0.0011420682239078, - -3.58899959862579e-05, - -5.65033199724358e-05, - -0.000430306162614323, - -0.000209182658650528, - -2.97747533959253e-08, - 7.1133290540898e-08, - 6.5302309614961e-05, - 0.000296244103385013, - 3.41578935239657e-05, - 0.000177439335375207, - -5.61547794560041e-05, - -7.41867052771127e-06, - 2.02795975956178e-08, - -5.26377311109581e-08, - 1.16486571878642e-08 - ], - "den_y": [ - 1.0, - 0.000664075249885829, - 0.000787737867482079, - 1.95553161999737e-05, - -6.34290181409911e-05, - -2.09111441196011e-08, - 7.49685233119868e-08, - 6.71956927888057e-05, - -0.000178783971165721, - 4.15945672120287e-05, - 1.73143045687708e-08, - 1.21572890629599e-07, - 9.30480789936818e-07, - 4.83476163789087e-08, - 6.81771409422775e-07, - -5.5091769191373e-07, - 9.11177226198066e-08, - 5.61598915283857e-09, - 1.71705095405644e-08, - 1.95349869420542e-09 - ], - "num_col": [ - 0.000571359522062809, - 0.240508664651831, - 1.18193080365322, - -0.000311064822796779, - 0.0011907097906364, - 9.3092386457989e-05, - 1.48841873767377e-05, - 0.000355736784630572, - 2.92315689106095e-05, - -3.76232470317424e-08, - 1.41044509499268e-07, - -6.12947435489968e-06, - -7.599237865126e-05, - -1.71868702694503e-05, - -0.000436833970938307, - -3.2912938937577e-05, - -8.452801852614e-05, - 2.02007111132787e-07, - -1.2179993498224e-07, - 2.22400068039184e-08 - ], - "den_col": [ - 1.0, - -0.0016244385994237, - -0.000997031309129848, - -3.97228163357384e-05, - 0.000112920335905623, - -4.53635700800615e-08, - -6.28923613522465e-08, - 0.000430777708756067, - -5.35461512153272e-05, - -7.15317202374209e-05, - 8.12427721842654e-09, - 2.4551497439837e-06, - -7.9946798540747e-08, - 1.89748428282841e-07, - 1.16725274418257e-06, - -1.81162688986827e-08, - 7.29532238815964e-08, - 1.38399789354239e-07, - -8.99001868967127e-10, - 4.53722631549632e-09 - ], - "num_row": [ - -0.000883094041871494, - 1.10710077979811, - -0.165723516965738, - 0.000138560840098775, - 0.000137508147048548, - 3.25602290695629e-05, - -4.7309525091896e-06, - 0.000566524889959542, - -0.000203002898954291, - 5.21759223015684e-08, - -6.20283272095123e-09, - -0.000213280129222989, - -1.47542827666434e-05, - -5.85043197640585e-05, - -4.61507967191095e-05, - 3.60644875907175e-06, - 8.76284470829693e-06, - 8.62895031760393e-08, - -1.1020819346039e-08, - -7.31873488716101e-09 - ], - "den_row": [ - 1.0, - 0.000373545825560489, - -0.000331911915364305, - -3.67246650834077e-05, - 2.35757725297648e-05, - -1.27063477972314e-08, - -2.81157466813385e-08, - 0.000147021287682039, - -2.68475707109499e-05, - -5.28675857733775e-05, - 8.97727334556582e-09, - -1.21664983676548e-06, - 7.53378407017969e-07, - 7.76919686543327e-09, - -1.18926235693016e-06, - -9.03411866073685e-08, - 3.11151318274824e-08, - 5.66070673008579e-08, - 3.62219975482947e-10, - 3.47727317976582e-09 - ] -} \ No newline at end of file diff --git a/tests/data/geomodel_template/RPC_P1BP.json b/tests/data/geomodel_template/RPC_P1BP.json deleted file mode 100644 index 58e5549..0000000 --- a/tests/data/geomodel_template/RPC_P1BP.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "driver_type": "dimap_v2.15", - "offset_row": 11470.0, - "offset_col": 20000.0, - "num_x": [ - -0.00216267283740478, - 0.99476665096815, - 0.000114508866718507, - -0.0049023114890601, - -0.000215872716989672, - -0.000427274205517627, - -0.000119324243929391, - 0.00176888708185964, - -7.10364364111257e-05, - -1.67556643958707e-06, - -8.21204863921981e-08, - 1.07953107491161e-05, - 4.33858654803222e-06, - 2.4122158664569e-06, - 1.29557975835698e-05, - -0.000163221649629262, - -3.96368605635385e-08, - -8.1740661263476e-07, - 8.83130834515652e-08, - -1.33102181603001e-08 - ], - "den_x": [ - 1.0, - 0.000389649825530527, - 0.000214414500978675, - 0.000414499991232059, - -2.15290472531396e-05, - 2.41883580329737e-07, - -9.39982059291604e-08, - -2.67948645657637e-06, - -1.28611779653202e-06, - 2.70471813553735e-06, - -2.08739202324809e-10, - -5.2382918663479e-08, - -1.05653210185694e-08, - -3.36900338956884e-09, - -9.26884750902506e-08, - 3.69838456996589e-07, - 9.62773384568123e-10, - 1.26279970387044e-08, - -1.01482191379087e-09, - 2.28629478655804e-09 - ], - "num_y": [ - 0.00307427158137086, - -0.022754942567019, - -0.96351278038229, - 0.0132362349117686, - -0.000706575591579423, - 2.5384922825267e-05, - 4.83624536885611e-05, - -0.00149711245999419, - -0.00157284574156877, - -8.74685979277811e-07, - 8.61790800612903e-07, - -4.76753362870271e-06, - -9.50051432525276e-06, - -1.92748026345547e-06, - -5.78364059603347e-05, - -0.000308607295681512, - -8.13683278156748e-05, - 3.26915962818304e-06, - -8.82623801296555e-07, - 1.11769058518809e-06 - ], - "den_y": [ - 1.0, - -0.000645474544263166, - 0.00142954980810177, - 6.37447918276721e-06, - -3.66414241388746e-06, - 6.18450147768807e-07, - -1.12467770044304e-06, - 5.66121722032651e-05, - -0.000165524734584013, - 8.43923345197855e-05, - 8.20947876246614e-09, - -2.10446563367918e-07, - 5.17036007137007e-07, - -1.16011177285474e-07, - 1.61367306414579e-07, - -6.85418419451155e-07, - -1.1695435701395e-08, - 1.24103963063179e-08, - 3.94561254140935e-07, - 3.02299222687414e-09 - ], - "num_col": [ - 0.0021737009210787, - 1.00525714360974, - 0.000118927446099487, - 0.00492690455761077, - -0.000222210312924215, - 0.000409443129672268, - -0.000126528243647543, - -0.00179664040198962, - 7.84401031360425e-05, - 3.32549534336851e-06, - 6.70072581370697e-07, - -5.1135734936954e-06, - -1.14831480750326e-05, - -3.30594287170937e-06, - 1.52770173261329e-05, - -0.000183142028762822, - -1.37304096538768e-07, - -1.81212336503884e-06, - 7.10787251036085e-06, - -1.51214721736946e-08 - ], - "den_col": [ - 1.0, - -0.000381194594013202, - 0.000223622059297665, - -0.000422217570203641, - -2.26923954441157e-05, - 9.09927741240968e-07, - -5.78227612356115e-07, - 3.33927467696316e-06, - 7.72196957128252e-06, - -3.5613972104774e-06, - -4.58827275412478e-08, - -8.00430673035027e-08, - 7.23817067866213e-07, - -3.12936105231232e-09, - 1.69531827355129e-07, - -1.21829187013636e-06, - -2.31845365171365e-09, - 3.10557859100329e-09, - 4.76013222107883e-08, - 2.64663990813134e-09 - ], - "num_row": [ - 0.00313924819508418, - -0.0237474392716818, - -1.03785778307581, - 0.0136210350918835, - 0.000639692627029046, - -8.64730452249582e-06, - 2.00901312432567e-05, - -0.00153692020192914, - -0.00174305854917321, - -1.37351384062187e-06, - -5.44105824083825e-07, - 2.42451766635437e-06, - 5.75334811075023e-06, - 2.23736523071738e-06, - 6.11972731515432e-05, - 0.000329532793653865, - 9.75201543451441e-05, - -8.32354417806354e-07, - -7.37135079205433e-06, - -1.27853489593406e-06 - ], - "den_row": [ - 1.0, - 0.000702383699016933, - 0.00146370955530911, - -2.19470964651431e-05, - 1.15302715971345e-05, - 3.83105014821457e-07, - -1.09889574285476e-05, - -6.21236421847309e-05, - 0.000208501769588198, - -9.38714133905457e-05, - 1.59572795228268e-08, - -8.35364688710469e-08, - -1.63371188530178e-06, - -1.27542061938798e-07, - 2.04455528496785e-06, - 1.01440529175472e-06, - 3.28201024292344e-08, - -1.67524390757524e-08, - -4.33595844874304e-07, - 2.23152082232837e-09 - ], - "scale_col": 19999.5, - "scale_row": 11469.5, - "offset_alt": 580.0, - "scale_alt": 540.0, - "offset_x": 7.178141415466419, - "scale_x": 0.1269157277506023, - "offset_y": 43.67753428488081, - "scale_y": 0.05436212948903929 -} \ No newline at end of file diff --git a/tests/data/geomodel_template/RPC_PHR1B.json b/tests/data/geomodel_template/RPC_PHR1B.json deleted file mode 100644 index 58e5549..0000000 --- a/tests/data/geomodel_template/RPC_PHR1B.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "driver_type": "dimap_v2.15", - "offset_row": 11470.0, - "offset_col": 20000.0, - "num_x": [ - -0.00216267283740478, - 0.99476665096815, - 0.000114508866718507, - -0.0049023114890601, - -0.000215872716989672, - -0.000427274205517627, - -0.000119324243929391, - 0.00176888708185964, - -7.10364364111257e-05, - -1.67556643958707e-06, - -8.21204863921981e-08, - 1.07953107491161e-05, - 4.33858654803222e-06, - 2.4122158664569e-06, - 1.29557975835698e-05, - -0.000163221649629262, - -3.96368605635385e-08, - -8.1740661263476e-07, - 8.83130834515652e-08, - -1.33102181603001e-08 - ], - "den_x": [ - 1.0, - 0.000389649825530527, - 0.000214414500978675, - 0.000414499991232059, - -2.15290472531396e-05, - 2.41883580329737e-07, - -9.39982059291604e-08, - -2.67948645657637e-06, - -1.28611779653202e-06, - 2.70471813553735e-06, - -2.08739202324809e-10, - -5.2382918663479e-08, - -1.05653210185694e-08, - -3.36900338956884e-09, - -9.26884750902506e-08, - 3.69838456996589e-07, - 9.62773384568123e-10, - 1.26279970387044e-08, - -1.01482191379087e-09, - 2.28629478655804e-09 - ], - "num_y": [ - 0.00307427158137086, - -0.022754942567019, - -0.96351278038229, - 0.0132362349117686, - -0.000706575591579423, - 2.5384922825267e-05, - 4.83624536885611e-05, - -0.00149711245999419, - -0.00157284574156877, - -8.74685979277811e-07, - 8.61790800612903e-07, - -4.76753362870271e-06, - -9.50051432525276e-06, - -1.92748026345547e-06, - -5.78364059603347e-05, - -0.000308607295681512, - -8.13683278156748e-05, - 3.26915962818304e-06, - -8.82623801296555e-07, - 1.11769058518809e-06 - ], - "den_y": [ - 1.0, - -0.000645474544263166, - 0.00142954980810177, - 6.37447918276721e-06, - -3.66414241388746e-06, - 6.18450147768807e-07, - -1.12467770044304e-06, - 5.66121722032651e-05, - -0.000165524734584013, - 8.43923345197855e-05, - 8.20947876246614e-09, - -2.10446563367918e-07, - 5.17036007137007e-07, - -1.16011177285474e-07, - 1.61367306414579e-07, - -6.85418419451155e-07, - -1.1695435701395e-08, - 1.24103963063179e-08, - 3.94561254140935e-07, - 3.02299222687414e-09 - ], - "num_col": [ - 0.0021737009210787, - 1.00525714360974, - 0.000118927446099487, - 0.00492690455761077, - -0.000222210312924215, - 0.000409443129672268, - -0.000126528243647543, - -0.00179664040198962, - 7.84401031360425e-05, - 3.32549534336851e-06, - 6.70072581370697e-07, - -5.1135734936954e-06, - -1.14831480750326e-05, - -3.30594287170937e-06, - 1.52770173261329e-05, - -0.000183142028762822, - -1.37304096538768e-07, - -1.81212336503884e-06, - 7.10787251036085e-06, - -1.51214721736946e-08 - ], - "den_col": [ - 1.0, - -0.000381194594013202, - 0.000223622059297665, - -0.000422217570203641, - -2.26923954441157e-05, - 9.09927741240968e-07, - -5.78227612356115e-07, - 3.33927467696316e-06, - 7.72196957128252e-06, - -3.5613972104774e-06, - -4.58827275412478e-08, - -8.00430673035027e-08, - 7.23817067866213e-07, - -3.12936105231232e-09, - 1.69531827355129e-07, - -1.21829187013636e-06, - -2.31845365171365e-09, - 3.10557859100329e-09, - 4.76013222107883e-08, - 2.64663990813134e-09 - ], - "num_row": [ - 0.00313924819508418, - -0.0237474392716818, - -1.03785778307581, - 0.0136210350918835, - 0.000639692627029046, - -8.64730452249582e-06, - 2.00901312432567e-05, - -0.00153692020192914, - -0.00174305854917321, - -1.37351384062187e-06, - -5.44105824083825e-07, - 2.42451766635437e-06, - 5.75334811075023e-06, - 2.23736523071738e-06, - 6.11972731515432e-05, - 0.000329532793653865, - 9.75201543451441e-05, - -8.32354417806354e-07, - -7.37135079205433e-06, - -1.27853489593406e-06 - ], - "den_row": [ - 1.0, - 0.000702383699016933, - 0.00146370955530911, - -2.19470964651431e-05, - 1.15302715971345e-05, - 3.83105014821457e-07, - -1.09889574285476e-05, - -6.21236421847309e-05, - 0.000208501769588198, - -9.38714133905457e-05, - 1.59572795228268e-08, - -8.35364688710469e-08, - -1.63371188530178e-06, - -1.27542061938798e-07, - 2.04455528496785e-06, - 1.01440529175472e-06, - 3.28201024292344e-08, - -1.67524390757524e-08, - -4.33595844874304e-07, - 2.23152082232837e-09 - ], - "scale_col": 19999.5, - "scale_row": 11469.5, - "offset_alt": 580.0, - "scale_alt": 540.0, - "offset_x": 7.178141415466419, - "scale_x": 0.1269157277506023, - "offset_y": 43.67753428488081, - "scale_y": 0.05436212948903929 -} \ No newline at end of file diff --git a/tests/data/geomodel_template/geom.json b/tests/data/geomodel_template/geom.json deleted file mode 100644 index 7d5d9cd..0000000 --- a/tests/data/geomodel_template/geom.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "driver_type": "ossim_kwl", - "den_row": [ - 1.0, - 0.000702383699016933, - 0.00146370955530911, - -2.19470964651431e-05, - 1.15302715971345e-05, - 3.83105014821457e-07, - -1.09889574285476e-05, - -6.21236421847309e-05, - 0.000208501769588198, - -9.38714133905457e-05, - 1.59572795228268e-08, - -8.35364688710469e-08, - -1.63371188530178e-06, - -1.27542061938798e-07, - 2.04455528496785e-06, - 1.01440529175472e-06, - 3.28201024292344e-08, - -1.67524390757524e-08, - -4.33595844874304e-07, - 2.23152082232837e-09 - ], - "num_row": [ - 0.00313924819508418, - -0.0237474392716818, - -1.03785778307581, - 0.0136210350918835, - 0.000639692627029046, - -8.64730452249582e-06, - 2.00901312432567e-05, - -0.00153692020192914, - -0.00174305854917321, - -1.37351384062187e-06, - -5.44105824083825e-07, - 2.42451766635437e-06, - 5.75334811075023e-06, - 2.23736523071738e-06, - 6.11972731515432e-05, - 0.000329532793653865, - 9.75201543451441e-05, - -8.32354417806354e-07, - -7.37135079205433e-06, - -1.27853489593406e-06 - ], - "den_col": [ - 1.0, - -0.000381194594013202, - 0.000223622059297665, - -0.000422217570203641, - -2.26923954441157e-05, - 9.09927741240968e-07, - -5.78227612356115e-07, - 3.33927467696316e-06, - 7.72196957128252e-06, - -3.5613972104774e-06, - -4.58827275412478e-08, - -8.00430673035027e-08, - 7.23817067866213e-07, - -3.12936105231232e-09, - 1.69531827355129e-07, - -1.21829187013636e-06, - -2.31845365171365e-09, - 3.10557859100329e-09, - 4.76013222107883e-08, - 2.64663990813134e-09 - ], - "num_col": [ - 0.0021737009210787, - 1.00525714360974, - 0.000118927446099487, - 0.00492690455761077, - -0.000222210312924215, - 0.000409443129672268, - -0.000126528243647543, - -0.00179664040198962, - 7.84401031360425e-05, - 3.32549534336851e-06, - 6.70072581370697e-07, - -5.1135734936954e-06, - -1.14831480750326e-05, - -3.30594287170937e-06, - 1.52770173261329e-05, - -0.000183142028762822, - -1.37304096538768e-07, - -1.81212336503884e-06, - 7.10787251036085e-06, - -1.51214721736946e-08 - ], - "offset_col": 20000.0, - "scale_col": 19999.5, - "offset_row": 11470.0, - "scale_row": 11469.5, - "offset_alt": 580.0, - "scale_alt": 540.0, - "offset_x": 7.17814141546642, - "scale_x": 0.126915727750602, - "offset_y": 43.6775342848808, - "scale_y": 0.0543621294890393, - "num_x": null, - "den_x": null, - "num_y": null, - "den_y": null -} \ No newline at end of file diff --git a/tests/data/geomodel_template/tif.json b/tests/data/geomodel_template/tif.json deleted file mode 100644 index 142c2b7..0000000 --- a/tests/data/geomodel_template/tif.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "den_row": [ - 1.0, - 0.00153702966600767, - -0.000850255148536953, - -3.31305939685797e-05, - -9.55977505058452e-05, - -3.45865692724229e-07, - 2.87774922284592e-05, - -9.71752015557712e-05, - 0.000393303600277223, - -0.000150584568710015, - 1.1205565753101e-08, - -4.19615731849281e-07, - -2.91756165031344e-06, - -4.08302077735705e-07, - 2.71755153784986e-06, - -2.06604788106092e-06, - -6.59870613549866e-08, - 4.48992839909244e-08, - -8.21670158409646e-07, - 6.70815592387116e-09 - ], - "num_row": [ - -0.00185020904067451, - 0.0698104447407509, - -1.09235324117618, - -0.021814249276077, - 0.00121171425410876, - 2.76638772076096e-05, - 8.0980462299178e-06, - -0.00119093530484733, - 0.00114897060816587, - 2.24620568639661e-06, - -3.60161922714853e-06, - -5.5151765856299e-06, - -7.83514754712989e-05, - -1.05772130483304e-05, - 0.000111691142895374, - 0.000548333220077419, - 0.000164910424389974, - 2.26464059524339e-06, - 1.78029600933359e-05, - 3.28894996895612e-06 - ], - "num_col": [ - 0.00374321456500487, - 1.01161201549305, - -0.000108650176255135, - 0.0113280713091793, - -0.000158400348205981, - 0.000456165294729941, - -0.000124246258872532, - -0.00339052319644368, - 0.00049260287439426, - 1.39382163458586e-06, - 1.52184293078925e-06, - 2.69142726729643e-06, - 1.25282679825342e-05, - -7.43627570471524e-07, - 1.25591796202999e-05, - -0.000192693282645298, - -3.13481994353245e-07, - -2.93387312507297e-06, - -1.2042569869777e-05, - -1.08970866389161e-08 - ], - "den_col": [ - 1.0, - -0.000362013732739162, - 0.000161936463848829, - -0.000503790573335675, - -1.96459240941631e-05, - 1.21810025788615e-06, - -1.46361989236423e-06, - 7.55579559413767e-06, - -1.58450537412824e-05, - -1.12284475568369e-06, - -1.2839719516335e-08, - -7.61965838022596e-08, - 2.24057677786235e-07, - 9.24695826346757e-11, - 1.77152806817512e-07, - 7.08760218732476e-07, - 1.07574931499556e-10, - -2.53815234783449e-09, - 5.83684624878976e-08, - 5.92090135139572e-10 - ], - "offset_col": 20000.0, - "scale_col": 19999.5, - "offset_row": 11470.0, - "scale_row": 11469.5, - "offset_alt": 670.0, - "scale_alt": 630.0, - "offset_x": 7.17744850367561, - "scale_x": 0.132212619668901, - "offset_y": 43.6772638723064, - "scale_y": 0.059094070033936, - "num_x": null, - "den_x": null, - "num_y": null, - "den_y": null -} \ No newline at end of file diff --git a/tests/geomodels/test_rpc_optim.py b/tests/geomodels/test_rpc_optim.py index 96f9aca..a49b8cc 100644 --- a/tests/geomodels/test_rpc_optim.py +++ b/tests/geomodels/test_rpc_optim.py @@ -23,49 +23,45 @@ """ # Third party imports -import json +import numpy as np +import pytest + +import rpc_c # Shareloc imports from shareloc.geomodels import GeoModel -def test_load_rpc_params(): +@pytest.mark.parametrize( + "geom_path", + [ + "tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", + "tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", + "tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML", + "tests/data/rpc/RPC_P1BP--2017092838284574CP.XML", + "tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", + ], +) +def test_load_rpc_params(geom_path): """ - test loading of rpc_params dict + test loading of rpc_params """ - geom = GeoModel("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", "RpcOptim").__dict__ - tif = GeoModel("tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", "RpcOptim").__dict__ - phrdimap = GeoModel("tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML", "RpcOptim").__dict__ - rpc_p1bp = GeoModel("tests/data/rpc/RPC_P1BP--2017092838284574CP.XML", "RpcOptim").__dict__ - rpc_phr1b = GeoModel("tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", "RpcOptim").__dict__ + geom = GeoModel(geom_path, "RpcOptim").__dict__ + geom_ref = GeoModel(geom_path).__dict__ del geom["type"] - del tif["type"] - del phrdimap["type"] - del rpc_p1bp["type"] - del rpc_phr1b["type"] - - with open("tests/data/geomodel_template/geom.json", "r", encoding="utf-8") as f: - geom_ref = json.load(f) - with open("tests/data/geomodel_template/tif.json", "r", encoding="utf-8") as f: - tif_ref = json.load(f) - with open("tests/data/geomodel_template/PHRDIMAP.json", "r", encoding="utf-8") as f: - phrdimap_ref = json.load(f) - with open("tests/data/geomodel_template/RPC_P1BP.json", "r", encoding="utf-8") as f: - rpc_p1bp_ref = json.load(f) - with open("tests/data/geomodel_template/RPC_PHR1B.json", "r", encoding="utf-8") as f: - rpc_phr1b_ref = json.load(f) - - assert geom == geom_ref - assert tif == tif_ref - assert phrdimap == phrdimap_ref - assert rpc_p1bp == rpc_p1bp_ref - assert rpc_phr1b == rpc_phr1b_ref + del geom_ref["type"] + for key, value in geom.items(): + if isinstance(value, np.ndarray): + np.testing.assert_array_equal(value, geom_ref[key]) + else: + assert value == geom_ref[key] -def test_function_rpc_cpp(): + +def test_method_rpc_cpp(): """ - Test call to methode parent class rpc in cpp + Test call to method parent class rpc in cpp """ rpc = GeoModel("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", "RpcOptim") @@ -84,3 +80,46 @@ def test_function_rpc_cpp(): rpc.direct_loc_inverse_iterative(vector_double, vector_double, double, integer, False) rpc.get_alt_min_max() rpc.los_extrema(double, double, double, double, False, integer) + + +def test_function_rpc_cpp(): + """ + Test call to function written in rpc.cpp + """ + + vector_double = [1.0, 1.0, 1.0] + double = 1.0 + + rpc_c.polynomial_equation(double, double, double, vector_double) + + rpc_c.compute_rational_function_polynomial( + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + double, + double, + double, + double, + ) + + rpc_c.derivative_polynomial_latitude(double, double, double, vector_double) + + rpc_c.derivative_polynomial_longitude(double, double, double, vector_double) + + rpc_c.compute_loc_inverse_derivates_numba( + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + vector_double, + double, + double, + double, + double, + ) From 7d8d18c8a977fdea9771ccc512f526885e0f2ef3 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Thu, 7 Dec 2023 13:23:54 +0100 Subject: [PATCH 17/18] refactor: Basics of the cpp dev (POC) --- shareloc/bindings/GeoModelTemplate.cpp | 1 - shareloc/bindings/GeoModelTemplate.hpp | 7 +- shareloc/bindings/bind.cpp | 3 +- shareloc/bindings/rpc.cpp | 25 +++- shareloc/bindings/rpc.hpp | 43 +++--- shareloc/geomodels/rpc.py | 1 - shareloc/geomodels/rpc_optim.py | 185 ++++++++----------------- tests/geomodels/test_rpc_optim.py | 35 +++-- 8 files changed, 134 insertions(+), 166 deletions(-) diff --git a/shareloc/bindings/GeoModelTemplate.cpp b/shareloc/bindings/GeoModelTemplate.cpp index e2e0d3e..696c6ee 100644 --- a/shareloc/bindings/GeoModelTemplate.cpp +++ b/shareloc/bindings/GeoModelTemplate.cpp @@ -1,5 +1,4 @@ /* -!/usr/bin/env python coding: utf8 Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). diff --git a/shareloc/bindings/GeoModelTemplate.hpp b/shareloc/bindings/GeoModelTemplate.hpp index c642d95..0009aa0 100644 --- a/shareloc/bindings/GeoModelTemplate.hpp +++ b/shareloc/bindings/GeoModelTemplate.hpp @@ -1,5 +1,4 @@ /* -!/usr/bin/env python coding: utf8 Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). @@ -26,16 +25,20 @@ Abstract class GeoModelTemplate. Child class: RPC */ -#include #include #include +#include #include +#include +#include using std::cout; using std::endl; using std::vector; using std::string; using std::tuple; +using std::array; +using std::map; /** Abstract class GeoModelTemplate: diff --git a/shareloc/bindings/bind.cpp b/shareloc/bindings/bind.cpp index ffdf5a2..09f6807 100644 --- a/shareloc/bindings/bind.cpp +++ b/shareloc/bindings/bind.cpp @@ -1,5 +1,4 @@ /* -!/usr/bin/env python coding: utf8 Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). @@ -42,7 +41,7 @@ PYBIND11_MODULE(rpc_c, m) { .def("inverse_loc", &GeoModelTemplate::inverse_loc); py::class_(m, "RPC") - .def(py::init<>()) + .def(py::init,array,array,array,array>()) .def("direct_loc_h", &RPC::direct_loc_h) .def("direct_loc_grid_h", &RPC::direct_loc_grid_h) .def("direct_loc_dtm", &RPC::direct_loc_dtm) diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index d979819..2052253 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -1,5 +1,4 @@ /* -!/usr/bin/env python coding: utf8 Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). @@ -28,6 +27,30 @@ Cpp copy of rpc.py //---- RPC methodes ----// +RPC::RPC(array num_col, + array den_col, + array num_row, + array den_row, + array norm_coeffs):GeoModelTemplate(){ + + num_col = num_col; + den_col = den_col; + num_row = num_row; + den_row = den_row; + + offset_lon = norm_coeffs[0]; + scale_lon = norm_coeffs[1]; + offset_lat = norm_coeffs[2]; + scale_lat = norm_coeffs[3]; + offset_alt = norm_coeffs[4]; + scale_alt = norm_coeffs[5]; + offset_col = norm_coeffs[6]; + scale_col = norm_coeffs[7]; + offset_row = norm_coeffs[8]; + scale_row = norm_coeffs[9]; + +} + vector> RPC::direct_loc_h( vector row, vector col, diff --git a/shareloc/bindings/rpc.hpp b/shareloc/bindings/rpc.hpp index 5b8b24d..3353611 100644 --- a/shareloc/bindings/rpc.hpp +++ b/shareloc/bindings/rpc.hpp @@ -1,5 +1,4 @@ /* -!/usr/bin/env python coding: utf8 Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). @@ -27,13 +26,10 @@ Cpp copy of rpc.py #include "GeoModelTemplate.hpp" #include "GeoModelTemplate.cpp" -#include - -using std::map; /** Class RPC -Squelette de la classe RPC.py de l'issue 221 +Framework of the RPC python class. */ class RPC : public GeoModelTemplate{ @@ -46,24 +42,24 @@ class RPC : public GeoModelTemplate{ map rpc_params;//map is a simple dict -> maybe inappropiate here double lim_extrapol; - vector> monomes; + vector> monomes;// to convert to array vector> monomes_deriv_1; vector> monomes_deriv_2; bool inverse_coefficient; bool direct_coefficient; - vector num_col; - vector den_col; - vector num_row; - vector den_row; + array num_col; + array den_col; + array num_row; + array den_row; - vector num_x; - vector den_x; - vector num_y; - vector den_y; + array num_x; + array den_x; + array num_y; + array den_y; - vector alt_minmax; + array alt_minmax; double col0; double colmax; @@ -76,13 +72,22 @@ class RPC : public GeoModelTemplate{ double scale_col; double offset_alt; double scale_alt; - double offset_x; - double scale_x; - double offset_y; - double scale_y; + double offset_lon; + double scale_lon; + double offset_lat; + double scale_lat; + public: using GeoModelTemplate::GeoModelTemplate; + + /**Constructor*/ + RPC(array num_col, + array den_col, + array num_row, + array den_row, + array norm_coeffs); + /**direct_loc_h*/ vector> direct_loc_h( vector row, diff --git a/shareloc/geomodels/rpc.py b/shareloc/geomodels/rpc.py index b5085c1..b1a0f75 100755 --- a/shareloc/geomodels/rpc.py +++ b/shareloc/geomodels/rpc.py @@ -2,7 +2,6 @@ # coding: utf8 # # Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). -# Copyright (c) 2023 CS GROUP - France, https://csgroup.eu # # This file is part of Shareloc # (see https://github.com/CNES/shareloc). diff --git a/shareloc/geomodels/rpc_optim.py b/shareloc/geomodels/rpc_optim.py index 42a089d..edd030e 100755 --- a/shareloc/geomodels/rpc_optim.py +++ b/shareloc/geomodels/rpc_optim.py @@ -2,7 +2,6 @@ # coding: utf8 # # Copyright (c) 2022 Centre National d'Etudes Spatiales (CNES). -# Copyright (c) 2023 CS GROUP - France, https://csgroup.eu # # This file is part of Shareloc # (see https://github.com/CNES/shareloc). @@ -25,12 +24,10 @@ """ -import numpy as np - # Third party imports from numba import config -import rpc_c as bind +import rpc_c # Shareloc imports from shareloc.geomodels.geomodel import GeoModel @@ -41,143 +38,33 @@ config.THREADING_LAYER = "omp" -# pylint: disable=duplicate-code @GeoModel.register("RpcOptim") -class RpcOptim(bind.RPC, GeoModelTemplate): +class RpcOptim(rpc_c.RPC, GeoModelTemplate): """ RPC optimized with cpp bindings class including direct and inverse localization instance methods """ # pylint: disable=too-many-instance-attributes def __init__(self, rpc_params): - bind.RPC.__init__(self) GeoModelTemplate.__init__(self) - self.offset_alt = None - self.scale_alt = None - self.offset_col = None - self.scale_col = None - self.offset_row = None - self.scale_row = None - self.offset_x = None - self.scale_x = None - self.offset_y = None - self.scale_y = None - - self.datum = None for key, value in rpc_params.items(): setattr(self, key, value) - self.type = "RpcOptim" - if self.epsg is None: - self.epsg = 4326 - if self.datum is None: - self.datum = "ellipsoid" - - self.lim_extrapol = 1.0001 - - # Each monome: c[0]*X**c[1]*Y**c[2]*Z**c[3] - monomes_order = [ - [1, 0, 0, 0], - [1, 1, 0, 0], - [1, 0, 1, 0], - [1, 0, 0, 1], - [1, 1, 1, 0], - [1, 1, 0, 1], - [1, 0, 1, 1], - [1, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [1, 1, 1, 1], - [1, 3, 0, 0], - [1, 1, 2, 0], - [1, 1, 0, 2], - [1, 2, 1, 0], - [1, 0, 3, 0], - [1, 0, 1, 2], - [1, 2, 0, 1], - [1, 0, 2, 1], - [1, 0, 0, 3], + norm_coeffs = [ + self.offset_x, + self.scale_x, # longitude + self.offset_y, + self.scale_y, # latitude + self.offset_alt, + self.scale_alt, + self.offset_col, + self.scale_col, + self.offset_row, + self.scale_row, ] - self.monomes = np.array(monomes_order) - - # monomial coefficients of 1st variable derivative - self.monomes_deriv_1 = np.array( - [ - [0, 0, 0, 0], - [1, 0, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - [1, 0, 1, 0], - [1, 0, 0, 1], - [0, 0, 1, 1], - [2, 1, 0, 0], - [0, 0, 2, 0], - [0, 0, 0, 2], - [1, 0, 1, 1], - [3, 2, 0, 0], - [1, 0, 2, 0], - [1, 0, 0, 2], - [2, 1, 1, 0], - [0, 0, 3, 0], - [0, 0, 1, 2], - [2, 1, 0, 1], - [0, 0, 2, 1], - [0, 0, 0, 3], - ] - ) - - # monomial coefficients of 2nd variable derivative - self.monomes_deriv_2 = np.array( - [ - [0, 0, 0, 0], - [0, 1, 0, 0], - [1, 0, 0, 0], - [0, 0, 0, 1], - [1, 1, 0, 0], - [0, 1, 0, 1], - [1, 0, 0, 1], - [0, 2, 0, 0], - [2, 0, 1, 0], - [0, 0, 0, 2], - [1, 1, 0, 1], - [0, 3, 0, 0], - [2, 1, 1, 0], - [0, 1, 0, 2], - [1, 2, 0, 0], - [3, 0, 2, 0], - [1, 0, 0, 2], - [0, 2, 0, 1], - [2, 0, 1, 1], - [0, 0, 0, 3], - ] - ) - - self.inverse_coefficient = False - self.direct_coefficient = False - - # pylint: disable=access-member-before-definition - if self.num_col: - self.inverse_coefficient = True - self.num_col = np.array(self.num_col) - self.den_col = np.array(self.den_col) - self.num_row = np.array(self.num_row) - self.den_row = np.array(self.den_row) - - # pylint: disable=access-member-before-definition - if self.num_x: - self.direct_coefficient = True - self.num_x = np.array(self.num_x) - self.den_x = np.array(self.den_x) - self.num_y = np.array(self.num_y) - self.den_y = np.array(self.den_y) - - self.alt_minmax = [self.offset_alt - self.scale_alt, self.offset_alt + self.scale_alt] - self.col0 = self.offset_col - self.scale_col - self.colmax = self.offset_col + self.scale_col - self.row0 = self.offset_row - self.scale_row - self.rowmax = self.offset_row + self.scale_row + rpc_c.RPC.__init__(self, self.num_col, self.den_col, self.num_row, self.den_row, norm_coeffs) @classmethod def load(cls, geomodel_path): @@ -194,3 +81,47 @@ def load(cls, geomodel_path): # Set topleftconvention (keeping historic option): to clean cls.geomodel_path = geomodel_path return cls(rpc_reader(geomodel_path, topleftconvention=True)) + + def direct_loc_h(self, row, col, alt, fill_nan=False): + """ + direct localization at constant altitude + + :param row: line sensor position + :type row: float or 1D numpy.ndarray dtype=float64 + :param col: column sensor position + :type col: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :param fill_nan: fill numpy.nan values with lon and lat offset if true (same as OTB/OSSIM), nan is returned + otherwise + :type fill_nan: boolean + :return: ground position (lon,lat,h) + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + def direct_loc_dtm(self, row, col, dtm): + """ + direct localization on dtm + + :param row: line sensor position + :type row: float + :param col: column sensor position + :type col: float + :param dtm: dtm intersection model + :type dtm: shareloc.geofunctions.dtm_intersection + :return: ground position (lon,lat,h) in dtm coordinates system + :rtype: numpy.ndarray 2D dimension with (N,3) shape, where N is number of input coordinates + """ + + def inverse_loc(self, lon, lat, alt): + """ + Inverse localization + + :param lon: longitude position + :type lon: float or 1D numpy.ndarray dtype=float64 + :param lat: latitude position + :type lat: float or 1D numpy.ndarray dtype=float64 + :param alt: altitude + :type alt: float + :return: sensor position (row, col, alt) + :rtype: tuple(1D np.array row position, 1D np.array col position, 1D np.array alt) + """ diff --git a/tests/geomodels/test_rpc_optim.py b/tests/geomodels/test_rpc_optim.py index a49b8cc..3342ba9 100644 --- a/tests/geomodels/test_rpc_optim.py +++ b/tests/geomodels/test_rpc_optim.py @@ -22,8 +22,9 @@ Module to test RpcOptim class """ -# Third party imports import numpy as np + +# Third party imports import pytest import rpc_c @@ -42,21 +43,29 @@ "tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", ], ) -def test_load_rpc_params(geom_path): +def test_construtor(geom_path): """ - test loading of rpc_params + Test RpcOptim constructor """ - geom = GeoModel(geom_path, "RpcOptim").__dict__ - geom_ref = GeoModel(geom_path).__dict__ - - del geom["type"] - del geom_ref["type"] - for key, value in geom.items(): - if isinstance(value, np.ndarray): - np.testing.assert_array_equal(value, geom_ref[key]) - else: - assert value == geom_ref[key] + rpc_optim = GeoModel(geom_path, "RpcOptim") + rpc_py = GeoModel(geom_path, "RPC") + + assert rpc_py.offset_x == rpc_optim.offset_x + assert rpc_py.scale_x == rpc_optim.scale_x + assert rpc_py.offset_y == rpc_optim.offset_y + assert rpc_py.scale_y == rpc_optim.scale_y + assert rpc_py.offset_alt == rpc_optim.offset_alt + assert rpc_py.scale_alt == rpc_optim.scale_alt + assert rpc_py.offset_col == rpc_optim.offset_col + assert rpc_py.scale_col == rpc_optim.scale_col + assert rpc_py.offset_row == rpc_optim.offset_row + assert rpc_py.scale_row == rpc_optim.scale_row + + np.testing.assert_array_equal(rpc_py.num_col, rpc_optim.num_col) + np.testing.assert_array_equal(rpc_py.den_col, rpc_optim.den_col) + np.testing.assert_array_equal(rpc_py.num_row, rpc_optim.num_row) + np.testing.assert_array_equal(rpc_py.den_row, rpc_optim.den_row) def test_method_rpc_cpp(): From fb2cb67ea42046d82604d1505555d5cdf3031662 Mon Sep 17 00:00:00 2001 From: DEVAUX Augustin Date: Fri, 8 Dec 2023 14:43:51 +0100 Subject: [PATCH 18/18] Refactor: RpcOptim & RPC cpp constructor refactor: header correction refactor: change names of func and str CI: cut long line refactor: Attribut in rpc cpp class + data test path CI: API docu on getter --- shareloc/bindings/GeoModelTemplate.cpp | 2 - shareloc/bindings/GeoModelTemplate.hpp | 2 - shareloc/bindings/bind.cpp | 30 +++++++++- shareloc/bindings/rpc.cpp | 49 ++++++++++++----- shareloc/bindings/rpc.hpp | 53 ++++++++++++++++-- shareloc/geomodels/rpc_optim.py | 32 ++++++----- tests/geomodels/test_rpc_optim.py | 76 ++++++++++++++++++-------- 7 files changed, 179 insertions(+), 65 deletions(-) diff --git a/shareloc/bindings/GeoModelTemplate.cpp b/shareloc/bindings/GeoModelTemplate.cpp index 696c6ee..1df7608 100644 --- a/shareloc/bindings/GeoModelTemplate.cpp +++ b/shareloc/bindings/GeoModelTemplate.cpp @@ -1,6 +1,4 @@ /* -coding: utf8 - Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). This file is part of shareloc diff --git a/shareloc/bindings/GeoModelTemplate.hpp b/shareloc/bindings/GeoModelTemplate.hpp index 0009aa0..4f7e3dc 100644 --- a/shareloc/bindings/GeoModelTemplate.hpp +++ b/shareloc/bindings/GeoModelTemplate.hpp @@ -1,6 +1,4 @@ /* -coding: utf8 - Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). This file is part of shareloc diff --git a/shareloc/bindings/bind.cpp b/shareloc/bindings/bind.cpp index 09f6807..86c2d62 100644 --- a/shareloc/bindings/bind.cpp +++ b/shareloc/bindings/bind.cpp @@ -41,7 +41,11 @@ PYBIND11_MODULE(rpc_c, m) { .def("inverse_loc", &GeoModelTemplate::inverse_loc); py::class_(m, "RPC") - .def(py::init,array,array,array,array>()) + .def(py::init, + array, + array, + array, + array>()) .def("direct_loc_h", &RPC::direct_loc_h) .def("direct_loc_grid_h", &RPC::direct_loc_grid_h) .def("direct_loc_dtm", &RPC::direct_loc_dtm) @@ -49,7 +53,25 @@ PYBIND11_MODULE(rpc_c, m) { .def("compute_loc_inverse_derivates", &RPC::compute_loc_inverse_derivates) .def("direct_loc_inverse_iterative", &RPC::direct_loc_inverse_iterative) .def("get_alt_min_max", &RPC::get_alt_min_max) - .def("los_extrema", &RPC::los_extrema); + .def("los_extrema", &RPC::los_extrema) + .def("get_num_col", &RPC::get_num_col) + .def("get_den_col", &RPC::get_den_col) + .def("get_num_row", &RPC::get_num_row) + .def("get_den_row", &RPC::get_den_row) + .def("get_num_lon", &RPC::get_num_lon) + .def("get_den_lon", &RPC::get_den_lon) + .def("get_num_lat", &RPC::get_num_lat) + .def("get_den_lat", &RPC::get_den_lat) + .def("get_offset_row", &RPC::get_offset_row) + .def("get_scale_row", &RPC::get_scale_row) + .def("get_offset_col", &RPC::get_offset_col) + .def("get_scale_col", &RPC::get_scale_col) + .def("get_offset_alt", &RPC::get_offset_alt) + .def("get_scale_alt", &RPC::get_scale_alt) + .def("get_offset_lon", &RPC::get_offset_lon) + .def("get_scale_lon", &RPC::get_scale_lon) + .def("get_offset_lat", &RPC::get_offset_lat) + .def("get_scale_lat", &RPC::get_scale_lat); //m.doc() = "Pybind hello world"; // optional module docstring m.def("polynomial_equation", &polynomial_equation, "TODO: doc"); @@ -57,7 +79,9 @@ PYBIND11_MODULE(rpc_c, m) { "TODO: doc"); m.def("derivative_polynomial_latitude", &derivative_polynomial_latitude, "TODO: doc"); m.def("derivative_polynomial_longitude", &derivative_polynomial_longitude, "TODO: doc"); - m.def("compute_loc_inverse_derivates_numba", &compute_loc_inverse_derivates_numba, "TODO: doc"); + m.def("compute_loc_inverse_derivates_optimized", + &compute_loc_inverse_derivates_optimized, + "TODO: doc"); } //c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) diff --git a/shareloc/bindings/rpc.cpp b/shareloc/bindings/rpc.cpp index 2052253..4229040 100644 --- a/shareloc/bindings/rpc.cpp +++ b/shareloc/bindings/rpc.cpp @@ -1,6 +1,4 @@ /* -coding: utf8 - Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). This file is part of shareloc @@ -22,25 +20,26 @@ limitations under the License. /** Cpp copy of rpc.py */ - +#include #include "rpc.hpp" //---- RPC methodes ----// -RPC::RPC(array num_col, - array den_col, - array num_row, - array den_row, +RPC::RPC(array num_col_input, + array den_col_input, + array num_row_input, + array den_row_input, array norm_coeffs):GeoModelTemplate(){ - num_col = num_col; - den_col = den_col; - num_row = num_row; - den_row = den_row; - offset_lon = norm_coeffs[0]; + std::copy(num_col_input.begin(), num_col_input.end(), num_col.begin()); + std::copy(den_col_input.begin(), den_col_input.end(), den_col.begin()); + std::copy(num_row_input.begin(), num_row_input.end(), num_row.begin()); + std::copy(den_row_input.begin(), den_row_input.end(), den_row.begin()); + + offset_lon = norm_coeffs[0];//offset_x scale_lon = norm_coeffs[1]; - offset_lat = norm_coeffs[2]; + offset_lat = norm_coeffs[2];//offset_t scale_lat = norm_coeffs[3]; offset_alt = norm_coeffs[4]; scale_alt = norm_coeffs[5]; @@ -135,6 +134,28 @@ vector> RPC::los_extrema( return vect; } +array RPC::get_num_col(){return num_col;} +array RPC::get_den_col(){return den_col;} +array RPC::get_num_row(){return num_row;} +array RPC::get_den_row(){return den_row;} + +array RPC::get_num_lon(){return num_lon;} +array RPC::get_den_lon(){return den_lon;} +array RPC::get_num_lat(){return num_lat;} +array RPC::get_den_lat(){return den_lat;} + +double RPC::get_offset_row(){return offset_row;} +double RPC::get_scale_row(){return scale_row;} +double RPC::get_offset_col(){return offset_col;} +double RPC::get_scale_col(){return scale_col;} +double RPC::get_offset_alt(){return offset_alt;} +double RPC::get_scale_alt(){return scale_alt;} +double RPC::get_offset_lon(){return offset_lon;} +double RPC::get_scale_lon(){return scale_lon;} +double RPC::get_offset_lat(){return offset_lat;} +double RPC::get_scale_lat(){return scale_lat;} + + //---- Functions ----// double polynomial_equation( @@ -188,7 +209,7 @@ double derivative_polynomial_longitude( tuple, vector, vector, -vector> compute_loc_inverse_derivates_numba( +vector> compute_loc_inverse_derivates_optimized( vector lon_norm, vector lat_norm, vector alt_norm, diff --git a/shareloc/bindings/rpc.hpp b/shareloc/bindings/rpc.hpp index 3353611..4bf07bc 100644 --- a/shareloc/bindings/rpc.hpp +++ b/shareloc/bindings/rpc.hpp @@ -1,6 +1,4 @@ /* -coding: utf8 - Copyright (c) 2023 Centre National d'Etudes Spatiales (CNES). This file is part of shareloc @@ -54,10 +52,10 @@ class RPC : public GeoModelTemplate{ array num_row; array den_row; - array num_x; - array den_x; - array num_y; - array den_y; + array num_lon; + array den_lon; + array num_lat; + array den_lat; array alt_minmax; @@ -152,5 +150,48 @@ class RPC : public GeoModelTemplate{ double alt_max, bool fill_nan=false, int epsg=4326); + + + //-- getter --// + + /**get_num_col*/ + array get_num_col(); + /**get_den_col*/ + array get_den_col(); + /**get_num_row*/ + array get_num_row(); + /**get_den_row*/ + array get_den_row(); + + /**get_num_lon*/ + array get_num_lon(); + /**get_den_lon*/ + array get_den_lon(); + /**get_num_lat*/ + array get_num_lat(); + /**get_den_lat*/ + array get_den_lat(); + + /**get_offset_row*/ + double get_offset_row(); + /**get_scale_row*/ + double get_scale_row(); + /**get_offset_col*/ + double get_offset_col(); + /**get_scale_col*/ + double get_scale_col(); + /**get_offset_alt*/ + double get_offset_alt(); + /**get_scale_alt*/ + double get_scale_alt(); + /**get_offset_lon*/ + double get_offset_lon(); + /**get_scale_lon*/ + double get_scale_lon(); + /**get_offset_lat*/ + double get_offset_lat(); + /**get_scale_lat*/ + double get_scale_lat(); }; + diff --git a/shareloc/geomodels/rpc_optim.py b/shareloc/geomodels/rpc_optim.py index edd030e..c10530b 100755 --- a/shareloc/geomodels/rpc_optim.py +++ b/shareloc/geomodels/rpc_optim.py @@ -48,23 +48,27 @@ class RpcOptim(rpc_c.RPC, GeoModelTemplate): def __init__(self, rpc_params): GeoModelTemplate.__init__(self) - for key, value in rpc_params.items(): - setattr(self, key, value) - norm_coeffs = [ - self.offset_x, - self.scale_x, # longitude - self.offset_y, - self.scale_y, # latitude - self.offset_alt, - self.scale_alt, - self.offset_col, - self.scale_col, - self.offset_row, - self.scale_row, + rpc_params["offset_x"], + rpc_params["scale_x"], # longitude + rpc_params["offset_y"], + rpc_params["scale_y"], # latitude + rpc_params["offset_alt"], + rpc_params["scale_alt"], + rpc_params["offset_col"], + rpc_params["scale_col"], + rpc_params["offset_row"], + rpc_params["scale_row"], ] - rpc_c.RPC.__init__(self, self.num_col, self.den_col, self.num_row, self.den_row, norm_coeffs) + rpc_c.RPC.__init__( + self, + rpc_params["num_col"], + rpc_params["den_col"], + rpc_params["num_row"], + rpc_params["den_row"], + norm_coeffs, + ) @classmethod def load(cls, geomodel_path): diff --git a/tests/geomodels/test_rpc_optim.py b/tests/geomodels/test_rpc_optim.py index 3342ba9..efcf7a2 100644 --- a/tests/geomodels/test_rpc_optim.py +++ b/tests/geomodels/test_rpc_optim.py @@ -22,25 +22,34 @@ Module to test RpcOptim class """ + +import os + import numpy as np # Third party imports import pytest +# Shareloc bindings import rpc_c # Shareloc imports from shareloc.geomodels import GeoModel +from shareloc.geomodels.rpc_readers import rpc_reader +# Shareloc test imports +from ..helpers import data_path + +# pylint: disable=duplicate-code @pytest.mark.parametrize( "geom_path", [ - "tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", - "tests/data/rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", - "tests/data/rpc/PHRDIMAP_P1BP--2017030824934340CP.XML", - "tests/data/rpc/RPC_P1BP--2017092838284574CP.XML", - "tests/data/rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", + "rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", + "rpc/PHR1B_P_201709281038393_SEN_PRG_FC_178609-001.tif", + "rpc/PHRDIMAP_P1BP--2017030824934340CP.XML", + "rpc/RPC_P1BP--2017092838284574CP.XML", + "rpc/RPC_PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.XML", ], ) def test_construtor(geom_path): @@ -48,24 +57,42 @@ def test_construtor(geom_path): Test RpcOptim constructor """ - rpc_optim = GeoModel(geom_path, "RpcOptim") - rpc_py = GeoModel(geom_path, "RPC") + file_path = os.path.join(data_path(), geom_path) + + rpc_py = GeoModel(file_path, "RPC") + + rpc_params = rpc_reader(file_path, topleftconvention=True) + norm_coeffs = [ + rpc_params["offset_x"], + rpc_params["scale_x"], # longitude + rpc_params["offset_y"], + rpc_params["scale_y"], # latitude + rpc_params["offset_alt"], + rpc_params["scale_alt"], + rpc_params["offset_col"], + rpc_params["scale_col"], + rpc_params["offset_row"], + rpc_params["scale_row"], + ] + rpc_cpp = rpc_c.RPC( + rpc_params["num_col"], rpc_params["den_col"], rpc_params["num_row"], rpc_params["den_row"], norm_coeffs + ) - assert rpc_py.offset_x == rpc_optim.offset_x - assert rpc_py.scale_x == rpc_optim.scale_x - assert rpc_py.offset_y == rpc_optim.offset_y - assert rpc_py.scale_y == rpc_optim.scale_y - assert rpc_py.offset_alt == rpc_optim.offset_alt - assert rpc_py.scale_alt == rpc_optim.scale_alt - assert rpc_py.offset_col == rpc_optim.offset_col - assert rpc_py.scale_col == rpc_optim.scale_col - assert rpc_py.offset_row == rpc_optim.offset_row - assert rpc_py.scale_row == rpc_optim.scale_row + assert rpc_py.offset_x == rpc_cpp.get_offset_lon() + assert rpc_py.scale_x == rpc_cpp.get_scale_lon() + assert rpc_py.offset_y == rpc_cpp.get_offset_lat() + assert rpc_py.scale_y == rpc_cpp.get_scale_lat() + assert rpc_py.offset_alt == rpc_cpp.get_offset_alt() + assert rpc_py.scale_alt == rpc_cpp.get_scale_alt() + assert rpc_py.offset_col == rpc_cpp.get_offset_col() + assert rpc_py.scale_col == rpc_cpp.get_scale_col() + assert rpc_py.offset_row == rpc_cpp.get_offset_row() + assert rpc_py.scale_row == rpc_cpp.get_scale_row() - np.testing.assert_array_equal(rpc_py.num_col, rpc_optim.num_col) - np.testing.assert_array_equal(rpc_py.den_col, rpc_optim.den_col) - np.testing.assert_array_equal(rpc_py.num_row, rpc_optim.num_row) - np.testing.assert_array_equal(rpc_py.den_row, rpc_optim.den_row) + np.testing.assert_array_equal(rpc_py.num_col, rpc_cpp.get_num_col()) + np.testing.assert_array_equal(rpc_py.den_col, rpc_cpp.get_den_col()) + np.testing.assert_array_equal(rpc_py.num_row, rpc_cpp.get_num_row()) + np.testing.assert_array_equal(rpc_py.den_row, rpc_cpp.get_den_row()) def test_method_rpc_cpp(): @@ -73,12 +100,13 @@ def test_method_rpc_cpp(): Test call to method parent class rpc in cpp """ - rpc = GeoModel("tests/data/rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom", "RpcOptim") + file_path = os.path.join(data_path(), "rpc/PHR1B_P_201709281038045_SEN_PRG_FC_178608-001.geom") + rpc = GeoModel(file_path, "RpcOptim") vector_double = [1.0, 1.0, 1.0] double = 1.0 integer = 1 - string = "peuimporte" + string = "string" rpc.direct_loc_h(vector_double, vector_double, double, False) rpc.direct_loc_grid_h(integer, integer, integer, integer, integer, integer, double) @@ -119,7 +147,7 @@ def test_function_rpc_cpp(): rpc_c.derivative_polynomial_longitude(double, double, double, vector_double) - rpc_c.compute_loc_inverse_derivates_numba( + rpc_c.compute_loc_inverse_derivates_optimized( vector_double, vector_double, vector_double,