#!/usr/bin/env python3
import sys
import argparse
import multiprocessing
import rerun as rr
import rerun . blueprint as rrb
from functools import partial
from cereal . services import SERVICE_LIST
from openpilot . tools . rerun . camera_reader import probe_packet_info , CameraReader , CameraConfig , CameraType
from openpilot . tools . lib . logreader import LogReader
from openpilot . tools . lib . route import Route , SegmentRange
NUM_CPUS = multiprocessing . cpu_count ( )
DEMO_ROUTE = " a2a0ccea32023010|2023-07-27--13-01-19 "
RR_TIMELINE_NAME = " Timeline "
RR_WIN = " openpilot logs "
"""
Relevant upstream Rerun issues :
- large time series : https : / / github . com / rerun - io / rerun / issues / 5967
- loading videos directly : https : / / github . com / rerun - io / rerun / issues / 6532
"""
class Rerunner :
def __init__ ( self , route , segment_range , camera_config , enabled_services ) :
self . enabled_services = [ s . lower ( ) for s in enabled_services ]
self . log_all = " all " in self . enabled_services
self . lr = LogReader ( route_or_segment_name )
# hevc files don't have start_time. We get it from qcamera.ts
start_time = 0
dat = probe_packet_info ( r . qcamera_paths ( ) [ 0 ] )
for d in dat :
if d . startswith ( " pts_time= " ) :
start_time = float ( d . split ( ' = ' ) [ 1 ] )
break
qcam , fcam , ecam , dcam = camera_config
self . camera_readers = { }
if qcam :
self . camera_readers [ CameraType . qcam ] = CameraReader ( route . qcamera_paths ( ) , start_time , segment_range . seg_idxs )
if fcam :
self . camera_readers [ CameraType . fcam ] = CameraReader ( route . camera_paths ( ) , start_time , segment_range . seg_idxs )
if ecam :
self . camera_readers [ CameraType . ecam ] = CameraReader ( route . ecamera_paths ( ) , start_time , segment_range . seg_idxs )
if dcam :
self . camera_readers [ CameraType . dcam ] = CameraReader ( route . dcamera_paths ( ) , start_time , segment_range . seg_idxs )
def _start_rerun ( self ) :
self . blueprint = self . _create_blueprint ( )
rr . init ( RR_WIN , spawn = True )
def _create_blueprint ( self ) :
blueprint = None
service_views = [ ]
log_msg_visible = len ( self . enabled_services ) < = 3 and not self . log_all
for topic in sorted ( SERVICE_LIST . keys ( ) ) :
if not self . log_all and topic . lower ( ) not in self . enabled_services :
continue
View = rrb . TimeSeriesView if topic != " thumbnail " else rrb . Spatial2DView
service_views . append ( View ( name = topic , origin = f " / { topic } / " , visible = log_msg_visible ) )
rr . log ( topic , rr . SeriesLine ( name = topic ) , timeless = True )
blueprint = rrb . Blueprint (
rrb . Horizontal (
rrb . Vertical ( * service_views ) ,
rrb . Vertical ( * [ rrb . Spatial2DView ( name = cam_type , origin = cam_type ) for cam_type in self . camera_readers . keys ( ) ] ) ,
) ,
rrb . SelectionPanel ( expanded = False ) ,
rrb . TimePanel ( expanded = False )
)
return blueprint
@staticmethod
def _log_msg ( msg , parent_key = ' ' ) :
stack = [ ( msg , parent_key ) ]
while stack :
current_msg , current_parent_key = stack . pop ( )
if isinstance ( current_msg , list ) :
for index , item in enumerate ( current_msg ) :
new_key = f " { current_parent_key } / { index } "
if isinstance ( item , ( int , float ) ) :
rr . log ( new_key , rr . Scalar ( item ) )
elif isinstance ( item , dict ) :
stack . append ( ( item , new_key ) )
elif isinstance ( current_msg , dict ) :
for key , value in current_msg . items ( ) :
new_key = f " { current_parent_key } / { key } "
if isinstance ( value , ( int , float ) ) :
rr . log ( new_key , rr . Scalar ( value ) )
elif isinstance ( value , dict ) :
stack . append ( ( value , new_key ) )
elif isinstance ( value , list ) :
for index , item in enumerate ( value ) :
if isinstance ( item , ( int , float ) ) :
rr . log ( f " { new_key } / { index } " , rr . Scalar ( item ) )
else :
pass # Not a plottable value
@staticmethod
@rr . shutdown_at_exit
def _process_log_msgs ( blueprint , enabled_services , log_all , lr ) :
rr . init ( RR_WIN )
rr . connect ( default_blueprint = blueprint )
for msg in lr :
rr . set_time_nanos ( RR_TIMELINE_NAME , msg . logMonoTime )
msg_type = msg . which ( )
if not log_all and msg_type . lower ( ) not in enabled_services :
continue
if msg_type != " thumbnail " :
Rerunner . _log_msg ( msg . to_dict ( ) [ msg . which ( ) ] , msg . which ( ) )
else :
rr . log ( " /thumbnail " , rr . ImageEncoded ( contents = msg . to_dict ( ) [ msg . which ( ) ] . get ( " thumbnail " ) ) )
return [ ]
@staticmethod
@rr . shutdown_at_exit
def _process_cam_readers ( blueprint , cam_type , h , w , fr ) :
rr . init ( RR_WIN )
rr . connect ( default_blueprint = blueprint )
for ts , frame in fr :
rr . set_time_nanos ( RR_TIMELINE_NAME , int ( ts * 1e9 ) )
rr . log ( cam_type , rr . Image ( bytes = frame , width = w , height = h , pixel_format = rr . PixelFormat . NV12 ) )
def load_data ( self ) :
self . _start_rerun ( )
if len ( self . enabled_services ) > 0 :
self . lr . run_across_segments ( NUM_CPUS , partial ( self . _process_log_msgs , self . blueprint , self . enabled_services , self . log_all ) )
for cam_type , cr in self . camera_readers . items ( ) :
cr . run_across_segments ( NUM_CPUS , partial ( self . _process_cam_readers , self . blueprint , cam_type , cr . h , cr . w ) )
if __name__ == ' __main__ ' :
parser = argparse . ArgumentParser ( description = " A helper to run rerun on openpilot routes " ,
formatter_class = argparse . ArgumentDefaultsHelpFormatter )
parser . add_argument ( " --demo " , action = " store_true " , help = " Use the demo route instead of providing one " )
parser . add_argument ( " --qcam " , action = " store_true " , help = " Show low-res road camera " )
parser . add_argument ( " --fcam " , action = " store_true " , help = " Show driving camera " )
parser . add_argument ( " --ecam " , action = " store_true " , help = " Show wide camera " )
parser . add_argument ( " --dcam " , action = " store_true " , help = " Show driver monitoring camera " )
parser . add_argument ( " --print_services " , action = " store_true " , help = " List out openpilot services " )
parser . add_argument ( " --services " , default = [ ] , nargs = ' * ' , help = " Specify openpilot services that will be logged. \
No service will be logged if not specified . \
To log all services include ' all ' as one of your services " )
parser . add_argument ( " --route " , nargs = ' ? ' , help = " The route or segment name to plot " )
args = parser . parse_args ( )
if not args . demo and not args . route :
parser . print_help ( )
sys . exit ( )
if args . print_services :
print ( " \n " . join ( SERVICE_LIST . keys ( ) ) )
sys . exit ( )
camera_config = CameraConfig ( args . qcam , args . fcam , args . ecam , args . dcam )
route_or_segment_name = DEMO_ROUTE if args . demo else args . route . strip ( )
sr = SegmentRange ( route_or_segment_name )
r = Route ( sr . route_name )
if len ( sr . seg_idxs ) > 10 :
print ( " You ' re requesting more than 10 segments of the route, " + \
" please be aware that might take a lot of memory " )
response = input ( " Do you wish to continue? (Y/n): " )
if response . strip ( ) . lower ( ) != " y " :
sys . exit ( )
rerunner = Rerunner ( r , sr , camera_config , args . services )
rerunner . load_data ( )