Source code for gui.sim

'''Provides the main simulator, which includes the Pygame visualiser and a
Tkinter panel'''
import random
import tkinter as tk
import tkinter.ttk as ttk
from time import time
from collections import deque

from visualiser import SimulatorVisualiser
from visualiser.util import Actions
from config import GUIConfig, SimulatorConfig
from audri import Agent
from data import saveData, loadData
from .panel import SimulatorPanel

visConf = SimulatorConfig()
conf = GUIConfig()

[docs]class Simulator(tk.Frame): '''The GUI component of the visualiser Contains the :class:`SimulatorVisualiser` (Pygame canvas) in a :class:`tkinter.ttk.Frame`. The :class:`SimulatorPanel` is displayed alongside it.''' def __init__(self, root, main): '''Initialise the window and its components''' super().__init__(root, background='white') self.columnconfigure(1, weight=1) self._main = main self.agent = Agent() self._datasetName = None self._lastRecord = time() # when last feature vector was recorded self._snapshots = [] self.agent = Agent() self._snapshots = deque() self._canvas = tk.Frame(self, width=conf.VisualiserWidth, height=conf.Height, background='white') self._canvas.grid(row=0, column=0) # update so visualiser can be attached self.update() self._visualiser = SimulatorVisualiser(str(self._canvas.winfo_id())) self._panel = SimulatorPanel(self, self, self._visualiser) self._panel.grid(row=0, column=1, sticky='nsew') self.mode = 0 random.seed(visConf.RandomSeed) @property def mode(self): ''' | The current mode in which the simulator should run in. | 0 = Manual: Collect training data | 1 = AUDRI: Train AUDRI and let it control the car | 2 = Compare: Allow both expert and AUDRI to control two different \ cars, side by side :getter: Get the current mode :setter: Set the current mode, also setting the mode of the :class:`~visualiser.visualiser.SimulatorVisualiser`. Prevents setting an invalid mode :type: :class:`int` ''' return self._mode @mode.setter def mode(self, val): if val < 0 or val > 2: return self._mode = val self._visualiser.mode = val
[docs] def setDataset(self, name): '''Set the :py:attr:`_datasetName` property from the :class:`~gui.sim.NameDatasetPopup`, unpause the :class:`~visualiser.visualiser.SimulatorVisualiser`, and return focus to the main window :param name: :class:`str` name of dataset to store training data in ''' self._datasetName = name self._visualiser.pause = False self._main.focus_force()
[docs] def focus(self): '''If loaded in manual mode, create a :class:`~gui.sim.NameDatasetPopup` to choose the training data name. If in AUDRI mode, train the model ''' self._visualiser.pause = True if self.mode == 0 and self._datasetName is None: NameDatasetPopup(self) elif self.mode > 0: self.agent.train(loadData('data')) self._visualiser.pause = False width = conf.VisualiserWidth *(1 if self.mode < 2 else 2) self._canvas.config(w=width)
[docs] def tick(self): '''Call visualiser and panel tick frequently''' if not self._visualiser.pause and self.mode == 0 \ and self._visualiser.lastActionTime >= self._lastRecord: # record actions when they are performed in training mode self._lastRecord = time() self._snapshots.append(self._visualiser.stateVector()) elif not self._visualiser.pause \ and time() -self._lastRecord >= visConf.RecordInterval: # record data or allow agent to perform an action at regular # intervals self._lastRecord = time() if self.mode > 0: state = self._visualiser.stateVector(agent=True) action = self.agent.action(state) print('Predict:', Actions(action), '\n') self._visualiser.doAct(Actions(action), agent=True) else: self._snapshots.append(self._visualiser.stateVector()) self._visualiser.tick() self._panel.tick()
[docs] def keyPress(self, event): '''Pass key press events to visualiser''' self._visualiser.keyPress(event.keysym)
[docs] def restart(self): '''Reset random seed and restart visualiser using :py:meth:`~visualiser.visualiser.SimulatorVisualiser.reset` ''' self._snapshots.clear() random.seed(visConf.RandomSeed) self._visualiser.reset()
[docs] def finish(self): '''Stop the visualiser and return to the main menu''' self._visualiser.reset() self._visualiser.pause = True if self.mode == 0: saveData(self._datasetName, self._snapshots) self._datasetName = None self._snapshots = [] self.mode = 0 self._main.back()
[docs]class NameDatasetPopup(tk.Toplevel): '''A Tkinter popup that requests a name for a training set. It will check if the name is in use; if it is, another prompt asks whether it should be overriden or another name should be input. ''' def __init__(self, main): super().__init__() self.wm_title(conf.SimPopupTitle) self.grab_set() padx = conf.SimPopupPad pady = conf.SimPopupPad /2 self.value = tk.StringVar() # Function called when a string is input self._main = main self._text = ttk.Label(self, text=conf.SimPopupText) self._text.grid(row=0, columnspan=2, padx=padx, pady=pady) self._input = ttk.Entry(self, textvariable=self.value) self._input.grid(row=1, columnspan=2, padx=padx, pady=pady, sticky='nesw') self._input.bind('<Return>', self.accept) self._input.focus() self._override = ttk.Button(self, text='Override') self._override.grid(row=2, pady=pady) self._override.grid_remove() self._save = ttk.Button(self, text='OK', command=self.accept) self._save.grid(row=2, column=1, padx=padx, pady=pady) self._warn = ttk.Label(self, text=conf.SimPopupDupWarn, foreground='red') self._warn.grid(row=3, columnspan=2, padx=padx, pady=pady) self._warn.grid_remove()
[docs] def accept(self, *args): '''Close the popup and set the input value on the :class:`~gui.sim.Simulator`''' val = self.value.get().strip() self.destroy() self._main.setDataset(val)