#!/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.
#
"""
This module contains triangulation methods.
"""
# Third party imports
import numpy as np
# Shareloc imports
from shareloc.geofunctions.rectification_grid import RectificationGrid
from shareloc.geomodels.los import LOS
from shareloc.proj_utils import coordinates_conversion
[docs]
def sensor_triangulation(
matches,
geometrical_model_left,
geometrical_model_right,
left_min_max=None,
right_min_max=None,
residues=False,
fill_nan=False,
):
"""
triangulation in sensor geometry
according to the formula:
.. math::
x = \\left(\\sum_i I-\\hat v_i \\hat v_i^\\top\\right)^{-1}
\\left(\\sum_i (I-\\hat v_i \\hat v_i^\\top) s_i\\right)
Delvit J.M. et al. "The geometric supersite of Salon de Provence", ISPRS Congress Paris, 2006.
:param matches: matches in sensor coordinates Nx[row (left), col (left), row (right), col (right)]
:type matches: np.array
:param geometrical_model_left: left image geometrical model
:type geometrical_model_left: GeomodelTemplate
:param geometrical_model_right: right image geometrical model
:type geometrical_model_right: GeomodelTemplate
:param left_min_max: left min/max for los creation, if None model min/max will be used
:type left_min_max: list
:param right_min_max: right min/max for los creation, if None model min/max will be used
:type right_min_max: list
:param residues: calculates residues (distance in meters between los and 3D points)
:type residues: boolean
: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: intersections in cartesian crs, intersections in wgs84 crs and optionnaly residues
:rtype: (numpy.array,numpy,array,numpy.array)
"""
# LOS instantiation
matches_left = matches[:, 0:2]
left_los = LOS(matches_left, geometrical_model_left, left_min_max, fill_nan)
matches_right = matches[:, 2:4]
right_los = LOS(matches_right, geometrical_model_right, right_min_max, fill_nan)
# LOS conversion
# LOS intersection
intersections_ecef = los_triangulation(left_los, right_los)
in_crs = 4978
out_crs = 4326
intersections_wgs84 = coordinates_conversion(intersections_ecef, in_crs, out_crs)
# refine matches
intersections_residues = None
if residues is True:
intersections_residues = distance_point_los(left_los, intersections_ecef)
return intersections_ecef, intersections_wgs84, intersections_residues
[docs]
def distance_point_los(los, points):
"""
distance between points and LOS
norm of cross product between vector defined by LOS sis and point and LOS vis
:param los: line of sight
:type los: shareloc.los
:param points: 3D points
:type points: numpy.array
:return: distance
:rtype: numpy.array
"""
# norm(BA vect u)/norm(u)
# only one view
vis = los.viewing_vectors[:, np.newaxis, :]
sis = los.starting_points[:, np.newaxis, :]
return n_view_distance(sis, vis, points)
[docs]
def n_view_distance(sis: np.ndarray, vis: np.ndarray, points: np.ndarray) -> np.ndarray:
"""
distance between points and LOS
norm of cross product between vector defined by LOS sis and point and LOS vis
:param sis: LOS hat, numpy np.ndarray of size (nb_points,nb_views,3)
:param vis: LOS viewing vector, np.ndarray of size (nb_points,nb_views,3)
:param points: 3D points
:return: distance
"""
# norm(BA vect u)/norm(u)
nb_views = sis.shape[1]
vect_sis_p = np.tile(points[:, np.newaxis, :], (1, nb_views, 1)) - sis
dist = np.linalg.norm(np.cross(vect_sis_p, vis), axis=2)
return dist
[docs]
def los_triangulation(left_los, right_los):
"""
LOS triangulation
:param left_los: left los
:type left_los: shareloc.los
:param right_los: right los
:type right_los: shareloc.los
:return: intersections in cartesian crs
:rtype: numpy.array
"""
vis = np.dstack((left_los.viewing_vectors, right_los.viewing_vectors))
vis = np.swapaxes(vis, 1, 2)
sis = np.dstack((left_los.starting_points, right_los.starting_points))
sis = np.swapaxes(sis, 1, 2)
return n_view_triangulation(sis, vis)
[docs]
def n_view_triangulation(sis: np.ndarray, vis: np.ndarray) -> np.ndarray:
"""
n view triangulation for nb_points mapping
:param sis: LOS hat as numpy np.ndarray of size (nb_points,nb_views,3)
:param vis: LOS viewing vector np.ndarray of size (nb_points,nb_views,3)
:return: triangulation as a np.ndarray of size (nb_points,3)
"""
vivi = vis[..., :, np.newaxis] * vis[..., np.newaxis, :]
id_vivi = np.eye(3) - vivi
sum_id_vivi = np.nansum(id_vivi, axis=-3)
id_vivi_si = np.nansum(id_vivi * sis[..., np.newaxis, :], axis=-1)
sum_id_vivi_si = np.nansum(id_vivi_si, axis=-2)
inv_sum_id_vivi = np.linalg.inv(sum_id_vivi)
intersection = np.nansum(inv_sum_id_vivi * sum_id_vivi_si[..., np.newaxis, :], axis=-1)
return intersection
# CARS API
# pylint: disable=too-many-arguments
[docs]
def epipolar_triangulation(
matches,
mask,
matches_type,
geometrical_model_left,
geometrical_model_right,
grid_left,
grid_right,
left_min_max=None,
right_min_max=None,
residues=False,
fill_nan=False,
is_displacement_grid=False,
):
"""
epipolar triangulation
:param matches: matches
:type matches: dependent of disp or sift
:param mask: mask for disparity (see transform_disp_to_matches)
:type mask: numpy.array
:param matches_type: 'disp' or 'sift'
:type matches_type: str
:param geometrical_model_left: left image geometrical model
:type geometrical_model_left: GeomodelTemplate
:param geometrical_model_right: right image geometrical model
:type geometrical_model_right: GeomodelTemplate
:param grid_left: left rectification grid filename
:type grid_left: str
:param grid_right: right rectification grid filename
:type grid_right: str
:param left_min_max: left min/max for los creation, if None model min/max will be used
:type left_min_max: list
:param right_min_max: right min/max for los creation, if None model min/max will be used
:type right_min_max: list
:param residues: calculates residues (distance in meters)
:type residues: boolean
: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 is_displacement_grid: True if grids are displacement grids
:type is_displacement_grid: bool
:return: intersections in cartesian crs
:rtype: numpy.array
"""
# retrieve point matches in sensor geometry
if matches_type == "sift":
epi_pos_left = matches[:, 0:2]
epi_pos_right = matches[:, 2:4]
values_ok = np.full((matches.shape[0]), True, dtype=bool)
elif matches_type == "disp":
[epi_pos_left, epi_pos_right, values_ok] = transform_disp_to_matches(matches, mask)
else:
raise KeyError("matches type should be sift or disp")
# interpolate left
rectif_grid_left = RectificationGrid(grid_left, is_displacement_grid=is_displacement_grid)
rectif_grid_right = RectificationGrid(grid_right, is_displacement_grid=is_displacement_grid)
# interpolate_right
matches_sensor_left = rectif_grid_left.interpolate(epi_pos_left)
matches_sensor_right = rectif_grid_right.interpolate(epi_pos_right)
matches_sensor = np.concatenate((matches_sensor_left, matches_sensor_right), axis=1)
# triangulate positions in sensor geometry
[intersections_ecef, intersections_wgs84, intersections_residues] = sensor_triangulation(
matches_sensor, geometrical_model_left, geometrical_model_right, left_min_max, right_min_max, residues, fill_nan
)
tab_size = values_ok.shape[0]
intersections_ecef_masked = np.zeros((tab_size, 3))
intersections_wgs84_masked = np.zeros((tab_size, 3))
intersections_residues_masked = np.zeros((tab_size, 1))
intersections_ecef_masked[values_ok, :] = intersections_ecef
intersections_wgs84_masked[values_ok, :] = intersections_wgs84
intersections_residues_masked[values_ok, :] = intersections_residues
return intersections_ecef_masked, intersections_wgs84_masked, intersections_residues_masked