#!/usr/bin/env python3
import sys
import numpy as np
import sympy as sp
from selfdrive . locationd . models . constants import ObservationKind
from rednose . helpers . ekf_sym import EKF_sym , gen_code
from rednose . helpers . sympy_helpers import euler_rotate , quat_matrix_r , quat_rotate
EARTH_GM = 3.986005e14 # m^3/s^2 (gravitational constant * mass of earth)
class States ( ) :
ECEF_POS = slice ( 0 , 3 ) # x, y and z in ECEF in meters
ECEF_ORIENTATION = slice ( 3 , 7 ) # quat for pose of phone in ecef
ECEF_VELOCITY = slice ( 7 , 10 ) # ecef velocity in m/s
ANGULAR_VELOCITY = slice ( 10 , 13 ) # roll, pitch and yaw rates in device frame in radians/s
GYRO_BIAS = slice ( 13 , 16 ) # roll, pitch and yaw biases
ODO_SCALE = slice ( 16 , 17 ) # odometer scale
ACCELERATION = slice ( 17 , 20 ) # Acceleration in device frame in m/s**2
IMU_OFFSET = slice ( 20 , 23 ) # imu offset angles in radians
# Error-state has different slices because it is an ESKF
ECEF_POS_ERR = slice ( 0 , 3 )
ECEF_ORIENTATION_ERR = slice ( 3 , 6 ) # euler angles for orientation error
ECEF_VELOCITY_ERR = slice ( 6 , 9 )
ANGULAR_VELOCITY_ERR = slice ( 9 , 12 )
GYRO_BIAS_ERR = slice ( 12 , 15 )
ODO_SCALE_ERR = slice ( 15 , 16 )
ACCELERATION_ERR = slice ( 16 , 19 )
IMU_OFFSET_ERR = slice ( 19 , 22 )
class LiveKalman ( ) :
name = ' live '
initial_x = np . array ( [ - 2.7e6 , 4.2e6 , 3.8e6 ,
1 , 0 , 0 , 0 ,
0 , 0 , 0 ,
0 , 0 , 0 ,
0 , 0 , 0 ,
1 ,
0 , 0 , 0 ,
0 , 0 , 0 ] )
# state covariance
initial_P_diag = np . array ( [ 1e16 , 1e16 , 1e16 ,
1e6 , 1e6 , 1e6 ,
1e4 , 1e4 , 1e4 ,
1 * * 2 , 1 * * 2 , 1 * * 2 ,
0.05 * * 2 , 0.05 * * 2 , 0.05 * * 2 ,
0.02 * * 2 ,
1 * * 2 , 1 * * 2 , 1 * * 2 ,
( 0.01 ) * * 2 , ( 0.01 ) * * 2 , ( 0.01 ) * * 2 ] )
# process noise
Q = np . diag ( [ 0.03 * * 2 , 0.03 * * 2 , 0.03 * * 2 ,
0.001 * * 2 , 0.001 * 2 , 0.001 * * 2 ,
0.01 * * 2 , 0.01 * * 2 , 0.01 * * 2 ,
0.1 * * 2 , 0.1 * * 2 , 0.1 * * 2 ,
( 0.005 / 100 ) * * 2 , ( 0.005 / 100 ) * * 2 , ( 0.005 / 100 ) * * 2 ,
( 0.02 / 100 ) * * 2 ,
3 * * 2 , 3 * * 2 , 3 * * 2 ,
( 0.05 / 60 ) * * 2 , ( 0.05 / 60 ) * * 2 , ( 0.05 / 60 ) * * 2 ] )
@staticmethod
def generate_code ( generated_dir ) :
name = LiveKalman . name
dim_state = LiveKalman . initial_x . shape [ 0 ]
dim_state_err = LiveKalman . initial_P_diag . shape [ 0 ]
state_sym = sp . MatrixSymbol ( ' state ' , dim_state , 1 )
state = sp . Matrix ( state_sym )
x , y , z = state [ States . ECEF_POS , : ]
q = state [ States . ECEF_ORIENTATION , : ]
v = state [ States . ECEF_VELOCITY , : ]
vx , vy , vz = v
omega = state [ States . ANGULAR_VELOCITY , : ]
vroll , vpitch , vyaw = omega
roll_bias , pitch_bias , yaw_bias = state [ States . GYRO_BIAS , : ]
odo_scale = state [ States . ODO_SCALE , : ] [ 0 , : ]
acceleration = state [ States . ACCELERATION , : ]
imu_angles = state [ States . IMU_OFFSET , : ]
dt = sp . Symbol ( ' dt ' )
# calibration and attitude rotation matrices
quat_rot = quat_rotate ( * q )
# Got the quat predict equations from here
# A New Quaternion-Based Kalman Filter for
# Real-Time Attitude Estimation Using the Two-Step
# Geometrically-Intuitive Correction Algorithm
A = 0.5 * sp . Matrix ( [ [ 0 , - vroll , - vpitch , - vyaw ] ,
[ vroll , 0 , vyaw , - vpitch ] ,
[ vpitch , - vyaw , 0 , vroll ] ,
[ vyaw , vpitch , - vroll , 0 ] ] )
q_dot = A * q
# Time derivative of the state as a function of state
state_dot = sp . Matrix ( np . zeros ( ( dim_state , 1 ) ) )
state_dot [ States . ECEF_POS , : ] = v
state_dot [ States . ECEF_ORIENTATION , : ] = q_dot
state_dot [ States . ECEF_VELOCITY , 0 ] = quat_rot * acceleration
# Basic descretization, 1st order intergrator
# Can be pretty bad if dt is big
f_sym = state + dt * state_dot
state_err_sym = sp . MatrixSymbol ( ' state_err ' , dim_state_err , 1 )
state_err = sp . Matrix ( state_err_sym )
quat_err = state_err [ States . ECEF_ORIENTATION_ERR , : ]
v_err = state_err [ States . ECEF_VELOCITY_ERR , : ]
omega_err = state_err [ States . ANGULAR_VELOCITY_ERR , : ]
acceleration_err = state_err [ States . ACCELERATION_ERR , : ]
# Time derivative of the state error as a function of state error and state
quat_err_matrix = euler_rotate ( quat_err [ 0 ] , quat_err [ 1 ] , quat_err [ 2 ] )
q_err_dot = quat_err_matrix * quat_rot * ( omega + omega_err )
state_err_dot = sp . Matrix ( np . zeros ( ( dim_state_err , 1 ) ) )
state_err_dot [ States . ECEF_POS_ERR , : ] = v_err
state_err_dot [ States . ECEF_ORIENTATION_ERR , : ] = q_err_dot
state_err_dot [ States . ECEF_VELOCITY_ERR , : ] = quat_err_matrix * quat_rot * ( acceleration + acceleration_err )
f_err_sym = state_err + dt * state_err_dot
# Observation matrix modifier
H_mod_sym = sp . Matrix ( np . zeros ( ( dim_state , dim_state_err ) ) )
H_mod_sym [ States . ECEF_POS , States . ECEF_POS_ERR ] = np . eye ( States . ECEF_POS . stop - States . ECEF_POS . start )
H_mod_sym [ States . ECEF_ORIENTATION , States . ECEF_ORIENTATION_ERR ] = 0.5 * quat_matrix_r ( state [ 3 : 7 ] ) [ : , 1 : ]
H_mod_sym [ States . ECEF_ORIENTATION . stop : , States . ECEF_ORIENTATION_ERR . stop : ] = np . eye ( dim_state - States . ECEF_ORIENTATION . stop )
# these error functions are defined so that say there
# is a nominal x and true x:
# true x = err_function(nominal x, delta x)
# delta x = inv_err_function(nominal x, true x)
nom_x = sp . MatrixSymbol ( ' nom_x ' , dim_state , 1 )
true_x = sp . MatrixSymbol ( ' true_x ' , dim_state , 1 )
delta_x = sp . MatrixSymbol ( ' delta_x ' , dim_state_err , 1 )
err_function_sym = sp . Matrix ( np . zeros ( ( dim_state , 1 ) ) )
delta_quat = sp . Matrix ( np . ones ( ( 4 ) ) )
delta_quat [ 1 : , : ] = sp . Matrix ( 0.5 * delta_x [ States . ECEF_ORIENTATION_ERR , : ] )
err_function_sym [ States . ECEF_POS , : ] = sp . Matrix ( nom_x [ States . ECEF_POS , : ] + delta_x [ States . ECEF_POS_ERR , : ] )
err_function_sym [ States . ECEF_ORIENTATION , 0 ] = quat_matrix_r ( nom_x [ States . ECEF_ORIENTATION , 0 ] ) * delta_quat
err_function_sym [ States . ECEF_ORIENTATION . stop : , : ] = sp . Matrix ( nom_x [ States . ECEF_ORIENTATION . stop : , : ] + delta_x [ States . ECEF_ORIENTATION_ERR . stop : , : ] )
inv_err_function_sym = sp . Matrix ( np . zeros ( ( dim_state_err , 1 ) ) )
inv_err_function_sym [ States . ECEF_POS_ERR , 0 ] = sp . Matrix ( - nom_x [ States . ECEF_POS , 0 ] + true_x [ States . ECEF_POS , 0 ] )
delta_quat = quat_matrix_r ( nom_x [ States . ECEF_ORIENTATION , 0 ] ) . T * true_x [ States . ECEF_ORIENTATION , 0 ]
inv_err_function_sym [ States . ECEF_ORIENTATION_ERR , 0 ] = sp . Matrix ( 2 * delta_quat [ 1 : ] )
inv_err_function_sym [ States . ECEF_ORIENTATION_ERR . stop : , 0 ] = sp . Matrix ( - nom_x [ States . ECEF_ORIENTATION . stop : , 0 ] + true_x [ States . ECEF_ORIENTATION . stop : , 0 ] )
eskf_params = [ [ err_function_sym , nom_x , delta_x ] ,
[ inv_err_function_sym , nom_x , true_x ] ,
H_mod_sym , f_err_sym , state_err_sym ]
#
# Observation functions
#
#imu_rot = euler_rotate(*imu_angles)
h_gyro_sym = sp . Matrix ( [ vroll + roll_bias ,
vpitch + pitch_bias ,
vyaw + yaw_bias ] )
pos = sp . Matrix ( [ x , y , z ] )
gravity = quat_rot . T * ( ( EARTH_GM / ( ( x * * 2 + y * * 2 + z * * 2 ) * * ( 3.0 / 2.0 ) ) ) * pos )
h_acc_sym = ( gravity + acceleration )
h_phone_rot_sym = sp . Matrix ( [ vroll , vpitch , vyaw ] )
speed = sp . sqrt ( vx * * 2 + vy * * 2 + vz * * 2 + 1e-6 )
h_speed_sym = sp . Matrix ( [ speed * odo_scale ] )
h_pos_sym = sp . Matrix ( [ x , y , z ] )
h_vel_sym = sp . Matrix ( [ vx , vy , vz ] )
h_orientation_sym = q
h_imu_frame_sym = sp . Matrix ( imu_angles )
h_relative_motion = sp . Matrix ( quat_rot . T * v )
obs_eqs = [ [ h_speed_sym , ObservationKind . ODOMETRIC_SPEED , None ] ,
[ h_gyro_sym , ObservationKind . PHONE_GYRO , None ] ,
[ h_phone_rot_sym , ObservationKind . NO_ROT , None ] ,
[ h_acc_sym , ObservationKind . PHONE_ACCEL , None ] ,
[ h_pos_sym , ObservationKind . ECEF_POS , None ] ,
[ h_vel_sym , ObservationKind . ECEF_VEL , None ] ,
[ h_orientation_sym , ObservationKind . ECEF_ORIENTATION_FROM_GPS , None ] ,
[ h_relative_motion , ObservationKind . CAMERA_ODO_TRANSLATION , None ] ,
[ h_phone_rot_sym , ObservationKind . CAMERA_ODO_ROTATION , None ] ,
[ h_imu_frame_sym , ObservationKind . IMU_FRAME , None ] ]
gen_code ( generated_dir , name , f_sym , dt , state_sym , obs_eqs , dim_state , dim_state_err , eskf_params )
def __init__ ( self , generated_dir ) :
self . dim_state = self . initial_x . shape [ 0 ]
self . dim_state_err = self . initial_P_diag . shape [ 0 ]
self . obs_noise = { ObservationKind . ODOMETRIC_SPEED : np . atleast_2d ( 0.2 * * 2 ) ,
ObservationKind . PHONE_GYRO : np . diag ( [ 0.025 * * 2 , 0.025 * * 2 , 0.025 * * 2 ] ) ,
ObservationKind . PHONE_ACCEL : np . diag ( [ .5 * * 2 , .5 * * 2 , .5 * * 2 ] ) ,
ObservationKind . CAMERA_ODO_ROTATION : np . diag ( [ 0.05 * * 2 , 0.05 * * 2 , 0.05 * * 2 ] ) ,
ObservationKind . IMU_FRAME : np . diag ( [ 0.05 * * 2 , 0.05 * * 2 , 0.05 * * 2 ] ) ,
ObservationKind . NO_ROT : np . diag ( [ 0.00025 * * 2 , 0.00025 * * 2 , 0.00025 * * 2 ] ) ,
ObservationKind . ECEF_POS : np . diag ( [ 5 * * 2 , 5 * * 2 , 5 * * 2 ] ) ,
ObservationKind . ECEF_VEL : np . diag ( [ .5 * * 2 , .5 * * 2 , .5 * * 2 ] ) ,
ObservationKind . ECEF_ORIENTATION_FROM_GPS : np . diag ( [ .2 * * 2 , .2 * * 2 , .2 * * 2 , .2 * * 2 ] ) }
# init filter
self . filter = EKF_sym ( generated_dir , self . name , self . Q , self . initial_x , np . diag ( self . initial_P_diag ) , self . dim_state , self . dim_state_err )
@property
def x ( self ) :
return self . filter . state ( )
@property
def t ( self ) :
return self . filter . filter_time
@property
def P ( self ) :
return self . filter . covs ( )
def rts_smooth ( self , estimates ) :
return self . filter . rts_smooth ( estimates , norm_quats = True )
def init_state ( self , state , covs_diag = None , covs = None , filter_time = None ) :
if covs_diag is not None :
P = np . diag ( covs_diag )
elif covs is not None :
P = covs
else :
P = self . filter . covs ( )
self . filter . init_state ( state , P , filter_time )
def predict_and_observe ( self , t , kind , meas , R = None ) :
if len ( meas ) > 0 :
meas = np . atleast_2d ( meas )
if kind == ObservationKind . CAMERA_ODO_TRANSLATION :
r = self . predict_and_update_odo_trans ( meas , t , kind )
elif kind == ObservationKind . CAMERA_ODO_ROTATION :
r = self . predict_and_update_odo_rot ( meas , t , kind )
elif kind == ObservationKind . ODOMETRIC_SPEED :
r = self . predict_and_update_odo_speed ( meas , t , kind )
else :
if R is None :
R = self . get_R ( kind , len ( meas ) )
elif len ( R . shape ) == 2 :
R = R [ None ]
r = self . filter . predict_and_update_batch ( t , kind , meas , R )
# Normalize quats
quat_norm = np . linalg . norm ( self . filter . x [ 3 : 7 , 0 ] )
self . filter . x [ States . ECEF_ORIENTATION , 0 ] = self . filter . x [ States . ECEF_ORIENTATION , 0 ] / quat_norm
return r
def get_R ( self , kind , n ) :
obs_noise = self . obs_noise [ kind ]
dim = obs_noise . shape [ 0 ]
R = np . zeros ( ( n , dim , dim ) )
for i in range ( n ) :
R [ i , : , : ] = obs_noise
return R
def predict_and_update_odo_speed ( self , speed , t , kind ) :
z = np . array ( speed )
R = np . zeros ( ( len ( speed ) , 1 , 1 ) )
for i , _ in enumerate ( z ) :
R [ i , : , : ] = np . diag ( [ 0.2 * * 2 ] )
return self . filter . predict_and_update_batch ( t , kind , z , R )
def predict_and_update_odo_trans ( self , trans , t , kind ) :
z = trans [ : , : 3 ]
R = np . zeros ( ( len ( trans ) , 3 , 3 ) )
for i , _ in enumerate ( z ) :
R [ i , : , : ] = np . diag ( trans [ i , 3 : ] * * 2 )
return self . filter . predict_and_update_batch ( t , kind , z , R )
def predict_and_update_odo_rot ( self , rot , t , kind ) :
z = rot [ : , : 3 ]
R = np . zeros ( ( len ( rot ) , 3 , 3 ) )
for i , _ in enumerate ( z ) :
R [ i , : , : ] = np . diag ( rot [ i , 3 : ] * * 2 )
return self . filter . predict_and_update_batch ( t , kind , z , R )
if __name__ == " __main__ " :
generated_dir = sys . argv [ 2 ]
LiveKalman . generate_code ( generated_dir )