Source code for autopilot.gui.menus.file

import ast
import copy
from collections import OrderedDict as odict

import autopilot
from autopilot.gui.widgets.protocol import Sound_Widget, Graduation_Widget
from PySide2 import QtWidgets, QtGui

[docs]class Protocol_Wizard(QtWidgets.QDialog): """ A dialog window to create a new protocol. Warning: This is a heavily overloaded class, and will be split into separate objects to handle parameters separately. For now this is what we got though and it works. Protocols are collections of multiple tasks (steps) with some graduation criterion for moving between them. This widget is composed of three windows: * **left**: possible task types from :func:`autopilot.get_task()` * **center**: current steps in task * **right**: :class:`.Parameters` for currently selected step. The parameters that are used are of the form used by :py:attr:`.Task.PARAMS` (see :py:attr:`.Nafc.PARAMS` for an example). TODO: Make specific parameter class so this definition is less squishy its general structure is:: {'parameter_key': {'tag':'Human Readable Name', 'type':'param_type'}} while some parameter types have extra items, eg.:: {'list_param': {'tag':'Select from a List of Parameters', 'type': 'list', 'values': {'First Option':0, 'Second Option':1}} where k:v pairs are still used with lists to allow parameter values (0, 1) be human readable. The available types include: * **int** - integer * **float** - floating point number * **bool** - boolean boolbox * **list** - a list of `values` to choose from * **sounds** - a :class:`.Sound_Widget` that allows sounds to be defined. * **graduation** - a :class:`.Graduation_Widget` that allows graduation criteria to be defined Attributes: task_list (:class:`QtWidgets.QListWidget`): The leftmost window, lists available tasks step_list (:class:`QtWidgets.QListWidget`): The center window, lists tasks currently in protocol param_layout (:class:`QtWidgets.QFormLayout`): The right window, allows changing available parameters for currently selected step. steps (list): A list of dictionaries defining the protocol. """ def __init__(self): QtWidgets.QDialog.__init__(self) # Left Task List/Add Step Box addstep_label = QtWidgets.QLabel("Add Step") addstep_label.setFixedHeight(40) self.task_list = QtWidgets.QListWidget() self.task_list.insertItems(0, autopilot.get_names('task')) self.add_button = QtWidgets.QPushButton("+") self.add_button.setFixedHeight(40) self.add_button.clicked.connect(self.add_step) addstep_layout = QtWidgets.QVBoxLayout() addstep_layout.addWidget(addstep_label) addstep_layout.addWidget(self.task_list) addstep_layout.addWidget(self.add_button) # Center Step List Box steplist_label = QtWidgets.QLabel("Step List") steplist_label.setFixedHeight(40) self.step_list = QtWidgets.QListWidget() self.step_list.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) self.step_list.selectionMode = QtWidgets.QAbstractItemView.SingleSelection self.step_list.itemSelectionChanged.connect(self.populate_params) self.list_model = self.step_list.model() self.list_model.rowsMoved.connect(self.reorder_steps) self.remove_step_button = QtWidgets.QPushButton('-') self.remove_step_button.setFixedHeight(40) self.remove_step_button.clicked.connect(self.remove_step) steplist_layout = QtWidgets.QVBoxLayout() steplist_layout.addWidget(steplist_label) steplist_layout.addWidget(self.step_list) steplist_layout.addWidget(self.remove_step_button) # Right Parameter Definition Window param_label = QtWidgets.QLabel("Step Parameters") param_label.setFixedHeight(40) self.param_layout = QtWidgets.QFormLayout() param_frame = QtWidgets.QFrame() param_frame.setLayout(self.param_layout) param_box_layout = QtWidgets.QVBoxLayout() param_box_layout.addWidget(param_label) param_box_layout.addWidget(param_frame) # Main Layout frame_layout = QtWidgets.QHBoxLayout() frame_layout.addLayout(addstep_layout, stretch=1) frame_layout.addLayout(steplist_layout, stretch=1) frame_layout.addLayout(param_box_layout, stretch=3) buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) main_layout = QtWidgets.QVBoxLayout() main_layout.addLayout(frame_layout) main_layout.addWidget(buttonBox) self.setLayout(main_layout) self.setWindowTitle("Make New Protocol") # List to store dicts of steps and params self.steps = []
[docs] def add_step(self): """ Loads `PARAMS` from task object, adds base parameters to :py:attr:`.steps` list """ task_type = self.task_list.currentItem().text() new_item = QtWidgets.QListWidgetItem() new_item.setText(task_type) task_params = copy.deepcopy(autopilot.get_task(task_type).PARAMS) # Add params that are non-task specific # Name of task type task_params['task_type'] = {'type':'label','value':task_type} # Prepend name of step shittily task_params_temp = odict() task_params_temp['step_name'] = {'type':'str', 'tag':'Step Name', 'value':task_type} task_params_temp.update(task_params) task_params.clear() task_params.update(task_params_temp) # add graduation field task_params['graduation'] = {'type':'graduation', 'tag':'Graduation Criterion', 'value':{}} self.steps.append(task_params) self.step_list.addItem(new_item) self.step_list.setCurrentItem(new_item)
[docs] def rename_step(self): """ When the step name widget's text is changed, fire this function to update :py:attr:`.step_list` which updates :py:attr:`.steps` """ sender = self.sender() sender_text = sender.text() current_step = self.step_list.item(self.step_list.currentRow()) current_step.setText(sender_text)
[docs] def remove_step(self): """ Remove step from :py:attr:`.step_list` and :py:attr:`.steps` """ step_index = self.step_list.currentRow() del self.steps[step_index] self.step_list.takeItem(step_index)
[docs] def populate_params(self): """ Calls :py:meth:`.clear_params` and then creates widgets to edit parameter values. Returns: """ self.clear_params() # Get current item index step_index = self.step_list.currentRow() step_dict = self.steps[step_index] # Iterate through params to make input widgets for k, v in step_dict.items(): # Make Input Widget depending on type # Each Input type needs a different widget type, # and each widget type has different methods to get/change values, so we have to do this ugly if v['type'] == 'int' or v['type'] == 'str' or v['type'] == 'float': rowtag = QtWidgets.QLabel(v['tag']) input_widget = QtWidgets.QLineEdit() input_widget.setObjectName(k) if v['type'] == 'int': input_widget.setValidator(QtGui.QIntValidator()) elif v['type'] == 'float': input_widget.setValidator(QtGui.QDoubleValidator()) input_widget.editingFinished.connect(self.set_param) if 'value' in v.keys(): input_widget.setText(v['value']) elif v['type'] == 'str': self.steps[step_index][k]['value'] = '' self.param_layout.addRow(rowtag,input_widget) elif v['type'] == 'bool': rowtag = QtWidgets.QLabel(v['tag']) input_widget = QtWidgets.QCheckBox() input_widget.setObjectName(k) input_widget.stateChanged.connect(self.set_param) if 'value' in v.keys(): input_widget.setChecked(v['value']) else: self.steps[step_index][k]['value'] = False self.param_layout.addRow(rowtag, input_widget) elif v['type'] == 'list': rowtag = QtWidgets.QLabel(v['tag']) input_widget = QtWidgets.QListWidget() input_widget.setObjectName(k) sorted_values = sorted(v['values'], key=v['values'].get) input_widget.insertItems(0, sorted_values) input_widget.itemSelectionChanged.connect(self.set_param) if 'value' in v.keys(): select_item = input_widget.item(v['value']) input_widget.setCurrentItem(select_item) else: self.steps[step_index][k]['value'] = sorted_values[0] self.param_layout.addRow(rowtag, input_widget) self.steps[step_index][k]['value'] = False elif v['type'] == 'sounds': self.sound_widget = Sound_Widget() self.sound_widget.setObjectName(k) self.sound_widget.pass_set_param_function(self.set_sounds) self.param_layout.addRow(self.sound_widget) self.steps[step_index][k]['sounds'] = {} if 'value' in v.keys(): self.sound_widget.populate_lists(v['value']) elif v['type'] == 'graduation': self.grad_widget = Graduation_Widget() self.grad_widget.setObjectName(k) self.grad_widget.set_graduation = self.set_graduation self.param_layout.addRow(self.grad_widget) if 'type' in v['value'].keys(): combo_index = self.grad_widget.type_selection.findText(v['value']['type']) self.grad_widget.type_selection.setCurrentIndex(combo_index) self.grad_widget.populate_params(v['value']['value']) elif v['type'] == 'label': # This is a .json label not for display pass # Step name needs to be hooked up to the step list text if k == 'step_name': input_widget.editingFinished.connect(self.rename_step)
# TODO: Implement dependencies between parameters
[docs] def clear_params(self): """ Clears widgets from parameter window """ while self.param_layout.count(): child = self.param_layout.takeAt(0) if child.widget(): child.widget().deleteLater()
[docs] def reorder_steps(self, *args): """ When steps are dragged into a different order, update the step dictionary Args: *args: Input from our :py:attr:`.step_list` 's :class:`.QtWidgets.QListModel` 's reorder signal. """ # arg positions 1 and 4 are starting and ending positions in the list, respectively # We reorder our step list so the params line up. before = args[1] after = args[4] self.steps.insert(after, self.steps.pop(before))
[docs] def set_param(self): """ Callback function connected to the signal each widget uses to signal it has changed. Identifies the param that was changed, gets the current value, and updates `self.steps` """ sender = self.sender() param_name = sender.objectName() current_step = self.step_list.currentRow() sender_type = self.steps[current_step][param_name]['type'] if sender_type == 'bool': self.steps[current_step][param_name]['value'] = sender.isChecked() elif sender_type == 'list': list_text = sender.currentItem().text() #list_value = self.steps[current_step][param_name]['values'][list_text] self.steps[current_step][param_name]['value'] = list_text elif sender_type == 'sounds': self.steps[current_step][param_name]['value'] = self.sound_widget.sound_dict else: try: sender_text = ast.literal_eval(sender.text()) except: sender_text = sender.text() self.steps[current_step][param_name]['value'] = sender_text
[docs] def set_sounds(self): """ Stores parameters that define sounds. Sound parameters work a bit differently, specifically we have to retrieve :py:attr:`.Sound_Widget.sound_dict`. """ current_step = self.step_list.currentRow() #if 'sounds' in self.steps[current_step]['stim'].keys(): # self.steps[current_step][param_name]['sounds']['value'].update(self.sound_widget.sound_dict) #else: self.steps[current_step]['stim']['sounds'] = self.sound_widget.sound_dict
[docs] def set_graduation(self): """ Stores parameters that define graduation criteria in `self.steps` Graduation parameters work a bit differently, specifically we have to retrieve :py:attr:`.Graduation_Widget.param_dict`. """ current_step = self.step_list.currentRow() grad_type = self.grad_widget.type grad_params = self.grad_widget.param_dict self.steps[current_step]['graduation']['value'] = {'type':grad_type,'value':grad_params}
[docs] def check_depends(self): """ Handle dependencies between parameters, eg. if "correction trials" are unchecked, the box that defines the correction trial percentage should be grayed out. TODO: Not implemented. """ # TODO: Make dependent fields unavailable if dependencies unmet # I mean if it really matters pass