├── donkeycar ├── util │ ├── times.py │ ├── __init__.py │ ├── web.py │ ├── proc.py │ ├── files.py │ ├── img.py │ └── data.py ├── parts │ ├── __init__.py │ ├── Backup │ │ ├── __init__.py │ │ ├── clock.py │ │ ├── lidar.py │ │ ├── imu.py │ │ ├── transform.py │ │ ├── teensy.py │ │ ├── encoder.py │ │ ├── autorope.py │ │ ├── camera.py │ │ ├── simulation.py │ │ ├── actuator.py │ │ └── keras.py │ ├── web_controller │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── static │ │ │ │ ├── bootstrap │ │ │ │ │ └── 3.3.7 │ │ │ │ │ │ └── fonts │ │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ │ └── style.css │ │ │ ├── base.html │ │ │ └── vehicle.html │ │ └── web.py │ ├── clock.py │ ├── lidar.py │ ├── imu.py │ ├── transform.py │ ├── teensy.py │ ├── encoder.py │ ├── autorope.py │ ├── camera.py │ ├── simulation.py │ └── keras.py ├── tests │ ├── test_parts.py │ ├── __init__.py │ ├── test_management.py │ ├── test_transform.py │ ├── test_actuator.py │ ├── test_web_controller.py │ ├── test_sensors.py │ ├── test_simulations.py │ ├── test_vehicle.py │ ├── test_keras.py │ ├── test_tub_reader.py │ ├── test_sim.py │ ├── test_tub_handler.py │ ├── test_tub_writer.py │ ├── test_scripts.py │ ├── test_tubgroup.py │ ├── setup.py │ ├── test_memory.py │ ├── test_util_data.py │ └── test_tub.py ├── management │ ├── __init__.py │ ├── tub_web │ │ ├── static │ │ │ ├── bootstrap │ │ │ │ └── 3.3.7 │ │ │ │ │ └── fonts │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ ├── style.css │ │ │ └── tub.js │ │ ├── tubs.html │ │ ├── base.html │ │ └── tub.html │ └── tub.py ├── __init__.py ├── LICENSE ├── templates │ ├── config_defaults.py │ ├── tk1.py │ ├── square.py │ └── donkey2.py ├── memory.py ├── log.py ├── config.py ├── vehicle.py └── Backup │ └── vehicle.py ├── _config.yml ├── setup.cfg ├── MANIFEST.in ├── Dockerfile ├── chassis └── arduino │ └── libraries │ ├── SSD1306.zip │ └── RBD_Timer.zip ├── install ├── envs │ ├── ubuntu.yml │ ├── sagemaker.yml │ ├── mac.yml │ ├── rpi.yml │ └── windows.yml ├── README.md ├── pi │ ├── opencv.sh │ ├── prepare.sh │ └── install.sh └── LICENSE ├── doc └── meeting minutes │ ├── release_note │ └── 1-Startup Meeting-20190120.md ├── LICENSE-MIT ├── mkdocs.yml ├── README.md └── setup.py /donkeycar/util/times.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /donkeycar/parts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /donkeycar/parts/Backup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /donkeycar/tests/test_parts.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /donkeycar/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | -------------------------------------------------------------------------------- /donkeycar/management/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include donkeycar/templates/* 2 | recursive-include donkeycar/parts/web_controller/templates/ * -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | WORKDIR /app 4 | 5 | ADD . /app 6 | RUN pip install -e . 7 | 8 | EXPOSE 8887 9 | -------------------------------------------------------------------------------- /chassis/arduino/libraries/SSD1306.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/chassis/arduino/libraries/SSD1306.zip -------------------------------------------------------------------------------- /chassis/arduino/libraries/RBD_Timer.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/chassis/arduino/libraries/RBD_Timer.zip -------------------------------------------------------------------------------- /donkeycar/parts/clock.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | class Timestamp(): 4 | 5 | def run(self,): 6 | return str(datetime.datetime.utcnow()) 7 | 8 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/clock.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | class Timestamp(): 4 | 5 | def run(self,): 6 | return str(datetime.datetime.utcnow()) 7 | 8 | -------------------------------------------------------------------------------- /install/envs/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: donkey 2 | dependencies: 3 | 4 | - h5py=2.5.0 5 | - pillow=2.9.0 6 | 7 | - pip: 8 | - theano==0.9.0 9 | - tensorflow==1.7.0 10 | -------------------------------------------------------------------------------- /donkeycar/util/__init__.py: -------------------------------------------------------------------------------- 1 | from . import (proc, 2 | data, 3 | files, 4 | img, 5 | times, 6 | web) 7 | -------------------------------------------------------------------------------- /install/envs/sagemaker.yml: -------------------------------------------------------------------------------- 1 | name: donkey 2 | dependencies: 3 | 4 | - numpy 5 | - pandas 6 | - pillow 7 | - h5py 8 | 9 | - pip: 10 | - tensorflow-gpu 11 | - docopt 12 | - moviepy 13 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /donkeycar/tests/test_management.py: -------------------------------------------------------------------------------- 1 | 2 | from donkeycar.management import base 3 | from tempfile import tempdir 4 | 5 | def get_test_tub_path(): 6 | tempdir() 7 | 8 | def test_tubcheck(): 9 | tc = base.TubCheck() 10 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/management/tub_web/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mavengers/DonkeyDrift/HEAD/donkeycar/parts/web_controller/templates/static/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /install/README.md: -------------------------------------------------------------------------------- 1 | ### Install on raspberry pi 2 | The easiest way to get donkey running on a pi is with 3 | a prebuilt disk image. To create your own disk 4 | image you can use the scripts in /pi. 5 | 6 | 7 | ### Install on other systems 8 | Create a conda environment using the env files in 9 | -------------------------------------------------------------------------------- /doc/meeting minutes/release_note: -------------------------------------------------------------------------------- 1 | ## First Version Released 2 | * 2019-3-14 Piday@MushroomCloud DonkeyDrift first version released in Shanghi, China. 3 | ## Features 4 | * Stripped wires. 5 | * Optimized KarasLinear algorithm. 6 | * fixed bugs tensorflow version's problem. 7 | * TODO: "oneshot shell scripts" for beginner. 8 | * TODO: "make beginner kits" 9 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/tubs.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% block content %} 4 | 5 | 6 |
7 | 12 | 13 |
14 | 15 | 16 | 17 | {% end %} 18 | -------------------------------------------------------------------------------- /donkeycar/tests/test_transform.py: -------------------------------------------------------------------------------- 1 | from donkeycar.parts.transform import Lambda 2 | 3 | 4 | def f(a): 5 | return a + 1 6 | 7 | def f2(a, b): 8 | return a + b + 1 9 | 10 | def test_lambda_one_arg(): 11 | l = Lambda(f) 12 | b = l.run(1) 13 | assert b == 2 14 | 15 | def test_lambda_two_args(): 16 | l = Lambda(f2) 17 | b = l.run(1, 1) 18 | assert b == 3 19 | -------------------------------------------------------------------------------- /donkeycar/util/web.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | def get_ip_address(): 4 | ip = ([l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], 5 | [[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in 6 | [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0]) 7 | return ip 8 | -------------------------------------------------------------------------------- /donkeycar/tests/test_actuator.py: -------------------------------------------------------------------------------- 1 | from .setup import on_pi 2 | 3 | from donkeycar.parts.actuator import PCA9685, PWMSteering, PWMThrottle 4 | import pytest 5 | 6 | 7 | @pytest.mark.skipif(on_pi() == False, reason='Not on RPi') 8 | def test_PCA9685(): 9 | c = PCA9685(0) 10 | 11 | @pytest.mark.skipif(on_pi() == False, reason='Not on RPi') 12 | def test_PWMSteering(): 13 | c = PCA9685(0) 14 | s = PWMSteering(c) 15 | 16 | -------------------------------------------------------------------------------- /donkeycar/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.5.1' 2 | 3 | print('using donkey v{} ...'.format(__version__)) 4 | 5 | import sys 6 | 7 | if sys.version_info.major < 3: 8 | msg = 'Donkey Requires Python 3.4 or greater. You are using {}'.format(sys.version) 9 | raise ValueError(msg) 10 | 11 | from . import parts 12 | from .vehicle import Vehicle 13 | from .memory import Memory 14 | from . import util 15 | from . import config 16 | from .config import load_config 17 | -------------------------------------------------------------------------------- /donkeycar/tests/test_web_controller.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pytest 3 | import json 4 | from donkeycar.parts.web_controller.web import LocalWebController 5 | 6 | @pytest.fixture 7 | def server(): 8 | server = LocalWebController() 9 | return server 10 | 11 | 12 | def test_json_output(server): 13 | result = server.run() 14 | json_result = json.dumps(result) 15 | d = json.loads(json_result) 16 | assert d is not None 17 | assert int(d[0]) == 0 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /donkeycar/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pytest 4 | from .setup import on_pi 5 | from donkeycar.parts.camera import BaseCamera 6 | 7 | 8 | def test_base_camera(): 9 | cam = BaseCamera() 10 | 11 | 12 | @pytest.mark.skipif(on_pi() == False, reason='only works on RPi') 13 | def test_picamera(): 14 | from donkeycar.parts.camera import PiCamera 15 | resolution = (120,160) 16 | cam = PiCamera(resolution=resolution) 17 | frame = cam.run() 18 | #assert shape is as expected. img_array shape shows (width, height, channels) 19 | assert frame.shape[:2] == resolution[:] 20 | 21 | -------------------------------------------------------------------------------- /donkeycar/tests/test_simulations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | import numpy as np 4 | from donkeycar.parts.simulation import MovingSquareTelemetry, SquareBoxCamera 5 | 6 | 7 | class TestMovingSquareTelemetry(unittest.TestCase): 8 | def setUp(self): 9 | self.tel = MovingSquareTelemetry() 10 | 11 | def test_run_types(self): 12 | x, y = self.tel.run() 13 | assert type(x) == int 14 | assert type(y) == int 15 | 16 | 17 | class TestSquareBoxCamera(unittest.TestCase): 18 | def setUp(self): 19 | self.cam = SquareBoxCamera() 20 | 21 | def test_run_types(self): 22 | arr = self.cam.run(50, 50) 23 | assert type(arr) == np.ndarray 24 | -------------------------------------------------------------------------------- /donkeycar/tests/test_vehicle.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import donkeycar as dk 3 | from donkeycar.parts.transform import Lambda 4 | 5 | @pytest.fixture() 6 | def vehicle(): 7 | v = dk.Vehicle() 8 | def f(): return 1 9 | l = Lambda(f) 10 | v.add(l, outputs=['test_out']) 11 | return v 12 | 13 | def test_create_vehicle(): 14 | v = dk.Vehicle() 15 | assert v.parts == [] 16 | 17 | 18 | def test_add_part(): 19 | v = dk.Vehicle() 20 | def f(): 21 | return 1 22 | l = Lambda(f) 23 | v.add(l, outputs=['test_out']) 24 | assert len(v.parts) == 1 25 | 26 | 27 | def test_vehicle_run(vehicle): 28 | vehicle.start(rate_hz=20, max_loop_count=2) 29 | assert vehicle is not None 30 | -------------------------------------------------------------------------------- /donkeycar/tests/test_keras.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pytest 3 | from donkeycar.parts.keras import KerasPilot, KerasCategorical 4 | from donkeycar.parts.keras import default_categorical, default_n_linear 5 | # content of ./test_smtpsimple.py 6 | 7 | @pytest.fixture 8 | def pilot(): 9 | kp = KerasPilot() 10 | return kp 11 | 12 | def test_pilot_types(pilot): 13 | assert 1 == 1 14 | 15 | 16 | 17 | def test_categorical(): 18 | kc = KerasCategorical() 19 | assert kc.model is not None 20 | 21 | def test_categorical_with_model(): 22 | kc = KerasCategorical(default_categorical()) 23 | assert kc.model is not None 24 | 25 | def test_def_n_linear_model(): 26 | model = default_n_linear(10) 27 | assert model is not None 28 | 29 | -------------------------------------------------------------------------------- /install/pi/opencv.sh: -------------------------------------------------------------------------------- 1 | #install opencv (1 hour) 2 | #instructions from:https://raspberrypi.stackexchange.com/questions/69169/how-to-install-opencv-on-raspberry-pi-3-in-raspbian-jessie 3 | 4 | # NOTE: this gets the dev version. Use tags to get specific version 5 | git clone --branch 3.4.1 --depth 1 https://github.com/opencv/opencv.git 6 | git clone --branch 3.4.1 --depth 1 https://github.com/opencv/opencv_contrib.git 7 | 8 | cd ~/opencv 9 | mkdir build 10 | cd build 11 | cmake -D CMAKE_BUILD_TYPE=RELEASE \ 12 | -D CMAKE_INSTALL_PREFIX=/usr/local \ 13 | -D INSTALL_C_EXAMPLES=OFF \ 14 | -D INSTALL_PYTHON_EXAMPLES=OFF \ 15 | -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \ 16 | -D BUILD_EXAMPLES=OFF .. 17 | make -j4 18 | sudo make install 19 | sudo ldconfig -------------------------------------------------------------------------------- /donkeycar/tests/test_tub_reader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | import tempfile 4 | import os 5 | 6 | from donkeycar.parts.datastore import Tub, TubReader, TubWriter 7 | 8 | 9 | def test_tubreader(): 10 | with tempfile.TemporaryDirectory() as tempfolder: 11 | path = os.path.join(tempfolder, 'new') 12 | inputs = ['name', 'age', 'pic'] 13 | types = ['str', 'float', 'str'] 14 | writer = TubWriter(path, inputs=inputs, types=types) 15 | writer.run('will', 323, 'asdfasdf') 16 | assert writer.get_num_records() == 1 17 | 18 | reader = TubReader(path) 19 | assert reader.get_num_records() == 1 20 | 21 | record = reader.run('name', 'age', 'pic') 22 | assert set(record) == set(['will', 323, 'asdfasdf']) -------------------------------------------------------------------------------- /install/envs/mac.yml: -------------------------------------------------------------------------------- 1 | name: donkey 2 | channels: 3 | - defaults 4 | dependencies: 5 | - mkl=2017.0.3=0 6 | - numpy=1.13.1=py36_0 7 | - openssl=1.0.2l=0 8 | - python=3.6.2=0 9 | - readline=6.2=2 10 | - setuptools=27.2.0=py36_0 11 | - sqlite=3.13.0=0 12 | - tk=8.5.18=0 13 | - wheel=0.29.0=py36_0 14 | - xz=5.2.3=0 15 | - zlib=1.2.11=0 16 | - pip: 17 | - bleach==1.5.0 18 | - certifi==2017.7.27.1 19 | - chardet==3.0.4 20 | - docopt==0.6.2 21 | - h5py==2.7.1 22 | - html5lib==0.9999999 23 | - idna==2.6 24 | - markdown==2.6.9 25 | - olefile==0.44 26 | - pillow==4.2.1 27 | - protobuf==3.4.0 28 | - pyyaml==3.12 29 | - requests==2.18.4 30 | - scipy==0.19.1 31 | - six==1.10.0 32 | - tornado==4.5.1 33 | - urllib3==1.22 34 | - werkzeug==0.12.2 35 | - tensorflow==1.7.0 36 | 37 | -------------------------------------------------------------------------------- /donkeycar/parts/lidar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lidar 3 | """ 4 | 5 | import time 6 | import numpy as np 7 | 8 | 9 | class RPLidar(): 10 | def __init__(self, port='/dev/ttyUSB0'): 11 | from rplidar import RPLidar 12 | self.port = port 13 | self.frame = np.zeros(shape=365) 14 | self.lidar = RPLidar(self.port) 15 | self.lidar.clear_input() 16 | time.sleep(1) 17 | self.on = True 18 | 19 | 20 | def update(self): 21 | self.measurements = self.lidar.iter_measurments(500) 22 | for new_scan, quality, angle, distance in self.measurements: 23 | angle = int(angle) 24 | self.frame[angle] = 2*distance/3 + self.frame[angle]/3 25 | if not self.on: 26 | break 27 | 28 | def run_threaded(self): 29 | return self.frame 30 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/lidar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lidar 3 | """ 4 | 5 | import time 6 | import numpy as np 7 | 8 | 9 | class RPLidar(): 10 | def __init__(self, port='/dev/ttyUSB0'): 11 | from rplidar import RPLidar 12 | self.port = port 13 | self.frame = np.zeros(shape=365) 14 | self.lidar = RPLidar(self.port) 15 | self.lidar.clear_input() 16 | time.sleep(1) 17 | self.on = True 18 | 19 | 20 | def update(self): 21 | self.measurements = self.lidar.iter_measurments(500) 22 | for new_scan, quality, angle, distance in self.measurements: 23 | angle = int(angle) 24 | self.frame[angle] = 2*distance/3 + self.frame[angle]/3 25 | if not self.on: 26 | break 27 | 28 | def run_threaded(self): 29 | return self.frame 30 | -------------------------------------------------------------------------------- /donkeycar/tests/test_sim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Oct 12 2017 5 | 6 | @author: tawnkramer 7 | """ 8 | 9 | 10 | import unittest 11 | from donkeycar.parts.simulation import SteeringServer, FPSTimer 12 | from donkeycar.parts.keras import KerasCategorical 13 | 14 | class TestSimServer(unittest.TestCase): 15 | 16 | def test_create_sim_server(self): 17 | import socketio 18 | kc = KerasCategorical() 19 | sio = socketio.Server() 20 | assert sio is not None 21 | ss = SteeringServer(_sio=sio, kpart=kc) 22 | assert ss is not None 23 | 24 | def test_timer(self): 25 | tm = FPSTimer() 26 | assert tm is not None 27 | tm.reset() 28 | tm.on_frame() 29 | tm.on_frame() 30 | assert tm.iter == 2 31 | tm.iter = 100 32 | tm.on_frame() 33 | 34 | 35 | -------------------------------------------------------------------------------- /donkeycar/tests/test_tub_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from donkeycar.parts.datastore import TubHandler 4 | from .setup import tubs 5 | 6 | 7 | def test_create_tub_handler(tubs): 8 | root_dir = tubs[0] 9 | th = TubHandler(root_dir) 10 | assert th is not None 11 | 12 | 13 | def test_get_tub_list(tubs): 14 | root_dir = tubs[0] 15 | th = TubHandler(root_dir) 16 | assert len(th.get_tub_list()) == 5 17 | 18 | 19 | def test_next_tub_number(tubs): 20 | root_dir = tubs[0] 21 | th = TubHandler(root_dir) 22 | assert th.next_tub_number() == 5 23 | 24 | 25 | def test_new_tub_writer(tubs): 26 | root_dir = tubs[0] 27 | th = TubHandler(root_dir) 28 | inputs=['cam/image_array', 'angle', 'throttle'] 29 | types=['image_array', 'float', 'float'] 30 | tw = th.new_tub_writer(inputs, types) 31 | assert len(th.get_tub_list()) == 6 32 | print(tw.path) 33 | assert int(tw.path.split('_')[-2]) == 5 -------------------------------------------------------------------------------- /donkeycar/util/proc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions to simplify working with processes. 3 | """ 4 | import subprocess 5 | import os 6 | import sys 7 | 8 | 9 | def run_shell_command(cmd, cwd=None, timeout=15): 10 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 11 | out = [] 12 | err = [] 13 | 14 | try: 15 | proc.wait(timeout=timeout) 16 | except subprocess.TimeoutExpired: 17 | kill(proc.pid) 18 | 19 | for line in proc.stdout.readlines(): 20 | out.append(line.decode()) 21 | 22 | for line in proc.stderr.readlines(): 23 | err.append(line) 24 | return out, err, proc.pid 25 | 26 | """ 27 | def kill(proc_pid): 28 | process = psutil.Process(proc_pid) 29 | for proc in process.children(recursive=True): 30 | proc.kill() 31 | process.kill() 32 | """ 33 | import signal 34 | 35 | def kill(proc_id): 36 | os.kill(proc_id, signal.SIGINT) 37 | 38 | 39 | 40 | 41 | def eprint(*args, **kwargs): 42 | print(*args, file=sys.stderr, **kwargs) 43 | -------------------------------------------------------------------------------- /install/envs/rpi.yml: -------------------------------------------------------------------------------- 1 | name: donkey 2 | dependencies: 3 | - certifi=14.05.14=py34_0 4 | - freetype=2.5.2=2 5 | - h5py=2.5.0=np19py34_3 6 | - hdf5=1.8.15.1=1 7 | - jpeg=8d=0 8 | - libgfortran=1.0=0 9 | - libpng=1.6.17=0 10 | - libtiff=4.0.2=1 11 | - numpy=1.9.2=py34_1 12 | - openblas=0.2.14=1 13 | - openssl=1.0.1k=1 14 | - pillow=2.9.0=py34_0 15 | - pip=7.1.2=py34_0 16 | - python=3.4.3=1 17 | - python-dateutil=2.4.2=py34_0 18 | - pytz=2015.4=py34_0 19 | - requests=2.7.0=py34_0 20 | - scipy=0.16.0=np19py34_1 21 | - setuptools=18.1=py34_0 22 | - six=1.9.0=py34_0 23 | - sqlite=3.8.4.1=1 24 | - tornado=4.2.1=py34_0 25 | - wheel=0.24.0=py34_0 26 | - xz=5.0.5=0 27 | - zlib=1.2.8=0 28 | - pip: 29 | - adafruit-gpio==1.0.3 30 | - adafruit-pca9685==1.0.1 31 | - adafruit-pureio==0.2.1 32 | - cheroot==5.8.3 33 | - docopt==0.6.2 34 | - envoy==0.0.3 35 | - picamera==1.13 36 | - portend==2.1.2 37 | - protobuf==3.3.0 38 | - pyyaml==3.12 39 | - spidev==3.2 40 | - tempora==1.8 41 | - theano==0.9.0 42 | - werkzeug==0.12.2 43 | - tensorflow==1.7.0 44 | -------------------------------------------------------------------------------- /doc/meeting minutes/1-Startup Meeting-20190120.md: -------------------------------------------------------------------------------- 1 | # 漂移驴车创始会议 2 | 3 | ## 一、会议背景 4 | 5 | - 参会人员:骑驴、昊男 6 | 7 | - 会议时间:2019年1月20日21点 8 | 9 | 10 | ## 二、会议目标 11 | 12 | - 讨论部署、结构、硬件和软件方面的优化目标 13 | - 构想Github仓库文件的基本协作框架 14 | 15 | ## 三、会议内容 16 | 17 | ### 1. 环境部署: 18 | 19 | - 优化.yml配置文件(统一软件版本) 20 | - Windows -miniConda 21 | - Mac-miniConda 22 | - Linux-VirtualEnv 23 | - 部署国内镜像服务器,方便相关软件下载 24 | 25 | ### 2. 机械结构 26 | 27 | - 舵机连杆位置调整(使左右转动范围接近相等,避免转向不足) 28 | - 摄像头随前轮转动(类似宝马随转前灯,增加视野范围i) 29 | 30 | ### 3. 硬件电路 31 | 32 | - 联线换用端子接头(固定稳固、方便插接) 33 | - 增加蜂鸣器和指示灯(提示机器状态) 34 | 35 | ### 4. 软件算法 36 | 37 | - 树莓派与Arduino通过I2C通信(与PCA9658并列) 38 | - 优化Web端功能(快捷键Bug、功能逻辑、转向键体验) 39 | - 提升驾驶操控性 40 | - 对象:实体手柄 41 | - 目标:线性输出、低时延、抗干扰 42 | - Arduino底盘实现独立参数调整并本地保存 43 | - 开发训练样本筛选功能,简便删除错误样本(半自动、交互式) 44 | - Python程序逐步用C重写(提升运行效率) 45 | 46 | ### 5. 赛道布局 47 | 48 | - 简单版:EVA+美纹纸(模块化、低成本) 49 | 50 | - 标注版:EVA+喷绘打印(标准化、更美观) 51 | - 未来设想: 52 | - 增加赛道计分功能(RFID读取、视觉识别等) 53 | - 提升赛道重构能力(投影、灯光指示等) 54 | 55 | ### 6. 协作框架 56 | 57 | - 文件结构:分类清晰、可扩展、易检索 58 | - 合作规则:代码风格统一、合并规则明确、版本迭代有序 59 | - 说明文档:逻辑清晰、内容可靠、及时更新 -------------------------------------------------------------------------------- /install/pi/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #Script to setup pi disk after base rpi image. 4 | 5 | MEDIA=/media/$USER 6 | BOOT=$MEDIA/boot 7 | ROOTFS=$MEDIA/rootfs 8 | 9 | sudo touch $BOOT/ssh 10 | 11 | #enable camera 12 | echo "start_x=1" | sudo tee -a $BOOT/config.txt 13 | echo "gpu_mem=128" | sudo tee -a $BOOT/config.txt 14 | 15 | #enable i2c 16 | echo "i2c-dev" | sudo tee -a $ROOTFS/etc/modules 17 | 18 | #Change hostname 19 | hostn=$(cat $ROOTFS/etc/hostname) 20 | echo "Existing hostname is $hostn" 21 | echo "Enter new hostname: " 22 | read newhost 23 | 24 | #change hostname in /etc/hosts & /etc/hostname 25 | sudo sed -i "s/$hostn/$newhost/g" $ROOTFS/etc/hosts 26 | sudo sed -i "s/$hostn/$newhost/g" $ROOTFS/etc/hostname 27 | echo "Your new hostname is $newhost" 28 | 29 | # setup default wifi config 30 | sudo truncate -s 0 $ROOTFS/etc/wpa_supplicant/wpa_supplicant.conf 31 | cat <<'EOF' | sudo tee $ROOTFS/etc/wpa_supplicant/wpa_supplicant.conf 32 | country=US 33 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 34 | update_config=1 35 | 36 | network={ 37 | ssid="network_name" 38 | psk="password" 39 | } 40 | EOF -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Will Roscoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /donkeycar/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Will Roscoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /install/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Will Roscoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Donkey Car 2 | theme: readthedocs 3 | 4 | repo_url: https://github.com/wroscoe/donkey/ 5 | edit_uri: edit/master/docs 6 | google_analytics: ['UA-99215530-2', 'github.io/donkey'] 7 | extra_css: 8 | - extra.css 9 | 10 | pages: 11 | - Home: index.md 12 | - User Guide: 13 | - 'Build a car.': guide/build_hardware.md 14 | - 'Install the software.': guide/install_software.md 15 | - 'Get driving.': guide/get_driving.md 16 | - 'Calibrate steering and throttle.': guide/calibrate.md 17 | - 'Train an autopilot.': guide/train_autopilot.md 18 | - 'Donkey Simulator.': guide/simulator.md 19 | - Parts: 20 | - 'About': parts/about.md 21 | - 'Actuators': parts/actuators.md 22 | - 'Controllers': parts/controllers.md 23 | - 'Stores': parts/stores.md 24 | - 'IMU': parts/imu.md 25 | - Utilities: 26 | - 'donkey': utility/donkey.md 27 | 28 | - Supported Cars: supported_cars.md 29 | - Roll Your Own: roll_your_own.md 30 | - Contribute : contribute.md 31 | - Tests : tests.md 32 | - Releases : release.md 33 | - FAQ : faq.md 34 | -------------------------------------------------------------------------------- /donkeycar/tests/test_tub_writer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | import tempfile 4 | import os 5 | 6 | from donkeycar.parts.datastore import Tub, TubWriter 7 | 8 | 9 | class TestTubWriter(unittest.TestCase): 10 | 11 | def setUp(self): 12 | tempfolder = tempfile.TemporaryDirectory() 13 | self.path = os.path.join(tempfolder.name, 'new') 14 | self.inputs = ['name', 'age', 'pic'] 15 | self.types = ['str', 'float', 'str'] 16 | 17 | def test_tub_create(self): 18 | tub = TubWriter(self.path, inputs=self.inputs, types=self.types) 19 | 20 | def test_tub_path(self): 21 | tub = TubWriter(self.path, inputs=self.inputs, types=self.types) 22 | print(tub.types, tub.inputs) 23 | tub.run('will', 323, 'asdfasdf') 24 | 25 | def test_make_paths_absolute(self): 26 | tub = Tub(self.path, inputs=['file_path'], types=['image']) 27 | rel_file_name = 'test.jpg' 28 | record_dict = {'file_path': rel_file_name} 29 | abs_record_dict = tub.make_record_paths_absolute(record_dict) 30 | 31 | assert abs_record_dict['file_path'] == os.path.join(self.path, rel_file_name) -------------------------------------------------------------------------------- /donkeycar/templates/config_defaults.py: -------------------------------------------------------------------------------- 1 | """ 2 | CAR CONFIG 3 | 4 | This file is read by your car application's manage.py script to change the car 5 | performance. 6 | 7 | EXMAPLE 8 | ----------- 9 | import dk 10 | cfg = dk.load_config(config_path='~/mycar/config.py') 11 | print(cfg.CAMERA_RESOLUTION) 12 | 13 | """ 14 | 15 | 16 | import os 17 | 18 | #PATHS 19 | CAR_PATH = PACKAGE_PATH = os.path.dirname(os.path.realpath(__file__)) 20 | DATA_PATH = os.path.join(CAR_PATH, 'data') 21 | MODELS_PATH = os.path.join(CAR_PATH, 'models') 22 | 23 | #VEHICLE 24 | DRIVE_LOOP_HZ = 20 25 | MAX_LOOPS = 100000 26 | 27 | #CAMERA 28 | CAMERA_RESOLUTION = (120, 160) #(height, width) 29 | CAMERA_FRAMERATE = DRIVE_LOOP_HZ 30 | 31 | #STEERING 32 | STEERING_CHANNEL = 1 33 | STEERING_LEFT_PWM = 420 34 | STEERING_RIGHT_PWM = 360 35 | 36 | #THROTTLE 37 | THROTTLE_CHANNEL = 0 38 | THROTTLE_FORWARD_PWM = 400 39 | THROTTLE_STOPPED_PWM = 360 40 | THROTTLE_REVERSE_PWM = 310 41 | 42 | #TRAINING 43 | BATCH_SIZE = 128 44 | TRAIN_TEST_SPLIT = 0.8 45 | 46 | 47 | #JOYSTICK 48 | USE_JOYSTICK_AS_DEFAULT = False 49 | JOYSTICK_MAX_THROTTLE = 0.25 50 | JOYSTICK_STEERING_SCALE = 1.0 51 | AUTO_RECORD_ON_THROTTLE = True 52 | 53 | 54 | TUB_PATH = os.path.join(CAR_PATH, 'tub') # if using a single tub 55 | 56 | #ROPE.DONKEYCAR.COM 57 | ROPE_TOKEN="GET A TOKEN AT ROPE.DONKEYCAR.COM" 58 | -------------------------------------------------------------------------------- /donkeycar/tests/test_scripts.py: -------------------------------------------------------------------------------- 1 | from donkeycar import util 2 | import pytest 3 | 4 | 5 | def is_error(err): 6 | for e in err: 7 | #Catch error if 'Error' is in the stderr output. 8 | if 'Error' in e.decode(): 9 | return True 10 | #Catch error when the wrong command is used. 11 | if 'Usage:' in e.decode(): 12 | return True 13 | return False 14 | 15 | 16 | @pytest.fixture 17 | def cardir(tmpdir): 18 | path = str(tmpdir.mkdir("mycar")) 19 | return path 20 | 21 | 22 | def test_createcar(cardir): 23 | cmd = ['donkey', 'createcar', '--path', cardir] 24 | out, err, proc_id = util.proc.run_shell_command(cmd) 25 | assert is_error(err) is False 26 | 27 | def test_drivesim(cardir): 28 | cmd = ['donkey', 'createcar', '--path', cardir ,'--template', 'square'] 29 | out, err, proc_id = util.proc.run_shell_command(cmd, timeout=10) 30 | cmd = ['python', 'manage.py', 'drive'] 31 | out, err, proc_id = util.proc.run_shell_command(cmd, cwd = cardir) 32 | print(err) 33 | 34 | if is_error(err) is True: 35 | print('out', out) 36 | print('error: ', err) 37 | raise ValueError (err) 38 | 39 | def test_bad_command_fails(): 40 | cmd = ['donkey', 'not a comand'] 41 | out, err, proc_id = util.proc.run_shell_command(cmd) 42 | print(err) 43 | print(out) 44 | assert is_error(err) is True 45 | -------------------------------------------------------------------------------- /donkeycar/tests/test_tubgroup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from donkeycar.parts.datastore import TubGroup 3 | from .setup import tubs 4 | 5 | 6 | def test_tubgroup_load(tubs): 7 | """ Load TubGroup from existing tubs dir """ 8 | list_of_tubs = tubs[1] 9 | str_of_tubs = ','.join(list_of_tubs) 10 | t = TubGroup(str_of_tubs) 11 | assert t is not None 12 | 13 | 14 | def test_tubgroup_inputs(tubs): 15 | """ Get TubGroup inputs """ 16 | list_of_tubs = tubs[1] 17 | str_of_tubs = ','.join(list_of_tubs) 18 | t = TubGroup(str_of_tubs) 19 | assert sorted(t.inputs) == sorted(['cam/image_array', 'angle', 'throttle']) 20 | 21 | 22 | def test_tubgroup_types(tubs): 23 | """ Get TubGroup types """ 24 | list_of_tubs = tubs[1] 25 | str_of_tubs = ','.join(list_of_tubs) 26 | t = TubGroup(str_of_tubs) 27 | assert sorted(t.types) == sorted(['image_array', 'float', 'float']) 28 | 29 | 30 | def test_tubgroup_get_num_tubs(tubs): 31 | """ Get number of tubs in TubGroup """ 32 | list_of_tubs = tubs[1] 33 | str_of_tubs = ','.join(list_of_tubs) 34 | t = TubGroup(str_of_tubs) 35 | assert t.get_num_tubs() == 5 36 | 37 | 38 | def test_tubgroup_get_num_records(tubs): 39 | """ Get number of records in TubGroup """ 40 | list_of_tubs = tubs[1] 41 | str_of_tubs = ','.join(list_of_tubs) 42 | t = TubGroup(str_of_tubs) 43 | assert t.get_num_records() == 25 44 | -------------------------------------------------------------------------------- /donkeycar/tests/setup.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import pytest 3 | from donkeycar.parts.datastore import Tub 4 | from donkeycar.parts.simulation import SquareBoxCamera, MovingSquareTelemetry 5 | 6 | 7 | def on_pi(): 8 | if 'arm' in platform.machine(): 9 | return True 10 | return False 11 | 12 | 13 | @pytest.fixture 14 | def tub_path(tmpdir): 15 | tub_path = tmpdir.mkdir('tubs').join('tub') 16 | return str(tub_path) 17 | 18 | 19 | @pytest.fixture 20 | def tub(tub_path): 21 | t = create_sample_tub(tub_path, records=10) 22 | return t 23 | 24 | 25 | @pytest.fixture 26 | def tubs(tmpdir, tubs=5): 27 | tubs_dir = tmpdir.mkdir('tubs') 28 | tub_paths = [ str(tubs_dir.join('tub_{}'.format(i))) for i in range(tubs) ] 29 | tubs = [ create_sample_tub(tub_path, records=5) for tub_path in tub_paths ] 30 | return (str(tubs_dir), tub_paths, tubs) 31 | 32 | 33 | def create_sample_tub(path, records=10): 34 | inputs=['cam/image_array', 'angle', 'throttle'] 35 | types=['image_array', 'float', 'float'] 36 | t = Tub(path, inputs=inputs, types=types) 37 | for _ in range(records): 38 | record = create_sample_record() 39 | t.put_record(record) 40 | return t 41 | 42 | 43 | def create_sample_record(): 44 | cam = SquareBoxCamera() 45 | tel = MovingSquareTelemetry() 46 | x, y = tel.run() 47 | img_arr = cam.run(x, y) 48 | return {'cam/image_array': img_arr, 'angle': x, 'throttle':y} 49 | 50 | 51 | -------------------------------------------------------------------------------- /donkeycar/memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 11:07:48 2017 5 | 6 | @author: wroscoe 7 | """ 8 | 9 | 10 | class Memory: 11 | """ 12 | A convenience class to save key/value pairs. 13 | """ 14 | def __init__(self, *args, **kw): 15 | self.d = {} 16 | 17 | def __setitem__(self, key, value): 18 | if type(key) is not tuple: 19 | key = (key,) 20 | value=(value,) 21 | 22 | for i, k in enumerate(key): 23 | self.d[k] = value[i] 24 | 25 | def __getitem__(self, key): 26 | if type(key) is tuple: 27 | return [self.d[k] for k in key] 28 | else: 29 | return self.d[key] 30 | 31 | def update(self, new_d): 32 | self.d.update(new_d) 33 | 34 | def put(self, keys, inputs): 35 | if len(keys) > 1: 36 | for i, key in enumerate(keys): 37 | try: 38 | self.d[key] = inputs[i] 39 | except IndexError as e: 40 | error = str(e) + ' issue with keys: ' + str(key) 41 | raise IndexError(error) 42 | else: 43 | self.d[keys[0]] = inputs 44 | 45 | def get(self, keys): 46 | result = [self.d.get(k) for k in keys] 47 | return result 48 | 49 | def keys(self): 50 | return self.d.keys() 51 | 52 | def values(self): 53 | return self.d.values() 54 | 55 | def items(self): 56 | return self.d.items() 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DonkeyDrift 2 | This is a new project from Shanghai, China, which is originally based on DonkeyCar. 3 | We have built an Arduino chassis with high performance, modified several configurations and chosen tensorflow version at 1.8.0 and donkeycar version at 2.5.1 of master branch. 4 | 5 | # Instruction 6 | 7 | ## 1. Reference & Tools: 8 | 9 | | https://pan.baidu.com/s/1Gl0iprFv91YLO-QGRjGgTQ | 10 | | ----------------------------------------------- | 11 | 12 | ## 2. Clone this repository: 13 | 14 | ``` 15 | git clone https://github.com/Haobot/DonkeyDrift 16 | 17 | cd DonkeyDrift/ 18 | ``` 19 | 20 | ## 3. Install DonkeyDrift: 21 | 22 | ``` 23 | activate donkey 24 | pip install -e . 25 | ``` 26 | 27 | ## 4. Create new instance: 28 | 29 | ``` 30 | donkey createcar ./mycar 31 | cd mycar/ 32 | ``` 33 | 34 | ## 5. Calibrate car: 35 | 36 | ``` 37 | donkey calibrate --channel 0 38 | donkey calibrate --channel 1 39 | ``` 40 | 41 | ## 6. Drive car: 42 | 43 | ``` 44 | python manage.py drive 45 | ``` 46 | 47 | ## 7. Train Model on PC or Server: 48 | ``` 49 | python manage.py train --tub=./data/your_tub_name --model=./models/your_model_name 50 | ``` 51 | 52 | ## 8. Test Model with Unity simulators: 53 | 54 | ``` 55 | donkey sim --model=./models/your_model_name --type=linear 56 | ``` 57 | 58 | **P.S. Simulators just work well on Linux and MAC now, Windows version need fixing.** 59 | 60 | ## 9. Drive car with model for Auto Drive: 61 | 62 | ``` 63 | python manage.py drive --model=./models/your_model_name 64 | ``` 65 | 66 | ------ 67 | 68 | # Have fun! 69 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tub Manager 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 42 | 43 | {% block content %}{% end %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /donkeycar/parts/imu.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Mpu6050: 4 | """ 5 | Installation: 6 | sudo apt install python3-smbus 7 | or 8 | sudo apt-get install i2c-tools libi2c-dev python-dev python3-dev 9 | git clone https://github.com/pimoroni/py-smbus.git 10 | cd py-smbus/library 11 | python setup.py build 12 | sudo python setup.py install 13 | 14 | pip install mpu6050-raspberrypi 15 | """ 16 | 17 | def __init__(self, addr=0x68, poll_delay=0.0166): 18 | from mpu6050 import mpu6050 19 | self.sensor = mpu6050(addr) 20 | self.accel = { 'x' : 0., 'y' : 0., 'z' : 0. } 21 | self.gyro = { 'x' : 0., 'y' : 0., 'z' : 0. } 22 | self.temp = 0. 23 | self.poll_delay = poll_delay 24 | self.on = True 25 | 26 | def update(self): 27 | while self.on: 28 | self.poll() 29 | time.sleep(self.poll_delay) 30 | 31 | def poll(self): 32 | self.accel, self.gyro, self.temp = self.sensor.get_all_data() 33 | 34 | def run_threaded(self): 35 | return self.accel['x'], self.accel['y'], self.accel['z'], self.gyro['x'], self.gyro['y'], self.gyro['z'], self.temp 36 | 37 | def run(self): 38 | self.poll() 39 | return self.accel['x'], self.accel['y'], self.accel['z'], self.gyro['x'], self.gyro['y'], self.gyro['z'], self.temp 40 | 41 | def shutdown(self): 42 | self.on = False 43 | 44 | 45 | if __name__ == "__main__": 46 | iter = 0 47 | p = Mpu6050() 48 | while iter < 100: 49 | data = p.run() 50 | print(data) 51 | time.sleep(0.1) 52 | iter += 1 53 | 54 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/imu.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Mpu6050: 4 | """ 5 | Installation: 6 | sudo apt install python3-smbus 7 | or 8 | sudo apt-get install i2c-tools libi2c-dev python-dev python3-dev 9 | git clone https://github.com/pimoroni/py-smbus.git 10 | cd py-smbus/library 11 | python setup.py build 12 | sudo python setup.py install 13 | 14 | pip install mpu6050-raspberrypi 15 | """ 16 | 17 | def __init__(self, addr=0x68, poll_delay=0.0166): 18 | from mpu6050 import mpu6050 19 | self.sensor = mpu6050(addr) 20 | self.accel = { 'x' : 0., 'y' : 0., 'z' : 0. } 21 | self.gyro = { 'x' : 0., 'y' : 0., 'z' : 0. } 22 | self.temp = 0. 23 | self.poll_delay = poll_delay 24 | self.on = True 25 | 26 | def update(self): 27 | while self.on: 28 | self.poll() 29 | time.sleep(self.poll_delay) 30 | 31 | def poll(self): 32 | self.accel, self.gyro, self.temp = self.sensor.get_all_data() 33 | 34 | def run_threaded(self): 35 | return self.accel['x'], self.accel['y'], self.accel['z'], self.gyro['x'], self.gyro['y'], self.gyro['z'], self.temp 36 | 37 | def run(self): 38 | self.poll() 39 | return self.accel['x'], self.accel['y'], self.accel['z'], self.gyro['x'], self.gyro['y'], self.gyro['z'], self.temp 40 | 41 | def shutdown(self): 42 | self.on = False 43 | 44 | 45 | if __name__ == "__main__": 46 | iter = 0 47 | p = Mpu6050() 48 | while iter < 100: 49 | data = p.run() 50 | print(data) 51 | time.sleep(0.1) 52 | iter += 1 53 | 54 | -------------------------------------------------------------------------------- /install/envs/windows.yml: -------------------------------------------------------------------------------- 1 | name: donkey 2 | channels: 3 | - defaults 4 | dependencies: 5 | - bzip2=1.0.6=vc14_3 6 | - cycler=0.10.0=py36_0 7 | - decorator=4.1.2=py36_0 8 | - freetype=2.5.5=vc14_2 9 | - h5py=2.7.0=np113py36_0 10 | - hdf5=1.8.15.1=vc14_4 11 | - icu=57.1=vc14_0 12 | - jpeg=9b=vc14_0 13 | - libpng=1.6.27=vc14_0 14 | - libtiff=4.0.6=vc14_3 15 | - matplotlib=2.0.2=np113py36_0 16 | - mkl=2017.0.3=0 17 | - networkx=1.11=py36_0 18 | - numpy=1.13.1=py36_0 19 | - olefile=0.44=py36_0 20 | - openssl=1.0.2l=vc14_0 21 | - pandas=0.20.3=py36_0 22 | - pillow=4.2.1=py36_0 23 | - pip=9.0.1=py36_1 24 | - pyparsing=2.2.0=py36_0 25 | - pyqt=5.6.0=py36_2 26 | - python=3.6.2=0 27 | - python-dateutil=2.6.1=py36_0 28 | - pytz=2017.2=py36_0 29 | - pywavelets=0.5.2=np113py36_0 30 | - qt=5.6.2=vc14_6 31 | - scikit-image=0.13.0=np113py36_0 32 | - scipy=0.19.1=np113py36_0 33 | - setuptools=27.2.0=py36_1 34 | - sip=4.18=py36_0 35 | - six=1.10.0=py36_0 36 | - tk=8.5.18=vc14_0 37 | - vs2015_runtime=14.0.25420=0 38 | - wheel=0.29.0=py36_0 39 | - zlib=1.2.8=vc14_3 40 | - pip: 41 | - backports.weakref==1.0rc1 42 | - bleach==1.5.0 43 | - certifi==2017.7.27.1 44 | - chainer==2.0.2 45 | - chardet==3.0.4 46 | - docopt==0.6.2 47 | - envoy==0.0.3 48 | - filelock==2.0.11 49 | - html5lib==0.9999999 50 | - idna==2.5 51 | - imageio==2.1.2 52 | - markdown==2.6.8 53 | - mock==2.0.0 54 | - moviepy==0.2.3.2 55 | - nose==1.3.7 56 | - opencv-python==3.2.0.8 57 | - pbr==3.1.1 58 | - protobuf==3.3.0 59 | - pyyaml==3.12 60 | - requests==2.18.3 61 | - tensorflow==1.7.0 62 | - tensorflow-tensorboard==0.1.4 63 | - theano==0.9.0 64 | - tornado==4.5.1 65 | - tqdm==4.11.2 66 | - urllib3==1.22 67 | - werkzeug==0.12.2 68 | -------------------------------------------------------------------------------- /donkeycar/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging.config 3 | 4 | 5 | def setup(log_file_path=None): 6 | 7 | if log_file_path is None: 8 | log_file_path = os.path.expanduser('~/donkey.log') 9 | 10 | config_default = { 11 | "version": 1, 12 | "disable_existing_loggers": False, 13 | "formatters": { 14 | "simple": { 15 | "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 16 | } 17 | }, 18 | "handlers": { 19 | "console": { 20 | "class": "logging.StreamHandler", 21 | "level": "INFO", 22 | "formatter": "simple", 23 | "stream": "ext://sys.stdout" 24 | }, 25 | "error_file_handler": { 26 | "class": "logging.handlers.RotatingFileHandler", 27 | "level": "INFO", 28 | "formatter": "simple", 29 | "filename": log_file_path, 30 | "maxBytes": 10485760, 31 | "backupCount": 20, 32 | "encoding": "utf8" 33 | }, 34 | }, 35 | "root": { 36 | "level": "DEBUG", 37 | "handlers": ["console", "error_file_handler"] 38 | } 39 | } 40 | 41 | logging.config.dictConfig(config_default) 42 | 43 | 44 | def get_logger(name): 45 | """ 46 | Return a logger that will contextualize the logs with the name. 47 | """ 48 | logger = logging.getLogger(name) 49 | return logger 50 | 51 | 52 | # get a logger specific to this file 53 | logger = get_logger(__name__) 54 | logger.info('Logging configured and loaded.') 55 | 56 | 57 | if __name__ == '__main__': 58 | print('run') 59 | logger.error('test') 60 | 61 | -------------------------------------------------------------------------------- /donkeycar/templates/tk1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Script to drive a donkey car using a webserver hosted on the vehicle. 5 | 6 | """ 7 | from datetime import datetime 8 | import donkeycar as dk 9 | 10 | V = dk.vehicle.Vehicle() 11 | 12 | cam = dk.parts.Webcam() 13 | V.add(cam, outputs = [ 'cam/image_array' ], threaded = True) 14 | 15 | rcin_controller = dk.parts.TeensyRCin() 16 | V.add(rcin_controller, outputs = [ 'rcin/angle', 'rcin/throttle' ], threaded = True) 17 | 18 | speed_controller = dk.parts.AStarSpeed() 19 | V.add(speed_controller, outputs = [ 'odo/speed' ], threaded = True) 20 | 21 | ctr = dk.parts.LocalWebController() 22 | V.add(ctr, 23 | inputs = [ 'cam/image_array', 'rcin/angle', 'rcin/throttle' ], 24 | outputs = [ 'user/angle', 'user/throttle', 'user/mode', 'user/recording' ], 25 | threaded = True) 26 | 27 | steering_controller = dk.parts.Teensy('S') 28 | steering = dk.parts.PWMSteering(controller = steering_controller, 29 | left_pulse = 496, right_pulse = 242) 30 | 31 | throttle_controller = dk.parts.Teensy('T') 32 | throttle = dk.parts.PWMThrottle(controller = throttle_controller, 33 | max_pulse = 496, zero_pulse = 369, min_pulse = 242) 34 | 35 | V.add(steering, inputs = [ 'user/angle', 'user/mode' ]) 36 | V.add(throttle, inputs = [ 'user/throttle', 'user/mode' ]) 37 | 38 | #add tub to save data 39 | path = '~/mydonkey/sessions/' + datetime.now().strftime('%Y_%m_%d__%H_%M_%S') 40 | inputs = [ 'user/angle', 'user/throttle', 'cam/image_array', 'user/mode', 'odo/speed', 'user/recording' ] 41 | types = [ 'float', 'float', 'image_array', 'str', 'float', 'boolean' ] 42 | tub = dk.parts.OriginalWriter(path, inputs = inputs, types = types) 43 | V.add(tub, inputs = inputs) 44 | 45 | #run the vehicle for 20 seconds 46 | V.start(rate_hz = 20) # , max_loop_count = 1000) 47 | -------------------------------------------------------------------------------- /donkeycar/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Wed Sep 13 21:27:44 2017 5 | 6 | @author: wroscoe 7 | """ 8 | import os 9 | import types 10 | 11 | 12 | class Config: 13 | 14 | def __init__(self): 15 | pass 16 | 17 | def from_pyfile(self, filename, silent=False): 18 | """ 19 | Read config class from a file. 20 | """ 21 | d = types.ModuleType('config') 22 | d.__file__ = filename 23 | try: 24 | with open(filename, mode='rb') as config_file: 25 | exec(compile(config_file.read(), filename, 'exec'), d.__dict__) 26 | except IOError as e: 27 | e.strerror = 'Unable to load configuration file (%s)' % e.strerror 28 | raise 29 | self.from_object(d) 30 | return True 31 | 32 | def from_object(self, obj): 33 | """ 34 | Read config class from another object. 35 | """ 36 | for key in dir(obj): 37 | if key.isupper(): 38 | setattr(self, key, getattr(obj, key)) 39 | 40 | def __str__(self): 41 | """ 42 | Get a string representation of the config class. 43 | """ 44 | result = [] 45 | for key in dir(self): 46 | if key.isupper(): 47 | result.append((key, getattr(self,key))) 48 | return str(result) 49 | 50 | 51 | def load_config(config_path=None): 52 | """ 53 | Load the config from a file and return the config class. 54 | """ 55 | if config_path is None: 56 | import __main__ as main 57 | main_path = os.path.dirname(os.path.realpath(main.__file__)) 58 | config_path = os.path.join(main_path, 'config.py') 59 | 60 | print('loading config file: {}'.format(config_path)) 61 | cfg = Config() 62 | cfg.from_pyfile(config_path) 63 | print('config loaded') 64 | return cfg 65 | -------------------------------------------------------------------------------- /donkeycar/util/files.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities to manipulate files and directories. 3 | """ 4 | 5 | import glob 6 | import zipfile 7 | import os 8 | 9 | def most_recent_file(dir_path, ext=''): 10 | """ 11 | return the most recent file given a directory path and extension 12 | """ 13 | query = dir_path + '/*' + ext 14 | newest = min(glob.iglob(query), key=os.path.getctime) 15 | return newest 16 | 17 | 18 | def make_dir(path): 19 | real_path = os.path.expanduser(path) 20 | if not os.path.exists(real_path): 21 | os.makedirs(real_path) 22 | return real_path 23 | 24 | 25 | def zip_dir(dir_path, zip_path): 26 | """ 27 | Create and save a zipfile of a one level directory 28 | """ 29 | file_paths = glob.glob(dir_path + "/*") # create path to search for files. 30 | 31 | zf = zipfile.ZipFile(zip_path, 'w') 32 | dir_name = os.path.basename(dir_path) 33 | for p in file_paths: 34 | file_name = os.path.basename(p) 35 | zf.write(p, arcname=os.path.join(dir_name, file_name)) 36 | zf.close() 37 | return zip_path 38 | 39 | 40 | def time_since_last_file_edited(path): 41 | """return seconds since last file was updated""" 42 | list_of_files = glob.glob(os.path.join(path, '*')) 43 | if len(list_of_files) > 0: 44 | latest_file = max(list_of_files, key=os.path.getctime) 45 | return int(time.time() - os.path.getctime(latest_file)) 46 | return 0 47 | 48 | 49 | 50 | def expand_path_mask(path): 51 | matches = [] 52 | path = os.path.expanduser(path) 53 | for file in glob.glob(path): 54 | if os.path.isdir(file): 55 | matches.append(os.path.join(os.path.abspath(file))) 56 | return matches 57 | 58 | 59 | def expand_path_arg(path_str): 60 | path_list = path_str.split(",") 61 | expanded_paths = [] 62 | for path in path_list: 63 | paths = expand_path_mask(path) 64 | expanded_paths += paths 65 | return expanded_paths 66 | -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Donkey Monitor 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 50 | 51 | {% block content %}{% end %} 52 | 53 | 54 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/static/style.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 1600px) { 2 | .container { 3 | width: 1570px; 4 | } 5 | } 6 | 7 | .train-img { 8 | width: 320px; 9 | height: 240px; 10 | } 11 | 12 | #cur-frame { 13 | margin-bottom: 20px; 14 | padding: 5px; 15 | } 16 | 17 | img.clip-thumbnail { 18 | width: 6.25%; 19 | } 20 | 21 | tr.active td:first-child, 22 | tr.active td:last-child { 23 | background-color: #5bc0de !important; 24 | } 25 | 26 | tbody#clips td { 27 | vertical-align: middle; 28 | } 29 | .affix { 30 | top: 0; 31 | width: 100%; 32 | } 33 | 34 | /* https://stackoverflow.com/questions/20652581/bootstrap-affix-nav-causes-div-below-to-jump-up */ 35 | .affix-wrapper { 36 | min-height: 272px; 37 | } 38 | 39 | .affix + #clips-container { 40 | padding-top: 272; 41 | } 42 | 43 | .preview { 44 | width: 100%; 45 | } 46 | 47 | .preview-centered { 48 | width: 460px; 49 | margin: 0 auto; 50 | padding-bottom: 12px; 51 | } 52 | 53 | .preview-wrapper { 54 | display: flex; 55 | } 56 | 57 | .preview-img { 58 | flex: 0 0 65%; 59 | } 60 | 61 | .preview-toolbar { 62 | flex: 1; 63 | background-color: white; 64 | } 65 | 66 | .preview-toolbar button { 67 | margin: 5px; 68 | width: 140px; 69 | } 70 | 71 | .preview-toolbar select { 72 | margin: 5px; 73 | width: 140px; 74 | } 75 | 76 | tbody#clips .progress { 77 | height: 3px; 78 | margin-bottom: 10px; 79 | } 80 | 81 | .progress .progress-bar { 82 | -webkit-transition: none; 83 | -moz-transition: none; 84 | -ms-transition: none; 85 | -o-transition: none; 86 | transition: none; 87 | } 88 | 89 | .steering-bar .progress { 90 | width: 48%; 91 | margin-bottom: 0px; 92 | border-radius: 0px; 93 | float: left; 94 | } 95 | 96 | .steering-bar .progress.negative { 97 | border-right-style: solid; 98 | border-right-width: thin; 99 | border-right-color: red; 100 | } 101 | 102 | .steering-bar .progress.positive { 103 | border-left-style: solid; 104 | border-left-width: thin; 105 | border-left-color: red; 106 | } 107 | -------------------------------------------------------------------------------- /install/pi/install.sh: -------------------------------------------------------------------------------- 1 | # Script to install everything needed for donkeycar except the donkeycar library 2 | 3 | 4 | # Get witch Pi version 5 | echo "Enter the Pi number (3 or 0)" 6 | read pi_num 7 | if [ $pi_num == 3 ]; then 8 | echo "installing for Pi 3." 9 | tf_file=tensorflow-1.8.0-cp35-none-linux_armv7l.whl 10 | elif [ $pi_num == 0 ]; then 11 | echo "installing for Pi Zero." 12 | tf_file=tensorflow-1.8.0-cp35-none-linux_armv6l.whl 13 | else 14 | echo "Only Pi 3 and Pi Zero are supported." 15 | exit 1 16 | fi 17 | 18 | 19 | #standard updates (5 min) 20 | sudo apt update -y 21 | sudo apt upgrade -y 22 | sudo rpi-update -y 23 | 24 | #helpful libraries (2 min) 25 | sudo apt install build-essential python3-dev python3-distlib python3-setuptools python3-pip python3-wheel -y 26 | 27 | sudo apt-get install git cmake pkg-config -y 28 | sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev -y 29 | sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev -y 30 | sudo apt-get install libxvidcore-dev libx264-dev -y 31 | sudo apt-get install libatlas-base-dev gfortran -y 32 | 33 | sudo apt install libzmq-dev -y 34 | sudo apt install xsel xclip -y 35 | sudo apt install python3-h5py -y 36 | 37 | #install numpy and pandas (3 min) 38 | sudo apt install libxml2-dev python3-lxml -y 39 | sudo apt install libxslt-dev -y 40 | 41 | #remove python2 (1 min) 42 | sudo apt-get remove python2.7 -y 43 | sudo apt-get autoremove -y 44 | 45 | #install redis-server (1 min) 46 | sudo apt install redis-server -y 47 | 48 | sudo bash make_virtual_env.sh 49 | 50 | 51 | #create a python virtualenv (2 min) 52 | sudo apt install virtualenv -y 53 | virtualenv ~/env --system-site-packages --python python3 54 | echo '#start env' >> ~/.bashrc 55 | echo 'source ~/env/bin/activate' >> ~/.bashrc 56 | source ~/env/bin/activate 57 | 58 | 59 | #make sure the virtual environment is active 60 | source ~/env/bin/activate 61 | 62 | # install pandas and numpy 63 | pip install pandas #also installs numpy 64 | 65 | 66 | #install tensorflow (5 min) 67 | echo "Installing Tensorflow" 68 | wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v1.8.0/${tf_file} 69 | pip install ${tf_file} 70 | rm ${tf_file} -------------------------------------------------------------------------------- /donkeycar/tests/test_memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | import pytest 5 | from donkeycar.memory import Memory 6 | 7 | class TestMemory(unittest.TestCase): 8 | 9 | def test_setitem_single_item(self): 10 | mem = Memory() 11 | mem['myitem'] = 999 12 | assert mem['myitem'] == 999 13 | 14 | def test_setitem_multi_items(self): 15 | mem = Memory() 16 | mem[('myitem1', 'myitem2')] = [888, '999'] 17 | assert mem[('myitem1', 'myitem2')] == [888, '999'] 18 | 19 | def test_put_single_item(self): 20 | mem = Memory() 21 | mem.put(['myitem'], 999) 22 | assert mem['myitem'] == 999 23 | 24 | def test_put_single_item_as_tuple(self): 25 | mem = Memory() 26 | mem.put(('myitem',), 999) 27 | assert mem['myitem'] == 999 28 | 29 | def test_put_multi_item(self): 30 | mem = Memory() 31 | mem.put(['my1stitem','my2nditem'], [777, '999']) 32 | assert mem['my1stitem'] == 777 33 | assert mem['my2nditem'] == '999' 34 | 35 | def test_put_multi_item_as_tuple(self): 36 | mem = Memory() 37 | mem.put(('my1stitem','my2nditem'), (777, '999')) 38 | assert mem['my1stitem'] == 777 39 | assert mem['my2nditem'] == '999' 40 | 41 | def test_get_multi_item(self): 42 | mem = Memory() 43 | mem.put(['my1stitem','my2nditem'], [777, '999']) 44 | assert mem.get(['my1stitem','my2nditem']) == [777, '999'] 45 | 46 | def test_update_item(self): 47 | mem = Memory() 48 | mem.put(['myitem'], 888) 49 | assert mem['myitem'] == 888 50 | 51 | mem.update({'myitem': '111'}) 52 | assert mem['myitem'] == '111' 53 | 54 | def test_get_keys(self): 55 | mem = Memory() 56 | mem.put(['myitem'], 888) 57 | assert list(mem.keys()) == ['myitem'] 58 | 59 | def test_get_values(self): 60 | mem = Memory() 61 | mem.put(['myitem'], 888) 62 | assert list(mem.values()) == [888] 63 | 64 | def test_get_iter(self): 65 | mem = Memory() 66 | mem.put(['myitem'], 888) 67 | 68 | assert dict(mem.items()) == {'myitem': 888} 69 | 70 | -------------------------------------------------------------------------------- /donkeycar/util/img.py: -------------------------------------------------------------------------------- 1 | 2 | import random 3 | import io 4 | import os 5 | 6 | from PIL import Image 7 | import numpy as np 8 | # TODO: put this in its own image_utils file. 9 | 10 | 11 | """ 12 | IMAGES 13 | """ 14 | 15 | def scale(im, size=128): 16 | """ 17 | accepts: PIL image, size of square sides 18 | returns: PIL image scaled so sides length = size 19 | """ 20 | size = (size,size) 21 | im.thumbnail(size, Image.ANTIALIAS) 22 | return im 23 | 24 | 25 | def img_to_binary(img): 26 | """ 27 | accepts: PIL image 28 | returns: binary stream (used to save to database) 29 | """ 30 | f = io.BytesIO() 31 | img.save(f, format='jpeg') 32 | return f.getvalue() 33 | 34 | 35 | def arr_to_binary(arr): 36 | """ 37 | accepts: numpy array with shape (Hight, Width, Channels) 38 | returns: binary stream (used to save to database) 39 | """ 40 | img = arr_to_img(arr) 41 | return img_to_binary(img) 42 | 43 | 44 | def arr_to_img(arr): 45 | """ 46 | accepts: numpy array with shape (Hight, Width, Channels) 47 | returns: binary stream (used to save to database) 48 | """ 49 | arr = np.uint8(arr) 50 | img = Image.fromarray(arr) 51 | return img 52 | 53 | def img_to_arr(img): 54 | """ 55 | accepts: numpy array with shape (Hight, Width, Channels) 56 | returns: binary stream (used to save to database) 57 | """ 58 | return np.array(img) 59 | 60 | 61 | def binary_to_img(binary): 62 | """ 63 | accepts: binary file object from BytesIO 64 | returns: PIL image 65 | """ 66 | img = io.BytesIO(binary) 67 | return Image.open(img) 68 | 69 | 70 | def norm_img(img): 71 | return (img - img.mean() / np.std(img))/255.0 72 | 73 | 74 | def create_video(img_dir_path, output_video_path): 75 | import envoy 76 | # Setup path to the images with telemetry. 77 | full_path = os.path.join(img_dir_path, 'frame_*.png') 78 | 79 | # Run ffmpeg. 80 | command = ("""ffmpeg 81 | -framerate 30/1 82 | -pattern_type glob -i '%s' 83 | -c:v libx264 84 | -r 15 85 | -pix_fmt yuv420p 86 | -y 87 | %s""" % (full_path, output_video_path)) 88 | response = envoy.run(command) -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/static/style.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | #joystick-column { 3 | /* This causes joystick to be offscreen on iPhone 5s, 6, iPad 2 4 | position: fixed; 5 | bottom:100; 6 | right:0;*/ 7 | width:100%; 8 | height:40%; 9 | 10 | } 11 | 12 | #joystick-padding { 13 | height:220px; 14 | } 15 | } 16 | 17 | #controls-column { 18 | margin-top:10px; 19 | } 20 | #control-bars { 21 | margin-top:10px; 22 | } 23 | .group-label { 24 | width:100%; 25 | } 26 | 27 | /* this setting is too large on iPhone5s */ 28 | #control-bars .progress { 29 | width: 40%; 30 | } 31 | 32 | #control-bars .glyphicon { 33 | margin-right: 3px; 34 | } 35 | 36 | #control-bars .progress.negative { 37 | float: left; 38 | border-top-right-radius: 0px; 39 | border-bottom-right-radius: 0px; 40 | } 41 | 42 | #control-bars .progress.positive { 43 | border-top-left-radius: 0px; 44 | border-bottom-left-radius: 0px; 45 | } 46 | 47 | #vehicle_controls input { 48 | width: 100%; 49 | } 50 | 51 | #mpeg-image { 52 | width:100%; 53 | } 54 | 55 | #joystick_container { 56 | text-align: center; 57 | height: 335px; 58 | padding-top: 155px; 59 | border-color: #bce8f1; 60 | background-color: #d9edf7; 61 | } 62 | 63 | #vehicle_footer { 64 | height:auto; 65 | } 66 | 67 | #brake_button { 68 | margin-top:5px; 69 | margin-bottom: 5px; 70 | } 71 | 72 | div.session-thumbnails .ui-selecting { 73 | background-color: #fcf8e3; 74 | border-color: #faebcc; 75 | } 76 | 77 | div.session-thumbnails .ui-selected { 78 | background-color: #f2dede; 79 | border-color: #ebccd1; 80 | } 81 | 82 | div.session-thumbnails img { 83 | width: 160px; 84 | height: auto; 85 | } 86 | 87 | div.session-thumbnails .desc { 88 | margin-bottom:0px; 89 | } 90 | 91 | div.session-thumbnails li.thumbnail { 92 | width:170px; 93 | float: left; 94 | margin-right:5px; 95 | } 96 | 97 | div.session-thumbnails .caption { 98 | padding:5px 5px 0px 5px; 99 | } 100 | 101 | div.session-thumbnails label { 102 | padding-left: 0px; 103 | } 104 | 105 | div.img-controls { 106 | margin: 20px 0; 107 | } 108 | 109 | body { 110 | /* Margin bottom by footer height */ 111 | margin-bottom: 60px; 112 | } 113 | .footer { 114 | position: fixed; 115 | bottom: 0; 116 | width: 100%; 117 | /* Set the fixed height of the footer here */ 118 | height: 60px; 119 | background-color: #f5f5f5; 120 | } 121 | -------------------------------------------------------------------------------- /donkeycar/parts/transform.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time 4 | 5 | class Lambda: 6 | """ 7 | Wraps a function into a donkey part. 8 | """ 9 | def __init__(self, f): 10 | """ 11 | Accepts the function to use. 12 | """ 13 | self.f = f 14 | 15 | def run(self, *args, **kwargs): 16 | return self.f(*args, **kwargs) 17 | 18 | def shutdown(self): 19 | return 20 | 21 | 22 | 23 | 24 | class PIDController: 25 | """ Performs a PID computation and returns a control value. 26 | This is based on the elapsed time (dt) and the current value of the process variable 27 | (i.e. the thing we're measuring and trying to change). 28 | https://github.com/chrisspen/pid_controller/blob/master/pid_controller/pid.py 29 | """ 30 | 31 | def __init__(self, p=0, i=0, d=0, debug=False): 32 | 33 | # initialize gains 34 | self.Kp = p 35 | self.Ki = i 36 | self.Kd = d 37 | 38 | # The value the controller is trying to get the system to achieve. 39 | self.target = 0 40 | 41 | # initialize delta t variables 42 | self.prev_tm = time.time() 43 | self.prev_feedback = 0 44 | self.error = None 45 | 46 | # initialize the output 47 | self.alpha = 0 48 | 49 | # debug flag (set to True for console output) 50 | self.debug = debug 51 | 52 | def run(self, target_value, feedback): 53 | curr_tm = time.time() 54 | 55 | self.target = target_value 56 | error = self.error = self.target - feedback 57 | 58 | # Calculate time differential. 59 | dt = curr_tm - self.prev_tm 60 | 61 | # Initialize output variable. 62 | curr_alpha = 0 63 | 64 | # Add proportional component. 65 | curr_alpha += self.Kp * error 66 | 67 | # Add integral component. 68 | curr_alpha += self.Ki * (error * dt) 69 | 70 | # Add differential component (avoiding divide-by-zero). 71 | if dt > 0: 72 | curr_alpha += self.Kd * ((feedback - self.prev_feedback) / float(dt)) 73 | 74 | # Maintain memory for next loop. 75 | self.prev_tm = curr_tm 76 | self.prev_feedback = feedback 77 | 78 | # Update the output 79 | self.alpha = curr_alpha 80 | 81 | if (self.debug): 82 | print('PID target value:', round(target_value, 4)) 83 | print('PID feedback value:', round(feedback, 4)) 84 | print('PID output:', round(curr_alpha, 4)) 85 | 86 | return curr_alpha 87 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/transform.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time 4 | 5 | class Lambda: 6 | """ 7 | Wraps a function into a donkey part. 8 | """ 9 | def __init__(self, f): 10 | """ 11 | Accepts the function to use. 12 | """ 13 | self.f = f 14 | 15 | def run(self, *args, **kwargs): 16 | return self.f(*args, **kwargs) 17 | 18 | def shutdown(self): 19 | return 20 | 21 | 22 | 23 | 24 | class PIDController: 25 | """ Performs a PID computation and returns a control value. 26 | This is based on the elapsed time (dt) and the current value of the process variable 27 | (i.e. the thing we're measuring and trying to change). 28 | https://github.com/chrisspen/pid_controller/blob/master/pid_controller/pid.py 29 | """ 30 | 31 | def __init__(self, p=0, i=0, d=0, debug=False): 32 | 33 | # initialize gains 34 | self.Kp = p 35 | self.Ki = i 36 | self.Kd = d 37 | 38 | # The value the controller is trying to get the system to achieve. 39 | self.target = 0 40 | 41 | # initialize delta t variables 42 | self.prev_tm = time.time() 43 | self.prev_feedback = 0 44 | self.error = None 45 | 46 | # initialize the output 47 | self.alpha = 0 48 | 49 | # debug flag (set to True for console output) 50 | self.debug = debug 51 | 52 | def run(self, target_value, feedback): 53 | curr_tm = time.time() 54 | 55 | self.target = target_value 56 | error = self.error = self.target - feedback 57 | 58 | # Calculate time differential. 59 | dt = curr_tm - self.prev_tm 60 | 61 | # Initialize output variable. 62 | curr_alpha = 0 63 | 64 | # Add proportional component. 65 | curr_alpha += self.Kp * error 66 | 67 | # Add integral component. 68 | curr_alpha += self.Ki * (error * dt) 69 | 70 | # Add differential component (avoiding divide-by-zero). 71 | if dt > 0: 72 | curr_alpha += self.Kd * ((feedback - self.prev_feedback) / float(dt)) 73 | 74 | # Maintain memory for next loop. 75 | self.prev_tm = curr_tm 76 | self.prev_feedback = feedback 77 | 78 | # Update the output 79 | self.alpha = curr_alpha 80 | 81 | if (self.debug): 82 | print('PID target value:', round(target_value, 4)) 83 | print('PID feedback value:', round(feedback, 4)) 84 | print('PID output:', round(curr_alpha, 4)) 85 | 86 | return curr_alpha 87 | -------------------------------------------------------------------------------- /donkeycar/util/data.py: -------------------------------------------------------------------------------- 1 | """ 2 | Assorted functions for manipulating data. 3 | """ 4 | import numpy as np 5 | import itertools 6 | 7 | 8 | def linear_bin(a): 9 | """ 10 | Convert a value to a categorical array. 11 | 12 | Parameters 13 | ---------- 14 | a : int or float 15 | A value between -1 and 1 16 | 17 | Returns 18 | ------- 19 | list of int 20 | A list of length 15 with one item set to 1, which represents the linear value, and all other items set to 0. 21 | """ 22 | a = a + 1 23 | b = round(a / (2 / 14)) 24 | arr = np.zeros(15) 25 | arr[int(b)] = 1 26 | return arr 27 | 28 | 29 | def linear_unbin(arr): 30 | """ 31 | Convert a categorical array to value. 32 | 33 | See Also 34 | -------- 35 | linear_bin 36 | """ 37 | if not len(arr) == 15: 38 | raise ValueError('Illegal array length, must be 15') 39 | b = np.argmax(arr) 40 | a = b * (2 / 14) - 1 41 | return a 42 | 43 | 44 | def bin_Y(Y): 45 | """ 46 | Convert a list of values to a list of categorical arrays. 47 | 48 | Parameters 49 | ---------- 50 | Y : iterable of int 51 | Iterable with values between -1 and 1 52 | 53 | Returns 54 | ------- 55 | A two dimensional array of int 56 | 57 | See Also 58 | -------- 59 | linear_bin 60 | """ 61 | d = [ linear_bin(y) for y in Y ] 62 | return np.array(d) 63 | 64 | 65 | def unbin_Y(Y): 66 | """ 67 | Convert a list of categorical arrays to a list of values. 68 | 69 | See Also 70 | -------- 71 | linear_bin 72 | """ 73 | d = [ linear_unbin(y) for y in Y ] 74 | return np.array(d) 75 | 76 | 77 | def map_range(x, X_min, X_max, Y_min, Y_max): 78 | """ 79 | Linear mapping between two ranges of values 80 | """ 81 | X_range = X_max - X_min 82 | Y_range = Y_max - Y_min 83 | XY_ratio = X_range / Y_range 84 | 85 | y = ((x - X_min) / XY_ratio + Y_min) // 1 86 | 87 | return int(y) 88 | 89 | 90 | def merge_two_dicts(x, y): 91 | """ 92 | Given two dicts, merge them into a new dict as a shallow copy 93 | """ 94 | z = x.copy() 95 | z.update(y) 96 | return z 97 | 98 | 99 | def param_gen(params): 100 | """ 101 | Accepts a dictionary of parameter options and returns 102 | a list of dictionary with the permutations of the parameters. 103 | """ 104 | for p in itertools.product(*params.values()): 105 | yield dict(zip(params.keys(), p )) 106 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/tub.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% block content %} 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |

0

25 | 31 | 34 | 37 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | 58 |
59 |
60 |
61 | 62 | 63 | {% end %} 64 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import os 4 | 5 | 6 | with open("README.md", "r") as fh: 7 | long_description = fh.read() 8 | 9 | 10 | setup(name='donkeycar', 11 | version='2.5.1', 12 | description='Self driving library for python.', 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url='https://github.com/wroscoe/donkey', 16 | download_url='https://github.com/wroscoe/donkey/archive/2.1.5.tar.gz', 17 | author='Will Roscoe', 18 | author_email='wroscoe@gmail.com', 19 | license='MIT', 20 | entry_points={ 21 | 'console_scripts': [ 22 | 'donkey=donkeycar.management.base:execute_from_command_line', 23 | ], 24 | }, 25 | install_requires=['numpy', 26 | 'pillow', 27 | 'docopt', 28 | 'tornado==4.5.3', 29 | 'requests', 30 | 'h5py', 31 | 'python-socketio', 32 | 'flask', 33 | 'eventlet', 34 | 'moviepy', 35 | 'pandas', 36 | ], 37 | 38 | extras_require={ 39 | 'tf': ['tensorflow>=1.7.0'], 40 | 'tf_gpu': ['tensorflow-gpu>=1.7.0'], 41 | 'pi': [ 42 | 'picamera', 43 | 'Adafruit_PCA9685', 44 | ], 45 | 'dev': [ 46 | 'pytest', 47 | 'pytest-cov', 48 | 'responses' 49 | ], 50 | 'ci': ['codecov'] 51 | }, 52 | 53 | include_package_data=True, 54 | 55 | classifiers=[ 56 | # How mature is this project? Common values are 57 | # 3 - Alpha 58 | # 4 - Beta 59 | # 5 - Production/Stable 60 | 'Development Status :: 3 - Alpha', 61 | 62 | # Indicate who your project is intended for 63 | 'Intended Audience :: Developers', 64 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 65 | 66 | # Pick your license as you wish (should match "license" above) 67 | 'License :: OSI Approved :: MIT License', 68 | 69 | # Specify the Python versions you support here. In particular, ensure 70 | # that you indicate whether you support Python 2, Python 3 or both. 71 | 72 | 'Programming Language :: Python :: 3.4', 73 | 'Programming Language :: Python :: 3.5', 74 | 'Programming Language :: Python :: 3.6', 75 | ], 76 | keywords='selfdriving cars donkeycar diyrobocars', 77 | 78 | packages=find_packages(exclude=(['tests', 'docs', 'site', 'env'])), 79 | ) 80 | -------------------------------------------------------------------------------- /donkeycar/parts/teensy.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import donkeycar as dk 3 | import re 4 | import time 5 | 6 | class TeensyRCin: 7 | def __init__(self): 8 | self.inSteering = 0.0 9 | self.inThrottle = 0.0 10 | 11 | self.sensor = dk.parts.Teensy(0); 12 | 13 | TeensyRCin.LEFT_ANGLE = -1.0 14 | TeensyRCin.RIGHT_ANGLE = 1.0 15 | TeensyRCin.MIN_THROTTLE = -1.0 16 | TeensyRCin.MAX_THROTTLE = 1.0 17 | 18 | TeensyRCin.LEFT_PULSE = 496.0 19 | TeensyRCin.RIGHT_PULSE = 242.0 20 | TeensyRCin.MAX_PULSE = 496.0 21 | TeensyRCin.MIN_PULSE = 242.0 22 | 23 | 24 | self.on = True 25 | 26 | def map_range(self, x, X_min, X_max, Y_min, Y_max): 27 | """ 28 | Linear mapping between two ranges of values 29 | """ 30 | X_range = X_max - X_min 31 | Y_range = Y_max - Y_min 32 | XY_ratio = X_range/Y_range 33 | 34 | return ((x-X_min) / XY_ratio + Y_min) 35 | 36 | def update(self): 37 | rcin_pattern = re.compile('^I +([.0-9]+) +([.0-9]+).*$') 38 | 39 | while self.on: 40 | start = datetime.now() 41 | 42 | l = self.sensor.teensy_readline() 43 | 44 | while l: 45 | # print("mw TeensyRCin line= " + l.decode('utf-8')) 46 | m = rcin_pattern.match(l.decode('utf-8')) 47 | 48 | if m: 49 | i = float(m.group(1)) 50 | if i == 0.0: 51 | self.inSteering = 0.0 52 | else: 53 | i = i / (1000.0 * 1000.0) # in seconds 54 | i *= self.sensor.frequency * 4096.0 55 | self.inSteering = self.map_range(i, 56 | TeensyRCin.LEFT_PULSE, TeensyRCin.RIGHT_PULSE, 57 | TeensyRCin.LEFT_ANGLE, TeensyRCin.RIGHT_ANGLE) 58 | 59 | k = float(m.group(2)) 60 | if k == 0.0: 61 | self.inThrottle = 0.0 62 | else: 63 | k = k / (1000.0 * 1000.0) # in seconds 64 | k *= self.sensor.frequency * 4096.0 65 | self.inThrottle = self.map_range(k, 66 | TeensyRCin.MIN_PULSE, TeensyRCin.MAX_PULSE, 67 | TeensyRCin.MIN_THROTTLE, TeensyRCin.MAX_THROTTLE) 68 | 69 | # print("matched %.1f %.1f %.1f %.1f" % (i, self.inSteering, k, self.inThrottle)) 70 | l = self.sensor.teensy_readline() 71 | 72 | stop = datetime.now() 73 | s = 0.01 - (stop - start).total_seconds() 74 | if s > 0: 75 | time.sleep(s) 76 | 77 | def run_threaded(self): 78 | return self.inSteering, self.inThrottle 79 | 80 | def shutdown(self): 81 | # indicate that the thread should be stopped 82 | self.on = False 83 | print('stopping TeensyRCin') 84 | time.sleep(.5) 85 | 86 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/teensy.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import donkeycar as dk 3 | import re 4 | import time 5 | 6 | class TeensyRCin: 7 | def __init__(self): 8 | self.inSteering = 0.0 9 | self.inThrottle = 0.0 10 | 11 | self.sensor = dk.parts.Teensy(0); 12 | 13 | TeensyRCin.LEFT_ANGLE = -1.0 14 | TeensyRCin.RIGHT_ANGLE = 1.0 15 | TeensyRCin.MIN_THROTTLE = -1.0 16 | TeensyRCin.MAX_THROTTLE = 1.0 17 | 18 | TeensyRCin.LEFT_PULSE = 496.0 19 | TeensyRCin.RIGHT_PULSE = 242.0 20 | TeensyRCin.MAX_PULSE = 496.0 21 | TeensyRCin.MIN_PULSE = 242.0 22 | 23 | 24 | self.on = True 25 | 26 | def map_range(self, x, X_min, X_max, Y_min, Y_max): 27 | """ 28 | Linear mapping between two ranges of values 29 | """ 30 | X_range = X_max - X_min 31 | Y_range = Y_max - Y_min 32 | XY_ratio = X_range/Y_range 33 | 34 | return ((x-X_min) / XY_ratio + Y_min) 35 | 36 | def update(self): 37 | rcin_pattern = re.compile('^I +([.0-9]+) +([.0-9]+).*$') 38 | 39 | while self.on: 40 | start = datetime.now() 41 | 42 | l = self.sensor.teensy_readline() 43 | 44 | while l: 45 | # print("mw TeensyRCin line= " + l.decode('utf-8')) 46 | m = rcin_pattern.match(l.decode('utf-8')) 47 | 48 | if m: 49 | i = float(m.group(1)) 50 | if i == 0.0: 51 | self.inSteering = 0.0 52 | else: 53 | i = i / (1000.0 * 1000.0) # in seconds 54 | i *= self.sensor.frequency * 4096.0 55 | self.inSteering = self.map_range(i, 56 | TeensyRCin.LEFT_PULSE, TeensyRCin.RIGHT_PULSE, 57 | TeensyRCin.LEFT_ANGLE, TeensyRCin.RIGHT_ANGLE) 58 | 59 | k = float(m.group(2)) 60 | if k == 0.0: 61 | self.inThrottle = 0.0 62 | else: 63 | k = k / (1000.0 * 1000.0) # in seconds 64 | k *= self.sensor.frequency * 4096.0 65 | self.inThrottle = self.map_range(k, 66 | TeensyRCin.MIN_PULSE, TeensyRCin.MAX_PULSE, 67 | TeensyRCin.MIN_THROTTLE, TeensyRCin.MAX_THROTTLE) 68 | 69 | # print("matched %.1f %.1f %.1f %.1f" % (i, self.inSteering, k, self.inThrottle)) 70 | l = self.sensor.teensy_readline() 71 | 72 | stop = datetime.now() 73 | s = 0.01 - (stop - start).total_seconds() 74 | if s > 0: 75 | time.sleep(s) 76 | 77 | def run_threaded(self): 78 | return self.inSteering, self.inThrottle 79 | 80 | def shutdown(self): 81 | # indicate that the thread should be stopped 82 | self.on = False 83 | print('stopping TeensyRCin') 84 | time.sleep(.5) 85 | 86 | -------------------------------------------------------------------------------- /donkeycar/management/tub.py: -------------------------------------------------------------------------------- 1 | """ 2 | tub.py 3 | 4 | Manage tubs 5 | """ 6 | 7 | import os, sys, time 8 | import json 9 | import tornado.web 10 | from stat import S_ISREG, ST_MTIME, ST_MODE, ST_CTIME, ST_ATIME 11 | 12 | 13 | class TubManager: 14 | 15 | def run(self, args): 16 | WebServer(args[0]).start() 17 | 18 | 19 | class WebServer(tornado.web.Application): 20 | 21 | def __init__(self, data_path): 22 | if not os.path.exists(data_path): 23 | raise ValueError('The path {} does not exist.'.format(data_path)) 24 | 25 | this_dir = os.path.dirname(os.path.realpath(__file__)) 26 | static_file_path = os.path.join(this_dir, 'tub_web', 'static') 27 | 28 | 29 | 30 | handlers = [ 31 | (r"/", tornado.web.RedirectHandler, dict(url="/tubs")), 32 | (r"/tubs", TubsView, dict(data_path=data_path)), 33 | (r"/tubs/?(?P[^/]+)?", TubView), 34 | (r"/api/tubs/?(?P[^/]+)?", TubApi, dict(data_path=data_path)), 35 | (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": static_file_path}), 36 | (r"/tub_data/(.*)", tornado.web.StaticFileHandler, {"path": data_path}), 37 | ] 38 | 39 | settings = {'debug': True} 40 | 41 | super().__init__(handlers, **settings) 42 | 43 | def start(self, port=8886): 44 | self.port = int(port) 45 | self.listen(self.port) 46 | print('Listening on {}...'.format(port)) 47 | tornado.ioloop.IOLoop.instance().start() 48 | 49 | 50 | class TubsView(tornado.web.RequestHandler): 51 | 52 | def initialize(self, data_path): 53 | self.data_path = data_path 54 | 55 | def get(self): 56 | import fnmatch 57 | dir_list = fnmatch.filter(os.listdir(self.data_path), '*') 58 | dir_list.sort() 59 | data = {"tubs": dir_list} 60 | self.render("tub_web/tubs.html", **data) 61 | 62 | 63 | class TubView(tornado.web.RequestHandler): 64 | 65 | def get(self, tub_id): 66 | data = {} 67 | self.render("tub_web/tub.html", **data) 68 | 69 | 70 | class TubApi(tornado.web.RequestHandler): 71 | 72 | def initialize(self, data_path): 73 | self.data_path = data_path 74 | 75 | def image_path(self, tub_path, frame_id): 76 | return os.path.join(tub_path, str(frame_id) + "_cam-image_array_.jpg") 77 | 78 | def record_path(self, tub_path, frame_id): 79 | return os.path.join(tub_path, "record_" + frame_id + ".json") 80 | 81 | def clips_of_tub(self, tub_path): 82 | seqs = [ int(f.split("_")[0]) for f in os.listdir(tub_path) if f.endswith('.jpg') ] 83 | seqs.sort() 84 | 85 | entries = ((os.stat(self.image_path(tub_path, seq))[ST_ATIME], seq) for seq in seqs) 86 | 87 | (last_ts, seq) = next(entries) 88 | clips = [[seq]] 89 | for next_ts, next_seq in entries: 90 | if next_ts - last_ts > 100: #greater than 1s apart 91 | clips.append([next_seq]) 92 | else: 93 | clips[-1].append(next_seq) 94 | last_ts = next_ts 95 | 96 | return clips 97 | 98 | def get(self, tub_id): 99 | clips = self.clips_of_tub(os.path.join(self.data_path, tub_id)) 100 | 101 | self.set_header("Content-Type", "application/json; charset=UTF-8") 102 | self.write(json.dumps({'clips': clips})) 103 | 104 | def post(self, tub_id): 105 | tub_path = os.path.join(self.data_path, tub_id) 106 | old_clips = self.clips_of_tub(tub_path) 107 | new_clips = tornado.escape.json_decode(self.request.body) 108 | 109 | import itertools 110 | old_frames = list(itertools.chain(*old_clips)) 111 | new_frames = list(itertools.chain(*new_clips['clips'])) 112 | frames_to_delete = [str(item) for item in old_frames if item not in new_frames] 113 | for frm in frames_to_delete: 114 | os.remove(self.record_path(tub_path, frm)) 115 | os.remove(self.image_path(tub_path, frm)) 116 | -------------------------------------------------------------------------------- /donkeycar/parts/encoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rotary Encoder 3 | """ 4 | 5 | from datetime import datetime 6 | from donkeycar.parts.teensy import TeensyRCin 7 | import re 8 | import time 9 | 10 | class AStarSpeed: 11 | def __init__(self): 12 | self.speed = 0 13 | self.linaccel = None 14 | 15 | self.sensor = TeensyRCin(0); 16 | 17 | self.on = True 18 | 19 | def update(self): 20 | encoder_pattern = re.compile('^E ([-0-9]+)( ([-0-9]+))?( ([-0-9]+))?$') 21 | linaccel_pattern = re.compile('^L ([-.0-9]+) ([-.0-9]+) ([-.0-9]+) ([-0-9]+)$') 22 | 23 | while self.on: 24 | start = datetime.now() 25 | 26 | l = self.sensor.astar_readline() 27 | while l: 28 | m = encoder_pattern.match(l.decode('utf-8')) 29 | 30 | if m: 31 | value = int(m.group(1)) 32 | # rospy.loginfo("%s: Receiver E got %d" % (self.node_name, value)) 33 | # Speed 34 | # 40 ticks/wheel rotation, 35 | # circumfence 0.377m 36 | # every 0.1 seconds 37 | if len(m.group(3)) > 0: 38 | period = 0.001 * int(m.group(3)) 39 | else: 40 | period = 0.1 41 | 42 | self.speed = 0.377 * (float(value) / 40) / period # now in m/s 43 | else: 44 | m = linaccel_pattern.match(l.decode('utf-8')) 45 | 46 | if m: 47 | la = { 'x': float(m.group(1)), 'y': float(m.group(2)), 'z': float(m.group(3)) } 48 | 49 | self.linaccel = la 50 | print("mw linaccel= " + str(self.linaccel)) 51 | 52 | l = self.sensor.astar_readline() 53 | 54 | stop = datetime.now() 55 | s = 0.1 - (stop - start).total_seconds() 56 | if s > 0: 57 | time.sleep(s) 58 | 59 | def run_threaded(self): 60 | return self.speed # , self.linaccel 61 | 62 | def shutdown(self): 63 | # indicate that the thread should be stopped 64 | self.on = False 65 | print('stopping AStarSpeed') 66 | time.sleep(.5) 67 | 68 | 69 | class RotaryEncoder(): 70 | def __init__(self, mm_per_tick=0.306096, pin=27, poll_delay=0.0166, debug=False): 71 | import RPi.GPIO as GPIO 72 | GPIO.setmode(GPIO.BCM) 73 | GPIO.setup(pin, GPIO.IN) 74 | GPIO.add_event_detect(pin, GPIO.RISING, callback=self.isr) 75 | 76 | # initialize the odometer values 77 | self.m_per_tick = mm_per_tick / 1000.0 78 | self.poll_delay = poll_delay 79 | self.meters = 0 80 | self.last_time = time.time() 81 | self.meters_per_second = 0 82 | self.counter = 0 83 | self.on = True 84 | self.debug = debug 85 | 86 | def isr(self, channel): 87 | self.counter += 1 88 | 89 | def update(self): 90 | # keep looping infinitely until the thread is stopped 91 | while(self.on): 92 | 93 | #save the ticks and reset the counter 94 | ticks = self.counter 95 | self.counter = 0 96 | 97 | #save off the last time interval and reset the timer 98 | start_time = self.last_time 99 | end_time = time.time() 100 | self.last_time = end_time 101 | 102 | #calculate elapsed time and distance traveled 103 | seconds = end_time - start_time 104 | distance = ticks * self.m_per_tick 105 | velocity = distance / seconds 106 | 107 | #update the odometer values 108 | self.meters += distance 109 | self.meters_per_second = velocity 110 | 111 | #console output for debugging 112 | if(self.debug): 113 | print('seconds:', seconds) 114 | print('distance:', distance) 115 | print('velocity:', velocity) 116 | 117 | print('distance (m):', round(self.meters, 4)) 118 | print('velocity (m/s):', self.meters_per_second) 119 | 120 | time.sleep(self.poll_delay) 121 | 122 | def run_threaded(self): 123 | return self.meters, self.meters_per_second 124 | 125 | def shutdown(self): 126 | # indicate that the thread should be stopped 127 | self.on = False 128 | print('stopping Rotary Encoder') 129 | print('top speed (m/s):', self.top_speed) 130 | time.sleep(.5) 131 | 132 | import RPi.GPIO as GPIO 133 | GPIO.cleanup() 134 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/encoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rotary Encoder 3 | """ 4 | 5 | from datetime import datetime 6 | from donkeycar.parts.teensy import TeensyRCin 7 | import re 8 | import time 9 | 10 | class AStarSpeed: 11 | def __init__(self): 12 | self.speed = 0 13 | self.linaccel = None 14 | 15 | self.sensor = TeensyRCin(0); 16 | 17 | self.on = True 18 | 19 | def update(self): 20 | encoder_pattern = re.compile('^E ([-0-9]+)( ([-0-9]+))?( ([-0-9]+))?$') 21 | linaccel_pattern = re.compile('^L ([-.0-9]+) ([-.0-9]+) ([-.0-9]+) ([-0-9]+)$') 22 | 23 | while self.on: 24 | start = datetime.now() 25 | 26 | l = self.sensor.astar_readline() 27 | while l: 28 | m = encoder_pattern.match(l.decode('utf-8')) 29 | 30 | if m: 31 | value = int(m.group(1)) 32 | # rospy.loginfo("%s: Receiver E got %d" % (self.node_name, value)) 33 | # Speed 34 | # 40 ticks/wheel rotation, 35 | # circumfence 0.377m 36 | # every 0.1 seconds 37 | if len(m.group(3)) > 0: 38 | period = 0.001 * int(m.group(3)) 39 | else: 40 | period = 0.1 41 | 42 | self.speed = 0.377 * (float(value) / 40) / period # now in m/s 43 | else: 44 | m = linaccel_pattern.match(l.decode('utf-8')) 45 | 46 | if m: 47 | la = { 'x': float(m.group(1)), 'y': float(m.group(2)), 'z': float(m.group(3)) } 48 | 49 | self.linaccel = la 50 | print("mw linaccel= " + str(self.linaccel)) 51 | 52 | l = self.sensor.astar_readline() 53 | 54 | stop = datetime.now() 55 | s = 0.1 - (stop - start).total_seconds() 56 | if s > 0: 57 | time.sleep(s) 58 | 59 | def run_threaded(self): 60 | return self.speed # , self.linaccel 61 | 62 | def shutdown(self): 63 | # indicate that the thread should be stopped 64 | self.on = False 65 | print('stopping AStarSpeed') 66 | time.sleep(.5) 67 | 68 | 69 | class RotaryEncoder(): 70 | def __init__(self, mm_per_tick=0.306096, pin=27, poll_delay=0.0166, debug=False): 71 | import RPi.GPIO as GPIO 72 | GPIO.setmode(GPIO.BCM) 73 | GPIO.setup(pin, GPIO.IN) 74 | GPIO.add_event_detect(pin, GPIO.RISING, callback=self.isr) 75 | 76 | # initialize the odometer values 77 | self.m_per_tick = mm_per_tick / 1000.0 78 | self.poll_delay = poll_delay 79 | self.meters = 0 80 | self.last_time = time.time() 81 | self.meters_per_second = 0 82 | self.counter = 0 83 | self.on = True 84 | self.debug = debug 85 | 86 | def isr(self, channel): 87 | self.counter += 1 88 | 89 | def update(self): 90 | # keep looping infinitely until the thread is stopped 91 | while(self.on): 92 | 93 | #save the ticks and reset the counter 94 | ticks = self.counter 95 | self.counter = 0 96 | 97 | #save off the last time interval and reset the timer 98 | start_time = self.last_time 99 | end_time = time.time() 100 | self.last_time = end_time 101 | 102 | #calculate elapsed time and distance traveled 103 | seconds = end_time - start_time 104 | distance = ticks * self.m_per_tick 105 | velocity = distance / seconds 106 | 107 | #update the odometer values 108 | self.meters += distance 109 | self.meters_per_second = velocity 110 | 111 | #console output for debugging 112 | if(self.debug): 113 | print('seconds:', seconds) 114 | print('distance:', distance) 115 | print('velocity:', velocity) 116 | 117 | print('distance (m):', round(self.meters, 4)) 118 | print('velocity (m/s):', self.meters_per_second) 119 | 120 | time.sleep(self.poll_delay) 121 | 122 | def run_threaded(self): 123 | return self.meters, self.meters_per_second 124 | 125 | def shutdown(self): 126 | # indicate that the thread should be stopped 127 | self.on = False 128 | print('stopping Rotary Encoder') 129 | print('top speed (m/s):', self.top_speed) 130 | time.sleep(.5) 131 | 132 | import RPi.GPIO as GPIO 133 | GPIO.cleanup() 134 | -------------------------------------------------------------------------------- /donkeycar/parts/autorope.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import requests 4 | from six.moves.urllib import parse 5 | import calendar 6 | import datetime 7 | 8 | from ..log import get_logger 9 | 10 | logger = get_logger(__name__) 11 | 12 | 13 | def _api_encode(data): 14 | for key, value in data.items(): 15 | if value is None: 16 | continue 17 | elif isinstance(value, datetime.datetime): 18 | yield (key, _encode_datetime(value)) 19 | else: 20 | yield (key, value) 21 | 22 | 23 | def _build_api_url(url, query): 24 | scheme, netloc, path, base_query, fragment = parse.urlsplit(url) 25 | if base_query: 26 | query = str('%s&%s' % (base_query, query)) 27 | 28 | return parse.urlunsplit((scheme, netloc, path, query, fragment)) 29 | 30 | 31 | def _encode_datetime(dttime): 32 | if dttime.tzinfo and dttime.tzinfo.utcoffset(dttime) is not None: 33 | utc_timestamp = calendar.timegm(dttime.utctimetuple()) 34 | else: 35 | utc_timestamp = time.mktime(dttime.timetuple()) 36 | 37 | return int(utc_timestamp) 38 | 39 | 40 | class AutoropeSession: 41 | 42 | def __init__(self, 43 | token, 44 | car_id, 45 | controller_url=None, 46 | api_base='https://rope.donkeycar.com/api/'): 47 | 48 | self.auth_token = token 49 | self.car_id = car_id 50 | self.connected = False 51 | self.api_base = api_base 52 | 53 | try: 54 | self.session_id = self.start_new_session(controller_url=controller_url) 55 | logger.info('started new autorope session {}'.format(self.session_id)) 56 | except Exception as e: 57 | logger.info('Autorope part was unable to load. Goto rope.donkeycar.com for instructions') 58 | logger.info(e) 59 | 60 | def start_new_session(self, controller_url=None): 61 | resp = self.post_request('sessions/', 62 | { 63 | 'bot_name': self.car_id, 64 | 'controller_url': controller_url 65 | } 66 | ) 67 | if resp.status_code == 200: 68 | resp_js = resp.json() 69 | self.session_id = resp_js.get('id') 70 | return self.session_id 71 | else: 72 | logger.info(resp.text) 73 | return None 74 | 75 | def update(self): 76 | self.measurements = self.lidar.iter_measurments(500) 77 | for new_scan, quality, angle, distance in self.measurements: 78 | angle = int(angle) 79 | self.frame[angle] = 2 * distance / 3 + self.frame[angle] / 3 80 | if not self.on: 81 | break 82 | 83 | def run_threaded(self): 84 | return self.frame 85 | 86 | def _build_headers(self, headers={}): 87 | 88 | auth_header = {'Authorization': 'Token {}'.format(self.auth_token)} 89 | headers.update(auth_header) 90 | return headers 91 | 92 | def get_request(self, url, params={}, supplied_headers={}, format='json'): 93 | # combine default params and given params 94 | params_all = {} 95 | params_all.update(params) 96 | 97 | abs_url = self.api_base + url 98 | encoded_params = parse.urlencode(list(_api_encode(params_all))) 99 | abs_url = _build_api_url(abs_url, encoded_params) 100 | 101 | # logger.info('abs_url: {}'.format(abs_url)) 102 | headers = self._build_headers(supplied_headers) 103 | 104 | logger.info(headers) 105 | logger.info(abs_url) 106 | resp = requests.get(abs_url, headers=headers) 107 | if format == 'json': 108 | return resp 109 | elif format == 'text': 110 | return resp.text 111 | elif format == 'gdf': 112 | import tempfile 113 | import geopandas as gp 114 | file = tempfile.NamedTemporaryFile(suffix='.json', delete=False, mode='w') 115 | file.write(resp.text) 116 | file.close() 117 | 118 | gdf = gp.read_file(file.name) 119 | return gdf 120 | 121 | 122 | def post_request(self, url, data, params=None, supplied_headers={}, files=None): 123 | abs_url = self.api_base + url 124 | 125 | encoded_params = parse.urlencode(list(_api_encode(params or {}))) 126 | abs_url = _build_api_url(abs_url, encoded_params) 127 | headers = self._build_headers(supplied_headers) 128 | logger.info(abs_url) 129 | resp = requests.post(abs_url, json=data, headers=headers, files=files) 130 | return resp 131 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/autorope.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import requests 4 | from six.moves.urllib import parse 5 | import calendar 6 | import datetime 7 | 8 | from ..log import get_logger 9 | 10 | logger = get_logger(__name__) 11 | 12 | 13 | def _api_encode(data): 14 | for key, value in data.items(): 15 | if value is None: 16 | continue 17 | elif isinstance(value, datetime.datetime): 18 | yield (key, _encode_datetime(value)) 19 | else: 20 | yield (key, value) 21 | 22 | 23 | def _build_api_url(url, query): 24 | scheme, netloc, path, base_query, fragment = parse.urlsplit(url) 25 | if base_query: 26 | query = str('%s&%s' % (base_query, query)) 27 | 28 | return parse.urlunsplit((scheme, netloc, path, query, fragment)) 29 | 30 | 31 | def _encode_datetime(dttime): 32 | if dttime.tzinfo and dttime.tzinfo.utcoffset(dttime) is not None: 33 | utc_timestamp = calendar.timegm(dttime.utctimetuple()) 34 | else: 35 | utc_timestamp = time.mktime(dttime.timetuple()) 36 | 37 | return int(utc_timestamp) 38 | 39 | 40 | class AutoropeSession: 41 | 42 | def __init__(self, 43 | token, 44 | car_id, 45 | controller_url=None, 46 | api_base='https://rope.donkeycar.com/api/'): 47 | 48 | self.auth_token = token 49 | self.car_id = car_id 50 | self.connected = False 51 | self.api_base = api_base 52 | 53 | try: 54 | self.session_id = self.start_new_session(controller_url=controller_url) 55 | logger.info('started new autorope session {}'.format(self.session_id)) 56 | except Exception as e: 57 | logger.info('Autorope part was unable to load. Goto rope.donkeycar.com for instructions') 58 | logger.info(e) 59 | 60 | def start_new_session(self, controller_url=None): 61 | resp = self.post_request('sessions/', 62 | { 63 | 'bot_name': self.car_id, 64 | 'controller_url': controller_url 65 | } 66 | ) 67 | if resp.status_code == 200: 68 | resp_js = resp.json() 69 | self.session_id = resp_js.get('id') 70 | return self.session_id 71 | else: 72 | logger.info(resp.text) 73 | return None 74 | 75 | def update(self): 76 | self.measurements = self.lidar.iter_measurments(500) 77 | for new_scan, quality, angle, distance in self.measurements: 78 | angle = int(angle) 79 | self.frame[angle] = 2 * distance / 3 + self.frame[angle] / 3 80 | if not self.on: 81 | break 82 | 83 | def run_threaded(self): 84 | return self.frame 85 | 86 | def _build_headers(self, headers={}): 87 | 88 | auth_header = {'Authorization': 'Token {}'.format(self.auth_token)} 89 | headers.update(auth_header) 90 | return headers 91 | 92 | def get_request(self, url, params={}, supplied_headers={}, format='json'): 93 | # combine default params and given params 94 | params_all = {} 95 | params_all.update(params) 96 | 97 | abs_url = self.api_base + url 98 | encoded_params = parse.urlencode(list(_api_encode(params_all))) 99 | abs_url = _build_api_url(abs_url, encoded_params) 100 | 101 | # logger.info('abs_url: {}'.format(abs_url)) 102 | headers = self._build_headers(supplied_headers) 103 | 104 | logger.info(headers) 105 | logger.info(abs_url) 106 | resp = requests.get(abs_url, headers=headers) 107 | if format == 'json': 108 | return resp 109 | elif format == 'text': 110 | return resp.text 111 | elif format == 'gdf': 112 | import tempfile 113 | import geopandas as gp 114 | file = tempfile.NamedTemporaryFile(suffix='.json', delete=False, mode='w') 115 | file.write(resp.text) 116 | file.close() 117 | 118 | gdf = gp.read_file(file.name) 119 | return gdf 120 | 121 | 122 | def post_request(self, url, data, params=None, supplied_headers={}, files=None): 123 | abs_url = self.api_base + url 124 | 125 | encoded_params = parse.urlencode(list(_api_encode(params or {}))) 126 | abs_url = _build_api_url(abs_url, encoded_params) 127 | headers = self._build_headers(supplied_headers) 128 | logger.info(abs_url) 129 | resp = requests.post(abs_url, json=data, headers=headers, files=files) 130 | return resp 131 | -------------------------------------------------------------------------------- /donkeycar/tests/test_util_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 14:17:59 2017 5 | 6 | @author: wroscoe 7 | """ 8 | import unittest 9 | import pytest 10 | 11 | 12 | from donkeycar.util.data import linear_bin 13 | from donkeycar.util.data import linear_unbin 14 | from donkeycar.util.data import bin_Y 15 | from donkeycar.util.data import unbin_Y 16 | from donkeycar.util.data import map_range 17 | from donkeycar.util.data import merge_two_dicts 18 | from donkeycar.util.data import param_gen 19 | 20 | 21 | def create_lbin(marker_index): 22 | """ Create a linear binary array with value set """ 23 | l = [0] * 15 24 | l[marker_index] = 1 25 | return l 26 | 27 | 28 | class TestLinearBin(unittest.TestCase): 29 | 30 | def test_zero(self): 31 | res = linear_bin(0) 32 | assert res[7] == 1 33 | assert sum(res[:7]) == 0 34 | assert sum(res[8:]) == 0 35 | 36 | def test_positive(self): 37 | res = linear_bin(1) 38 | assert res[14] == 1 39 | assert sum(res[:14]) == 0 40 | 41 | def test_negative(self): 42 | res = linear_bin(-1) 43 | assert res[0] == 1 44 | assert sum(res[1:]) == 0 45 | 46 | def test_illegal_pos(self): 47 | with pytest.raises(IndexError): 48 | linear_bin(2) 49 | 50 | def test_illegal_type(self): 51 | with pytest.raises(TypeError): 52 | linear_bin('0') 53 | 54 | 55 | class TestLinearUnbin(unittest.TestCase): 56 | 57 | def test_zero(self): 58 | l = create_lbin(7) 59 | res = linear_unbin(l) 60 | assert res == 0.0 61 | 62 | def test_positive(self): 63 | l = create_lbin(14) 64 | res = linear_unbin(l) 65 | assert res == 1.0 66 | 67 | def test_negative(self): 68 | l = create_lbin(0) 69 | res = linear_unbin(l) 70 | assert res == -1.0 71 | 72 | def test_empty_list(self): 73 | res = linear_unbin( [0] * 15 ) 74 | assert res == -1.0 75 | 76 | def test_illegal_list(self): 77 | with pytest.raises(ValueError): 78 | linear_unbin( [0] * 10 ) 79 | 80 | 81 | class TestBinY(unittest.TestCase): 82 | 83 | def test_normal_list(self): 84 | l = [ -1, 0, 1 ] 85 | res = bin_Y(l) 86 | 87 | # negative 88 | assert res[0][0] == 1 89 | assert sum(res[0][1:]) == 0 90 | 91 | # zero 92 | assert res[1][7] == 1 93 | assert sum(res[1][:7]) == 0 94 | assert sum(res[1][8:]) == 0 95 | 96 | # positive 97 | assert res[2][14] == 1 98 | assert sum(res[2][:14]) == 0 99 | 100 | class TestUnbinY(unittest.TestCase): 101 | 102 | def test_normal_list(self): 103 | l = [ create_lbin(0), create_lbin(7), create_lbin(14) ] 104 | res = unbin_Y(l) 105 | 106 | # negative 107 | assert res[0] == -1.0 108 | 109 | # zero 110 | assert res[1] == 0.0 111 | 112 | # positive 113 | assert res[2] == 1.0 114 | 115 | 116 | class TestMapping(unittest.TestCase): 117 | 118 | def test_positive(self): 119 | min = map_range(-100, -100, 100, 0, 1000) 120 | half = map_range(0, -100, 100, 0, 1000) 121 | max = map_range(100, -100, 100, 0, 1000) 122 | assert min == 0 123 | assert half == 500 124 | assert max == 1000 125 | 126 | def test_negative(self): 127 | ranges = (0, 100, 0, 1000) 128 | min = map_range(0, *ranges) 129 | half = map_range(50, *ranges) 130 | max = map_range(100, *ranges) 131 | assert min == 0 132 | assert half == 500 133 | assert max == 1000 134 | 135 | def test_reverse(self): 136 | ranges = (100, 0, 0, 1000) 137 | min = map_range(0, *ranges) 138 | half = map_range(50, *ranges) 139 | max = map_range(100, *ranges) 140 | assert min == 1000 141 | assert half == 500 142 | assert max == 0 143 | 144 | 145 | class TestMergeDicts(unittest.TestCase): 146 | 147 | def test_merge_two_dicts(self): 148 | d1 = { 'a' : 1, 'b' : 2, 'c' : 3 } 149 | d2 = { 10 : 'hi', 'bob' : 20 } 150 | res = merge_two_dicts(d1, d2) 151 | 152 | assert res == { 'a' : 1, 'b' : 2, 'c' : 3, 10 : 'hi', 'bob' : 20 } 153 | 154 | class TestParamGen(unittest.TestCase): 155 | 156 | def test_param_gen(self): 157 | g = param_gen({ 'a' : [ 'opt1', 'opt2' ], 'b' : [ 'opt3', 'opt4' ] }) 158 | l = [ x for x in g ] 159 | expected = [ 160 | {'a': 'opt1', 'b': 'opt3'}, 161 | {'a': 'opt1', 'b': 'opt4'}, 162 | {'a': 'opt2', 'b': 'opt3'}, 163 | {'a': 'opt2', 'b': 'opt4'} 164 | ] 165 | self.assertCountEqual(expected, l) -------------------------------------------------------------------------------- /donkeycar/vehicle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 10:44:24 2017 5 | 6 | @author: wroscoe 7 | """ 8 | 9 | import time 10 | from threading import Thread 11 | from .memory import Memory 12 | from .log import get_logger 13 | 14 | logger = get_logger(__name__) 15 | 16 | 17 | class Vehicle: 18 | def __init__(self, mem=None): 19 | if not mem: 20 | mem = Memory() 21 | self.mem = mem 22 | self.parts = [] 23 | self.on = True 24 | self.threads = [] 25 | 26 | def add(self, part, inputs=[], outputs=[], 27 | threaded=False, run_condition=None): 28 | """ 29 | Method to add a part to the vehicle drive loop. 30 | 31 | Parameters 32 | ---------- 33 | inputs : list 34 | Channel names to get from memory. 35 | outputs : list 36 | Channel names to save to memory. 37 | threaded : boolean 38 | If a part should be run in a separate thread. 39 | run_condition: boolean 40 | If a part should be run at all. 41 | """ 42 | 43 | p = part 44 | logger.info('Adding part {}.'.format(p.__class__.__name__)) 45 | entry = dict() 46 | entry['part'] = p 47 | entry['inputs'] = inputs 48 | entry['outputs'] = outputs 49 | entry['run_condition'] = run_condition 50 | 51 | if threaded: 52 | t = Thread(target=part.update, args=()) 53 | t.daemon = True 54 | entry['thread'] = t 55 | self.parts.append(entry) 56 | 57 | def start(self, rate_hz=10, max_loop_count=None): 58 | """ 59 | Start vehicle's main drive loop. 60 | 61 | This is the main thread of the vehicle. It starts all the new 62 | threads for the threaded parts then starts an infinit loop 63 | that runs each part and updates the memory. 64 | 65 | Parameters 66 | ---------- 67 | 68 | rate_hz : int 69 | The max frequency that the drive loop should run. The actual 70 | frequency may be less than this if there are many blocking parts. 71 | max_loop_count : int 72 | Maxiumum number of loops the drive loop should execute. This is 73 | used for testing the all the parts of the vehicle work. 74 | """ 75 | 76 | try: 77 | self.on = True 78 | 79 | for entry in self.parts: 80 | if entry.get('thread'): 81 | # start the update thread 82 | entry.get('thread').start() 83 | 84 | # wait until the parts warm up. 85 | logger.info('Starting vehicle...') 86 | time.sleep(1) 87 | 88 | loop_count = 0 89 | while self.on: 90 | start_time = time.time() 91 | loop_count += 1 92 | 93 | self.update_parts() 94 | 95 | # stop drive loop if loop_count exceeds max_loopcount 96 | if max_loop_count and loop_count > max_loop_count: 97 | self.on = False 98 | 99 | sleep_time = 1.0 / rate_hz - (time.time() - start_time) 100 | if sleep_time > 0.0: 101 | time.sleep(sleep_time) 102 | 103 | except KeyboardInterrupt: 104 | pass 105 | finally: 106 | self.stop() 107 | 108 | def update_parts(self): 109 | """ 110 | loop over all parts 111 | """ 112 | for entry in self.parts: 113 | # don't run if there is a run condition that is False 114 | run = True 115 | if entry.get('run_condition'): 116 | run_condition = entry.get('run_condition') 117 | run = self.mem.get([run_condition])[0] 118 | # print('run_condition', entry['part'], entry.get('run_condition'), run) 119 | 120 | if run: 121 | p = entry['part'] 122 | # get inputs from memory 123 | inputs = self.mem.get(entry['inputs']) 124 | 125 | # run the part 126 | if entry.get('thread'): 127 | outputs = p.run_threaded(*inputs) 128 | else: 129 | outputs = p.run(*inputs) 130 | 131 | # save the output to memory 132 | if outputs is not None: 133 | self.mem.put(entry['outputs'], outputs) 134 | 135 | def stop(self): 136 | logger.info('Shutting down vehicle and its parts...') 137 | for entry in self.parts: 138 | try: 139 | entry['part'].shutdown() 140 | except Exception as e: 141 | logger.debug(e) 142 | -------------------------------------------------------------------------------- /donkeycar/Backup/vehicle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 10:44:24 2017 5 | 6 | @author: wroscoe 7 | """ 8 | 9 | import time 10 | from threading import Thread 11 | from .memory import Memory 12 | from .log import get_logger 13 | 14 | logger = get_logger(__name__) 15 | 16 | 17 | class Vehicle: 18 | def __init__(self, mem=None): 19 | if not mem: 20 | mem = Memory() 21 | self.mem = mem 22 | self.parts = [] 23 | self.on = True 24 | self.threads = [] 25 | 26 | def add(self, part, inputs=[], outputs=[], 27 | threaded=False, run_condition=None): 28 | """ 29 | Method to add a part to the vehicle drive loop. 30 | 31 | Parameters 32 | ---------- 33 | inputs : list 34 | Channel names to get from memory. 35 | outputs : list 36 | Channel names to save to memory. 37 | threaded : boolean 38 | If a part should be run in a separate thread. 39 | run_condition: boolean 40 | If a part should be run at all. 41 | """ 42 | 43 | p = part 44 | logger.info('Adding part {}.'.format(p.__class__.__name__)) 45 | entry = dict() 46 | entry['part'] = p 47 | entry['inputs'] = inputs 48 | entry['outputs'] = outputs 49 | entry['run_condition'] = run_condition 50 | 51 | if threaded: 52 | t = Thread(target=part.update, args=()) 53 | t.daemon = True 54 | entry['thread'] = t 55 | self.parts.append(entry) 56 | 57 | def start(self, rate_hz=10, max_loop_count=None): 58 | """ 59 | Start vehicle's main drive loop. 60 | 61 | This is the main thread of the vehicle. It starts all the new 62 | threads for the threaded parts then starts an infinit loop 63 | that runs each part and updates the memory. 64 | 65 | Parameters 66 | ---------- 67 | 68 | rate_hz : int 69 | The max frequency that the drive loop should run. The actual 70 | frequency may be less than this if there are many blocking parts. 71 | max_loop_count : int 72 | Maxiumum number of loops the drive loop should execute. This is 73 | used for testing the all the parts of the vehicle work. 74 | """ 75 | 76 | try: 77 | self.on = True 78 | 79 | for entry in self.parts: 80 | if entry.get('thread'): 81 | # start the update thread 82 | entry.get('thread').start() 83 | 84 | # wait until the parts warm up. 85 | logger.info('Starting vehicle...') 86 | time.sleep(1) 87 | 88 | loop_count = 0 89 | while self.on: 90 | start_time = time.time() 91 | loop_count += 1 92 | 93 | self.update_parts() 94 | 95 | # stop drive loop if loop_count exceeds max_loopcount 96 | if max_loop_count and loop_count > max_loop_count: 97 | self.on = False 98 | 99 | sleep_time = 1.0 / rate_hz - (time.time() - start_time) 100 | if sleep_time > 0.0: 101 | time.sleep(sleep_time) 102 | 103 | except KeyboardInterrupt: 104 | pass 105 | finally: 106 | self.stop() 107 | 108 | def update_parts(self): 109 | """ 110 | loop over all parts 111 | """ 112 | for entry in self.parts: 113 | # don't run if there is a run condition that is False 114 | run = True 115 | if entry.get('run_condition'): 116 | run_condition = entry.get('run_condition') 117 | run = self.mem.get([run_condition])[0] 118 | # print('run_condition', entry['part'], entry.get('run_condition'), run) 119 | 120 | if run: 121 | p = entry['part'] 122 | # get inputs from memory 123 | inputs = self.mem.get(entry['inputs']) 124 | 125 | # run the part 126 | if entry.get('thread'): 127 | outputs = p.run_threaded(*inputs) 128 | else: 129 | outputs = p.run(*inputs) 130 | 131 | # save the output to memory 132 | if outputs is not None: 133 | self.mem.put(entry['outputs'], outputs) 134 | 135 | def stop(self): 136 | logger.info('Shutting down vehicle and its parts...') 137 | for entry in self.parts: 138 | try: 139 | entry['part'].shutdown() 140 | except Exception as e: 141 | logger.debug(e) 142 | -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/web.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sat Jun 24 20:10:44 2017 5 | 6 | @author: wroscoe 7 | 8 | remotes.py 9 | 10 | The client and web server needed to control a car remotely. 11 | """ 12 | 13 | import random 14 | 15 | 16 | import os 17 | import time 18 | 19 | import tornado 20 | import tornado.ioloop 21 | import tornado.web 22 | import tornado.gen 23 | 24 | from donkeycar import util 25 | 26 | 27 | class LocalWebController(tornado.web.Application): 28 | port = 8887 29 | def __init__(self, use_chaos=False): 30 | """ 31 | Create and publish variables needed on many of 32 | the web handlers. 33 | """ 34 | print('Starting Donkey Server...') 35 | 36 | this_dir = os.path.dirname(os.path.realpath(__file__)) 37 | self.static_file_path = os.path.join(this_dir, 'templates', 'static') 38 | 39 | self.angle = 0.0 40 | self.throttle = 0.0 41 | self.mode = 'user' 42 | self.recording = False 43 | self.ip_address = util.web.get_ip_address() 44 | self.access_url = 'http://{}:{}'.format(self.ip_address, self.port) 45 | 46 | self.chaos_on = False 47 | self.chaos_counter = 0 48 | self.chaos_frequency = 1000 #frames 49 | self.chaos_duration = 10 50 | 51 | if use_chaos: 52 | self.run_threaded = self.run_chaos 53 | else: 54 | self.run_threaded = self._run_threaded 55 | 56 | handlers = [ 57 | (r"/", tornado.web.RedirectHandler, dict(url="/drive")), 58 | (r"/drive", DriveAPI), 59 | (r"/video", VideoAPI), 60 | (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": self.static_file_path}), 61 | ] 62 | 63 | settings = {'debug': True} 64 | super().__init__(handlers, **settings) 65 | 66 | def run_chaos(self, img_arr=None): 67 | """ 68 | Run function where steering is made random to add corrective 69 | """ 70 | self.img_arr = img_arr 71 | if self.chaos_counter == self.chaos_frequency: 72 | self.chaos_on = True 73 | random_steering = random.random() 74 | elif self.chaos_counter == self.chaos_duration: 75 | self.chaos_on = False 76 | 77 | if self.chaos_on: 78 | return random_steering, self.throttle, self.mode, False 79 | else: 80 | return self.angle, self.throttle, self.mode, self.recording 81 | 82 | def say_hello(self): 83 | """ 84 | Print friendly message to user 85 | """ 86 | print("You can now go to {} to drive your car.".format(self.access_url)) 87 | 88 | def update(self): 89 | """ Start the tornado web server. """ 90 | self.port = int(self.port) 91 | self.listen(self.port) 92 | instance = tornado.ioloop.IOLoop.instance() 93 | instance.add_callback(self.say_hello) 94 | instance.start() 95 | 96 | def _run_threaded(self, img_arr=None): 97 | self.img_arr = img_arr 98 | return self.angle, self.throttle, self.mode, self.recording 99 | 100 | def run(self, img_arr=None): 101 | return self.run_threaded(img_arr) 102 | 103 | 104 | class DriveAPI(tornado.web.RequestHandler): 105 | def get(self): 106 | data = {} 107 | self.render("templates/vehicle.html", **data) 108 | 109 | def post(self): 110 | """ 111 | Receive post requests as user changes the angle 112 | and throttle of the vehicle on a the index webpage 113 | """ 114 | data = tornado.escape.json_decode(self.request.body) 115 | self.application.angle = data['angle'] 116 | self.application.throttle = data['throttle'] 117 | self.application.mode = data['drive_mode'] 118 | self.application.recording = data['recording'] 119 | 120 | 121 | class VideoAPI(tornado.web.RequestHandler): 122 | """ 123 | Serves a MJPEG of the images posted from the vehicle. 124 | """ 125 | 126 | @tornado.web.asynchronous 127 | @tornado.gen.coroutine 128 | def get(self): 129 | 130 | ioloop = tornado.ioloop.IOLoop.current() 131 | self.set_header("Content-type", "multipart/x-mixed-replace;boundary=--boundarydonotcross") 132 | 133 | self.served_image_timestamp = time.time() 134 | my_boundary = "--boundarydonotcross" 135 | while True: 136 | 137 | interval = .1 138 | if self.served_image_timestamp + interval < time.time(): 139 | 140 | img = util.img.arr_to_binary(self.application.img_arr) 141 | 142 | self.write(my_boundary) 143 | self.write("Content-type: image/jpeg\r\n") 144 | self.write("Content-length: %s\r\n\r\n" % len(img)) 145 | self.write(img) 146 | self.served_image_timestamp = time.time() 147 | yield tornado.gen.Task(self.flush) 148 | else: 149 | yield tornado.gen.Task(ioloop.add_timeout, ioloop.time() + interval) 150 | -------------------------------------------------------------------------------- /donkeycar/parts/camera.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import numpy as np 4 | from PIL import Image 5 | import glob 6 | 7 | class BaseCamera: 8 | 9 | def run_threaded(self): 10 | return self.frame 11 | 12 | class PiCamera(BaseCamera): 13 | def __init__(self, resolution=(120, 160), framerate=20): 14 | from picamera.array import PiRGBArray 15 | from picamera import PiCamera 16 | resolution = (resolution[1], resolution[0]) 17 | # initialize the camera and stream 18 | self.camera = PiCamera() #PiCamera gets resolution (height, width) 19 | self.camera.resolution = resolution 20 | self.camera.framerate = framerate 21 | self.rawCapture = PiRGBArray(self.camera, size=resolution) 22 | self.stream = self.camera.capture_continuous(self.rawCapture, 23 | format="rgb", use_video_port=True) 24 | 25 | # initialize the frame and the variable used to indicate 26 | # if the thread should be stopped 27 | self.frame = None 28 | self.on = True 29 | 30 | print('PiCamera loaded.. .warming camera') 31 | time.sleep(2) 32 | 33 | 34 | def run(self): 35 | f = next(self.stream) 36 | frame = f.array 37 | self.rawCapture.truncate(0) 38 | return frame 39 | 40 | def update(self): 41 | # keep looping infinitely until the thread is stopped 42 | for f in self.stream: 43 | # grab the frame from the stream and clear the stream in 44 | # preparation for the next frame 45 | self.frame = f.array 46 | self.rawCapture.truncate(0) 47 | 48 | # if the thread indicator variable is set, stop the thread 49 | if not self.on: 50 | break 51 | 52 | def shutdown(self): 53 | # indicate that the thread should be stopped 54 | self.on = False 55 | print('stoping PiCamera') 56 | time.sleep(.5) 57 | self.stream.close() 58 | self.rawCapture.close() 59 | self.camera.close() 60 | 61 | class Webcam(BaseCamera): 62 | def __init__(self, resolution = (160, 120), framerate = 20): 63 | import pygame 64 | import pygame.camera 65 | 66 | super().__init__() 67 | 68 | pygame.init() 69 | pygame.camera.init() 70 | l = pygame.camera.list_cameras() 71 | self.cam = pygame.camera.Camera(l[0], resolution, "RGB") 72 | self.resolution = resolution 73 | self.cam.start() 74 | self.framerate = framerate 75 | 76 | # initialize variable used to indicate 77 | # if the thread should be stopped 78 | self.frame = None 79 | self.on = True 80 | 81 | print('WebcamVideoStream loaded.. .warming camera') 82 | 83 | time.sleep(2) 84 | 85 | def update(self): 86 | from datetime import datetime, timedelta 87 | import pygame.image 88 | while self.on: 89 | start = datetime.now() 90 | 91 | if self.cam.query_image(): 92 | # snapshot = self.cam.get_image() 93 | # self.frame = list(pygame.image.tostring(snapshot, "RGB", False)) 94 | snapshot = self.cam.get_image() 95 | snapshot1 = pygame.transform.scale(snapshot, self.resolution) 96 | self.frame = pygame.surfarray.pixels3d(pygame.transform.rotate(pygame.transform.flip(snapshot1, True, False), 90)) 97 | 98 | stop = datetime.now() 99 | s = 1 / self.framerate - (stop - start).total_seconds() 100 | if s > 0: 101 | time.sleep(s) 102 | 103 | self.cam.stop() 104 | 105 | def run_threaded(self): 106 | return self.frame 107 | 108 | def shutdown(self): 109 | # indicate that the thread should be stopped 110 | self.on = False 111 | print('stoping Webcam') 112 | time.sleep(.5) 113 | 114 | class MockCamera(BaseCamera): 115 | """ 116 | Fake camera. Returns only a single static frame 117 | """ 118 | def __init__(self, resolution=(160, 120), image=None): 119 | if image is not None: 120 | self.frame = image 121 | else: 122 | self.frame = Image.new('RGB', resolution) 123 | 124 | def update(self): 125 | pass 126 | 127 | def shutdown(self): 128 | pass 129 | 130 | class ImageListCamera(BaseCamera): 131 | """ 132 | Use the images from a tub as a fake camera output 133 | """ 134 | def __init__(self, path_mask='~/mycar/data/**/*.jpg'): 135 | self.image_filenames = glob.glob(os.path.expanduser(path_mask), recursive=True) 136 | 137 | def get_image_index(fnm): 138 | sl = os.path.basename(fnm).split('_') 139 | return int(sl[0]) 140 | 141 | """ 142 | I feel like sorting by modified time is almost always 143 | what you want. but if you tared and moved your data around, 144 | sometimes it doesn't preserve a nice modified time. 145 | so, sorting by image index works better, but only with one path. 146 | """ 147 | self.image_filenames.sort(key=get_image_index) 148 | #self.image_filenames.sort(key=os.path.getmtime) 149 | self.num_images = len(self.image_filenames) 150 | print('%d images loaded.' % self.num_images) 151 | print( self.image_filenames[:10]) 152 | self.i_frame = 0 153 | self.frame = None 154 | self.update() 155 | 156 | def update(self): 157 | pass 158 | 159 | def run_threaded(self): 160 | if self.num_images > 0: 161 | self.i_frame = (self.i_frame + 1) % self.num_images 162 | self.frame = Image.open(self.image_filenames[self.i_frame]) 163 | 164 | return np.asarray(self.frame) 165 | 166 | def shutdown(self): 167 | pass 168 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/camera.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import numpy as np 4 | from PIL import Image 5 | import glob 6 | 7 | class BaseCamera: 8 | 9 | def run_threaded(self): 10 | return self.frame 11 | 12 | class PiCamera(BaseCamera): 13 | def __init__(self, resolution=(120, 160), framerate=20): 14 | from picamera.array import PiRGBArray 15 | from picamera import PiCamera 16 | resolution = (resolution[1], resolution[0]) 17 | # initialize the camera and stream 18 | self.camera = PiCamera() #PiCamera gets resolution (height, width) 19 | self.camera.resolution = resolution 20 | self.camera.framerate = framerate 21 | self.rawCapture = PiRGBArray(self.camera, size=resolution) 22 | self.stream = self.camera.capture_continuous(self.rawCapture, 23 | format="rgb", use_video_port=True) 24 | 25 | # initialize the frame and the variable used to indicate 26 | # if the thread should be stopped 27 | self.frame = None 28 | self.on = True 29 | 30 | print('PiCamera loaded.. .warming camera') 31 | time.sleep(2) 32 | 33 | 34 | def run(self): 35 | f = next(self.stream) 36 | frame = f.array 37 | self.rawCapture.truncate(0) 38 | return frame 39 | 40 | def update(self): 41 | # keep looping infinitely until the thread is stopped 42 | for f in self.stream: 43 | # grab the frame from the stream and clear the stream in 44 | # preparation for the next frame 45 | self.frame = f.array 46 | self.rawCapture.truncate(0) 47 | 48 | # if the thread indicator variable is set, stop the thread 49 | if not self.on: 50 | break 51 | 52 | def shutdown(self): 53 | # indicate that the thread should be stopped 54 | self.on = False 55 | print('stoping PiCamera') 56 | time.sleep(.5) 57 | self.stream.close() 58 | self.rawCapture.close() 59 | self.camera.close() 60 | 61 | class Webcam(BaseCamera): 62 | def __init__(self, resolution = (160, 120), framerate = 20): 63 | import pygame 64 | import pygame.camera 65 | 66 | super().__init__() 67 | 68 | pygame.init() 69 | pygame.camera.init() 70 | l = pygame.camera.list_cameras() 71 | self.cam = pygame.camera.Camera(l[0], resolution, "RGB") 72 | self.resolution = resolution 73 | self.cam.start() 74 | self.framerate = framerate 75 | 76 | # initialize variable used to indicate 77 | # if the thread should be stopped 78 | self.frame = None 79 | self.on = True 80 | 81 | print('WebcamVideoStream loaded.. .warming camera') 82 | 83 | time.sleep(2) 84 | 85 | def update(self): 86 | from datetime import datetime, timedelta 87 | import pygame.image 88 | while self.on: 89 | start = datetime.now() 90 | 91 | if self.cam.query_image(): 92 | # snapshot = self.cam.get_image() 93 | # self.frame = list(pygame.image.tostring(snapshot, "RGB", False)) 94 | snapshot = self.cam.get_image() 95 | snapshot1 = pygame.transform.scale(snapshot, self.resolution) 96 | self.frame = pygame.surfarray.pixels3d(pygame.transform.rotate(pygame.transform.flip(snapshot1, True, False), 90)) 97 | 98 | stop = datetime.now() 99 | s = 1 / self.framerate - (stop - start).total_seconds() 100 | if s > 0: 101 | time.sleep(s) 102 | 103 | self.cam.stop() 104 | 105 | def run_threaded(self): 106 | return self.frame 107 | 108 | def shutdown(self): 109 | # indicate that the thread should be stopped 110 | self.on = False 111 | print('stoping Webcam') 112 | time.sleep(.5) 113 | 114 | class MockCamera(BaseCamera): 115 | """ 116 | Fake camera. Returns only a single static frame 117 | """ 118 | def __init__(self, resolution=(160, 120), image=None): 119 | if image is not None: 120 | self.frame = image 121 | else: 122 | self.frame = Image.new('RGB', resolution) 123 | 124 | def update(self): 125 | pass 126 | 127 | def shutdown(self): 128 | pass 129 | 130 | class ImageListCamera(BaseCamera): 131 | """ 132 | Use the images from a tub as a fake camera output 133 | """ 134 | def __init__(self, path_mask='~/mycar/data/**/*.jpg'): 135 | self.image_filenames = glob.glob(os.path.expanduser(path_mask), recursive=True) 136 | 137 | def get_image_index(fnm): 138 | sl = os.path.basename(fnm).split('_') 139 | return int(sl[0]) 140 | 141 | """ 142 | I feel like sorting by modified time is almost always 143 | what you want. but if you tared and moved your data around, 144 | sometimes it doesn't preserve a nice modified time. 145 | so, sorting by image index works better, but only with one path. 146 | """ 147 | self.image_filenames.sort(key=get_image_index) 148 | #self.image_filenames.sort(key=os.path.getmtime) 149 | self.num_images = len(self.image_filenames) 150 | print('%d images loaded.' % self.num_images) 151 | print( self.image_filenames[:10]) 152 | self.i_frame = 0 153 | self.frame = None 154 | self.update() 155 | 156 | def update(self): 157 | pass 158 | 159 | def run_threaded(self): 160 | if self.num_images > 0: 161 | self.i_frame = (self.i_frame + 1) % self.num_images 162 | self.frame = Image.open(self.image_filenames[self.i_frame]) 163 | 164 | return np.asarray(self.frame) 165 | 166 | def shutdown(self): 167 | pass 168 | -------------------------------------------------------------------------------- /donkeycar/tests/test_tub.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import pytest 4 | import tempfile 5 | import tarfile 6 | from PIL import Image 7 | from donkeycar.parts.datastore import Tub 8 | from .setup import tub, tub_path, create_sample_record 9 | 10 | 11 | def test_tub_load(tub, tub_path): 12 | """Tub loads from existing tub path.""" 13 | t = Tub(tub_path) 14 | assert t is not None 15 | 16 | 17 | def test_get_last_ix(tub): 18 | assert tub.get_last_ix() == 9 19 | 20 | 21 | def test_get_last_ix_after_adding_new_record(tub): 22 | record = create_sample_record() 23 | tub.put_record(record) 24 | assert tub.get_last_ix() == 10 25 | 26 | 27 | def test_get_last_ix_for_empty_tub(tub_path): 28 | inputs=['cam/image_array', 'angle', 'throttle'] 29 | types=['image_array', 'float', 'float'] 30 | t = Tub(tub_path, inputs=inputs, types=types) 31 | assert t.get_last_ix() == -1 32 | 33 | 34 | def test_get_last_ix_for_one_record(tub_path): 35 | inputs=['cam/image_array', 'angle', 'throttle'] 36 | types=['image_array', 'float', 'float'] 37 | t = Tub(tub_path, inputs=inputs, types=types) 38 | record = create_sample_record() 39 | t.put_record(record) 40 | assert t.get_last_ix() == 0 41 | 42 | 43 | def test_tub_update_df(tub): 44 | """ Tub updats its dataframe """ 45 | tub.update_df() 46 | assert len(tub.df) == 10 47 | 48 | 49 | def test_tub_get_df(tub): 50 | """ Get Tub dataframe """ 51 | df = tub.get_df() 52 | assert len(df) == 10 53 | 54 | 55 | def test_tub_add_record(tub): 56 | """Tub can save a record and then retrieve it.""" 57 | rec_in = create_sample_record() 58 | rec_index = tub.put_record(rec_in) 59 | rec_out = tub.get_record(rec_index-1) 60 | assert rec_in.keys() == rec_out.keys() 61 | 62 | 63 | def test_tub_get_num_records(tub): 64 | """ Get nbr of records in Tub """ 65 | cnt = tub.get_num_records() 66 | assert cnt == 10 67 | 68 | 69 | def test_tub_check_removes_illegal_records(tub): 70 | """ Get Tub dataframe """ 71 | record = tub.get_json_record_path(tub.get_last_ix()) 72 | with open(record, 'w') as f: 73 | f.write('illegal json data') 74 | assert tub.get_num_records() == 10 75 | 76 | tub.check(fix=True) 77 | assert tub.get_num_records() == 9 78 | 79 | 80 | def test_tub_remove_record(tub): 81 | """ Remove record from tub """ 82 | assert tub.get_num_records() == 10 83 | tub.remove_record(0) 84 | assert tub.get_num_records() == 9 85 | 86 | 87 | def test_tub_put_image(tub_path): 88 | """ Add an encoded image to the tub """ 89 | inputs = ['user/speed', 'cam/image'] 90 | types = ['float', 'image'] 91 | img = Image.new('RGB', (120, 160)) 92 | t=Tub(path=tub_path, inputs=inputs, types=types) 93 | t.put_record({'cam/image': img, 'user/speed': 0.2, }) 94 | assert t.get_record(t.get_last_ix())['user/speed'] == 0.2 95 | 96 | 97 | def test_tub_put_unknown_type(tub_path): 98 | """ Creating a record with unknown type should fail """ 99 | inputs = ['user/speed'] 100 | types = ['bob'] 101 | t=Tub(path=tub_path, inputs=inputs, types=types) 102 | with pytest.raises(TypeError): 103 | t.put_record({'user/speed': 0.2, }) 104 | 105 | 106 | def test_delete_tub(tub): 107 | """ Delete the tub content """ 108 | assert tub.get_num_records() == 10 109 | tub.delete() 110 | assert tub.get_num_records() == 0 111 | 112 | 113 | def test_get_record_gen(tub): 114 | """ Create a records generator and pull 20 records from it """ 115 | records = tub.get_record_gen() 116 | assert len([ next(records) for x in range(20) ]) == 20 117 | 118 | 119 | def test_get_batch_gen(tub): 120 | """ Create a batch generator and pull 1 batch (128) records from it """ 121 | batches = tub.get_batch_gen() 122 | batch = next(batches) 123 | 124 | assert len( batch.keys() ) == 3 125 | assert len( list( batch.values() )[0] ) == 128 126 | 127 | 128 | def test_get_train_val_gen(tub): 129 | """ Create training and validation generators. """ 130 | x = ['angle', 'throttle'] 131 | y = ['cam/image_array'] 132 | train_gen, val_gen = tub.get_train_val_gen(x, y) 133 | 134 | train_batch = next(train_gen) 135 | assert len(train_batch) 136 | 137 | # X is a list of all requested features (angle & throttle) 138 | X = train_batch[0] 139 | assert len(X) == 2 140 | assert len(X[0]) == 128 141 | assert len(X[1]) == 128 142 | 143 | # Y is a list of all requested labels (image_array) 144 | Y = train_batch[1] 145 | assert len(Y) == 1 146 | assert len(Y[0]) == 128 147 | 148 | val_batch = next(val_gen) 149 | # X is a list of all requested features (angle & throttle) 150 | X = val_batch[0] 151 | assert len(X) == 2 152 | assert len(X[0]) == 128 153 | assert len(X[1]) == 128 154 | 155 | # Y is a list of all requested labels (image_array) 156 | Y = train_batch[1] 157 | assert len(Y) == 1 158 | assert len(Y[0]) == 128 159 | 160 | 161 | def test_tar_records(tub): 162 | """ Tar all records in the tub """ 163 | with tempfile.TemporaryDirectory() as tmpdirname: 164 | tar_path = os.path.join(tmpdirname, 'tub.tar.gz') 165 | tub.tar_records(tar_path) 166 | 167 | with tarfile.open(name=tar_path, mode='r') as t: 168 | assert len(t.getnames()) == 11 169 | 170 | 171 | def test_recreating_tub(tub): 172 | """ Recreating a Tub should restore it to working state """ 173 | assert tub.get_num_records() == 10 174 | assert tub.current_ix == 10 175 | assert tub.get_last_ix() == 9 176 | path = tub.path 177 | tub = None 178 | 179 | inputs=['cam/image_array', 'angle', 'throttle'] 180 | types=['image_array', 'float', 'float'] 181 | t = Tub(path, inputs=inputs, types=types) 182 | assert t.get_num_records() == 10 183 | assert t.current_ix == 10 184 | assert t.get_last_ix() == 9 -------------------------------------------------------------------------------- /donkeycar/templates/square.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Web controller. 5 | 6 | This example shows how a user use a web controller to controll 7 | a square that move around the image frame. 8 | 9 | 10 | Usage: 11 | manage.py (drive) [--model=] 12 | manage.py (train) [--tub=] (--model=) 13 | 14 | """ 15 | 16 | 17 | import os 18 | from docopt import docopt 19 | import donkeycar as dk 20 | 21 | from donkeycar.parts.datastore import TubGroup, TubWriter 22 | from donkeycar.parts.transform import Lambda 23 | from donkeycar.parts.simulation import SquareBoxCamera 24 | from donkeycar.parts.controller import LocalWebController 25 | from donkeycar.parts.keras import KerasCategorical 26 | from donkeycar.parts.clock import Timestamp 27 | 28 | log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sq.log') 29 | dk.log.setup(log_path) 30 | logger = dk.log.get_logger(__name__) 31 | logger.info('Loading manage.py') 32 | 33 | 34 | def drive(cfg, model_path=None): 35 | 36 | V = dk.vehicle.Vehicle() 37 | V.mem.put(['square/angle', 'square/throttle'], (100, 100)) 38 | 39 | # display square box given by cooridantes. 40 | cam = SquareBoxCamera(resolution=cfg.CAMERA_RESOLUTION) 41 | V.add(cam, 42 | inputs=['square/angle', 'square/throttle'], 43 | outputs=['cam/image_array']) 44 | 45 | # display the image and read user values from a local web controller 46 | ctr = LocalWebController() 47 | V.add(ctr, 48 | inputs=['cam/image_array'], 49 | outputs=['user/angle', 'user/throttle', 50 | 'user/mode', 'recording'], 51 | threaded=True) 52 | 53 | # See if we should even run the pilot module. 54 | # This is only needed because the part run_contion only accepts boolean 55 | def pilot_condition(mode): 56 | if mode == 'user': 57 | return False 58 | else: 59 | return True 60 | 61 | pilot_condition_part = Lambda(pilot_condition) 62 | V.add(pilot_condition_part, inputs=['user/mode'], outputs=['run_pilot']) 63 | 64 | # Run the pilot if the mode is not user. 65 | kl = KerasCategorical() 66 | if model_path: 67 | kl.load(model_path) 68 | 69 | V.add(kl, inputs=['cam/image_array'], 70 | outputs=['pilot/angle', 'pilot/throttle'], 71 | run_condition='run_pilot') 72 | 73 | # See if we should even run the pilot module. 74 | def drive_mode(mode, 75 | user_angle, user_throttle, 76 | pilot_angle, pilot_throttle): 77 | if mode == 'user': 78 | return user_angle, user_throttle 79 | 80 | elif mode == 'pilot_angle': 81 | return pilot_angle, user_throttle 82 | 83 | else: 84 | return pilot_angle, pilot_throttle 85 | 86 | drive_mode_part = Lambda(drive_mode) 87 | V.add(drive_mode_part, 88 | inputs=['user/mode', 'user/angle', 'user/throttle', 89 | 'pilot/angle', 'pilot/throttle'], 90 | outputs=['angle', 'throttle']) 91 | 92 | clock = Timestamp() 93 | V.add(clock, outputs=['timestamp']) 94 | 95 | # transform angle and throttle values to coordinate values 96 | def f(x): 97 | return int(x * 100 + 100) 98 | l = Lambda(f) 99 | V.add(l, inputs=['user/angle'], outputs=['square/angle']) 100 | V.add(l, inputs=['user/throttle'], outputs=['square/throttle']) 101 | 102 | # add tub to save data 103 | inputs=['cam/image_array', 104 | 'user/angle', 'user/throttle', 105 | 'pilot/angle', 'pilot/throttle', 106 | 'square/angle', 'square/throttle', 107 | 'user/mode', 108 | 'timestamp'] 109 | types=['image_array', 110 | 'float', 'float', 111 | 'float', 'float', 112 | 'float', 'float', 113 | 'str', 114 | 'str'] 115 | 116 | #multiple tubs 117 | #th = TubHandler(path=cfg.DATA_PATH) 118 | #tub = th.new_tub_writer(inputs=inputs, types=types) 119 | 120 | # single tub 121 | tub = TubWriter(path=cfg.TUB_PATH, inputs=inputs, types=types) 122 | V.add(tub, inputs=inputs, run_condition='recording') 123 | 124 | 125 | # run the vehicle for 20 seconds 126 | V.start(rate_hz=50, max_loop_count=10000) 127 | 128 | 129 | def train(cfg, tub_names, model_name): 130 | 131 | X_keys = ['cam/image_array'] 132 | y_keys = ['user/angle', 'user/throttle'] 133 | 134 | 135 | def rt(record): 136 | record['user/angle'] = donkeycar.utils.utils.linear_bin(record['user/angle']) 137 | return record 138 | 139 | def combined_gen(gens): 140 | import itertools 141 | combined_gen = itertools.chain() 142 | for gen in gens: 143 | combined_gen = itertools.chain(combined_gen, gen) 144 | return combined_gen 145 | 146 | kl = KerasCategorical() 147 | logger.info('tub_names', tub_names) 148 | if not tub_names: 149 | tub_names = os.path.join(cfg.DATA_PATH, '*') 150 | tubgroup = TubGroup(tub_names) 151 | train_gen, val_gen = tubgroup.get_train_val_gen(X_keys, y_keys, record_transform=rt, 152 | batch_size=cfg.BATCH_SIZE, 153 | train_frac=cfg.TRAIN_TEST_SPLIT) 154 | 155 | model_path = os.path.expanduser(model_name) 156 | 157 | total_records = len(tubgroup.df) 158 | total_train = int(total_records * cfg.TRAIN_TEST_SPLIT) 159 | total_val = total_records - total_train 160 | logger.info('train: %d, validation: %d' % (total_train, total_val)) 161 | steps_per_epoch = total_train // cfg.BATCH_SIZE 162 | logger.ino('steps_per_epoch', steps_per_epoch) 163 | 164 | kl.train(train_gen, 165 | val_gen, 166 | saved_model_path=model_path, 167 | steps=steps_per_epoch, 168 | train_split=cfg.TRAIN_TEST_SPLIT) 169 | 170 | 171 | 172 | if __name__ == '__main__': 173 | args = docopt(__doc__) 174 | cfg = dk.load_config() 175 | 176 | if args['drive']: 177 | drive(cfg, args['--model']) 178 | 179 | elif args['train']: 180 | tub = args['--tub'] 181 | model = args['--model'] 182 | train(cfg, tub, model) 183 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/simulation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 17:30:28 2017 5 | 6 | @author: wroscoe 7 | """ 8 | 9 | 10 | 11 | 12 | import base64 13 | import random 14 | import numpy as np 15 | import socketio 16 | import eventlet 17 | import eventlet.wsgi 18 | from PIL import Image 19 | from flask import Flask 20 | from io import BytesIO 21 | import time 22 | 23 | 24 | class FPSTimer(object): 25 | def __init__(self): 26 | self.t = time.time() 27 | self.iter = 0 28 | 29 | def reset(self): 30 | self.t = time.time() 31 | self.iter = 0 32 | 33 | def on_frame(self): 34 | self.iter += 1 35 | if self.iter == 100: 36 | e = time.time() 37 | print('fps', 100.0 / (e - self.t)) 38 | self.t = time.time() 39 | self.iter = 0 40 | 41 | 42 | class SteeringServer(object): 43 | """ 44 | A SocketIO based Websocket server designed to integrate with 45 | the Donkey Sim Unity project. Check the donkey branch of 46 | https://github.com/tawnkramer/sdsandbox for source of simulator. 47 | Prebuilt simulators available: 48 | Windows: https://drive.google.com/file/d/0BxSsaxmEV-5YRC1ZWHZ4Y1dZTkE/view?usp=sharing 49 | """ 50 | def __init__(self, _sio, kpart, top_speed=4.0, image_part=None, steering_scale=1.0): 51 | self.model = None 52 | self.timer = FPSTimer() 53 | self.sio = _sio 54 | # TODO: convert this flask app to a tornado app to minimize dependencies. 55 | self.app = Flask(__name__) 56 | self.kpart = kpart 57 | self.image_part = image_part 58 | self.steering_scale = steering_scale 59 | self.top_speed = top_speed 60 | 61 | def throttle_control(self, last_steering, last_throttle, speed, nn_throttle): 62 | """ 63 | super basic throttle control, derive from this Server and override as needed 64 | """ 65 | if speed < self.top_speed: 66 | return 0.3 67 | 68 | return 0.0 69 | 70 | def telemetry(self, sid, data): 71 | """ 72 | Callback when we get new data from Unity simulator. 73 | We use it to process the image, do a forward inference, 74 | then send controls back to client. 75 | Takes sid (?) and data, a dictionary of json elements. 76 | """ 77 | if data: 78 | # The current steering angle of the car 79 | last_steering = float(data["steering_angle"]) 80 | 81 | # The current throttle of the car 82 | last_throttle = float(data["throttle"]) 83 | 84 | # The current speed of the car 85 | speed = float(data["speed"]) 86 | 87 | # The current image from the center camera of the car 88 | imgString = data["image"] 89 | 90 | # decode string based data into bytes, then to Image 91 | image = Image.open(BytesIO(base64.b64decode(imgString))) 92 | 93 | # then as numpy array 94 | image_array = np.asarray(image) 95 | 96 | # optional change to pre-preocess image before NN sees it 97 | if self.image_part is not None: 98 | image_array = self.image_part.run(image_array) 99 | 100 | # forward pass - inference 101 | steering, throttle = self.kpart.run(image_array) 102 | 103 | # filter throttle here, as our NN doesn't always do a greate job 104 | throttle = self.throttle_control(last_steering, last_throttle, speed, throttle) 105 | 106 | # simulator will scale our steering based on it's angle based input. 107 | # but we have an opportunity for more adjustment here. 108 | steering *= self.steering_scale 109 | 110 | # send command back to Unity simulator 111 | self.send_control(steering, throttle) 112 | 113 | else: 114 | # NOTE: DON'T EDIT THIS. 115 | self.sio.emit('manual', data={}, skip_sid=True) 116 | 117 | self.timer.on_frame() 118 | 119 | def connect(self, sid, environ): 120 | print("connect ", sid) 121 | self.timer.reset() 122 | self.send_control(0, 0) 123 | 124 | def send_control(self, steering_angle, throttle): 125 | self.sio.emit( 126 | "steer", 127 | data={ 128 | 'steering_angle': steering_angle.__str__(), 129 | 'throttle': throttle.__str__() 130 | }, 131 | skip_sid=True) 132 | 133 | def go(self, address): 134 | 135 | # wrap Flask application with engineio's middleware 136 | self.app = socketio.Middleware(self.sio, self.app) 137 | 138 | # deploy as an eventlet WSGI server 139 | try: 140 | eventlet.wsgi.server(eventlet.listen(address), self.app) 141 | 142 | except KeyboardInterrupt: 143 | # unless some hits Ctrl+C and then we get this interrupt 144 | print('stopping') 145 | 146 | 147 | class MovingSquareTelemetry: 148 | """ 149 | Generator of cordinates of a bouncing moving square for simulations. 150 | """ 151 | def __init__(self, max_velocity=29, 152 | x_min = 10, x_max=150, 153 | y_min = 10, y_max=110): 154 | 155 | self.velocity = random.random() * max_velocity 156 | 157 | self.x_min, self.x_max = x_min, x_max 158 | self.y_min, self.y_max = y_min, y_max 159 | 160 | self.x_direction = random.random() * 2 - 1 161 | self.y_direction = random.random() * 2 - 1 162 | 163 | self.x = random.random() * x_max 164 | self.y = random.random() * y_max 165 | 166 | self.tel = self.x, self.y 167 | 168 | def run(self): 169 | #move 170 | self.x += self.x_direction * self.velocity 171 | self.y += self.y_direction * self.velocity 172 | 173 | #make square bounce off walls 174 | if self.y < self.y_min or self.y > self.y_max: 175 | self.y_direction *= -1 176 | if self.x < self.x_min or self.x > self.x_max: 177 | self.x_direction *= -1 178 | 179 | return int(self.x), int(self.y) 180 | 181 | def update(self): 182 | self.tel = self.run() 183 | 184 | def run_threaded(self): 185 | return self.tel 186 | 187 | 188 | class SquareBoxCamera: 189 | """ 190 | Fake camera that returns an image with a square box. 191 | 192 | This can be used to test if a learning algorithm can learn. 193 | """ 194 | 195 | def __init__(self, resolution=(120,160), box_size=4, color=(255, 0, 0)): 196 | self.resolution = resolution 197 | self.box_size = box_size 198 | self.color = color 199 | 200 | 201 | def run(self, x,y, box_size=None, color=None): 202 | """ 203 | Create an image of a square box at a given coordinates. 204 | """ 205 | radius = int((box_size or self.box_size)/2) 206 | color = color or self.color 207 | frame = np.zeros(shape=self.resolution + (3,)) 208 | frame[y - radius: y + radius, 209 | x - radius: x + radius, :] = color 210 | return frame 211 | 212 | 213 | -------------------------------------------------------------------------------- /donkeycar/parts/simulation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Jun 25 17:30:28 2017 5 | 6 | @author: wroscoe 7 | """ 8 | 9 | 10 | 11 | 12 | import base64 13 | import random 14 | import numpy as np 15 | import socketio 16 | import eventlet 17 | import eventlet.wsgi 18 | from PIL import Image 19 | from flask import Flask 20 | from io import BytesIO 21 | import time 22 | 23 | 24 | class FPSTimer(object): 25 | def __init__(self): 26 | self.t = time.time() 27 | self.iter = 0 28 | 29 | def reset(self): 30 | self.t = time.time() 31 | self.iter = 0 32 | 33 | def on_frame(self): 34 | self.iter += 1 35 | if self.iter == 100: 36 | e = time.time() 37 | print('fps', 100.0 / (e - self.t)) 38 | self.t = time.time() 39 | self.iter = 0 40 | 41 | 42 | class SteeringServer(object): 43 | ''' 44 | A SocketIO based Websocket server designed to integrate with 45 | the Donkey Sim Unity project. Check the donkey branch of 46 | https://github.com/tawnkramer/sdsandbox for source of simulator. 47 | Prebuilt simulators available: 48 | Windows: https://drive.google.com/file/d/0BxSsaxmEV-5YRC1ZWHZ4Y1dZTkE/view?usp=sharing 49 | ''' 50 | def __init__(self, _sio, kpart, top_speed=4.0, image_part=None, steering_scale=1.0): 51 | self.model = None 52 | self.timer = FPSTimer() 53 | self.sio = _sio 54 | # TODO: convert this flask app to a tornado app to minimize dependencies. 55 | self.app = Flask(__name__) 56 | self.kpart = kpart 57 | self.image_part = image_part 58 | self.steering_scale = steering_scale 59 | self.top_speed = top_speed 60 | 61 | def throttle_control(self, last_steering, last_throttle, speed, nn_throttle): 62 | ''' 63 | super basic throttle control, derive from this Server and override as needed 64 | ''' 65 | if speed < self.top_speed: 66 | return 0.3 67 | 68 | return 0.0 69 | 70 | def telemetry(self, sid, data): 71 | ''' 72 | Callback when we get new data from Unity simulator. 73 | We use it to process the image, do a forward inference, 74 | then send controls back to client. 75 | Takes sid (?) and data, a dictionary of json elements. 76 | ''' 77 | if data: 78 | # The current steering angle of the car 79 | last_steering = float(data["steering_angle"]) 80 | 81 | # The current throttle of the car 82 | last_throttle = float(data["throttle"]) 83 | 84 | # The current speed of the car 85 | speed = float(data["speed"]) 86 | 87 | # The current image from the center camera of the car 88 | imgString = data["image"] 89 | 90 | # decode string based data into bytes, then to Image 91 | image = Image.open(BytesIO(base64.b64decode(imgString))) 92 | 93 | # then as numpy array 94 | image_array = np.asarray(image) 95 | 96 | # optional change to pre-preocess image before NN sees it 97 | if self.image_part is not None: 98 | image_array = self.image_part.run(image_array) 99 | 100 | # forward pass - inference 101 | steering, throttle = self.kpart.run(image_array) 102 | 103 | # filter throttle here, as our NN doesn't always do a greate job 104 | throttle = self.throttle_control(last_steering, last_throttle, speed, throttle) 105 | 106 | # simulator will scale our steering based on it's angle based input. 107 | # but we have an opportunity for more adjustment here. 108 | steering *= self.steering_scale 109 | 110 | # send command back to Unity simulator 111 | self.send_control(steering, throttle) 112 | 113 | else: 114 | # NOTE: DON'T EDIT THIS. 115 | self.sio.emit('manual', data={}, skip_sid=True) 116 | 117 | self.timer.on_frame() 118 | 119 | def connect(self, sid, environ): 120 | print("connect ", sid) 121 | self.timer.reset() 122 | self.send_control(0, 0) 123 | 124 | def send_control(self, steering_angle, throttle): 125 | self.sio.emit( 126 | "steer", 127 | data={ 128 | 'steering_angle': steering_angle.__str__(), 129 | 'throttle': throttle.__str__() 130 | }, 131 | skip_sid=True) 132 | 133 | def go(self, address): 134 | 135 | # wrap Flask application with engineio's middleware 136 | self.app = socketio.Middleware(self.sio, self.app) 137 | 138 | # deploy as an eventlet WSGI server 139 | try: 140 | eventlet.wsgi.server(eventlet.listen(address), self.app) 141 | 142 | except KeyboardInterrupt: 143 | # unless some hits Ctrl+C and then we get this interrupt 144 | print('stopping') 145 | 146 | 147 | class MovingSquareTelemetry: 148 | """ 149 | Generator of cordinates of a bouncing moving square for simulations. 150 | """ 151 | def __init__(self, max_velocity=29, 152 | x_min = 10, x_max=150, 153 | y_min = 10, y_max=110): 154 | 155 | self.velocity = random.random() * max_velocity 156 | 157 | self.x_min, self.x_max = x_min, x_max 158 | self.y_min, self.y_max = y_min, y_max 159 | 160 | self.x_direction = random.random() * 2 - 1 161 | self.y_direction = random.random() * 2 - 1 162 | 163 | self.x = random.random() * x_max 164 | self.y = random.random() * y_max 165 | 166 | self.tel = self.x, self.y 167 | 168 | def run(self): 169 | #move 170 | self.x += self.x_direction * self.velocity 171 | self.y += self.y_direction * self.velocity 172 | 173 | #make square bounce off walls 174 | if self.y < self.y_min or self.y > self.y_max: 175 | self.y_direction *= -1 176 | if self.x < self.x_min or self.x > self.x_max: 177 | self.x_direction *= -1 178 | 179 | return int(self.x), int(self.y) 180 | 181 | def update(self): 182 | self.tel = self.run() 183 | 184 | def run_threaded(self): 185 | return self.tel 186 | 187 | 188 | class SquareBoxCamera: 189 | """ 190 | Fake camera that returns an image with a square box. 191 | 192 | This can be used to test if a learning algorithm can learn. 193 | """ 194 | 195 | def __init__(self, resolution=(120,160), box_size=4, color=(255, 0, 0)): 196 | self.resolution = resolution 197 | self.box_size = box_size 198 | self.color = color 199 | 200 | 201 | def run(self, x,y, box_size=None, color=None): 202 | """ 203 | Create an image of a square box at a given coordinates. 204 | """ 205 | radius = int((box_size or self.box_size)/2) 206 | color = color or self.color 207 | frame = np.zeros(shape=self.resolution + (3,)) 208 | frame[y - radius: y + radius, 209 | x - radius: x + radius, :] = color 210 | return frame 211 | 212 | 213 | -------------------------------------------------------------------------------- /donkeycar/parts/web_controller/templates/vehicle.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 | 16 |
17 |
18 | 21 | 24 | 27 |
28 |
29 |
30 |
31 |
32 | 38 |
39 |
40 |
41 |
42 |
43 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 | 72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 |
80 |
81 |
82 |
83 | 84 |
85 | 86 |
87 | 93 |
94 |
95 | 98 |
99 |
100 |
101 | 102 |
103 |
104 | 105 |
106 |
107 | 108 |
109 |
110 |
111 |

Click/touch to use joystic.

112 |
113 |
114 |
115 |
116 |
117 | 118 |
119 |
120 |
121 |
122 |
123 | 124 |
125 |
126 |
127 |
128 | 129 | 130 | 161 | 162 | 168 | 169 | {% end %} 170 | -------------------------------------------------------------------------------- /donkeycar/templates/donkey2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Scripts to drive a donkey 2 car and train a model for it. 4 | 5 | Usage: 6 | manage.py (drive) [--model=] [--js] [--chaos] 7 | manage.py (train) [--tub=] (--model=) [--base_model=] [--no_cache] 8 | 9 | Options: 10 | -h --help Show this screen. 11 | --tub TUBPATHS List of paths to tubs. Comma separated. Use quotes to use wildcards. ie "~/tubs/*" 12 | --js Use physical joystick. 13 | --chaos Add periodic random steering when manually driving 14 | """ 15 | import os 16 | from docopt import docopt 17 | 18 | import donkeycar as dk 19 | 20 | #import parts 21 | from donkeycar.parts.camera import PiCamera 22 | from donkeycar.parts.transform import Lambda 23 | from donkeycar.parts.keras import KerasLinear 24 | from donkeycar.parts.actuator import PCA9685, PWMSteering, PWMThrottle 25 | from donkeycar.parts.datastore import TubGroup, TubWriter 26 | from donkeycar.parts.controller import LocalWebController, JoystickController 27 | from donkeycar.parts.clock import Timestamp 28 | from tensorflow.python.keras.models import Model, load_model 29 | 30 | def drive(cfg, model_path=None, use_joystick=False, use_chaos=False): 31 | """ 32 | Construct a working robotic vehicle from many parts. 33 | Each part runs as a job in the Vehicle loop, calling either 34 | it's run or run_threaded method depending on the constructor flag `threaded`. 35 | All parts are updated one after another at the framerate given in 36 | cfg.DRIVE_LOOP_HZ assuming each part finishes processing in a timely manner. 37 | Parts may have named outputs and inputs. The framework handles passing named outputs 38 | to parts requesting the same named input. 39 | """ 40 | 41 | V = dk.vehicle.Vehicle() 42 | 43 | clock = Timestamp() 44 | V.add(clock, outputs='timestamp') 45 | 46 | cam = PiCamera(resolution=cfg.CAMERA_RESOLUTION) 47 | V.add(cam, outputs=['cam/image_array'], threaded=True) 48 | 49 | if use_joystick or cfg.USE_JOYSTICK_AS_DEFAULT: 50 | ctr = JoystickController(max_throttle=cfg.JOYSTICK_MAX_THROTTLE, 51 | steering_scale=cfg.JOYSTICK_STEERING_SCALE, 52 | auto_record_on_throttle=cfg.AUTO_RECORD_ON_THROTTLE) 53 | else: 54 | # This web controller will create a web server that is capable 55 | # of managing steering, throttle, and modes, and more. 56 | ctr = LocalWebController(use_chaos=use_chaos) 57 | 58 | V.add(ctr, 59 | inputs=['cam/image_array'], 60 | outputs=['user/angle', 'user/throttle', 'user/mode', 'recording'], 61 | threaded=True) 62 | 63 | # See if we should even run the pilot module. 64 | # This is only needed because the part run_condition only accepts boolean 65 | def pilot_condition(mode): 66 | if mode == 'user': 67 | return False 68 | else: 69 | return True 70 | 71 | pilot_condition_part = Lambda(pilot_condition) 72 | V.add(pilot_condition_part, 73 | inputs=['user/mode'], 74 | outputs=['run_pilot']) 75 | 76 | # Run the pilot if the mode is not user. 77 | kl = KerasLinear() 78 | if model_path: 79 | kl.load(model_path) 80 | 81 | V.add(kl, 82 | inputs=['cam/image_array'], 83 | outputs=['pilot/angle', 'pilot/throttle'], 84 | run_condition='run_pilot') 85 | 86 | # Choose what inputs should change the car. 87 | def drive_mode(mode, 88 | user_angle, user_throttle, 89 | pilot_angle, pilot_throttle): 90 | if mode == 'user': 91 | return user_angle, user_throttle 92 | 93 | elif mode == 'local_angle': 94 | return pilot_angle, user_throttle 95 | 96 | else: 97 | return pilot_angle, pilot_throttle 98 | 99 | drive_mode_part = Lambda(drive_mode) 100 | V.add(drive_mode_part, 101 | inputs=['user/mode', 'user/angle', 'user/throttle', 102 | 'pilot/angle', 'pilot/throttle'], 103 | outputs=['angle', 'throttle']) 104 | 105 | steering_controller = PCA9685(cfg.STEERING_CHANNEL) 106 | steering = PWMSteering(controller=steering_controller, 107 | left_pulse=cfg.STEERING_LEFT_PWM, 108 | right_pulse=cfg.STEERING_RIGHT_PWM) 109 | 110 | throttle_controller = PCA9685(cfg.THROTTLE_CHANNEL) 111 | throttle = PWMThrottle(controller=throttle_controller, 112 | max_pulse=cfg.THROTTLE_FORWARD_PWM, 113 | zero_pulse=cfg.THROTTLE_STOPPED_PWM, 114 | min_pulse=cfg.THROTTLE_REVERSE_PWM) 115 | 116 | V.add(steering, inputs=['angle']) 117 | V.add(throttle, inputs=['throttle']) 118 | 119 | # add tub to save data 120 | inputs = ['cam/image_array', 'user/angle', 'user/throttle', 'user/mode', 'timestamp'] 121 | types = ['image_array', 'float', 'float', 'str', 'str'] 122 | 123 | #multiple tubs 124 | #th = TubHandler(path=cfg.DATA_PATH) 125 | #tub = th.new_tub_writer(inputs=inputs, types=types) 126 | 127 | # single tub 128 | tub = TubWriter(path=cfg.TUB_PATH, inputs=inputs, types=types) 129 | V.add(tub, inputs=inputs, run_condition='recording') 130 | 131 | # run the vehicle 132 | V.start(rate_hz=cfg.DRIVE_LOOP_HZ, 133 | max_loop_count=cfg.MAX_LOOPS) 134 | 135 | 136 | 137 | 138 | def train(cfg, tub_names, new_model_path, base_model_path=None ): 139 | """ 140 | use the specified data in tub_names to train an artifical neural network 141 | saves the output trained model as model_name 142 | """ 143 | X_keys = ['cam/image_array'] 144 | y_keys = ['user/angle', 'user/throttle'] 145 | def train_record_transform(record): 146 | """ convert categorical steering to linear and apply image augmentations """ 147 | record['user/angle'] = dk.util.data.linear_bin(record['user/angle']) 148 | # TODO add augmentation that doesn't use opencv 149 | return record 150 | 151 | def val_record_transform(record): 152 | """ convert categorical steering to linear """ 153 | record['user/angle'] = dk.util.data.linear_bin(record['user/angle']) 154 | return record 155 | 156 | new_model_path = os.path.expanduser(new_model_path) 157 | 158 | kl = KerasLinear() 159 | if base_model_path is not None: 160 | base_model_path = os.path.expanduser(base_model_path) 161 | kl.load(base_model_path) 162 | 163 | print('tub_names', tub_names) 164 | if not tub_names: 165 | tub_names = os.path.join(cfg.DATA_PATH, '*') 166 | tubgroup = TubGroup(tub_names) 167 | train_gen, val_gen = tubgroup.get_train_val_gen(X_keys, y_keys, 168 | batch_size=cfg.BATCH_SIZE, 169 | train_frac=cfg.TRAIN_TEST_SPLIT) 170 | 171 | total_records = len(tubgroup.df) 172 | total_train = int(total_records * cfg.TRAIN_TEST_SPLIT) 173 | total_val = total_records - total_train 174 | print('train: %d, validation: %d' % (total_train, total_val)) 175 | steps_per_epoch = total_train // cfg.BATCH_SIZE 176 | print('steps_per_epoch', steps_per_epoch) 177 | 178 | kl.train(train_gen, 179 | val_gen, 180 | saved_model_path=new_model_path, 181 | steps=steps_per_epoch, 182 | train_split=cfg.TRAIN_TEST_SPLIT) 183 | 184 | 185 | if __name__ == '__main__': 186 | args = docopt(__doc__) 187 | cfg = dk.load_config() 188 | 189 | if args['drive']: 190 | drive(cfg, model_path = args['--model'], use_joystick=args['--js'], use_chaos=args['--chaos']) 191 | 192 | elif args['train']: 193 | tub = args['--tub'] 194 | new_model_path = args['--model'] 195 | base_model_path = args['--base_model'] 196 | cache = not args['--no_cache'] 197 | train(cfg, tub, new_model_path, base_model_path) 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /donkeycar/management/tub_web/static/tub.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | var tubId = window.location.href.split('/').slice(-1)[0]; 3 | 4 | var clips = []; 5 | var selectedClipIdx = 0; 6 | var currentFrameIdx = 0; 7 | var playing = null; 8 | 9 | var selectedClip = function() { 10 | return clips[selectedClipIdx]; 11 | }; 12 | 13 | var pause = function() { 14 | if (playing) { 15 | clearInterval(playing); 16 | playing = null; 17 | } 18 | updateStreamControls(); 19 | }; 20 | 21 | var play = function() { 22 | if (playing === null) { 23 | playing = setInterval(function(){ 24 | currentFrameIdx ++; 25 | if (currentFrameIdx >= selectedClip().frames.length) { 26 | currentFrameIdx = 0; 27 | clearInterval(playing); 28 | playing = null; 29 | updateStreamControls(); 30 | } 31 | updateStreamImg(); 32 | updatePreviewProgress(); 33 | }, 1000/$('#preview-speed').val()); 34 | } 35 | updateStreamControls(); 36 | }; 37 | 38 | var getTub = function(tId, cb) { 39 | $.getJSON('/api/tubs/' + tubId, function( data ) { 40 | clips = data.clips.map(function(clip) { 41 | return {frames: clip, markedToDelete: false}; 42 | }); 43 | selectedClipIdx = 0; 44 | updateStreamImg(); 45 | updateClipTable(); 46 | }); 47 | }; 48 | 49 | // UI elements update 50 | var updateStreamImg = function() { 51 | var curFrame = selectedClip().frames[currentFrameIdx]; 52 | $('#img-preview').attr('src', '/tub_data/' + tubId + '/' + curFrame + '_cam-image_array_.jpg'); 53 | $('#cur-frame').text(curFrame); 54 | $.getJSON('/tub_data/' + tubId + '/' + 'record_' + curFrame + '.json', function(data) { 55 | var angle = data["user/angle"]; 56 | var steeringPercent = Math.round(Math.abs(angle) * 100) + '%'; 57 | var steeringRounded = angle.toFixed(2) 58 | 59 | $('.steering-bar .progress-bar').css('width', '0%').html(''); 60 | if(angle < 0) { 61 | $('#angle-bar-backward').css('width', steeringPercent).html(steeringRounded) 62 | } 63 | if (angle > 0) { 64 | $('#angle-bar-forward').css('width', steeringPercent).html(steeringRounded) 65 | } 66 | }); 67 | }; 68 | 69 | var updateStreamControls = function() { 70 | if (playing) { 71 | $('button#play-stream').switchClass("btn-primary", "btn-danger", 0).html(' Pause'); 72 | } else { 73 | $('button#play-stream').switchClass("btn-danger", "btn-primary", 0).html(' Play'); 74 | } 75 | }; 76 | 77 | var updateClipTable = function() { 78 | $('tbody#clips tr').remove(); 79 | clips.forEach(function(clip, i) { 80 | clz = i === selectedClipIdx ? 'active' : ''; 81 | $('tbody#clips').append('' + playBtnOfClip(i) + '' + thumnailsOfClip(i) + '' + checkboxOfClip(i) + ''); 82 | $('#mark-to-delete-' + i).click(function() {toggleMarkToDelete(i);}); 83 | $('#play-clip-' + i).click(function() {playClipBtnClicked(i);}); 84 | }); 85 | }; 86 | 87 | var playBtnOfClip = function(clipIdx) { 88 | return ''; 89 | }; 90 | 91 | var checkboxOfClip = function(clipIdx) { 92 | var frames = clips[clipIdx].frames; 93 | if (clips[clipIdx].markedToDelete) { 94 | return ''; 95 | } else { 96 | return ''; 97 | } 98 | }; 99 | 100 | var thumnailsOfClip = function(clipIdx) { 101 | var frames = clips[clipIdx].frames; 102 | var html = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15].map(function(i) { 103 | return Math.round(frames.length/16*i); 104 | }) 105 | .map(function(frameIdx) { 106 | return ''; 107 | }) 108 | .join(''); 109 | 110 | if (clipIdx === selectedClipIdx) { 111 | html += previewProgress(); 112 | } 113 | 114 | return html; 115 | }; 116 | 117 | var previewProgress = function() { 118 | return '\ 119 |
\ 120 |
\ 122 |
\ 123 |
'; 124 | }; 125 | 126 | var updatePreviewProgress = function() { 127 | var progress = currentFrameIdx*100/selectedClip().frames.length; 128 | $('#preview-progress').css('width', progress+'%').attr('aria-valuenow', progress); 129 | }; 130 | 131 | 132 | // UI event handlers 133 | var playBtnClicked = function(event) { 134 | if (playing) { 135 | pause(); 136 | } else { 137 | play(); 138 | } 139 | }; 140 | 141 | var rewindBtnClicked = function(event) { 142 | currentFrameIdx -= 10; 143 | if (currentFrameIdx < 0) { 144 | currentFrameIdx = 0; 145 | } 146 | updateStreamImg(); 147 | }; 148 | 149 | var splitBtnClicked = function(event) { 150 | if (currentFrameIdx === 0 || currentFrameIdx >= selectedClip().frames.length-1) { 151 | return; 152 | } 153 | 154 | clip = selectedClip(); 155 | frames = clip.frames.splice(currentFrameIdx, clip.frames.length); // Remove frames from currentFrameIdx and assign them to another array 156 | selectedClipIdx++; 157 | clips.splice(selectedClipIdx, 0, {frames: frames, markedToDelete: false}); //Javascript's way of inserting to array at index 158 | currentFrameIdx = 0; 159 | 160 | updateStreamImg(); 161 | updateClipTable(); 162 | }; 163 | 164 | var toggleMarkToDelete = function(clipIdx) { 165 | clips[clipIdx].markedToDelete = !clips[clipIdx].markedToDelete; 166 | updateClipTable(); 167 | } 168 | 169 | var playClipBtnClicked = function(clipIdx) { 170 | pause(); 171 | selectedClipIdx = clipIdx; 172 | currentFrameIdx = 0; 173 | play(); 174 | updateClipTable(); 175 | }; 176 | 177 | var submitBtnClicked = function() { 178 | $('button#submit').prop('disabled', true); 179 | var clipsToKeep = clips.filter(function(clip) { 180 | return !clip.markedToDelete; 181 | }) 182 | .map(function(clip) { 183 | return clip.frames; 184 | }); 185 | 186 | $.ajax({ 187 | type: 'POST', 188 | url: '/api/tubs/' + tubId, 189 | data: JSON.stringify({clips: clipsToKeep}), 190 | contentType: "application/json", 191 | dataType: 'json', 192 | complete: function() { 193 | location.reload(); 194 | } 195 | }); 196 | } 197 | 198 | getTub(); 199 | 200 | $('button#play-stream').click(playBtnClicked); 201 | $('button#split-stream').click(splitBtnClicked); 202 | $('button#rewind-stream').click(rewindBtnClicked); 203 | $('button#submit').click(submitBtnClicked); 204 | $(document).keydown(function(e) { 205 | switch(e.which) { 206 | case 32: // space 207 | playBtnClicked(); 208 | break; 209 | 210 | case 66: // 'b' 211 | rewindBtnClicked(); 212 | break; 213 | 214 | case 67: // 'c' 215 | splitBtnClicked(); 216 | break; 217 | 218 | default: return; // exit this handler for other keys 219 | } 220 | e.preventDefault(); // prevent the default action (scroll / move caret) 221 | }); 222 | }); 223 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/actuator.py: -------------------------------------------------------------------------------- 1 | """ 2 | actuators.py 3 | Classes to control the motors and servos. These classes 4 | are wrapped in a mixer class before being used in the drive loop. 5 | """ 6 | 7 | import time 8 | import donkeycar as dk 9 | 10 | class PCA9685: 11 | """ 12 | PWM motor controler using PCA9685 boards. 13 | This is used for most RC Cars 14 | """ 15 | def __init__(self, channel, frequency=60): 16 | import Adafruit_PCA9685 17 | # Initialise the PCA9685 using the default address (0x40). 18 | self.pwm = Adafruit_PCA9685.PCA9685() 19 | self.pwm.set_pwm_freq(frequency) 20 | self.channel = channel 21 | 22 | def set_pulse(self, pulse): 23 | self.pwm.set_pwm(self.channel, 0, pulse) 24 | 25 | def run(self, pulse): 26 | self.set_pulse(pulse) 27 | 28 | class PWMSteering: 29 | """ 30 | Wrapper over a PWM motor cotnroller to convert angles to PWM pulses. 31 | """ 32 | LEFT_ANGLE = -1 33 | RIGHT_ANGLE = 1 34 | 35 | def __init__(self, controller=None, 36 | left_pulse=290, 37 | right_pulse=490): 38 | 39 | self.controller = controller 40 | self.left_pulse = left_pulse 41 | self.right_pulse = right_pulse 42 | 43 | 44 | def run(self, angle): 45 | #map absolute angle to angle that vehicle can implement. 46 | pulse = dk.util.data.map_range(angle, 47 | self.LEFT_ANGLE, self.RIGHT_ANGLE, 48 | self.left_pulse, self.right_pulse) 49 | 50 | self.controller.set_pulse(pulse) 51 | 52 | def shutdown(self): 53 | self.run(0) #set steering straight 54 | 55 | 56 | 57 | class PWMThrottle: 58 | """ 59 | Wrapper over a PWM motor cotnroller to convert -1 to 1 throttle 60 | values to PWM pulses. 61 | """ 62 | MIN_THROTTLE = -1 63 | MAX_THROTTLE = 1 64 | 65 | def __init__(self, controller=None, 66 | max_pulse=300, 67 | min_pulse=490, 68 | zero_pulse=350): 69 | 70 | self.controller = controller 71 | self.max_pulse = max_pulse 72 | self.min_pulse = min_pulse 73 | self.zero_pulse = zero_pulse 74 | 75 | #send zero pulse to calibrate ESC 76 | self.controller.set_pulse(self.zero_pulse) 77 | time.sleep(1) 78 | 79 | 80 | def run(self, throttle): 81 | if throttle > 0: 82 | pulse = dk.util.data.map_range(throttle, 83 | 0, self.MAX_THROTTLE, 84 | self.zero_pulse, self.max_pulse) 85 | else: 86 | pulse = dk.util.data.map_range(throttle, 87 | self.MIN_THROTTLE, 0, 88 | self.min_pulse, self.zero_pulse) 89 | 90 | self.controller.set_pulse(pulse) 91 | 92 | def shutdown(self): 93 | self.run(0) #stop vehicle 94 | 95 | 96 | 97 | class Adafruit_DCMotor_Hat: 98 | """ 99 | Adafruit DC Motor Controller 100 | Used for each motor on a differential drive car. 101 | """ 102 | def __init__(self, motor_num): 103 | from Adafruit_MotorHAT import Adafruit_MotorHAT 104 | import atexit 105 | 106 | self.FORWARD = Adafruit_MotorHAT.FORWARD 107 | self.BACKWARD = Adafruit_MotorHAT.BACKWARD 108 | self.mh = Adafruit_MotorHAT(addr=0x60) 109 | 110 | self.motor = self.mh.getMotor(motor_num) 111 | self.motor_num = motor_num 112 | 113 | atexit.register(self.turn_off_motors) 114 | self.speed = 0 115 | self.throttle = 0 116 | 117 | 118 | def run(self, speed): 119 | """ 120 | Update the speed of the motor where 1 is full forward and 121 | -1 is full backwards. 122 | """ 123 | if speed > 1 or speed < -1: 124 | raise ValueError( "Speed must be between 1(forward) and -1(reverse)") 125 | 126 | self.speed = speed 127 | self.throttle = int(dk.util.data.map_range(abs(speed), -1, 1, -255, 255)) 128 | 129 | if speed > 0: 130 | self.motor.run(self.FORWARD) 131 | else: 132 | self.motor.run(self.BACKWARD) 133 | 134 | self.motor.setSpeed(self.throttle) 135 | 136 | 137 | def shutdown(self): 138 | self.mh.getMotor(self.motor_num).run(Adafruit_MotorHAT.RELEASE) 139 | 140 | class Maestro: 141 | """ 142 | Pololu Maestro Servo controller 143 | Use the MaestroControlCenter to set the speed & acceleration values to 0! 144 | """ 145 | import threading 146 | 147 | maestro_device = None 148 | astar_device = None 149 | maestro_lock = threading.Lock() 150 | astar_lock = threading.Lock() 151 | 152 | def __init__(self, channel, frequency = 60): 153 | import serial 154 | 155 | if Maestro.maestro_device == None: 156 | Maestro.maestro_device = serial.Serial('/dev/ttyACM0', 115200) 157 | 158 | self.channel = channel 159 | self.frequency = frequency 160 | self.lturn = False 161 | self.rturn = False 162 | self.headlights = False 163 | self.brakelights = False 164 | 165 | if Maestro.astar_device == None: 166 | Maestro.astar_device = serial.Serial('/dev/ttyACM2', 115200, timeout= 0.01) 167 | 168 | def set_pulse(self, pulse): 169 | # Recalculate pulse width from the Adafruit values 170 | w = pulse * (1 / (self.frequency * 4096)) # in seconds 171 | w *= 1000 * 1000 # in microseconds 172 | w *= 4 # in quarter microsenconds the maestro wants 173 | w = int(w) 174 | 175 | with Maestro.maestro_lock: 176 | Maestro.maestro_device.write(bytearray([ 0x84, 177 | self.channel, 178 | (w & 0x7F), 179 | ((w >> 7) & 0x7F)])) 180 | 181 | def set_turn_left(self, v): 182 | if self.lturn != v: 183 | self.lturn = v 184 | b = bytearray('L' if v else 'l', 'ascii') 185 | with Maestro.astar_lock: 186 | Maestro.astar_device.write(b) 187 | 188 | def set_turn_right(self, v): 189 | if self.rturn != v: 190 | self.rturn = v 191 | b = bytearray('R' if v else 'r', 'ascii') 192 | with Maestro.astar_lock: 193 | Maestro.astar_device.write(b) 194 | 195 | def set_headlight(self, v): 196 | if self.headlights != v: 197 | self.headlights = v 198 | b = bytearray('H' if v else 'h', 'ascii') 199 | with Maestro.astar_lock: 200 | Maestro.astar_device.write(b) 201 | 202 | def set_brake(self, v): 203 | if self.brakelights != v: 204 | self.brakelights = v 205 | b = bytearray('B' if v else 'b', 'ascii') 206 | with Maestro.astar_lock: 207 | Maestro.astar_device.write(b) 208 | 209 | def readline(self): 210 | ret = None 211 | with Maestro.astar_lock: 212 | # expecting lines like 213 | # E n nnn n 214 | if Maestro.astar_device.inWaiting() > 8: 215 | ret = Maestro.astar_device.readline() 216 | 217 | if ret != None: 218 | ret = ret.rstrip() 219 | 220 | return ret 221 | 222 | class Teensy: 223 | """ 224 | Teensy Servo controller 225 | """ 226 | import threading 227 | 228 | teensy_device = None 229 | astar_device = None 230 | teensy_lock = threading.Lock() 231 | astar_lock = threading.Lock() 232 | 233 | def __init__(self, channel, frequency = 60): 234 | import serial 235 | 236 | if Teensy.teensy_device == None: 237 | Teensy.teensy_device = serial.Serial('/dev/teensy', 115200, timeout = 0.01) 238 | 239 | self.channel = channel 240 | self.frequency = frequency 241 | self.lturn = False 242 | self.rturn = False 243 | self.headlights = False 244 | self.brakelights = False 245 | 246 | if Teensy.astar_device == None: 247 | Teensy.astar_device = serial.Serial('/dev/astar', 115200, timeout = 0.01) 248 | 249 | def set_pulse(self, pulse): 250 | # Recalculate pulse width from the Adafruit values 251 | w = pulse * (1 / (self.frequency * 4096)) # in seconds 252 | w *= 1000 * 1000 # in microseconds 253 | 254 | with Teensy.teensy_lock: 255 | Teensy.teensy_device.write(("%c %.1f\n" % (self.channel, w)).encode('ascii')) 256 | 257 | def set_turn_left(self, v): 258 | if self.lturn != v: 259 | self.lturn = v 260 | b = bytearray('L' if v else 'l', 'ascii') 261 | with Teensy.astar_lock: 262 | Teensy.astar_device.write(b) 263 | 264 | def set_turn_right(self, v): 265 | if self.rturn != v: 266 | self.rturn = v 267 | b = bytearray('R' if v else 'r', 'ascii') 268 | with Teensy.astar_lock: 269 | Teensy.astar_device.write(b) 270 | 271 | def set_headlight(self, v): 272 | if self.headlights != v: 273 | self.headlights = v 274 | b = bytearray('H' if v else 'h', 'ascii') 275 | with Teensy.astar_lock: 276 | Teensy.astar_device.write(b) 277 | 278 | def set_brake(self, v): 279 | if self.brakelights != v: 280 | self.brakelights = v 281 | b = bytearray('B' if v else 'b', 'ascii') 282 | with Teensy.astar_lock: 283 | Teensy.astar_device.write(b) 284 | 285 | def teensy_readline(self): 286 | ret = None 287 | with Teensy.teensy_lock: 288 | # expecting lines like 289 | # E n nnn n 290 | if Teensy.teensy_device.inWaiting() > 8: 291 | ret = Teensy.teensy_device.readline() 292 | 293 | if ret != None: 294 | ret = ret.rstrip() 295 | 296 | return ret 297 | 298 | def astar_readline(self): 299 | ret = None 300 | with Teensy.astar_lock: 301 | # expecting lines like 302 | # E n nnn n 303 | if Teensy.astar_device.inWaiting() > 8: 304 | ret = Teensy.astar_device.readline() 305 | 306 | if ret != None: 307 | ret = ret.rstrip() 308 | 309 | return ret 310 | 311 | class MockController(object): 312 | def __init__(self): 313 | pass 314 | 315 | def run(self, pulse): 316 | pass 317 | 318 | def shutdown(self): 319 | pass 320 | -------------------------------------------------------------------------------- /donkeycar/parts/keras.py: -------------------------------------------------------------------------------- 1 | """" 2 | 3 | keras.py 4 | 5 | functions to run and train autopilots using keras 6 | 7 | """ 8 | 9 | from tensorflow.python.keras.layers import Input 10 | from tensorflow.python.keras.models import Model, load_model 11 | from tensorflow.python.keras.layers import Convolution2D 12 | from tensorflow.python.keras.layers import Dropout, Flatten, Dense, Cropping2D, Lambda 13 | from tensorflow.python.keras.callbacks import ModelCheckpoint, EarlyStopping 14 | 15 | from donkeycar import util 16 | 17 | 18 | class KerasPilot: 19 | 20 | def load(self, model_path): 21 | self.model = load_model(model_path) 22 | 23 | def shutdown(self): 24 | pass 25 | 26 | def train(self, train_gen, val_gen, 27 | saved_model_path, epochs=100, steps=100, train_split=0.8, 28 | verbose=1, min_delta=.0005, patience=5, use_early_stop=True): 29 | """ 30 | train_gen: generator that yields an array of images an array of 31 | 32 | """ 33 | 34 | # checkpoint to save model after each epoch 35 | save_best = ModelCheckpoint(saved_model_path, 36 | monitor='val_loss', 37 | verbose=verbose, 38 | save_best_only=True, 39 | mode='min') 40 | 41 | # stop training if the validation error stops improving. 42 | early_stop = EarlyStopping(monitor='val_loss', 43 | min_delta=min_delta, 44 | patience=patience, 45 | verbose=verbose, 46 | mode='auto') 47 | 48 | callbacks_list = [save_best] 49 | 50 | if use_early_stop: 51 | callbacks_list.append(early_stop) 52 | 53 | hist = self.model.fit_generator( 54 | train_gen, 55 | steps_per_epoch=steps, 56 | epochs=epochs, 57 | verbose=1, 58 | validation_data=val_gen, 59 | callbacks=callbacks_list, 60 | validation_steps=steps * (1.0 - train_split) / train_split) 61 | return hist 62 | 63 | 64 | class KerasCategorical(KerasPilot): 65 | def __init__(self, model=None, *args, **kwargs): 66 | super(KerasCategorical, self).__init__(*args, **kwargs) 67 | if model: 68 | self.model = model 69 | else: 70 | self.model = default_categorical() 71 | 72 | def run(self, img_arr): 73 | img_arr = img_arr.reshape((1,) + img_arr.shape) 74 | angle_binned, throttle = self.model.predict(img_arr) 75 | angle_unbinned = util.data.linear_unbin(angle_binned[0]) 76 | return angle_unbinned, throttle[0][0] 77 | 78 | 79 | class KerasLinear(KerasPilot): 80 | def __init__(self, model=None, num_outputs=None, *args, **kwargs): 81 | super(KerasLinear, self).__init__(*args, **kwargs) 82 | if model: 83 | self.model = model 84 | elif num_outputs is not None: 85 | self.model = default_n_linear(num_outputs) 86 | else: 87 | self.model = default_linear() 88 | 89 | def run(self, img_arr): 90 | img_arr = img_arr.reshape((1,) + img_arr.shape) 91 | outputs = self.model.predict(img_arr) 92 | # print(len(outputs), outputs) 93 | steering = outputs[0] 94 | throttle = outputs[1] 95 | return steering[0][0], throttle[0][0] 96 | 97 | 98 | def default_categorical(): 99 | img_in = Input(shape=(120, 160, 3), 100 | name='img_in') # First layer, input layer, Shape comes from camera.py resolution, RGB 101 | x = img_in 102 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')( 103 | x) # 24 features, 5 pixel x 5 pixel kernel (convolution, feauture) window, 2wx2h stride, relu activation 104 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')( 105 | x) # 32 features, 5px5p kernel window, 2wx2h stride, relu activatiion 106 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')( 107 | x) # 64 features, 5px5p kernal window, 2wx2h stride, relu 108 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')( 109 | x) # 64 features, 3px3p kernal window, 2wx2h stride, relu 110 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')( 111 | x) # 64 features, 3px3p kernal window, 1wx1h stride, relu 112 | 113 | # Possibly add MaxPooling (will make it less sensitive to position in image). Camera angle fixed, so may not to be needed 114 | 115 | x = Flatten(name='flattened')(x) # Flatten to 1D (Fully connected) 116 | x = Dense(100, activation='relu')(x) # Classify the data into 100 features, make all negatives 0 117 | x = Dropout(.1)(x) # Randomly drop out (turn off) 10% of the neurons (Prevent overfitting) 118 | x = Dense(50, activation='relu')(x) # Classify the data into 50 features, make all negatives 0 119 | x = Dropout(.1)(x) # Randomly drop out 10% of the neurons (Prevent overfitting) 120 | # categorical output of the angle 121 | angle_out = Dense(15, activation='softmax', name='angle_out')( 122 | x) # Connect every input with every output and output 15 hidden units. Use Softmax to give percentage. 15 categories and find best one based off percentage 0.0-1.0 123 | 124 | # continous output of throttle 125 | throttle_out = Dense(1, activation='relu', name='throttle_out')(x) # Reduce to 1 number, Positive number only 126 | 127 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 128 | model.compile(optimizer='adam', 129 | loss={'angle_out': 'categorical_crossentropy', 130 | 'throttle_out': 'mean_absolute_error'}, 131 | loss_weights={'angle_out': 0.9, 'throttle_out': .01}) 132 | 133 | return model 134 | 135 | 136 | from tensorflow.python.keras import backend as K 137 | 138 | 139 | def linear_unbin_layer(tnsr): 140 | bin = K.constant((2 / 14), dtype='float32') 141 | norm = K.constant(1, dtype='float32') 142 | 143 | b = K.cast(K.argmax(tnsr), dtype='float32') 144 | a = b - norm 145 | # print('linear_unbin_layer out: {}'.format(a)) 146 | return a 147 | 148 | 149 | def default_catlin(): 150 | """ 151 | Categorial Steering output before linear conversion. 152 | :return: 153 | """ 154 | img_in = Input(shape=(120, 160, 3), 155 | name='img_in') # First layer, input layer, Shape comes from camera.py resolution, RGB 156 | x = img_in 157 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')( 158 | x) # 24 features, 5 pixel x 5 pixel kernel (convolution, feauture) window, 2wx2h stride, relu activation 159 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')( 160 | x) 161 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')( 162 | x) # 64 features, 5px5p kernal window, 2wx2h stride, relu 163 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')( 164 | x) # 64 features, 3px3p kernal window, 2wx2h stride, relu 165 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')( 166 | x) # 64 features, 3px3p kernal window, 1wx1h stride, relu 167 | 168 | # Possibly add MaxPooling (will make it less sensitive to position in image). Camera angle fixed, so may not to be needed 169 | 170 | x = Flatten(name='flattened')(x) # Flatten to 1D (Fully connected) 171 | x = Dense(100, activation='relu')(x) # Classify the data into 100 features, make all negatives 0 172 | x = Dropout(.1)(x) # Randomly drop out (turn off) 10% of the neurons (Prevent overfitting) 173 | x = Dense(50, activation='relu')(x) # Classify the data into 50 features, make all negatives 0 174 | x = Dropout(.1)(x) # Randomly drop out 10% of the neurons (Prevent overfitting) 175 | # categorical output of the angle 176 | angle_cat_out = Dense(15, activation='softmax', name='angle_cat_out')(x) 177 | angle_out = Dense(1, activation='sigmoid', name='angle_out')(angle_cat_out) 178 | # angle_out = Lambda(linear_unbin_layer, output_shape=(1,1, ), name='angle_out')(angle_cat_out) 179 | 180 | # continuous output of throttle 181 | throttle_out = Dense(1, activation='relu', name='throttle_out')(x) # Reduce to 1 number, Positive number only 182 | 183 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 184 | model.compile(optimizer='adam', 185 | loss={'angle_out': 'mean_squared_error', 186 | 'throttle_out': 'mean_absolute_error'}, 187 | loss_weights={'angle_out': 0.9, 'throttle_out': .01}) 188 | 189 | return model 190 | 191 | 192 | def default_linear(): 193 | img_in = Input(shape=(120, 160, 3), name='img_in') 194 | x = img_in 195 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')(x) 196 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')(x) 197 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')(x) 198 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')(x) 199 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 200 | 201 | x = Flatten(name='flattened')(x) 202 | x = Dense(100, activation='linear')(x) 203 | x = Dropout(.1)(x) 204 | x = Dense(50, activation='linear')(x) 205 | x = Dropout(.1)(x) 206 | # categorical output of the angle 207 | angle_out = Dense(1, activation='linear', name='angle_out')(x) 208 | 209 | # continous output of throttle 210 | throttle_out = Dense(1, activation='linear', name='throttle_out')(x) 211 | 212 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 213 | 214 | model.compile(optimizer='adam', 215 | loss={'angle_out': 'mean_squared_error', 216 | 'throttle_out': 'mean_squared_error'}, 217 | loss_weights={'angle_out': 0.5, 'throttle_out': .5}) 218 | 219 | return model 220 | 221 | 222 | def default_n_linear(num_outputs): 223 | img_in = Input(shape=(120, 160, 3), name='img_in') 224 | x = img_in 225 | x = Cropping2D(cropping=((60, 0), (0, 0)))(x) # trim 60 pixels off top 226 | x = Lambda(lambda x: x / 127.5 - 1.)(x) # normalize and re-center 227 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')(x) 228 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')(x) 229 | x = Convolution2D(64, (5, 5), strides=(1, 1), activation='relu')(x) 230 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 231 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 232 | 233 | x = Flatten(name='flattened')(x) 234 | x = Dense(100, activation='relu')(x) 235 | x = Dropout(.1)(x) 236 | x = Dense(50, activation='relu')(x) 237 | x = Dropout(.1)(x) 238 | 239 | outputs = [] 240 | 241 | for i in range(num_outputs): 242 | outputs.append(Dense(1, activation='linear', name='n_outputs' + str(i))(x)) 243 | 244 | model = Model(inputs=[img_in], outputs=outputs) 245 | 246 | model.compile(optimizer='adam', 247 | loss='mse') 248 | 249 | return model 250 | -------------------------------------------------------------------------------- /donkeycar/parts/Backup/keras.py: -------------------------------------------------------------------------------- 1 | """" 2 | 3 | keras.py 4 | 5 | functions to run and train autopilots using keras 6 | 7 | """ 8 | 9 | from tensorflow.python.keras.layers import Input 10 | from tensorflow.python.keras.models import Model, load_model 11 | from tensorflow.python.keras.layers import Convolution2D 12 | from tensorflow.python.keras.layers import Dropout, Flatten, Dense, Cropping2D, Lambda 13 | from tensorflow.python.keras.callbacks import ModelCheckpoint, EarlyStopping 14 | 15 | from donkeycar import util 16 | 17 | 18 | class KerasPilot: 19 | 20 | def load(self, model_path): 21 | self.model = load_model(model_path) 22 | 23 | def shutdown(self): 24 | pass 25 | 26 | def train(self, train_gen, val_gen, 27 | saved_model_path, epochs=100, steps=100, train_split=0.8, 28 | verbose=1, min_delta=.0005, patience=5, use_early_stop=True): 29 | """ 30 | train_gen: generator that yields an array of images an array of 31 | 32 | """ 33 | 34 | # checkpoint to save model after each epoch 35 | save_best = ModelCheckpoint(saved_model_path, 36 | monitor='val_loss', 37 | verbose=verbose, 38 | save_best_only=True, 39 | mode='min') 40 | 41 | # stop training if the validation error stops improving. 42 | early_stop = EarlyStopping(monitor='val_loss', 43 | min_delta=min_delta, 44 | patience=patience, 45 | verbose=verbose, 46 | mode='auto') 47 | 48 | callbacks_list = [save_best] 49 | 50 | if use_early_stop: 51 | callbacks_list.append(early_stop) 52 | 53 | hist = self.model.fit_generator( 54 | train_gen, 55 | steps_per_epoch=steps, 56 | epochs=epochs, 57 | verbose=1, 58 | validation_data=val_gen, 59 | callbacks=callbacks_list, 60 | validation_steps=steps * (1.0 - train_split) / train_split) 61 | return hist 62 | 63 | 64 | class KerasCategorical(KerasPilot): 65 | def __init__(self, model=None, *args, **kwargs): 66 | super(KerasCategorical, self).__init__(*args, **kwargs) 67 | if model: 68 | self.model = model 69 | else: 70 | self.model = default_categorical() 71 | 72 | def run(self, img_arr): 73 | img_arr = img_arr.reshape((1,) + img_arr.shape) 74 | angle_binned, throttle = self.model.predict(img_arr) 75 | angle_unbinned = util.data.linear_unbin(angle_binned[0]) 76 | return angle_unbinned, throttle[0][0] 77 | 78 | 79 | class KerasLinear(KerasPilot): 80 | def __init__(self, model=None, num_outputs=None, *args, **kwargs): 81 | super(KerasLinear, self).__init__(*args, **kwargs) 82 | if model: 83 | self.model = model 84 | elif num_outputs is not None: 85 | self.model = default_n_linear(num_outputs) 86 | else: 87 | self.model = default_linear() 88 | 89 | def run(self, img_arr): 90 | img_arr = img_arr.reshape((1,) + img_arr.shape) 91 | outputs = self.model.predict(img_arr) 92 | # print(len(outputs), outputs) 93 | steering = outputs[0] 94 | throttle = outputs[1] 95 | return steering[0][0], throttle[0][0] 96 | 97 | 98 | def default_categorical(): 99 | img_in = Input(shape=(120, 160, 3), 100 | name='img_in') # First layer, input layer, Shape comes from camera.py resolution, RGB 101 | x = img_in 102 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')( 103 | x) # 24 features, 5 pixel x 5 pixel kernel (convolution, feauture) window, 2wx2h stride, relu activation 104 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')( 105 | x) # 32 features, 5px5p kernel window, 2wx2h stride, relu activatiion 106 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')( 107 | x) # 64 features, 5px5p kernal window, 2wx2h stride, relu 108 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')( 109 | x) # 64 features, 3px3p kernal window, 2wx2h stride, relu 110 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')( 111 | x) # 64 features, 3px3p kernal window, 1wx1h stride, relu 112 | 113 | # Possibly add MaxPooling (will make it less sensitive to position in image). Camera angle fixed, so may not to be needed 114 | 115 | x = Flatten(name='flattened')(x) # Flatten to 1D (Fully connected) 116 | x = Dense(100, activation='relu')(x) # Classify the data into 100 features, make all negatives 0 117 | x = Dropout(.1)(x) # Randomly drop out (turn off) 10% of the neurons (Prevent overfitting) 118 | x = Dense(50, activation='relu')(x) # Classify the data into 50 features, make all negatives 0 119 | x = Dropout(.1)(x) # Randomly drop out 10% of the neurons (Prevent overfitting) 120 | # categorical output of the angle 121 | angle_out = Dense(15, activation='softmax', name='angle_out')( 122 | x) # Connect every input with every output and output 15 hidden units. Use Softmax to give percentage. 15 categories and find best one based off percentage 0.0-1.0 123 | 124 | # continous output of throttle 125 | throttle_out = Dense(1, activation='relu', name='throttle_out')(x) # Reduce to 1 number, Positive number only 126 | 127 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 128 | model.compile(optimizer='adam', 129 | loss={'angle_out': 'categorical_crossentropy', 130 | 'throttle_out': 'mean_absolute_error'}, 131 | loss_weights={'angle_out': 0.9, 'throttle_out': .01}) 132 | 133 | return model 134 | 135 | 136 | from tensorflow.python.keras import backend as K 137 | 138 | 139 | def linear_unbin_layer(tnsr): 140 | bin = K.constant((2 / 14), dtype='float32') 141 | norm = K.constant(1, dtype='float32') 142 | 143 | b = K.cast(K.argmax(tnsr), dtype='float32') 144 | a = b - norm 145 | # print('linear_unbin_layer out: {}'.format(a)) 146 | return a 147 | 148 | 149 | def default_catlin(): 150 | """ 151 | Categorial Steering output before linear conversion. 152 | :return: 153 | """ 154 | img_in = Input(shape=(120, 160, 3), 155 | name='img_in') # First layer, input layer, Shape comes from camera.py resolution, RGB 156 | x = img_in 157 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')( 158 | x) # 24 features, 5 pixel x 5 pixel kernel (convolution, feauture) window, 2wx2h stride, relu activation 159 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')( 160 | x) 161 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')( 162 | x) # 64 features, 5px5p kernal window, 2wx2h stride, relu 163 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')( 164 | x) # 64 features, 3px3p kernal window, 2wx2h stride, relu 165 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')( 166 | x) # 64 features, 3px3p kernal window, 1wx1h stride, relu 167 | 168 | # Possibly add MaxPooling (will make it less sensitive to position in image). Camera angle fixed, so may not to be needed 169 | 170 | x = Flatten(name='flattened')(x) # Flatten to 1D (Fully connected) 171 | x = Dense(100, activation='relu')(x) # Classify the data into 100 features, make all negatives 0 172 | x = Dropout(.1)(x) # Randomly drop out (turn off) 10% of the neurons (Prevent overfitting) 173 | x = Dense(50, activation='relu')(x) # Classify the data into 50 features, make all negatives 0 174 | x = Dropout(.1)(x) # Randomly drop out 10% of the neurons (Prevent overfitting) 175 | # categorical output of the angle 176 | angle_cat_out = Dense(15, activation='softmax', name='angle_cat_out')(x) 177 | angle_out = Dense(1, activation='sigmoid', name='angle_out')(angle_cat_out) 178 | # angle_out = Lambda(linear_unbin_layer, output_shape=(1,1, ), name='angle_out')(angle_cat_out) 179 | 180 | # continuous output of throttle 181 | throttle_out = Dense(1, activation='relu', name='throttle_out')(x) # Reduce to 1 number, Positive number only 182 | 183 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 184 | model.compile(optimizer='adam', 185 | loss={'angle_out': 'mean_squared_error', 186 | 'throttle_out': 'mean_absolute_error'}, 187 | loss_weights={'angle_out': 0.9, 'throttle_out': .01}) 188 | 189 | return model 190 | 191 | 192 | def default_linear(): 193 | img_in = Input(shape=(120, 160, 3), name='img_in') 194 | x = img_in 195 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')(x) 196 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')(x) 197 | x = Convolution2D(64, (5, 5), strides=(2, 2), activation='relu')(x) 198 | x = Convolution2D(64, (3, 3), strides=(2, 2), activation='relu')(x) 199 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 200 | 201 | x = Flatten(name='flattened')(x) 202 | x = Dense(100, activation='linear')(x) 203 | x = Dropout(.1)(x) 204 | x = Dense(50, activation='linear')(x) 205 | x = Dropout(.1)(x) 206 | # categorical output of the angle 207 | angle_out = Dense(1, activation='linear', name='angle_out')(x) 208 | 209 | # continous output of throttle 210 | throttle_out = Dense(1, activation='linear', name='throttle_out')(x) 211 | 212 | model = Model(inputs=[img_in], outputs=[angle_out, throttle_out]) 213 | 214 | model.compile(optimizer='adam', 215 | loss={'angle_out': 'mean_squared_error', 216 | 'throttle_out': 'mean_squared_error'}, 217 | loss_weights={'angle_out': 0.5, 'throttle_out': .5}) 218 | 219 | return model 220 | 221 | 222 | def default_n_linear(num_outputs): 223 | img_in = Input(shape=(120, 160, 3), name='img_in') 224 | x = img_in 225 | x = Cropping2D(cropping=((60, 0), (0, 0)))(x) # trim 60 pixels off top 226 | x = Lambda(lambda x: x / 127.5 - 1.)(x) # normalize and re-center 227 | x = Convolution2D(24, (5, 5), strides=(2, 2), activation='relu')(x) 228 | x = Convolution2D(32, (5, 5), strides=(2, 2), activation='relu')(x) 229 | x = Convolution2D(64, (5, 5), strides=(1, 1), activation='relu')(x) 230 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 231 | x = Convolution2D(64, (3, 3), strides=(1, 1), activation='relu')(x) 232 | 233 | x = Flatten(name='flattened')(x) 234 | x = Dense(100, activation='relu')(x) 235 | x = Dropout(.1)(x) 236 | x = Dense(50, activation='relu')(x) 237 | x = Dropout(.1)(x) 238 | 239 | outputs = [] 240 | 241 | for i in range(num_outputs): 242 | outputs.append(Dense(1, activation='linear', name='n_outputs' + str(i))(x)) 243 | 244 | model = Model(inputs=[img_in], outputs=outputs) 245 | 246 | model.compile(optimizer='adam', 247 | loss='mse') 248 | 249 | return model 250 | --------------------------------------------------------------------------------