use apex_camera_models::{CameraModel, CameraModelError};
use apex_manifolds::se3::SE3;
use apex_manifolds::se23::SE23;
use apex_manifolds::so3::SO3Tangent;
use apex_manifolds::{LieGroup, Tangent};
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::validate::expect_block_sizes;
use crate::factors::visual::extrinsic_projection::inverse_act_jacobians;
pub struct TimeOffsetProjectionFactor<CAM: CameraModel> {
camera: CAM,
measurement: Vector2<f64>,
angular_velocity: Vector3<f64>,
verbose_cheirality: bool,
}
impl<CAM: CameraModel> TimeOffsetProjectionFactor<CAM> {
pub fn new(camera: CAM, measurement: Vector2<f64>, angular_velocity: Vector3<f64>) -> Self {
Self {
camera,
measurement,
angular_velocity,
verbose_cheirality: false,
}
}
pub fn with_verbose_cheirality(mut self) -> Self {
self.verbose_cheirality = true;
self
}
fn body_pose_at_exposure(
&self,
state: &SE23,
t_d: f64,
) -> (SE3, SMatrix<f64, 6, 9>, SMatrix<f64, 6, 1>) {
let rotation = state.rotation_matrix();
let delta_rot = SO3Tangent::new(self.angular_velocity * t_d).exp(None);
let r_d = delta_rot.rotation_matrix();
let pose = SE3::new(
state.translation() + state.velocity() * t_d,
(state.rotation_so3().compose(&delta_rot, None, None)).quaternion(),
);
let r_d_t = r_d.transpose();
let mut d_state = SMatrix::<f64, 6, 9>::zeros();
d_state.fixed_view_mut::<3, 3>(0, 0).copy_from(&r_d_t);
d_state
.fixed_view_mut::<3, 3>(0, 6)
.copy_from(&(r_d_t * t_d));
d_state.fixed_view_mut::<3, 3>(3, 3).copy_from(&r_d_t);
let mut d_offset = SMatrix::<f64, 6, 1>::zeros();
let r_c_t: Matrix3<f64> = (rotation * r_d).transpose();
d_offset
.fixed_view_mut::<3, 1>(0, 0)
.copy_from(&(r_c_t * state.velocity()));
d_offset
.fixed_view_mut::<3, 1>(3, 0)
.copy_from(&self.angular_velocity);
(pose, d_state, d_offset)
}
}
impl<CAM: CameraModel + Send + Sync + 'static> Factor for TimeOffsetProjectionFactor<CAM> {
fn linearize(
&self,
params: &[&[f64]],
residual: &mut [f64],
jacobian: Option<faer::mat::MatMut<'_, f64>>,
) {
let state = SE23::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 t_d = params[3][0];
let (t_wb, d_body_d_state, d_body_d_offset) = self.body_pose_at_exposure(&state, t_d);
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!("TimeOffsetProjectionFactor: 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 {
for row in 0..2 {
for col in 0..19 {
*jac.rb_mut().get_mut(row, col) = 0.0;
}
}
}
return;
}
Err(err) => {
if self.verbose_cheirality {
warn!("TimeOffsetProjectionFactor: 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_wb = d_uv_d_wc * d_wc_d_wb;
let d_uv_d_state = d_uv_d_wb * d_body_d_state;
let d_uv_d_offset = d_uv_d_wb * d_body_d_offset;
let d_uv_d_point = d_uv_d_cam * d_cam_d_point;
for row in 0..2 {
for col in 0..9 {
*jac.rb_mut().get_mut(row, col) = d_uv_d_state[(row, col)];
}
for col in 0..6 {
*jac.rb_mut().get_mut(row, 9 + col) = d_uv_d_wc[(row, col)];
}
for col in 0..3 {
*jac.rb_mut().get_mut(row, 15 + col) = d_uv_d_point[(row, col)];
}
*jac.rb_mut().get_mut(row, 18) = d_uv_d_offset[(row, 0)];
}
}
fn residual_dim(&self) -> usize {
2
}
fn jacobian_shape(&self) -> (usize, usize) {
(2, 19)
}
fn validate_variables(&self, variables: &[&dyn ManifoldVariable]) -> Result<(), String> {
expect_block_sizes(
variables,
&[SE23::REP_SIZE, SE3::REP_SIZE, 3, 1],
"TimeOffsetProjectionFactor expects \
[SE23 state, SE3 T_BC, 3D landmark, time offset (1D)]",
)
}
}