use apex_camera_models::{CameraModel, CameraModelError};
use apex_manifolds::LieGroup;
use apex_manifolds::se3::SE3;
use faer::prelude::ReborrowMut;
use nalgebra::{Matrix3, SMatrix, Vector2, Vector3};
use tracing::warn;
use crate::core::variable::ManifoldVariable;
use crate::factors::Factor;
use crate::factors::common::cheirality::{CHEIRALITY_BASE_PENALTY, CHEIRALITY_DEPTH_SCALE};
use crate::factors::common::math::skew;
use crate::factors::common::validate::expect_block_sizes;
pub(crate) fn inverse_act_jacobians(
pose: &SE3,
point_world: &Vector3<f64>,
) -> (Vector3<f64>, SMatrix<f64, 3, 6>, Matrix3<f64>) {
let rotation = pose.rotation_so3().rotation_matrix();
let q = rotation.transpose() * (point_world - pose.translation());
let mut d_pose = SMatrix::<f64, 3, 6>::zeros();
d_pose
.fixed_view_mut::<3, 3>(0, 0)
.copy_from(&(-Matrix3::identity()));
d_pose.fixed_view_mut::<3, 3>(0, 3).copy_from(&skew(&q));
(q, d_pose, rotation.transpose())
}
pub struct ExtrinsicProjectionFactor<CAM: CameraModel> {
camera: CAM,
measurement: Vector2<f64>,
verbose_cheirality: bool,
}
impl<CAM: CameraModel> ExtrinsicProjectionFactor<CAM> {
pub fn new(camera: CAM, measurement: Vector2<f64>) -> Self {
Self {
camera,
measurement,
verbose_cheirality: false,
}
}
pub fn with_verbose_cheirality(mut self) -> Self {
self.verbose_cheirality = true;
self
}
}
impl<CAM: CameraModel + Send + Sync + 'static> Factor for ExtrinsicProjectionFactor<CAM> {
fn linearize(
&self,
params: &[&[f64]],
residual: &mut [f64],
jacobian: Option<faer::mat::MatMut<'_, f64>>,
) {
let t_wb = SE3::from_param_slice(params[0]);
let t_bc = SE3::from_param_slice(params[1]);
let p_world = Vector3::new(params[2][0], params[2][1], params[2][2]);
let mut d_wc_d_wb = SE3::zero_jacobian();
let t_wc = t_wb.compose(&t_bc, Some(&mut d_wc_d_wb), None);
let (p_cam, d_cam_d_wc, d_cam_d_point) = inverse_act_jacobians(&t_wc, &p_world);
let projection = match self.camera.project(&p_cam) {
Ok(uv) => uv,
Err(CameraModelError::PointBehindCamera { z, min_z }) => {
if self.verbose_cheirality {
warn!("ExtrinsicProjectionFactor: point behind camera (z={z}, min_z={min_z})");
}
let penalty =
CHEIRALITY_BASE_PENALTY + CHEIRALITY_DEPTH_SCALE * (min_z - z).max(0.0);
residual[0] = penalty;
residual[1] = penalty;
if let Some(mut jac) = jacobian {
let d_pose = -CHEIRALITY_DEPTH_SCALE * (d_cam_d_wc.row(2) * d_wc_d_wb);
let d_ext = -CHEIRALITY_DEPTH_SCALE * d_cam_d_wc.row(2);
let d_point = -CHEIRALITY_DEPTH_SCALE * d_cam_d_point.row(2);
for row in 0..2 {
for col in 0..6 {
*jac.rb_mut().get_mut(row, col) = d_pose[(0, col)];
*jac.rb_mut().get_mut(row, 6 + col) = d_ext[(0, col)];
}
for col in 0..3 {
*jac.rb_mut().get_mut(row, 12 + col) = d_point[(0, col)];
}
}
}
return;
}
Err(err) => {
if self.verbose_cheirality {
warn!("ExtrinsicProjectionFactor: invalid projection: {err}");
}
residual[0] = CHEIRALITY_BASE_PENALTY;
residual[1] = CHEIRALITY_BASE_PENALTY;
return;
}
};
residual[0] = projection.x - self.measurement.x;
residual[1] = projection.y - self.measurement.y;
let Some(mut jac) = jacobian else { return };
let d_uv_d_cam = self.camera.jacobian_point(&p_cam);
let d_uv_d_wc = d_uv_d_cam.clone() * d_cam_d_wc;
let d_uv_d_body = d_uv_d_wc * d_wc_d_wb;
let d_uv_d_point = d_uv_d_cam * d_cam_d_point;
for row in 0..2 {
for col in 0..6 {
*jac.rb_mut().get_mut(row, col) = d_uv_d_body[(row, col)];
*jac.rb_mut().get_mut(row, 6 + col) = d_uv_d_wc[(row, col)];
}
for col in 0..3 {
*jac.rb_mut().get_mut(row, 12 + col) = d_uv_d_point[(row, col)];
}
}
}
fn residual_dim(&self) -> usize {
2
}
fn jacobian_shape(&self) -> (usize, usize) {
(2, 15)
}
fn validate_variables(&self, variables: &[&dyn ManifoldVariable]) -> Result<(), String> {
expect_block_sizes(
variables,
&[SE3::REP_SIZE, SE3::REP_SIZE, 3],
"ExtrinsicProjectionFactor expects [SE3 T_WB, SE3 T_BC, 3D landmark]",
)
}
}