Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to donkey UI and donkey command #962

Merged
merged 1 commit into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion donkeycar/management/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import donkeycar as dk
from donkeycar.management.joystick_creator import CreateJoystick
from donkeycar.management.tub import TubManager
from donkeycar.pipeline.types import TubRecord, TubDataset
from donkeycar.pipeline.types import TubDataset
from donkeycar.utils import *

PACKAGE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
Expand Down Expand Up @@ -519,6 +519,28 @@ def run(self, args):
f"'tensorflow' or 'pytorch'")


class ModelDatabase(BaseCommand):

def parse_args(self, args):
parser = argparse.ArgumentParser(prog='models',
usage='%(prog)s [options]')
parser.add_argument('--config', default='./config.py', help=HELP_CONFIG)
parser.add_argument('--group', action="store_true",
default=False,
help='group tubs and plot separately')
parsed_args = parser.parse_args(args)
return parsed_args

def run(self, args):
from donkeycar.pipeline.database import PilotDatabase
args = self.parse_args(args)
cfg = load_config(args.config)
p = PilotDatabase(cfg)
pilot_txt, tub_txt, _ = p.pretty_print(args.group)
print(pilot_txt)
print(tub_txt)


class Gui(BaseCommand):
def run(self, args):
from donkeycar.management.kivy_ui import main
Expand All @@ -541,6 +563,7 @@ def execute_from_command_line():
'cnnactivations': ShowCnnActivations,
'update': UpdateCar,
'train': Train,
'models': ModelDatabase,
'ui': Gui,
}

Expand Down
128 changes: 69 additions & 59 deletions donkeycar/management/kivy_ui.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import re
import time
from copy import copy
from copy import copy, deepcopy
from datetime import datetime
from functools import partial
from subprocess import Popen, PIPE, STDOUT
Expand Down Expand Up @@ -33,7 +33,6 @@
from kivy.uix.spinner import SpinnerOption, Spinner

from donkeycar import load_config
from donkeycar.parts.keras import KerasMemory
from donkeycar.parts.tub_v2 import Tub
from donkeycar.pipeline.augmentations import ImageAugmentation
from donkeycar.pipeline.database import PilotDatabase
Expand Down Expand Up @@ -251,6 +250,8 @@ def update_tub(self, event=None):
tub_screen().status(f'Path {self.file_path} is not a valid tub.')
return False
try:
if self.tub:
self.tub.close()
self.tub = Tub(self.file_path)
except Exception as e:
tub_screen().status(f'Failed loading tub: {str(e)}')
Expand Down Expand Up @@ -713,6 +714,7 @@ class OverlayImage(FullImage):

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.is_left = True

def augment(self, img_arr):
if pilot_screen().trans_list:
Expand All @@ -723,13 +725,13 @@ def augment(self, img_arr):

def get_image(self, record):
from donkeycar.management.makemovie import MakeMovie
config = tub_screen().ids.config_manager.config
orig_img_arr = super().get_image(record)
aug_img_arr = self.augment(orig_img_arr)
img_arr = copy(aug_img_arr)
angle = record.underlying['user/angle']
throttle = get_norm_value(
record.underlying[self.throttle_field],
tub_screen().ids.config_manager.config,
record.underlying[self.throttle_field], config,
rc_handler.field_properties[self.throttle_field])
rgb = (0, 255, 0)
MakeMovie.draw_line_into_image(angle, throttle, False, img_arr, rgb)
Expand All @@ -745,7 +747,7 @@ def get_image(self, record):

rgb = (0, 0, 255)
MakeMovie.draw_line_into_image(output[0], output[1], True, img_arr, rgb)
out_record = copy(record)
out_record = deepcopy(record)
out_record.underlying['pilot/angle'] = output[0]
# rename and denormalise the throttle output
pilot_throttle_field \
Expand Down Expand Up @@ -798,7 +800,7 @@ def initialise(self, e):

def map_pilot_field(self, text):
""" Method to return user -> pilot mapped fields except for the
intial vale called Add/remove. """
initial value called Add/remove. """
if text == LABEL_SPINNER_TEXT:
return text
return rc_handler.data['user_pilot_map'][text]
Expand Down Expand Up @@ -894,11 +896,13 @@ def train_call(self, model_type, *args):
comment=self.ids.comment.text)
self.ids.status.text = f'Training completed.'
self.ids.comment.text = 'Comment'
self.ids.train_button.state = 'normal'
self.ids.transfer_spinner.text = 'Choose transfer model'
self.reload_database()
except Exception as e:
self.ids.status.text = f'Train error {e}'
Logger.error(e)
self.ids.status.text = f'Train failed see console'
finally:
self.ids.train_button.state = 'normal'

def train(self, model_type):
self.config.SHOW_PLOT = False
Expand Down Expand Up @@ -933,42 +937,21 @@ def reload_database(self):
self.database = PilotDatabase(self.config)

def on_database(self, obj, database):
if self.ids.check.state == 'down':
self.pilot_df, self.tub_df = self.database.to_df_tubgrouped()
self.ids.scroll_tubs.text = self.tub_df.to_string()
else:
self.pilot_df = self.database.to_df()
self.tub_df = pd.DataFrame()
self.ids.scroll_tubs.text = ''

self.pilot_df.drop(columns=['History', 'Config'], errors='ignore',
inplace=True)
text = self.pilot_df.to_string(formatters=self.formatter())
self.ids.scroll_pilots.text = text
values = ['Choose transfer model']
if not self.pilot_df.empty:
values += self.pilot_df['Name'].tolist()
self.ids.transfer_spinner.values = values

@staticmethod
def formatter():
def time_fmt(t):
fmt = '%Y-%m-%d %H:%M:%S'
return datetime.fromtimestamp(t).strftime(format=fmt)

def transfer_fmt(model_name):
return model_name.replace('.h5', '')

return {'Time': time_fmt, 'Transfer': transfer_fmt}
group_tubs = self.ids.check.state == 'down'
pilot_txt, tub_txt, pilot_names = self.database.pretty_print(group_tubs)
self.ids.scroll_tubs.text = tub_txt
self.ids.scroll_pilots.text = pilot_txt
self.ids.transfer_spinner.values \
= ['Choose transfer model'] + pilot_names
self.ids.delete_spinner.values \
= ['Pilot'] + pilot_names


class CarScreen(Screen):
""" Screen for interacting with the car. """
config = ObjectProperty(force_dispatch=True, allownone=True)
files = ListProperty()
car_dir = StringProperty(rc_handler.data.get('robot_car_dir', '~/mycar'))
pull_bar = NumericProperty(0)
push_bar = NumericProperty(0)
event = ObjectProperty(None, allownone=True)
connection = ObjectProperty(None, allownone=True)
pid = NumericProperty(None, allownone=True)
Expand Down Expand Up @@ -1002,9 +985,9 @@ def update_pilots(self):
def pull(self, tub_dir):
target = f'{self.config.PI_USERNAME}@{self.config.PI_HOSTNAME}' + \
f':{os.path.join(self.car_dir, tub_dir)}'
dest = self.config.DATA_PATH
if self.ids.create_dir.state == 'normal':
target += '/'
dest = self.config.DATA_PATH
cmd = ['rsync', '-rv', '--progress', '--partial', target, dest]
Logger.info('car pull: ' + str(cmd))
proc = Popen(cmd, shell=False, stdout=PIPE, text=True,
Expand All @@ -1014,63 +997,82 @@ def pull(self, tub_dir):
event = Clock.schedule_interval(call, 0.0001)

def send_pilot(self):
src = self.config.MODELS_PATH
# add trailing '/'
src = os.path.join(self.config.MODELS_PATH,'')
# check if any sync buttons are pressed and update path accordingly
buttons = ['h5', 'savedmodel', 'tflite', 'trt']
select = [btn for btn in buttons if self.ids[f'btn_{btn}'].state
== 'down']
# build filter: for example this rsyncs all .tfilte models
# --include="*/" --include="*.tflite" --exclude="*"
filter = ['--include=*/']
# build filter: for example this rsyncs all .tfilte and .trt models
# --include=*.trt/*** --include=*.tflite --exclude=*
filter = ['--include=database.json']
for ext in select:
if ext in ['savedmodel', 'trt']:
ext += '/***'
filter.append(f'--include=*.{ext}')
# if nothing selected, sync all
if not select:
filter.append('--include=*')
filter.append('--exclude=*')
else:
filter.append('--exclude=*')
dest = f'{self.config.PI_USERNAME}@{self.config.PI_HOSTNAME}:' + \
f'{self.car_dir}'
f'{os.path.join(self.car_dir, "models")}'
cmd = ['rsync', '-rv', '--progress', '--partial', *filter, src, dest]
Logger.info('car push: ' + ' '.join(cmd))
proc = Popen(cmd, shell=False, stdout=PIPE,
encoding='utf-8', universal_newlines=True)
repeats = 1
repeats = 0
call = partial(self.show_progress, proc, repeats, False)
event = Clock.schedule_interval(call, 0.0001)

def show_progress(self, proc, repeats, is_pull, e):
if proc.poll() is not None:
# find 'to-check=33/4551)' in OSX or 'to-chk=33/4551)' in
# Linux which is end of line
pattern = 'to-(check|chk)=(.*)\)'

def end():
# call ended this stops the schedule
if is_pull:
button = self.ids.pull_tub
self.ids.pull_bar.value = 0
# merge in previous deleted indexes which now might have been
# overwritten
old_tub = tub_screen().ids.tub_loader.tub
if old_tub:
deleted_indexes = old_tub.manifest.deleted_indexes
tub_screen().ids.tub_loader.update_tub()
if deleted_indexes:
new_tub = tub_screen().ids.tub_loader.tub
new_tub.manifest.add_deleted_indexes(deleted_indexes)
else:
button = self.ids.send_pilots
self.ids.push_bar.value = 0
self.update_pilots()
button.disabled = False

if proc.poll() is not None:
end()
return False
# find the next repeats lines with update info
count = 0
while True:
stdout_data = proc.stdout.readline()
if stdout_data:
# find 'to-check=33/4551)' which is end of line
pattern = 'to-check=(.*)\)'
res = re.search(pattern, stdout_data)
if res:
if count < repeats:
count += 1
else:
remain, total = tuple(res.group(1).split('/'))
remain, total = tuple(res.group(2).split('/'))
bar = 100 * (1. - float(remain) / float(total))
if is_pull:
self.pull_bar = bar
self.ids.pull_bar.value = bar
else:
self.push_bar = bar
self.ids.push_bar.value = bar
return True
else:
# end of stream command completed
if is_pull:
button = self.ids['pull_tub']
self.pull_bar = 0
else:
button = self.ids['send_pilots']
self.push_bar = 0
self.update_pilots()
button.disabled = False
end()
return False

def connected(self, event):
Expand Down Expand Up @@ -1175,6 +1177,7 @@ def initialise(self, event):
Clock.schedule_once(self.tub_screen.ids.tub_loader.update_tub)

def build(self):
Window.bind(on_request_close=self.on_request_close)
self.start_screen = StartScreen(name='donkey')
self.tub_screen = TubScreen(name='tub')
self.train_screen = TrainScreen(name='train')
Expand All @@ -1191,6 +1194,13 @@ def build(self):
sm.add_widget(self.car_screen)
return sm

def on_request_close(self, *args):
tub = self.tub_screen.ids.tub_loader.tub
if tub:
tub.close()
Logger.info("Good bye Donkey")
return False


def main():
tub_app = DonkeyApp()
Expand Down
26 changes: 23 additions & 3 deletions donkeycar/management/ui.kv
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,12 @@
id: img_1
pilot: pilot_loader_1.pilot
throttle_field: data_in.throttle_field
is_left: True
OverlayImage:
id: img_2
pilot: pilot_loader_2.pilot
throttle_field: data_in.throttle_field
is_left: False
PaddedBoxLayout:
size_hint_y: 0.5
DataPanel:
Expand Down Expand Up @@ -482,8 +484,10 @@
font_name: 'data/fonts/RobotoMono-Regular.ttf'
font_size: 12 if platform == 'linux' else 24
size_hint_y: None
size_hint_x: None
height: self.texture_size[1]
text_size: self.width, None
width: self.texture_size[0]
#text_size: self.width, None

<TransferSelector>:
title: 'Choose transfer model'
Expand Down Expand Up @@ -563,6 +567,20 @@
size_hint_y: 1.0 if check.state == 'down' else 0.1
DataFrameLabel:
id: scroll_tubs
BoxLayout:
size_hint_y: None
height: layout_height
padding: layout_pad_xy
spacing: layout_pad_x
MySpinner:
id: delete_spinner
text: 'Pilot'
Button:
id: delete_btn
on_press:
root.database.delete_entry(delete_spinner.text)
root.reload_database()
text: 'Delete pilot'
BoxLayout:
size_hint_y: None
height: layout_height
Expand Down Expand Up @@ -642,7 +660,8 @@
self.disabled = True
root.pull(tub_dir_spinner.text)
ProgressBar:
value: root.pull_bar
id: pull_bar
#value: root.pull_bar
Label:
#size_hint_y: None
#height: common_height
Expand Down Expand Up @@ -677,7 +696,8 @@
self.disabled = True
root.send_pilot()
ProgressBar:
value: root.push_bar
id: push_bar
# value: root.push_bar

Label:
# size_hint_y: None
Expand Down
Loading