Skip to content

Commit

Permalink
Merge pull request #101 from CAMBI-tech/immutable-params
Browse files Browse the repository at this point in the history
Immutable params
  • Loading branch information
lawhead authored Oct 28, 2020
2 parents 2ae689b + d480fb2 commit dfa2e2e
Show file tree
Hide file tree
Showing 11 changed files with 809 additions and 147 deletions.
31 changes: 15 additions & 16 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,22 +25,28 @@ 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,
Expand All @@ -52,15 +59,10 @@ def bci_main(parameters: dict, user: str, exp_type: int, mode: str, experiment:
'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

0 comments on commit dfa2e2e

Please sign in to comment.