Skip to content

Commit

Permalink
feat: use cubic interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
steuxyo authored and dyoussef committed Feb 8, 2025
1 parent 15d1557 commit ecba82c
Show file tree
Hide file tree
Showing 132 changed files with 251 additions and 93 deletions.
132 changes: 93 additions & 39 deletions cars/core/geometry/abstract_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import numpy as np
import rasterio as rio
import xarray as xr
from json_checker import And, Checker
from scipy import interpolate
from scipy.interpolate import LinearNDInterpolator
from shapely.geometry import Polygon
Expand All @@ -47,16 +48,25 @@ class AbstractGeometry(metaclass=ABCMeta):

available_plugins: Dict = {}

def __new__(cls, geometry_plugin=None, **kwargs):
def __new__(cls, geometry_plugin_conf=None, **kwargs):
"""
Return the required plugin
:raises:
- KeyError when the required plugin is not registered
:param geometry_plugin: plugin name to instantiate
:param geometry_plugin_conf: plugin name or plugin configuration
to instantiate
:type geometry_plugin_conf: str or dict
:return: a geometry_plugin object
"""
if geometry_plugin is not None:
if geometry_plugin_conf is not None:
if isinstance(geometry_plugin_conf, str):
geometry_plugin = geometry_plugin_conf
elif isinstance(geometry_plugin_conf, dict):
geometry_plugin = geometry_plugin_conf.get("plugin_name", None)
else:
raise RuntimeError("Not a supported type")

if geometry_plugin not in cls.available_plugins:
logging.error(
"No geometry plugin named {} registered".format(
Expand All @@ -81,10 +91,19 @@ def __new__(cls, geometry_plugin=None, **kwargs):
return super().__new__(cls)

def __init__(
self, geometry_plugin, dem=None, geoid=None, default_alt=None, **kwargs
self,
geometry_plugin_conf,
dem=None,
geoid=None,
default_alt=None,
**kwargs
):

self.plugin_name = geometry_plugin
config = self.check_conf(geometry_plugin_conf)

self.plugin_name = config["plugin_name"]
self.interpolator = config["interpolator"]

self.dem = dem
self.dem_roi = None
self.dem_roi_epsg = None
Expand All @@ -111,9 +130,44 @@ def decorator(subclass):

return decorator

@staticmethod
def check_conf(self, conf):
"""
Check configuration
:param conf: configuration to check
:type conf: str or dict
:return: full dict
:rtype: dict
"""

if conf is None:
raise RuntimeError("Geometry plugin configuration is None")

overloaded_conf = {}

if isinstance(conf, str):
conf = {"plugin_name": conf}

# overload conf
overloaded_conf["plugin_name"] = conf.get("plugin_name", None)
overloaded_conf["interpolator"] = conf.get("interpolator", "cubic")

geometry_schema = {
"plugin_name": str,
"interpolator": And(str, lambda x: x in ["cubic", "linear"]),
}

# Check conf
checker = Checker(geometry_schema)
checker.validate(overloaded_conf)

return overloaded_conf

@abstractmethod
def triangulate(
self,
sensor1,
sensor2,
geomodel1,
Expand Down Expand Up @@ -266,12 +320,12 @@ def matches_to_sensor_coords(
)

# convert epipolar matches to sensor coordinates
sensor_pos_left = AbstractGeometry.sensor_position_from_grid(
grid1, vec_epi_pos_left
)
sensor_pos_right = AbstractGeometry.sensor_position_from_grid(
grid2, vec_epi_pos_right
)
sensor_pos_left = AbstractGeometry(
{"plugin_name": "SharelocGeometry", "interpolator": "linear"}
).sensor_position_from_grid(grid1, vec_epi_pos_left)
sensor_pos_right = AbstractGeometry(
{"plugin_name": "SharelocGeometry", "interpolator": "linear"}
).sensor_position_from_grid(grid2, vec_epi_pos_right)

if matches_type == cst.DISP_MODE:
# rearrange matches in the original epipolar geometry
Expand Down Expand Up @@ -302,8 +356,8 @@ def matches_to_sensor_coords(

return sensor_pos_left, sensor_pos_right

@staticmethod
def sensor_position_from_grid(
self,
grid: Union[str, cars_dataset.CarsDataset],
positions: np.ndarray,
) -> np.ndarray:
Expand All @@ -315,6 +369,7 @@ def sensor_position_from_grid(
array of size [number of points, 2]. The last index indicates
the 'x' coordinate (last index set to 0) or the 'y' coordinate
(last index set to 1).
:param interpolator: interpolator to use
:return: sensors positions as a numpy array of size
[number of points, 2]. The last index indicates the 'x'
coordinate (last index set to 0) or
Expand Down Expand Up @@ -363,34 +418,31 @@ def sensor_position_from_grid(
rows = np.arange(ori_row, last_row, step_row)

# create regular grid points positions
points = (cols, rows)
sensor_row_positions = row_dep
sensor_col_positions = col_dep

# interpolate sensor positions
interp_row = interpolate.interpn(
interpolator = interpolate.RegularGridInterpolator(
(cols, rows),
sensor_row_positions.transpose(),
positions,
method="linear",
bounds_error=False,
fill_value=None,
)
interp_col = interpolate.interpn(
points,
sensor_col_positions.transpose(),
positions,
method="linear",
np.stack(
(
sensor_row_positions.transpose(),
sensor_col_positions.transpose(),
),
axis=2,
),
method=self.interpolator,
bounds_error=False,
fill_value=None,
)

# stack both coordinates
sensor_positions = np.transpose(np.vstack([interp_col, interp_row]))
sensor_positions = interpolator(positions)
# swap
sensor_positions[:, [0, 1]] = sensor_positions[:, [1, 0]]

return sensor_positions

@staticmethod
def epipolar_position_from_grid(grid, sensor_positions, step=30):
def epipolar_position_from_grid(self, grid, sensor_positions, step=30):
"""
Compute epipolar position from grid
Expand All @@ -413,9 +465,7 @@ def epipolar_position_from_grid(grid, sensor_positions, step=30):
[epi_grid_row.flatten(), epi_grid_col.flatten()], axis=1
)

sensor_interp_pos = AbstractGeometry.sensor_position_from_grid(
grid, full_epi_pos
)
sensor_interp_pos = self.sensor_position_from_grid(grid, full_epi_pos)
interp_row = LinearNDInterpolator(
list(
zip( # noqa: B905
Expand Down Expand Up @@ -446,9 +496,13 @@ def epipolar_position_from_grid(grid, sensor_positions, step=30):

return epipolar_positions

@staticmethod
def transform_matches_from_grids(
matches_array, grid_left, grid_right, new_grid_left, new_grid_right
self,
matches_array,
grid_left,
grid_right,
new_grid_left,
new_grid_right,
):
"""
Transform epipolar matches with grid transformation
Expand All @@ -462,18 +516,18 @@ def transform_matches_from_grids(
"""

# Transform to sensors
sensor_matches_left = AbstractGeometry.sensor_position_from_grid(
sensor_matches_left = self.sensor_position_from_grid(
grid_left, matches_array[:, 0:2]
)
sensor_matches_right = AbstractGeometry.sensor_position_from_grid(
sensor_matches_right = self.sensor_position_from_grid(
grid_right, matches_array[:, 2:4]
)

# Transform to new grids
new_grid_matches_left = AbstractGeometry.epipolar_position_from_grid(
new_grid_matches_left = self.epipolar_position_from_grid(
new_grid_left, sensor_matches_left
)
new_grid_matches_right = AbstractGeometry.epipolar_position_from_grid(
new_grid_matches_right = self.epipolar_position_from_grid(
new_grid_right, sensor_matches_right
)

Expand Down
16 changes: 11 additions & 5 deletions cars/core/geometry/shareloc_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ class SharelocGeometry(AbstractGeometry):

def __init__(
self,
geometry_plugin,
geometry_plugin_conf,
dem=None,
geoid=None,
default_alt=None,
pairs_for_roi=None,
rectification_grid_margin=0,
):

super().__init__(
geometry_plugin,
geometry_plugin_conf,
dem=dem,
geoid=geoid,
default_alt=default_alt,
Expand All @@ -76,7 +76,11 @@ def __init__(
self.dem_roi = None
self.roi_shareloc = None
self.elevation = None
self.rectification_grid_margin = rectification_grid_margin

# a margin is needed for cubic interpolation
self.rectification_grid_margin = 0
if self.interpolator == "cubic":
self.rectification_grid_margin = 5

# compute roi only when generating geometry object with dem
# even if dem is None
Expand Down Expand Up @@ -250,8 +254,8 @@ def check_product_consistency(sensor: str, geomodel: dict) -> bool:

return sensor, overloaded_geomodel

@staticmethod
def triangulate(
self,
sensor1,
sensor2,
geomodel1,
Expand Down Expand Up @@ -300,6 +304,7 @@ def triangulate(
grid_right=grid2,
residues=True,
fill_nan=True,
interpolator=self.interpolator,
)

llh = point_wgs84.reshape((point_wgs84.shape[0], 1, 3))
Expand All @@ -315,6 +320,7 @@ def triangulate(
grid_right=grid2,
residues=True,
fill_nan=True,
interpolator=self.interpolator,
)

row = np.array(
Expand Down
2 changes: 1 addition & 1 deletion cars/pipelines/default/default_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1479,7 +1479,7 @@ def sensor_to_depth_maps(self): # noqa: C901
# Correct grids with former matches
# Transform matches to new grids
new_grid_matches_array = (
AbstractGeometry.transform_matches_from_grids(
geom_plugin.transform_matches_from_grids(
matches,
self.pairs[pair_key]["corrected_grid_left"],
self.pairs[pair_key]["corrected_grid_right"],
Expand Down
11 changes: 10 additions & 1 deletion cars/pipelines/parameters/sensor_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,20 @@ def check_geometry_plugin(conf_inputs, conf_advanced, conf_geom_plugin):
if not total_input_roi_poly.contains_properly(
dem_generation_roi_poly
):
raise RuntimeError(
base_message = (
"Given initial elevation ROI is not covering needed ROI: "
" EPSG:4326, ROI: {}".format(dem_generation_roi_poly.bounds)
)

if total_input_roi_poly.intersects(dem_generation_roi_poly):
logging.warning(
"{}. Only a part of it intersects. "
"Errors might occur".format(base_message)
)
else:
# Exit, Error is certain to occur
raise RuntimeError(base_message)

else:
logging.warning(
"Current geometry plugin doesnt compute dem roi needed "
Expand Down
4 changes: 2 additions & 2 deletions cars/pipelines/pipeline_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from abc import ABCMeta, abstractmethod

from json_checker import Checker, OptionalKey
from json_checker import Checker, OptionalKey, Or

# CARS imports
from cars.orchestrator import orchestrator
Expand Down Expand Up @@ -68,7 +68,7 @@ def check_global_schema(self, conf):
pipeline_constants.INPUTS: dict,
pipeline_constants.OUTPUT: dict,
OptionalKey(pipeline_constants.APPLICATIONS): dict,
OptionalKey(pipeline_constants.GEOMETRY_PLUGIN): str,
OptionalKey(pipeline_constants.GEOMETRY_PLUGIN): Or(str, dict),
OptionalKey(pipeline_constants.ORCHESTRATOR): dict,
OptionalKey(pipeline_constants.PIPELINE): str,
OptionalKey(pipeline_constants.ADVANCED): dict,
Expand Down
34 changes: 29 additions & 5 deletions docs/source/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -795,11 +795,23 @@ The structure follows this organization:

This section describes configuration of the geometry plugins for CARS, please refer to :ref:`plugins` section for details on plugins installation.

+-------------------+-----------------------+--------+-------------------------+---------------------------------------+----------+
| Name | Description | Type | Default value | Available values | Required |
+===================+=======================+========+=========================+=======================================+==========+
| *geometry_plugin* | The plugin to use | str | "SharelocGeometry" | "SharelocGeometry" | False |
+-------------------+-----------------------+--------+-------------------------+---------------------------------------+----------+
+-------------------+-----------------------+----------------+-------------------------+---------------------------------------+----------+
| Name | Description | Type | Default value | Available values | Required |
+===================+=======================+================+=========================+=======================================+==========+
| *geometry_plugin* | The plugin to use | str or dict | "SharelocGeometry" | "SharelocGeometry" | False |
+-------------------+-----------------------+----------------+-------------------------+---------------------------------------+----------+

**geometry_plugin** allow user to specify other parameters, through a dictionary:

+-------------------+--------------------------+----------------+-------------------------+---------------------------------------+----------+
| Name | Description | Type | Default value | Available values | Required |
+===================+==========================+================+=========================+=======================================+==========+
| *plugin_name* | The plugin name to use | str | "SharelocGeometry" | "SharelocGeometry" | False |
+-------------------+--------------------------+----------------+-------------------------+---------------------------------------+----------+
| *interpolator* | Interpolator to use | str | "cubic" | "cubic" , "linear" | False |
+-------------------+--------------------------+----------------+-------------------------+---------------------------------------+----------+



To use Shareloc geometry library, CARS input configuration should be defined as :

Expand Down Expand Up @@ -834,6 +846,18 @@ The structure follows this organization:
}
}
**geometry_plugin** specify the plugin to use, but other configuration parameters can be specified :

.. code-block:: json
"geometry_plugin": {
"plugin_name": "SharelocGeometry",
"interpolator": "cubic"
}
The particularities in the configuration file are:

* **geomodel.model_type**: Depending on the nature of the geometric models indicated above, this field as to be defined as `RPC` or `GRID`. By default, "RPC".
Expand Down
Loading

0 comments on commit ecba82c

Please sign in to comment.