Example #1
0
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)
Example #2
0
    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)
Example #3
0
    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'
                                     )]
Example #4
0
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)
Example #5
0
    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')
Example #6
0
    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))
Example #7
0
 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()
Example #8
0
    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)
Example #9
0
    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)
Example #10
0
    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',
            '<', '_'
        ])
Example #11
0
    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', '<', '_'
        ])
Example #12
0
    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
Example #13
0
 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)
Example #14
0
    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)
Example #15
0
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
Example #16
0
    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)
Example #17
0
    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()
Example #18
0
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)
Example #19
0
    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)
Example #20
0
    # 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))
Example #21
0
            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)
Example #22
0
    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')
Example #23
0
 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)
Example #24
0
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