From 53aee6722307b8ef09beab60a72383f5e1e19bd1 Mon Sep 17 00:00:00 2001 From: Andrew Salamon Date: Sun, 5 Jul 2020 09:30:43 -0700 Subject: [PATCH 1/3] Added support and test for saving dict's in tub files. Requires that all dict entries be json compatible. --- donkeycar/parts/datastore.py | 2 +- donkeycar/tests/test_tub.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/donkeycar/parts/datastore.py b/donkeycar/parts/datastore.py index 0f4faac19..11f3679af 100644 --- a/donkeycar/parts/datastore.py +++ b/donkeycar/parts/datastore.py @@ -222,7 +222,7 @@ def put_record(self, data): # in case val is a numpy.float32, which json doesn't like json_data[key] = float(val) - elif typ in ['str', 'float', 'int', 'boolean', 'vector']: + elif typ in ['str', 'float', 'int', 'boolean', 'vector', 'dict']: json_data[key] = val elif typ is 'image': diff --git a/donkeycar/tests/test_tub.py b/donkeycar/tests/test_tub.py index 5fe636c10..77fd8ba4b 100644 --- a/donkeycar/tests/test_tub.py +++ b/donkeycar/tests/test_tub.py @@ -48,6 +48,24 @@ def test_tub_write_numpy(tub): rec_out = tub.get_record(rec_index) assert type(rec_out['user/throttle']) == float +def test_tub_dict(tub): + """Tub can save a record that contains a dictionary with json-able types.""" + d = {'pos': (49.99962, 0.7410042, 50.16277), + 'cte': -0.0006235633, + 'hit': None, + 'done': True } + x=123 + z=0.0 + rec_in = {'user/angle': x, 'user/throttle':z, 'sim/info':d} + tub.meta['inputs'].append('sim/info') + tub.meta['types'].append('dict') + rec_index = tub.put_record(rec_in) + rec_out = tub.get_record(rec_index) + assert type(rec_out['sim/info']) == dict + assert type(rec_out['sim/info']['cte']) == float + assert rec_out['sim/info']['hit'] is None + assert rec_out['sim/info']['done'] is True + def test_tub_exclude(tub): """ Make sure the Tub will exclude records in the exclude set """ From 4eabe7c2f3e04e7dd7d8b950757782bfd44141eb Mon Sep 17 00:00:00 2001 From: Andrew Salamon Date: Sun, 5 Jul 2020 09:57:49 -0700 Subject: [PATCH 2/3] Added support to drive code to save simulator info dict to Tub files. --- donkeycar/parts/dgym.py | 17 ++++++++++++++--- donkeycar/templates/cfg_complete.py | 1 + donkeycar/templates/cfg_path_follow.py | 1 + donkeycar/templates/complete.py | 13 ++++++++++--- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/donkeycar/parts/dgym.py b/donkeycar/parts/dgym.py index fc9f1a42e..1665c36d4 100644 --- a/donkeycar/parts/dgym.py +++ b/donkeycar/parts/dgym.py @@ -9,7 +9,7 @@ def is_exe(fpath): class DonkeyGymEnv(object): - def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name="donkey-generated-track-v0", sync="asynchronous", conf={}, delay=0): + def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name="donkey-generated-track-v0", sync="asynchronous", conf={}, delay=0, return_info=False): os.environ['DONKEY_SIM_PATH'] = sim_path os.environ['DONKEY_SIM_PORT'] = str(port) os.environ['DONKEY_SIM_HEADLESS'] = str(headless) @@ -22,11 +22,16 @@ def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name=" if not is_exe(sim_path): raise Exception("The path you provided is not an executable.") + self.return_info = return_info + self.env = gym.make(env_name, exe_path=sim_path, host=host, port=port) self.frame = self.env.reset() self.action = [0.0, 0.0] self.running = True self.info = { 'pos' : (0., 0., 0.)} + # only allow known json-able entries from the sim's info dictionary + self.valid = ['pos', 'cte', 'speed', 'hit'] + self.delay = float(delay) if "body_style" in conf: @@ -36,7 +41,10 @@ def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name=" def update(self): while self.running: - self.frame, _, _, self.info = self.env.step(self.action) + self.frame, reward, done, info = self.env.step(self.action) + self.info = {key: info[key] for key in info if key in self.valid} + self.info['reward'] = reward + self.info['done'] = done def run_threaded(self, steering, throttle): if steering is None or throttle is None: @@ -45,7 +53,10 @@ def run_threaded(self, steering, throttle): if self.delay > 0.0: time.sleep(self.delay / 1000.0) self.action = [steering, throttle] - return self.frame + if self.return_info: + return self.frame, self.info + else: + return self.frame def shutdown(self): self.running = False diff --git a/donkeycar/templates/cfg_complete.py b/donkeycar/templates/cfg_complete.py index 94711b62b..05dc051d6 100644 --- a/donkeycar/templates/cfg_complete.py +++ b/donkeycar/templates/cfg_complete.py @@ -223,6 +223,7 @@ GYM_CONF = { "body_style" : "donkey", "body_rgb" : (128, 128, 128), "car_name" : "me", "font_size" : 100} # body style(donkey|bare|car01) body rgb 0-255 SIM_HOST = "127.0.0.1" # when racing on virtual-race-league use host "trainmydonkey.com" SIM_ARTIFICIAL_LATENCY = 0 # this is the millisecond latency in controls. Can use useful in emulating the delay when useing a remote server. values of 100 to 400 probably reasonable. +DONKEY_GYM_INFO = False # Include Sim info dict when saving Tub files. #publish camera over network #This is used to create a tcp service to pushlish the camera feed diff --git a/donkeycar/templates/cfg_path_follow.py b/donkeycar/templates/cfg_path_follow.py index d104b6ccb..6655b3a66 100644 --- a/donkeycar/templates/cfg_path_follow.py +++ b/donkeycar/templates/cfg_path_follow.py @@ -109,3 +109,4 @@ DONKEY_GYM = False DONKEY_SIM_PATH = "path to sim" #"/home/tkramer/projects/sdsandbox/sdsim/build/DonkeySimLinux/donkey_sim.x86_64" DONKEY_GYM_ENV_NAME = "donkey-generated-track-v0" # ("donkey-generated-track-v0"|"donkey-generated-roads-v0"|"donkey-warehouse-v0"|"donkey-avc-sparkfun-v0") +DONKEY_GYM_INFO = False # Include Sim info dict when saving Tub files. diff --git a/donkeycar/templates/complete.py b/donkeycar/templates/complete.py index 45b44dad8..a25e35852 100644 --- a/donkeycar/templates/complete.py +++ b/donkeycar/templates/complete.py @@ -103,12 +103,15 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type from donkeycar.parts.dgym import DonkeyGymEnv inputs = [] + outputs=['cam/image_array'] threaded = True if cfg.DONKEY_GYM: from donkeycar.parts.dgym import DonkeyGymEnv - cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) + cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY, return_info=cfg.DONKEY_GYM_INFO) threaded = True inputs = ['angle', 'throttle'] + if cfg.DONKEY_GYM_INFO: + outputs.append( 'sim/info' ) elif cfg.CAMERA_TYPE == "PICAM": from donkeycar.parts.camera import PiCamera cam = PiCamera(image_w=cfg.IMAGE_W, image_h=cfg.IMAGE_H, image_d=cfg.IMAGE_DEPTH, framerate=cfg.CAMERA_FRAMERATE, vflip=cfg.CAMERA_VFLIP, hflip=cfg.CAMERA_HFLIP) @@ -130,7 +133,7 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type else: raise(Exception("Unkown camera type: %s" % cfg.CAMERA_TYPE)) - V.add(cam, inputs=inputs, outputs=['cam/image_array'], threaded=threaded) + V.add(cam, inputs=inputs, outputs=outputs, threaded=threaded) if use_joystick or cfg.USE_JOYSTICK_AS_DEFAULT: #modify max_throttle closer to 1.0 to have more power @@ -569,7 +572,11 @@ def run(self, mode, recording): if cfg.RECORD_DURING_AI: inputs += ['pilot/angle', 'pilot/throttle'] types += ['float', 'float'] - + + if cfg.DONKEY_GYM_INFO: + inputs += ['sim/info'] + types += ['dict'] + th = TubHandler(path=cfg.DATA_PATH) tub = th.new_tub_writer(inputs=inputs, types=types, user_meta=meta) V.add(tub, inputs=inputs, outputs=["tub/num_records"], run_condition='recording') From 0d7cae3bc1eb2c79957776e75530f0d11bfbe152 Mon Sep 17 00:00:00 2001 From: Andrew Salamon Date: Sun, 5 Jul 2020 14:16:52 -0700 Subject: [PATCH 3/3] Moved simulator info code into a subclass of DonkeyGymEnv. --- donkeycar/parts/dgym.py | 36 ++++++++++++++++++++------------- donkeycar/templates/complete.py | 10 +++++---- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/donkeycar/parts/dgym.py b/donkeycar/parts/dgym.py index 1665c36d4..a0082f43c 100644 --- a/donkeycar/parts/dgym.py +++ b/donkeycar/parts/dgym.py @@ -9,7 +9,7 @@ def is_exe(fpath): class DonkeyGymEnv(object): - def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name="donkey-generated-track-v0", sync="asynchronous", conf={}, delay=0, return_info=False): + def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name="donkey-generated-track-v0", sync="asynchronous", conf={}, delay=0): os.environ['DONKEY_SIM_PATH'] = sim_path os.environ['DONKEY_SIM_PORT'] = str(port) os.environ['DONKEY_SIM_HEADLESS'] = str(headless) @@ -22,15 +22,11 @@ def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name=" if not is_exe(sim_path): raise Exception("The path you provided is not an executable.") - self.return_info = return_info - self.env = gym.make(env_name, exe_path=sim_path, host=host, port=port) self.frame = self.env.reset() self.action = [0.0, 0.0] self.running = True self.info = { 'pos' : (0., 0., 0.)} - # only allow known json-able entries from the sim's info dictionary - self.valid = ['pos', 'cte', 'speed', 'hit'] self.delay = float(delay) @@ -41,10 +37,7 @@ def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name=" def update(self): while self.running: - self.frame, reward, done, info = self.env.step(self.action) - self.info = {key: info[key] for key in info if key in self.valid} - self.info['reward'] = reward - self.info['done'] = done + self.frame, _, _, self.info = self.env.step(self.action) def run_threaded(self, steering, throttle): if steering is None or throttle is None: @@ -53,15 +46,30 @@ def run_threaded(self, steering, throttle): if self.delay > 0.0: time.sleep(self.delay / 1000.0) self.action = [steering, throttle] - if self.return_info: - return self.frame, self.info - else: - return self.frame + return self.frame def shutdown(self): self.running = False time.sleep(0.2) self.env.close() +class DonkeyGymEnvWithInfo(DonkeyGymEnv): + """ This subclass will return the Simulator's info dictionary along with the current image. + The info dictionary will include the reward and done flags. + """ + def __init__(self, *args, **kwargs): + # only allow known json-able entries from the sim's info dictionary + self.valid = ['pos', 'cte', 'speed', 'hit'] + + super(DonkeyGymEnvWithInfo,self).__init__(*args, **kwargs) - + def update(self): + while self.running: + self.frame, reward, done, info = self.env.step(self.action) + self.info = {key: info[key] for key in info if key in self.valid} + self.info['reward'] = reward + self.info['done'] = done + + def run_threaded(self, steering, throttle): + super(DonkeyGymEnvWithInfo,self).run_threaded(steering, throttle) + return self.frame, self.info diff --git a/donkeycar/templates/complete.py b/donkeycar/templates/complete.py index a25e35852..09b910f5e 100644 --- a/donkeycar/templates/complete.py +++ b/donkeycar/templates/complete.py @@ -106,12 +106,14 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type outputs=['cam/image_array'] threaded = True if cfg.DONKEY_GYM: - from donkeycar.parts.dgym import DonkeyGymEnv - cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY, return_info=cfg.DONKEY_GYM_INFO) - threaded = True - inputs = ['angle', 'throttle'] + from donkeycar.parts.dgym import DonkeyGymEnv, DonkeyGymEnvWithInfo if cfg.DONKEY_GYM_INFO: outputs.append( 'sim/info' ) + cam = DonkeyGymEnvWithInfo(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) + else: + cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) + threaded = True + inputs = ['angle', 'throttle'] elif cfg.CAMERA_TYPE == "PICAM": from donkeycar.parts.camera import PiCamera cam = PiCamera(image_w=cfg.IMAGE_W, image_h=cfg.IMAGE_H, image_d=cfg.IMAGE_DEPTH, framerate=cfg.CAMERA_FRAMERATE, vflip=cfg.CAMERA_VFLIP, hflip=cfg.CAMERA_HFLIP)