forked from RyanZotti/Self-Driving-Car
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ryan Zotti
authored and
Ryan Zotti
committed
Jul 3, 2016
0 parents
commit e9fd810
Showing
8 changed files
with
585 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import numpy as np | ||
|
||
class DataSet(object): | ||
|
||
def __init__(self, | ||
images, | ||
labels): | ||
|
||
assert images.shape[0] == labels.shape[0], ( | ||
'images.shape: %s labels.shape: %s' % (images.shape, labels.shape)) | ||
self._num_examples = images.shape[0] | ||
|
||
self._images = images | ||
self._labels = labels | ||
self._epochs_completed = 0 | ||
self._index_in_epoch = 0 | ||
|
||
def next_batch(self, batch_size): | ||
"""Return the next `batch_size` examples from this data set.""" | ||
|
||
start = self._index_in_epoch | ||
self._index_in_epoch += batch_size | ||
if self._index_in_epoch > self._num_examples: | ||
# Finished epoch | ||
self._epochs_completed += 1 | ||
# Shuffle the data | ||
perm = np.arange(self._num_examples) | ||
np.random.shuffle(perm) | ||
self._images = self._images[perm] | ||
self._labels = self._labels[perm] | ||
# Start next epoch | ||
start = 0 | ||
self._index_in_epoch = batch_size | ||
assert batch_size <= self._num_examples | ||
end = self._index_in_epoch | ||
return self._images[start:end], self._labels[start:end] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import numpy as np | ||
import cv2 | ||
import re | ||
import os | ||
from datetime import datetime | ||
|
||
|
||
def process_session(session_path): | ||
|
||
cap = cv2.VideoCapture(session_path + "/output.mov") | ||
video_timestamps = [] | ||
with open(session_path + '/video_timestamps.txt') as video_timestamps_reader: | ||
for line in video_timestamps_reader: | ||
line = line.replace("\n", "") | ||
ts = datetime.strptime(line, '%Y-%m-%d %H:%M:%S.%f') | ||
video_timestamps.append(ts) | ||
|
||
commands = [] | ||
with open(session_path + '/clean_session.txt') as clean_session_reader: | ||
for line in clean_session_reader: | ||
line = line.replace("\n", "") | ||
match = re.match(r"^.*\['(.*)'\].*$", line) | ||
if match is not None: | ||
command = match.group(1) | ||
else: | ||
command = 'no command' | ||
raw_ts = line[line.index(" ") + 1:] | ||
ts = datetime.strptime(raw_ts, '%Y-%m-%d %H:%M:%S.%f') | ||
commands.append([command, ts]) | ||
|
||
# time after which no other data is relevant because driving session has ended | ||
end_time = commands[len(commands) - 1][1] | ||
|
||
# cleanup to track only command transitions | ||
compact_commands = [] | ||
prev_command = None | ||
for item in commands: | ||
command, ts = item[0], item[1] | ||
if command != prev_command and command != 'no command': | ||
compact_commands.append([command, ts]) | ||
prev_command = command | ||
commands = compact_commands | ||
|
||
# time before which no other data is relevant because driving session just started | ||
start_time = commands[0][1] | ||
|
||
current_command = commands[0][0] | ||
command_counter = 1 | ||
future_command = commands[command_counter][0] | ||
future_command_ts = commands[command_counter][1] | ||
|
||
predictors = [] | ||
targets = [] | ||
|
||
frame_counter = -1 | ||
while (cap.isOpened()): | ||
frame_counter = frame_counter + 1 | ||
ret, frame = cap.read() | ||
if cv2.waitKey(1) & 0xFF == ord('q'): # don't remove this if statement or video feed will die | ||
break | ||
video_timestamp = video_timestamps[frame_counter] | ||
if video_timestamp > start_time: | ||
if video_timestamp < end_time: | ||
if video_timestamp > future_command_ts: | ||
current_command = future_command | ||
command_counter = command_counter + 1 | ||
if command_counter < len(commands): | ||
future_command = commands[command_counter][0] | ||
future_command_ts = commands[command_counter][1] | ||
else: | ||
future_command = "END" | ||
future_command_ts = end_time | ||
print(current_command) | ||
cv2.imshow('frame', frame) | ||
predictors.append(frame) | ||
target = [0, 0, 0] # in order: left, up, right | ||
if current_command == 'left': | ||
target[0] = 1 | ||
elif current_command == 'up': | ||
target[1] = 1 | ||
elif current_command == 'right': | ||
target[2] = 1 | ||
targets.append(target) | ||
else: | ||
cap.release() | ||
cv2.destroyAllWindows() | ||
|
||
return predictors, targets | ||
|
||
|
||
def data_prep(data_path): | ||
|
||
data_folders = os.listdir(data_path) | ||
train_folder_size = int(len(data_folders) * 0.8) | ||
|
||
train_predictors = [] | ||
train_targets = [] | ||
for folder in data_folders[:train_folder_size]: | ||
predictors, targets = process_session(data_path+'/'+folder) | ||
train_predictors.append(predictors) | ||
train_targets.append(targets) | ||
train_predictors_np = np.array(predictors) | ||
train_targets_np = np.array(targets) | ||
|
||
validation_predictors = [] | ||
validation_targets = [] | ||
for folder in data_folders[train_folder_size:]: | ||
predictors, targets = process_session(data_path + '/' + folder) | ||
validation_predictors.append(predictors) | ||
validation_targets.append(targets) | ||
validation_predictors_np = np.array(predictors) | ||
validation_targets_np = np.array(targets) | ||
|
||
np.savez(data_path+'/final_processed_data', train_predictors=train_predictors_np, | ||
train_targets=train_targets_np,validation_predictors = validation_predictors_np, | ||
validation_targets = validation_targets_np) | ||
|
||
if __name__ == '__main__': | ||
data_path = str(os.path.dirname(os.path.realpath(__file__))) + "/data" | ||
data_prep(data_path) | ||
print("Finished.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
import tornado.ioloop | ||
import tornado.web | ||
from datetime import datetime | ||
import os | ||
from operator import itemgetter | ||
import RPi.GPIO as GPIO | ||
import requests | ||
from time import sleep | ||
|
||
class PostHandler(tornado.web.RequestHandler): | ||
|
||
def post(self): | ||
timestamp = datetime.now() | ||
data_json = tornado.escape.json_decode(self.request.body) | ||
allowed_commands = set(['37','38','39','40']) | ||
command = data_json['command'] | ||
command = list(command.keys()) | ||
command = set(command) | ||
command = allowed_commands & command | ||
file_path = str(os.path.dirname(os.path.realpath(__file__)))+"/session.txt" | ||
log_entry = str(command)+" "+str(timestamp) | ||
log_entries.append((command,timestamp)) | ||
with open(file_path,"a") as writer: | ||
writer.write(log_entry+"\n") | ||
print(log_entry) | ||
command_duration = 0.1 | ||
|
||
if '37' in command: | ||
motor.forward_left(100) | ||
elif '38' in command: | ||
motor.forward(100) | ||
elif '39' in command: | ||
motor.forward_right(100) | ||
elif '40' in command: | ||
motor.backward(100) | ||
else: | ||
motor.stop() | ||
|
||
# This only works on data from the same live python process. It doesn't | ||
# read from the session.txt file. It only sorts data from the active | ||
# python process. This is required because it reads from a list instead | ||
# of a file on data from the same live python process. It doesn't | ||
# read from the session.txt file. It only sorts data from the active | ||
# log_entries python list | ||
class StoreLogEntriesHandler(tornado.web.RequestHandler): | ||
def get(self): | ||
file_path = str(os.path.dirname(os.path.realpath(__file__)))+"/clean_session.txt" | ||
sorted_log_entries = sorted(log_entries,key=itemgetter(1)) | ||
prev_command = set() | ||
allowed_commands = set(['38','37','39','40']) | ||
for log_entry in sorted_log_entries: | ||
command = log_entry[0] | ||
timestamp = log_entry[1] | ||
if len(command ^ prev_command) > 0: | ||
prev_command = command | ||
with open(file_path,"a") as writer: | ||
readable_command = [] | ||
for element in list(command): | ||
if element == '37': | ||
readable_command.append("left") | ||
if element == '38': | ||
readable_command.append("up") | ||
if element == '39': | ||
readable_command.append("right") | ||
if element == '40': | ||
readable_command.append("down") | ||
log_entry = str(list(readable_command))+" "+str(timestamp) | ||
writer.write(log_entry+"\n") | ||
print(log_entry) | ||
self.write("Finished") | ||
|
||
class MultipleKeysHandler(tornado.web.RequestHandler): | ||
|
||
def get(self): | ||
print("HelloWorld") | ||
self.write(''' | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script> | ||
<script> | ||
var keys = {}; | ||
$(document).keydown(function (e) { | ||
keys[e.which] = true; | ||
var json_upload = JSON.stringify({command:keys}); | ||
var xmlhttp = new XMLHttpRequest(); | ||
xmlhttp.open("POST", "/post"); | ||
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | ||
xmlhttp.send(json_upload); | ||
printKeys(); | ||
}); | ||
$(document).keyup(function (e) { | ||
delete keys[e.which]; | ||
var json_upload = JSON.stringify({command:keys}); | ||
var xmlhttp = new XMLHttpRequest(); | ||
xmlhttp.open("POST", "/post"); | ||
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | ||
xmlhttp.send(json_upload); | ||
printKeys(); | ||
}); | ||
function printKeys() { | ||
var html = ''; | ||
for (var i in keys) { | ||
if (!keys.hasOwnProperty(i)) continue; | ||
html += '<p>' + i + '</p>'; | ||
} | ||
$('#out').html(html); | ||
} | ||
</script> | ||
</head> | ||
<body> | ||
Click in this frame, then try holding down some keys | ||
<div id="out"></div> | ||
</body> | ||
</html> | ||
''') | ||
|
||
|
||
class Motor: | ||
|
||
def __init__(self, pinForward, pinBackward, pinControlStraight,pinLeft, pinRight, pinControlSteering): | ||
""" Initialize the motor with its control pins and start pulse-width | ||
modulation """ | ||
|
||
self.pinForward = pinForward | ||
self.pinBackward = pinBackward | ||
self.pinControlStraight = pinControlStraight | ||
self.pinLeft = pinLeft | ||
self.pinRight = pinRight | ||
self.pinControlSteering = pinControlSteering | ||
GPIO.setup(self.pinForward, GPIO.OUT) | ||
GPIO.setup(self.pinBackward, GPIO.OUT) | ||
GPIO.setup(self.pinControlStraight, GPIO.OUT) | ||
|
||
GPIO.setup(self.pinLeft, GPIO.OUT) | ||
GPIO.setup(self.pinRight, GPIO.OUT) | ||
GPIO.setup(self.pinControlSteering, GPIO.OUT) | ||
|
||
self.pwm_forward = GPIO.PWM(self.pinForward, 100) | ||
self.pwm_backward = GPIO.PWM(self.pinBackward, 100) | ||
self.pwm_forward.start(0) | ||
self.pwm_backward.start(0) | ||
|
||
self.pwm_left = GPIO.PWM(self.pinLeft, 100) | ||
self.pwm_right = GPIO.PWM(self.pinRight, 100) | ||
self.pwm_left.start(0) | ||
self.pwm_right.start(0) | ||
|
||
GPIO.output(self.pinControlStraight,GPIO.HIGH) | ||
GPIO.output(self.pinControlSteering,GPIO.HIGH) | ||
|
||
def forward(self, speed): | ||
""" pinForward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
self.pwm_backward.ChangeDutyCycle(0) | ||
self.pwm_forward.ChangeDutyCycle(speed) | ||
|
||
def forward_left(self, speed): | ||
""" pinForward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
self.pwm_backward.ChangeDutyCycle(0) | ||
self.pwm_forward.ChangeDutyCycle(speed) | ||
self.pwm_right.ChangeDutyCycle(0) | ||
self.pwm_left.ChangeDutyCycle(100) | ||
|
||
def forward_right(self, speed): | ||
""" pinForward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
self.pwm_backward.ChangeDutyCycle(0) | ||
self.pwm_forward.ChangeDutyCycle(speed) | ||
self.pwm_left.ChangeDutyCycle(0) | ||
self.pwm_right.ChangeDutyCycle(100) | ||
|
||
def backward(self, speed): | ||
""" pinBackward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
|
||
self.pwm_forward.ChangeDutyCycle(0) | ||
self.pwm_backward.ChangeDutyCycle(speed) | ||
|
||
def left(self, speed): | ||
""" pinForward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
self.pwm_right.ChangeDutyCycle(0) | ||
self.pwm_left.ChangeDutyCycle(speed) | ||
|
||
def right(self, speed): | ||
""" pinForward is the forward Pin, so we change its duty | ||
cycle according to speed. """ | ||
self.pwm_left.ChangeDutyCycle(0) | ||
self.pwm_right.ChangeDutyCycle(speed) | ||
|
||
def stop(self): | ||
""" Set the duty cycle of both control pins to zero to stop the motor. """ | ||
|
||
self.pwm_forward.ChangeDutyCycle(0) | ||
self.pwm_backward.ChangeDutyCycle(0) | ||
self.pwm_left.ChangeDutyCycle(0) | ||
self.pwm_right.ChangeDutyCycle(0) | ||
|
||
def make_app(): | ||
return tornado.web.Application([ | ||
(r"/drive",MultipleKeysHandler),(r"/post", PostHandler), | ||
(r"/StoreLogEntries",StoreLogEntriesHandler) | ||
]) | ||
|
||
if __name__ == "__main__": | ||
GPIO.setmode(GPIO.BOARD) | ||
command_duration = 0.1 | ||
motor = Motor(16, 18, 22, 19, 21, 23) | ||
log_entries = [] | ||
app = make_app() | ||
app.listen(81) | ||
tornado.ioloop.IOLoop.current().start() |
Oops, something went wrong.