Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Immutable params #101

Merged
merged 14 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions bci_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from bcipy.helpers.system_utils import get_system_info, configure_logger
from bcipy.helpers.language_model import init_language_model
from bcipy.helpers.load import load_signal_model
from bcipy.helpers.parameters import DEFAULT_PARAMETERS_PATH
from bcipy.helpers.save import init_save_data_structure, DEFAULT_EXPERIMENT_ID
from bcipy.tasks.start_task import start_task
from bcipy.tasks.task_registry import ExperimentType


def bci_main(parameters: dict, user: str, exp_type: int, mode: str, experiment: str = DEFAULT_EXPERIMENT_ID) -> bool:
def bci_main(parameter_location: str, user: str, exp_type: int, mode: str, experiment: str = DEFAULT_EXPERIMENT_ID) -> bool:
"""BCI Main.

The BCI main function will initialize a save folder, construct needed information
Expand All @@ -24,43 +25,44 @@ def bci_main(parameters: dict, user: str, exp_type: int, mode: str, experiment:
Ex. `python bci_main.py --user "bci_user" --mode "SHUFFLE"`

Input:
parameters (dict): parameter dictionary
parameter_location (str): location of parameters file to use
user (str): name of the user
exp_type (int): type of experiment. Ex. 1 = calibration
mode (str): BCI mode. Ex. RSVP, SHUFFLE, MATRIX
experiment_id (str): Name of the experiment. Default name is DEFAULT_EXPERIMENT_ID.


"""
# Load parameters
parameters = load_json_parameters(parameter_location, value_cast=True)

# Update property to reflect the parameter source
parameters['parameter_location'] = parameter_location
if parameter_location != DEFAULT_PARAMETERS_PATH:
parameters.save()

# Define the parameter and data save location
parameter_location = parameters['parameter_location']
data_save_location = parameters['data_save_loc']
# update our parameters file with system related information
sys_info = get_system_info()

# Initialize Save Folder
save_folder = init_save_data_structure(
data_save_location,
parameters['data_save_loc'],
user,
parameter_location,
mode=mode,
experiment_type=exp_type,
experiment=experiment)
experiment_id=experiment)

# Register Task Type
task_type = {
'mode': mode,
'exp_type': exp_type
}

# TODO: Update parameters before init_save_data_structure so sys info gets written to the file
# update our parameters file with system related information
sys_info = get_system_info()
parameters.update(sys_info)

# configure bcipy session logging
configure_logger(save_folder,
log_name=parameters['log_name'],
version=parameters['bcipy_version'])
version=sys_info['bcipy_version'])

logging.getLogger(__name__).info(sys_info)

Expand Down Expand Up @@ -167,8 +169,5 @@ def _clean_up_session(display, daq, server):
help='BCI mode. Ex. RSVP, MATRIX, SHUFFLE')
args = parser.parse_args()

# Load parameters
parameters = load_json_parameters(args.parameters, value_cast=True)

# Start BCI Main
bci_main(parameters, str(args.user), int(args.type), str(args.mode))
bci_main(args.parameters, str(args.user), int(args.type), str(args.mode))
63 changes: 48 additions & 15 deletions bcipy/gui/mode/RSVPKeyboard.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
# pylint: disable=no-member
"""GUI for running RSVP tasks"""
import datetime
import itertools
import os
import subprocess

import wx

from bcipy.gui.gui_main import BCIGui
from bcipy.helpers.load import load_json_parameters
from bcipy.helpers.load import load_json_parameters, copy_parameters
from bcipy.helpers.parameters import DEFAULT_PARAMETERS_PATH

from bcipy.tasks.task_registry import ExperimentType


class RSVPKeyboard(BCIGui):
"""GUI for launching the RSVP tasks."""
event_started = False
PARAMETER_LOCATION = 'bcipy/parameters/parameters.json'

def __init__(self, *args, **kwargs):
super(RSVPKeyboard, self).__init__(*args, **kwargs)
self.event_started = False
self.parameter_location = DEFAULT_PARAMETERS_PATH

def bind_action(self, action: str, btn) -> None:
if action == 'launch_bci':
self.Bind(wx.EVT_BUTTON, self.launch_bci_main, btn)
elif action == 'edit_parameters':
self.Bind(wx.EVT_BUTTON, self.edit_parameters, btn)
elif action == 'select_parameters':
self.Bind(wx.EVT_BUTTON, self.select_parameters, btn)
elif action == 'refresh':
self.Bind(wx.EVT_BUTTON, self.refresh, btn)
elif action == 'offline_analysis':
Expand All @@ -31,12 +37,34 @@ def bind_action(self, action: str, btn) -> None:
else:
self.Bind(wx.EVT_BUTTON, self.on_clicked, btn)

def edit_parameters(self, _event) -> None:
"""Edit Parameters.
def select_parameters(self, _event) -> None:
"""Dialog to select the parameters.json configuration to use."""
with wx.FileDialog(self,
'Select parameters file',
wildcard='JSON files (*.json)|*.json',
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fd:
if fd.ShowModal() != wx.ID_CANCEL:
self.parameter_location = fd.GetPath()

Function for executing the edit parameter window
def edit_parameters(self, _event) -> None:
"""Edit Parameters. Prompts for a parameters.json file to use. If the default parameters
are selected a copy is used.
"""
subprocess.call('python bcipy/gui/params_form.py', shell=True)
if self.parameter_location == DEFAULT_PARAMETERS_PATH:
# Don't allow the user to overwrite the defaults
with wx.MessageDialog(
self,
"The default parameters.json can't be overridden. A copy will be used.",
'Info', wx.OK | wx.CANCEL) as confirm_dialog:
response = confirm_dialog.ShowModal()
if response == wx.ID_OK:
self.parameter_location = copy_parameters()
else:
return

subprocess.call(
f'python bcipy/gui/params_form.py -p {self.parameter_location}',
shell=True)

def launch_bci_main(self, event: wx.Event) -> None:
"""Launch BCI MAIN"""
Expand All @@ -45,8 +73,8 @@ def launch_bci_main(self, event: wx.Event) -> None:
username = self.comboboxes[0].GetValue().replace(" ", "_")
experiment_type = event.GetEventObject().GetId()
mode = 'RSVP'
cmd = 'python bci_main.py -m {} -t {} -u {}'.format(
mode, experiment_type, username)
cmd = 'python bci_main.py -m {} -t {} -u {} -p {}'.format(
mode, experiment_type, username, self.parameter_location)

subprocess.Popen(cmd, shell=True)

Expand Down Expand Up @@ -85,7 +113,7 @@ def load_items_from_txt(self, _event):
parameters.json, and adds those directory names as items to the user id
selection combobox."""
parameters = load_json_parameters(
self.PARAMETER_LOCATION, value_cast=True)
self.parameter_location, value_cast=True)
data_save_loc = parameters['data_save_loc']
if os.path.isdir(data_save_loc):
saved_users = os.listdir(data_save_loc)
Expand Down Expand Up @@ -140,23 +168,28 @@ def run_rsvp_gui():
id=task.value)
btn_pos_x += btn_width_apart

command_btn_height = 40
gui.add_button(
message='Load Parameters', position=(side_padding, 450),
size=(105, command_btn_height), color='white',
action='select_parameters')
gui.add_button(
message='Edit Parameters', position=(side_padding, 450),
size=(100, 50), color='white',
message='Edit', position=(side_padding + 110, 450),
size=(50, command_btn_height), color='white',
action='edit_parameters')

btn_auc_width = 100
btn_auc_x = window_width - (2 * side_padding + btn_auc_width)
gui.add_button(
message='Calculate AUC', position=(btn_auc_x, 450),
size=(btn_auc_width, 50), color='white',
size=(btn_auc_width, command_btn_height), color='white',
action='offline_analysis')

btn_refresh_width = 50
btn_refresh_x = window_width - (2 * side_padding + btn_refresh_width)
gui.add_button(
message='Refresh', position=(btn_refresh_x, 230),
size=(btn_refresh_width, 50), color='white',
size=(btn_refresh_width, command_btn_height), color='white',
action='refresh')

# TEXT INPUT
Expand Down
47 changes: 32 additions & 15 deletions bcipy/gui/params_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,23 @@ def static_text_control(parent,

class Form(wx.Panel):
"""The Form class is a wx.Panel that creates controls/inputs for each
parameter in the provided json file."""
parameter in the provided json file.

Parameters:
-----------
json_file - path of parameters file to be edited.
load_file - optional path of parameters file to load;
parameters from this file will be copied over to the json_file.
control_width - optional; used to set the size of the form controls.
control_height - optional; used to set the size of the form controls.
"""

def __init__(self, parent,
json_file: str = 'bcipy/parameters/parameters.json',
json_file: str,
load_file: str = None,
control_width: int = 300, control_height: int = 25, **kwargs):
super(Form, self).__init__(parent, **kwargs)

self.parent = parent
self.json_file = json_file
self.load_file = json_file if not load_file else load_file
self.control_size = (control_width, control_height)
Expand All @@ -64,7 +73,10 @@ def __init__(self, parent,

def createControls(self):
"""Create controls (inputs, labels, etc) for each item in the
parameters file."""
parameters file.

TODO: include a search box for finding an input
"""

self.controls = {}
for key, param in self.params.items():
Expand Down Expand Up @@ -210,7 +222,7 @@ def doLayout(self):
gridSizer.Add(control, **noOptions)

# Add the save button
gridSizer.Add(self.saveButton, flag=wx.ALIGN_CENTER)
gridSizer.Add(self.saveButton, flag=wx.ALIGN_LEFT)

sizer.Add(gridSizer, border=10, flag=wx.ALL | wx.ALIGN_CENTER)
self.SetSizerAndFit(sizer)
Expand All @@ -227,7 +239,6 @@ def onSave(self, event: wx.EVT_BUTTON) -> None:
self, "Parameters Successfully Updated!", 'Info',
wx.OK | wx.ICON_INFORMATION)
dialog.ShowModal()
dialog.Destroy()

def textEventHandler(self, key: str) -> Callable[[wx.EVT_TEXT], None]:
"""Returns a handler function that updates the parameter for the
Expand Down Expand Up @@ -301,8 +312,7 @@ class MainPanel(scrolled.ScrolledPanel):
"""Panel which contains the Form. Responsible for selecting the json data
to load and handling scrolling."""

def __init__(self, parent, title='BCI Parameters',
json_file='bcipy/parameters/parameters.json'):
def __init__(self, parent, json_file, title):
super(MainPanel, self).__init__(parent, -1)
self.json_file = json_file
vbox = wx.BoxSizer(wx.VERTICAL)
Expand All @@ -311,17 +321,16 @@ def __init__(self, parent, title='BCI Parameters',
self.form = Form(self, json_file)
vbox.Add(static_text_control(self, label=title, size=20),
0, wx.TOP | wx.ALIGN_CENTER_HORIZONTAL, border=5)
vbox.AddSpacer(10)

loading_box = wx.BoxSizer(wx.VERTICAL)
loading_box.Add(static_text_control(self, label=f'Editing: {json_file}',
size=14))
loading_box.AddSpacer(7)
self.loaded_from = static_text_control(
self,
label=f'Loaded from: {json_file}',
label='Set values from another file',
size=14)
loading_box.Add(self.loaded_from)
loading_box.AddSpacer(10)

self.loadButton = wx.Button(self, label='Load')
self.loadButton.Bind(wx.EVT_BUTTON, self.onLoad)
Expand Down Expand Up @@ -360,16 +369,24 @@ def OnChildFocus(self, event):
event.Skip()


def main(title='BCI Parameters', size=(650, 550),
json_file='bcipy/parameters/parameters.json'):
def main(json_file, title='BCI Parameters', size=(650, 550)):
"""Set up the GUI components and start the main loop."""

app = wx.App(0)
frame = wx.Frame(None, wx.ID_ANY, size=size, title=title)
fa = MainPanel(frame, title=title, json_file=json_file)
fa = MainPanel(frame, json_file=json_file, title=title)
frame.Show()
app.MainLoop()


if __name__ == '__main__':
main()
import argparse
from bcipy.helpers.parameters import DEFAULT_PARAMETERS_PATH

parser = argparse.ArgumentParser()

# Command line utility for adding arguments/ paths via command line
parser.add_argument('-p', '--parameters', default=DEFAULT_PARAMETERS_PATH,
help='Path to parameters.json configuration file.')
args = parser.parse_args()
main(args.parameters)
Loading