#!/usr/bin/env python
import zmq
import numpy as np
import numpy . matlib
import importlib
from collections import defaultdict , deque
from fastcluster import linkage_vector
import selfdrive . messaging as messaging
from selfdrive . services import service_list
from selfdrive . controls . lib . latcontrol_helpers import calc_lookahead_offset
from selfdrive . controls . lib . model_parser import ModelParser
from selfdrive . controls . lib . radar_helpers import Track , Cluster , fcluster , \
RDR_TO_LDR , NO_FUSION_SCORE
from selfdrive . controls . lib . vehicle_model import VehicleModel
from selfdrive . swaglog import cloudlog
from cereal import car
from common . params import Params
from common . realtime import set_realtime_priority , Ratekeeper
from common . kalman . ekf import EKF , SimpleSensor
DEBUG = False
#vision point
DIMSV = 2
XV , SPEEDV = 0 , 1
VISION_POINT = - 1
class EKFV1D ( EKF ) :
def __init__ ( self ) :
super ( EKFV1D , self ) . __init__ ( False )
self . identity = numpy . matlib . identity ( DIMSV )
self . state = np . matlib . zeros ( ( DIMSV , 1 ) )
self . var_init = 1e2 # ~ model variance when probability is 70%, so good starting point
self . covar = self . identity * self . var_init
self . process_noise = np . matlib . diag ( [ 0.5 , 1 ] )
def calc_transfer_fun ( self , dt ) :
tf = np . matlib . identity ( DIMSV )
tf [ XV , SPEEDV ] = dt
tfj = tf
return tf , tfj
## fuses camera and radar data for best lead detection
# FIXME: radard has a memory leak of about 50MB/hr
# BOUNTY: $100 coupon on shop.comma.ai
def radard_thread ( gctx = None ) :
set_realtime_priority ( 2 )
# wait for stats about the car to come in from controls
cloudlog . info ( " radard is waiting for CarParams " )
CP = car . CarParams . from_bytes ( Params ( ) . get ( " CarParams " , block = True ) )
mocked = CP . carName == " mock "
VM = VehicleModel ( CP )
cloudlog . info ( " radard got CarParams " )
# import the radar from the fingerprint
cloudlog . info ( " radard is importing %s " , CP . carName )
RadarInterface = importlib . import_module ( ' selfdrive.car. %s .radar_interface ' % CP . carName ) . RadarInterface
context = zmq . Context ( )
# *** subscribe to features and model from visiond
poller = zmq . Poller ( )
model = messaging . sub_sock ( context , service_list [ ' model ' ] . port , conflate = True , poller = poller )
live100 = messaging . sub_sock ( context , service_list [ ' live100 ' ] . port , conflate = True , poller = poller )
live_parameters_sock = messaging . sub_sock ( context , service_list [ ' liveParameters ' ] . port , conflate = True , poller = poller )
# Default parameters
live_parameters = messaging . new_message ( )
live_parameters . init ( ' liveParameters ' )
live_parameters . liveParameters . valid = True
live_parameters . liveParameters . steerRatio = CP . steerRatio
live_parameters . liveParameters . stiffnessFactor = 1.0
MP = ModelParser ( )
RI = RadarInterface ( CP )
last_md_ts = 0
last_l100_ts = 0
# *** publish live20 and liveTracks
live20 = messaging . pub_sock ( context , service_list [ ' live20 ' ] . port )
liveTracks = messaging . pub_sock ( context , service_list [ ' liveTracks ' ] . port )
path_x = np . arange ( 0.0 , 140.0 , 0.1 ) # 140 meters is max
# Time-alignment
rate = 20. # model and radar are both at 20Hz
tsv = 1. / rate
v_len = 20 # how many speed data points to remember for t alignment with rdr data
active = 0
steer_angle = 0.
steer_override = False
tracks = defaultdict ( dict )
# Kalman filter stuff:
ekfv = EKFV1D ( )
speedSensorV = SimpleSensor ( XV , 1 , 2 )
# v_ego
v_ego = None
v_ego_hist_t = deque ( maxlen = v_len )
v_ego_hist_v = deque ( maxlen = v_len )
v_ego_t_aligned = 0.
rk = Ratekeeper ( rate , print_delay_threshold = np . inf )
while 1 :
rr = RI . update ( )
ar_pts = { }
for pt in rr . points :
ar_pts [ pt . trackId ] = [ pt . dRel + RDR_TO_LDR , pt . yRel , pt . vRel , pt . measured ]
# receive the live100s
l100 = None
md = None
for socket , event in poller . poll ( 0 ) :
if socket is live100 :
l100 = messaging . recv_one ( socket )
elif socket is model :
md = messaging . recv_one ( socket )
elif socket is live_parameters_sock :
live_parameters = messaging . recv_one ( socket )
VM . update_params ( live_parameters . liveParameters . stiffnessFactor , live_parameters . liveParameters . steerRatio )
if l100 is not None :
active = l100 . live100 . active
v_ego = l100 . live100 . vEgo
steer_angle = l100 . live100 . angleSteers
steer_override = l100 . live100 . steerOverride
v_ego_hist_v . append ( v_ego )
v_ego_hist_t . append ( float ( rk . frame ) / rate )
last_l100_ts = l100 . logMonoTime
if v_ego is None :
continue
if md is not None :
last_md_ts = md . logMonoTime
# *** get path prediction from the model ***
MP . update ( v_ego , md )
# run kalman filter only if prob is high enough
if MP . lead_prob > 0.7 :
reading = speedSensorV . read ( MP . lead_dist , covar = np . matrix ( MP . lead_var ) )
ekfv . update_scalar ( reading )
ekfv . predict ( tsv )
# When changing lanes the distance to the lead car can suddenly change,
# which makes the Kalman filter output large relative acceleration
if mocked and abs ( MP . lead_dist - ekfv . state [ XV ] ) > 2.0 :
ekfv . state [ XV ] = MP . lead_dist
ekfv . covar = ( np . diag ( [ MP . lead_var , ekfv . var_init ] ) )
ekfv . state [ SPEEDV ] = 0.
ar_pts [ VISION_POINT ] = ( float ( ekfv . state [ XV ] ) , np . polyval ( MP . d_poly , float ( ekfv . state [ XV ] ) ) ,
float ( ekfv . state [ SPEEDV ] ) , False )
else :
ekfv . state [ XV ] = MP . lead_dist
ekfv . covar = ( np . diag ( [ MP . lead_var , ekfv . var_init ] ) )
ekfv . state [ SPEEDV ] = 0.
if VISION_POINT in ar_pts :
del ar_pts [ VISION_POINT ]
# *** compute the likely path_y ***
if ( active and not steer_override ) or mocked :
# use path from model (always when mocking as steering is too noisy)
path_y = np . polyval ( MP . d_poly , path_x )
else :
# use path from steer, set angle_offset to 0 it does not only report the physical offset
path_y = calc_lookahead_offset ( v_ego , steer_angle , path_x , VM , angle_offset = live_parameters . liveParameters . angleOffsetAverage ) [ 0 ]
# *** remove missing points from meta data ***
for ids in tracks . keys ( ) :
if ids not in ar_pts :
tracks . pop ( ids , None )
# *** compute the tracks ***
for ids in ar_pts :
# ignore standalone vision point, unless we are mocking the radar
if ids == VISION_POINT and not mocked :
continue
rpt = ar_pts [ ids ]
# align v_ego by a fixed time to align it with the radar measurement
cur_time = float ( rk . frame ) / rate
v_ego_t_aligned = np . interp ( cur_time - RI . delay , v_ego_hist_t , v_ego_hist_v )
d_path = np . sqrt ( np . amin ( ( path_x - rpt [ 0 ] ) * * 2 + ( path_y - rpt [ 1 ] ) * * 2 ) )
# add sign
d_path * = np . sign ( rpt [ 1 ] - np . interp ( rpt [ 0 ] , path_x , path_y ) )
# create the track if it doesn't exist or it's a new track
if ids not in tracks :
tracks [ ids ] = Track ( )
tracks [ ids ] . update ( rpt [ 0 ] , rpt [ 1 ] , rpt [ 2 ] , d_path , v_ego_t_aligned , rpt [ 3 ] , steer_override )
# allow the vision model to remove the stationary flag if distance and rel speed roughly match
if VISION_POINT in ar_pts :
fused_id = None
best_score = NO_FUSION_SCORE
for ids in tracks :
dist_to_vision = np . sqrt ( ( 0.5 * ( ar_pts [ VISION_POINT ] [ 0 ] - tracks [ ids ] . dRel ) ) * * 2 + ( 2 * ( ar_pts [ VISION_POINT ] [ 1 ] - tracks [ ids ] . yRel ) ) * * 2 )
rel_speed_diff = abs ( ar_pts [ VISION_POINT ] [ 2 ] - tracks [ ids ] . vRel )
tracks [ ids ] . update_vision_score ( dist_to_vision , rel_speed_diff )
if best_score > tracks [ ids ] . vision_score :
fused_id = ids
best_score = tracks [ ids ] . vision_score
if fused_id is not None :
tracks [ fused_id ] . vision_cnt + = 1
tracks [ fused_id ] . update_vision_fusion ( )
if DEBUG :
print ( " NEW CYCLE " )
if VISION_POINT in ar_pts :
print ( " vision " , ar_pts [ VISION_POINT ] )
getting ready for Python 3 (#619)
* tabs to spaces
python 2 to 3: https://portingguide.readthedocs.io/en/latest/syntax.html#tabs-and-spaces
* use the new except syntax
python 2 to 3: https://portingguide.readthedocs.io/en/latest/exceptions.html#the-new-except-syntax
* make relative imports absolute
python 2 to 3: https://portingguide.readthedocs.io/en/latest/imports.html#absolute-imports
* Queue renamed to queue in python 3
Use the six compatibility library to support both python 2 and 3: https://portingguide.readthedocs.io/en/latest/stdlib-reorg.html#renamed-modules
* replace dict.has_key() with in
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#removed-dict-has-key
* make dict views compatible with python 3
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#dict-views-and-iterators
Where needed, wrapping things that will be a view in python 3 with a list(). For example, if it's accessed with []
Python 3 has no iter*() methods, so just using the values() instead of itervalues() as long as it's not too performance intensive. Note that any minor performance hit of using a list instead of a view will go away when switching to python 3. If it is intensive, we could use the six version.
* Explicitly use truncating division
python 2 to 3: https://portingguide.readthedocs.io/en/latest/numbers.html#division
python 3 treats / as float division. When we want the result to be an integer, use //
* replace map() with list comprehension where a list result is needed.
In python 3, map() returns an iterator.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* replace filter() with list comprehension
In python 3, filter() returns an interatoooooooooooor.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* wrap zip() in list() where we need the result to be a list
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-zip
* clean out some lint
Removes these pylint warnings:
************* Module selfdrive.car.chrysler.chryslercan
W: 15, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 16, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 25, 0: Unnecessary semicolon (unnecessary-semicolon)
************* Module common.dbc
W:101, 0: Anomalous backslash in string: '\?'. String constant might be missing an r prefix. (anomalous-backslash-in-string)
************* Module selfdrive.car.gm.interface
R:102, 6: Redefinition of ret.minEnableSpeed type from float to int (redefined-variable-type)
R:103, 6: Redefinition of ret.mass type from int to float (redefined-variable-type)
************* Module selfdrive.updated
R: 20, 6: Redefinition of r type from int to str (redefined-variable-type)
6 years ago
idens = list ( tracks . keys ( ) )
track_pts = np . array ( [ tracks [ iden ] . get_key_for_cluster ( ) for iden in idens ] )
# If we have multiple points, cluster them
if len ( track_pts ) > 1 :
link = linkage_vector ( track_pts , method = ' centroid ' )
cluster_idxs = fcluster ( link , 2.5 , criterion = ' distance ' )
clusters = [ None ] * max ( cluster_idxs )
for idx in xrange ( len ( track_pts ) ) :
cluster_i = cluster_idxs [ idx ] - 1
if clusters [ cluster_i ] == None :
clusters [ cluster_i ] = Cluster ( )
clusters [ cluster_i ] . add ( tracks [ idens [ idx ] ] )
elif len ( track_pts ) == 1 :
# TODO: why do we need this?
clusters = [ Cluster ( ) ]
clusters [ 0 ] . add ( tracks [ idens [ 0 ] ] )
else :
clusters = [ ]
if DEBUG :
for i in clusters :
print ( i )
# *** extract the lead car ***
lead_clusters = [ c for c in clusters
if c . is_potential_lead ( v_ego ) ]
lead_clusters . sort ( key = lambda x : x . dRel )
lead_len = len ( lead_clusters )
# *** extract the second lead from the whole set of leads ***
lead2_clusters = [ c for c in lead_clusters
if c . is_potential_lead2 ( lead_clusters ) ]
lead2_clusters . sort ( key = lambda x : x . dRel )
lead2_len = len ( lead2_clusters )
# *** publish live20 ***
dat = messaging . new_message ( )
dat . init ( ' live20 ' )
dat . live20 . mdMonoTime = last_md_ts
dat . live20 . canMonoTimes = list ( rr . canMonoTimes )
dat . live20 . radarErrors = list ( rr . errors )
dat . live20 . l100MonoTime = last_l100_ts
if lead_len > 0 :
dat . live20 . leadOne = lead_clusters [ 0 ] . toLive20 ( )
if lead2_len > 0 :
dat . live20 . leadTwo = lead2_clusters [ 0 ] . toLive20 ( )
else :
dat . live20 . leadTwo . status = False
else :
dat . live20 . leadOne . status = False
dat . live20 . cumLagMs = - rk . remaining * 1000.
live20 . send ( dat . to_bytes ( ) )
# *** publish tracks for UI debugging (keep last) ***
dat = messaging . new_message ( )
dat . init ( ' liveTracks ' , len ( tracks ) )
for cnt , ids in enumerate ( tracks . keys ( ) ) :
if DEBUG :
print ( " id: %4.0f x: %4.1f y: %4.1f vr: %4.1f d: %4.1f va: %4.1f vl: %4.1f vlk: %4.1f alk: %4.1f s: %1.0f v: %1.0f " % \
( ids , tracks [ ids ] . dRel , tracks [ ids ] . yRel , tracks [ ids ] . vRel ,
tracks [ ids ] . dPath , tracks [ ids ] . vLat ,
tracks [ ids ] . vLead , tracks [ ids ] . vLeadK ,
tracks [ ids ] . aLeadK ,
tracks [ ids ] . stationary ,
tracks [ ids ] . measured ) )
dat . liveTracks [ cnt ] = {
" trackId " : ids ,
" dRel " : float ( tracks [ ids ] . dRel ) ,
" yRel " : float ( tracks [ ids ] . yRel ) ,
" vRel " : float ( tracks [ ids ] . vRel ) ,
" aRel " : float ( tracks [ ids ] . aRel ) ,
" stationary " : bool ( tracks [ ids ] . stationary ) ,
" oncoming " : bool ( tracks [ ids ] . oncoming ) ,
}
liveTracks . send ( dat . to_bytes ( ) )
rk . monitor_time ( )
def main ( gctx = None ) :
radard_thread ( gctx )
if __name__ == " __main__ " :
main ( )