def bci_main(parameter_location: str, user: str, task: TaskType, experiment: str = DEFAULT_EXPERIMENT_ID) -> bool: """BCI Main. The BCI main function will initialize a save folder, construct needed information and execute the task. This is the main connection between any UI and running the app. It may also be invoked via tha command line. Ex. `python bci_main.py` this will default parameters, mode, user, and type. You can pass it those attributes with flags, if desired. Ex. `python bci_main.py --user "bci_user" --task "RSVP Calibration" --experiment "default" Input: parameter_location (str): location of parameters file to use user (str): name of the user task (TaskType): registered bcipy TaskType experiment_id (str): Name of the experiment. Default name is DEFAULT_EXPERIMENT_ID. """ validate_experiment(experiment) # 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() default_params = load_json_parameters(DEFAULT_PARAMETERS_PATH, value_cast=True) if parameters.add_missing_items(default_params): raise Exception('Parameters file out of date.') # update our parameters file with system related information sys_info = get_system_info() # Initialize Save Folder save_folder = init_save_data_structure( parameters['data_save_loc'], user, parameter_location, task=task.label, experiment_id=experiment) # configure bcipy session logging configure_logger(save_folder, log_name=parameters['log_name'], version=sys_info['bcipy_version']) logging.getLogger(__name__).info(sys_info) # Collect experiment field data collect_experiment_field_data(experiment, save_folder) return execute_task(task, parameters, save_folder)
def test_copy_default_parameters(self): """Test that default parameters can be copied.""" path = copy_parameters(destination=self.temp_dir) self.assertTrue(path != self.parameters) copy = load_json_parameters(path) self.assertTrue(type(copy), 'dict') parameters = load_json_parameters(self.parameters) self.assertEqual(copy, parameters)
def __init__(self, *args, **kwargs): super(BCInterface, self).__init__(*args, **kwargs) self.parameter_location = DEFAULT_PARAMETERS_PATH self.parameters = load_json_parameters(self.parameter_location, value_cast=True) # These are set in the build_inputs and represent text inputs from the user self.user_input = None self.experiment_input = None self.task_input = None self.user = None self.experiment = None self.task = None self.user_id_validations = [(invalid_length( min=self.min_length, max=self.max_length ), f'User ID must contain between {self.min_length} and {self.max_length} alphanumeric characters.' ), (contains_whitespaces, 'User ID cannot contain white spaces'), (contains_special_characters, 'User ID cannot contain special characters' )]
def convert_to_edf(data_dir: str, edf_path: str = None, overwrite=False, use_event_durations=False) -> Path: """ Converts BciPy raw_data to the EDF+ filetype using pyEDFlib. Parameters ---------- data_dir - directory which contains the data to be converted. This location must also contain a parameters.json configuration file. edf_path - optional path to write converted data; defaults to writing a file named raw.edf in the data_dir. overwrite - If True, the destination file (if it exists) will be overwritten. If False (default), an error will be raised if the file exists. use_event_durations - optional; if True assigns a duration to each event. Returns ------- Path to new edf file """ if not edf_path: edf_path = Path(data_dir, 'raw.edf') params = load_json_parameters(Path(data_dir, 'parameters.json'), value_cast=True) raw_data, _, ch_names, _, sfreq = read_data_csv( Path(data_dir, params['raw_data_name'])) durations = trigger_durations(params) if use_event_durations else {} with open(Path(data_dir, params.get('trigger_file_name', 'triggers.txt')), 'r') as trg_file: triggers = read_triggers(trg_file) events = edf_annotations(triggers, durations) return write_edf(edf_path, raw_data, ch_names, sfreq, events, overwrite)
def test_load_json_parameters_returns_dict(self): """Test load parameters returns a Python dict.""" # call the load parameters function parameters = load_json_parameters(self.parameters) # assert that load function turned json parameters into a dict self.assertTrue(type(parameters), 'dict')
def test_load_json_parameters_returns_dict(self): """Test load parameters returns a Python dict.""" # call the load parameters function parameters = load_json_parameters(self.parameters) # assert that load function turned json parameters into a dict-like obj self.assertEqual(type(parameters), Parameters) self.assertTrue(isinstance(parameters, abc.MutableMapping))
def set_parameter_location(self, path: str) -> None: """Sets the parameter_location to the given path. Reloads the parameters and updates any GUI widgets that are populated based on these params.""" self.parameter_location = path self.parameters = load_json_parameters(self.parameter_location, value_cast=True) # update GUI options if self.user_input: self.update_user_list()
def setUp(self): """set up the needed path for load functions.""" self.parameters_used = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters( self.parameters_used, value_cast=True) self.display = visual.Window(size=[1,1], screen=0, allowGUI=False, useFBO=False, fullscr=False, allowStencil=False, monitor='mainMonitor', winType='pyglet', units='norm', waitBlanking=False, color='black') self.text_mock = mock() self.image_mock = mock() self.rect_mock = mock() self.clock = core.Clock() self.visual_feedback = VisualFeedback( display=self.display, parameters=self.parameters, clock=self.clock) when(psychopy.visual).TextStim( win=self.display, font=any(), text=any(), height=any(), pos=any(), color=any()).thenReturn(self.text_mock) when(psychopy.visual).TextStim( win=self.display, font=any(), text=any(), height=any(), pos=any()).thenReturn(self.text_mock) when(psychopy.visual).ImageStim( win=self.display, image=any(), mask=None, pos=any(), ori=any() ).thenReturn(self.image_mock) when(psychopy.visual).Rect( win=self.display, width=any(), height=any(), lineColor=any(), pos=any(), lineWidth=any(), ori=any() ).thenReturn(self.rect_mock)
def setUp(self): """set up the needed path for load functions.""" self.parameters_used = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters(self.parameters_used, value_cast=True) self.data_save_path = 'data/' self.user_information = 'test_user_001' self.save = init_save_data_structure(self.data_save_path, self.user_information, self.parameters_used)
def test_alphabet_text(self): parameters_used = 'bcipy/parameters/parameters.json' parameters = load_json_parameters(parameters_used, value_cast=True) parameters['is_txt_stim'] = True alp = alphabet(parameters) self.assertEqual(alp, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '<', '_' ])
def test_alphabet_images(self): parameters_used = 'bcipy/parameters/parameters.json' parameters = load_json_parameters(parameters_used, value_cast=True) parameters['is_txt_stim'] = False parameters['path_to_presentation_images'] = ('bcipy/static/images/' 'rsvp_images/') alp = alphabet(parameters) self.assertNotEqual(alp, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'V', 'Y', 'Z', '<', '_' ])
def setUp(self): """Set Up.""" # set up the needed data to start a task parameters_used = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters(parameters_used, value_cast=True) self.parameters['num_sti'] = 1 # Mock the display window self.display_window = mock() self.display_window.size = [1] # Mock the frame rate return when(self.display_window).getActualFrameRate().thenReturn(60) self.text_stim = mock() self.text_stim.height = 2 self.text_stim.boundingBox = [1] # Mock the psychopy text stims and image stims we would expect when(psychopy.visual).TextStim(win=self.display_window, text=any(), font=any()).thenReturn(self.text_stim) when(psychopy.visual).TextStim(win=self.display_window, color=any(str), height=any(), text=any(), font=any(), pos=any(), wrapWidth=any(), colorSpace=any(), opacity=any(), depth=any()).thenReturn(self.text_stim) when(psychopy.visual).ImageStim(self.display_window, image=any(str), size=any(), pos=any(), mask=None, ori=any()).thenReturn(self.text_stim) # save data information self.data_save_path = 'data/' self.user_information = 'test_user_001' self.file_save = init_save_data_structure(self.data_save_path, self.user_information, parameters_used) # Mock the data acquistion self.daq = mock() self.daq.is_calibrated = True self.daq.marker_writer = None
def load_items_from_txt(self, _event): """Loads user directory names from the data path defined in 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) data_save_loc = parameters['data_save_loc'] if os.path.isdir(data_save_loc): saved_users = os.listdir(data_save_loc) elif os.path.isdir('bcipy/' + data_save_loc): saved_users = os.listdir('bcipy/' + data_save_loc) else: raise IOError('User data save location not found') self.comboboxes[0].Clear() self.comboboxes[0].AppendItems(saved_users)
def setUp(self): """set up the needed path for load functions.""" self.parameters_used = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters(self.parameters_used) self.data_save_path = 'data/' self.user_information = 'test_user_0010' self.clock = core.Clock() self.sound = mock() self.fs = mock() when(sd).play(self.sound, self.fs, blocking=True).thenReturn(None) self.auditory_feedback = AuditoryFeedback( parameters=self.parameters, clock=self.clock)
def session_data(data_dir: str, alp=None): """Returns a dict of session data transformed to map the alphabet letter to the likelihood when presenting the evidence. Also removes attributes not useful for debugging.""" # TODO: Better error handling for missing parameters. # Get the alphabet based on the provided parameters (txt or icon). parameters = load_json_parameters(os.path.join(data_dir, "parameters.json"), value_cast=True) if parameters.get('is_txt_sti', False): parameters['is_txt_stim'] = parameters['is_txt_sti'] if not alp: alp = alphabet(parameters=parameters) session_path = os.path.join(data_dir, "session.json") with open(session_path, 'r') as json_file: data = json.load(json_file) data['copy_phrase'] = parameters['task_text'] for epoch in data['epochs'].keys(): for trial in data['epochs'][epoch].keys(): likelihood = dict( zip(alp, data['epochs'][epoch][trial]['likelihood'])) # Remove unused properties unused = [ 'eeg_len', 'timing_sti', 'triggers', 'target_info', 'copy_phrase' ] remove_props(data['epochs'][epoch][trial], unused) data['epochs'][epoch][trial]['stimuli'] = data['epochs'][ epoch][trial]['stimuli'][0] # Associate letters to values data['epochs'][epoch][trial]['lm_evidence'] = dict( zip(alp, data['epochs'][epoch][trial]['lm_evidence'])) data['epochs'][epoch][trial]['eeg_evidence'] = dict( zip(alp, data['epochs'][epoch][trial]['eeg_evidence'])) data['epochs'][epoch][trial]['likelihood'] = likelihood # Display the 5 most likely values. data['epochs'][epoch][trial]['most_likely'] = dict( Counter(likelihood).most_common(5)) return data
def setUp(self): """Set up needed items for test.""" parameters_used = 'bcipy/parameters/parameters.json' self.window = mock() when(psychopy.visual).Window(size=any(), screen=any(), allowGUI=False, useFBO=False, fullscr=any(bool), allowStencil=False, monitor=any(str), winType='pyglet', units=any(), waitBlanking=False, color=any(str)).thenReturn(self.window) self.parameters = load_json_parameters(parameters_used, value_cast=True)
def select_parameters(self) -> None: """Select Parameters. Opens a dialog to select the parameters.json configuration to use. """ response = self.get_filename_dialog(message='Select parameters file', file_type='JSON files (*.json)') if response: self.set_parameter_location(response) # If outdated, prompt to merge with the current defaults default_parameters = load_json_parameters(DEFAULT_PARAMETERS_PATH, value_cast=True) if self.parameters.add_missing_items(default_parameters): save_response = self.throw_alert_message( title='BciPy Alert', message='The selected parameters file is out of date.' 'Would you like to update it with the latest options?', message_type=AlertMessageType.INFO, okay_or_cancel=True) if save_response == AlertResponse.OK.value: self.parameters.save()
def main(): import bci_main from bcipy.helpers.load import load_json_parameters # Load a parameters file parameters = load_json_parameters('bcipy/parameters/parameters.json', value_cast=True) # Mode: ex. RSVP, Shuffle, Matrix test_mode = 'RSVP' # Test Type: ex. RSVP Calibration = 1, Copy Phrase = 2 test_type = 2 # Define a user user = '******' # Try and intialize with bci main try: bci_main.bci_main(parameters, user, test_type, test_mode) except Exception as e: print("BCI MAIN Fail. Exiting. Error: \n") print(e)
def setUp(self): """set up the needed path for load functions.""" self.parameters_used = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters(self.parameters_used, value_cast=True) self.display = mock() self.text_mock = mock() self.image_mock = mock() self.clock = core.Clock() self.visual_feedback = VisualFeedback(display=self.display, parameters=self.parameters, clock=self.clock) when(psychopy.visual).TextStim(win=self.display, font=any(), text=any(), height=any(), pos=any(), color=any()).thenReturn(self.text_mock) when(psychopy.visual).TextStim(win=self.display, font=any(), text=any(), height=any(), pos=any()).thenReturn(self.text_mock) when(psychopy.visual).ImageStim(win=self.display, image=any(), size=any(), mask=None, pos=any(), ori=any()).thenReturn(self.image_mock)
# Needed for windows machines multiprocessing.freeze_support() task_options = '; '.join([(f"{task.name.title().replace('_',' ')}:" f" {task.value}") for task in ExperimentType]) parser = argparse.ArgumentParser() # Command line utility for adding arguments/ paths via command line parser.add_argument( '-p', '--parameters', default='bcipy/parameters/parameters.json', help= 'Parameter location. Must be in parameters directory. Pass as parameters/parameters.json' ) parser.add_argument('-u', '--user', default='test_user') parser.add_argument('-t', '--type', default=1, help=f'Task type. Options: ({task_options})') parser.add_argument('-m', '--mode', default='RSVP', help='BCI mode. Ex. RSVP, MATRIX, SHUFFLE') args = parser.parse_args() # Load a parameters file parameters = load_json_parameters(args.parameters, value_cast=True) # Start BCI Main bci_main(parameters, str(args.user), int(args.type), str(args.mode))
set of variables. """ return self.width, self.height def _reset_stimuli(self) -> None: """Reset Stimuli. Used when redrawing stimuli. Sets the stimuli array and starting x positions to their starting values. If using vertical bars this should reset the y positions instead. """ # reset stimuli self.stimuli = [] self.position_x = self.parameters['feedback_pos_x'] if __name__ == '__main__': from bcipy.helpers.load import load_json_parameters from bcipy.display.display_main import init_display_window # Load a parameters file parameters = load_json_parameters('bcipy/parameters/parameters.json', value_cast=True) display = init_display_window(parameters) clock = core.Clock() feedback = LevelFeedback(display, parameters, clock) for index, _ in enumerate(feedback.level_colors): # the position argument is based on index starting at 1 index += 1 feedback.administer(position=index)
def test_load_json_parameters_throws_error_on_wrong_path(self): """Test load parameters returns error on entering wrong path.""" # call the load parameters function with incorrect path with self.assertRaises(Exception): load_json_parameters('/garbage/dir/wont/work')
def setUp(self): """set up the needed path for load functions.""" params_path = 'bcipy/parameters/parameters.json' self.parameters = load_json_parameters(params_path, value_cast=True)
def session_db(data_dir: str, db_name='session.db', alp=None): """Writes a relational database (sqlite3) of session data that can be used for exploratory analysis. Parameters: ----------- data_dir - directory with the session.json data (and parameters.json) db_name - name of database to write; defaults to session.db alp - optional alphabet to use; may be required if using icons that do not exist on the current machine. Returns: -------- Creates a sqlite3 database and returns a pandas dataframe of the evidence table for use within a repl. Schema: ------ trial: - id: int - target: str evidence: - trial integer (0-based) - sequence integer (0-based) - letter text (letter or icon) - lm real (language model probability for the trial; same for every sequence and only considered in the cumulative value during the first sequence) - eeg real (likelihood for the given sequence; a value of 1.0 indicates that the letter was not presented) - cumulative real (cumulative likelihood for the trial thus far) - seq_position integer (sequence position; null if not presented) - is_target integer (boolean; true(1) if this letter is the target) - presented integer (boolean; true if the letter was presented in this sequence) - above_threshold (boolean; true if cumulative likelihood was above the configured threshold) """ # TODO: Better error handling for missing parameters. # Get the alphabet based on the provided parameters (txt or icon). parameters = load_json_parameters(os.path.join(data_dir, "parameters.json"), value_cast=True) if parameters.get('is_txt_sti', False): parameters['is_txt_stim'] = parameters['is_txt_sti'] if not alp: alp = alphabet(parameters=parameters) session_path = os.path.join(data_dir, "session.json") with open(session_path, 'r') as json_file: data = json.load(json_file) # Create database conn = sqlite3.connect(db_name) cursor = conn.cursor() cursor.execute('CREATE TABLE trial (id integer, target text)') cursor.execute( 'CREATE TABLE evidence (series integer, sequence integer, ' 'stim text, lm real, eeg real, cumulative real, seq_position ' 'integer, is_target integer, presented integer, above_threshold)') conn.commit() for series in data['epochs'].keys(): for i, seq_index in enumerate(data['epochs'][series].keys()): sequence = data['epochs'][series][seq_index] session_type = data['session_type'] target_letter = get_target( session_type, sequence, max(sequence['likelihood']) > parameters['decision_threshold']) stimuli = get_stimuli(session_type, sequence) if i == 0: # create record for the trial conn.executemany('INSERT INTO trial VALUES (?,?)', [(int(series), target_letter)]) lm_ev = dict(zip(alp, sequence['lm_evidence'])) cumulative_likelihoods = dict(zip(alp, sequence['likelihood'])) ev_rows = [] for letter, prob in zip(alp, sequence['eeg_evidence']): seq_position = None if letter in stimuli: seq_position = stimuli.index(letter) if target_letter: is_target = 1 if target_letter == letter else 0 else: is_target = None cumulative = cumulative_likelihoods[letter] above_threshold = cumulative >= parameters[ 'decision_threshold'] ev_row = (int(series), int(seq_index), letter, lm_ev[letter], prob, cumulative, seq_position, is_target, seq_position is not None, above_threshold) ev_rows.append(ev_row) conn.executemany( 'INSERT INTO evidence VALUES (?,?,?,?,?,?,?,?,?,?)', ev_rows) conn.commit() dataframe = pd.read_sql_query("SELECT * FROM evidence", conn) conn.close() return dataframe