Example #1
0
 def display_feedback(self, selection: str, correct: bool):
     """Display feedback for the given selection."""
     feedback = VisualFeedback(display=self.window,
                               parameters=self.parameters,
                               clock=self.experiment_clock)
     feedback.message_color = 'green' if correct else 'red'
     feedback.administer(self.img_path(selection),
                         compare_assertion=None,
                         message='Decision: ',
                         stimuli_type=FeedbackType.IMAGE)
Example #2
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 #3
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 #4
0
    def __init__(
            self, win, daq, parameters, file_save, signal_model, language_model, fake):
        super(RSVPCopyPhraseTask, self).__init__()

        self.window = win
        self.frame_rate = self.window.getActualFrameRate()
        self.parameters = parameters
        self.daq = daq
        self.static_clock = core.StaticPeriod(screenHz=self.frame_rate)
        self.experiment_clock = core.Clock()
        self.buffer_val = parameters['task_buffer_len']
        self.alp = alphabet(parameters)
        self.rsvp = _init_copy_phrase_display(
            self.parameters, self.window, self.daq,
            self.static_clock, self.experiment_clock)
        self.file_save = file_save

        trigger_save_location = f"{self.file_save}/{parameters['triggers_file_name']}"
        self.trigger_file = open(trigger_save_location, 'w')
        self.session_save_location = f"{self.file_save}/{parameters['session_file_name']}"

        self.wait_screen_message = parameters['wait_screen_message']
        self.wait_screen_message_color = parameters[
            'wait_screen_message_color']

        self.num_sti = parameters['num_sti']
        self.len_sti = parameters['len_sti']
        self.time_cross = parameters['time_cross']
        self.time_target = parameters['time_target']
        self.time_flash = parameters['time_flash']
        self.timing = [self.time_target,
                       self.time_cross,
                       self.time_flash]

        self.color = [parameters['target_letter_color'],
                      parameters['fixation_color'],
                      parameters['stimuli_color']]

        self.task_info_color = parameters['task_color']

        self.stimuli_height = parameters['sti_height']

        self.is_txt_sti = parameters['is_txt_sti']
        self.eeg_buffer = parameters['eeg_buffer_len']
        self.copy_phrase = parameters['text_task']
        self.spelled_letters_count = int(
            parameters['spelled_letters_count'])
        if self.spelled_letters_count > len(self.copy_phrase):
            logging.debug("Already spelled letters exceeds phrase length.")
            self.spelled_letters_count = 0

        self.max_seq_length = parameters['max_seq_len']
        self.max_seconds = parameters['max_minutes'] * 60  # convert to seconds
        self.fake = fake
        self.language_model = language_model
        self.signal_model = signal_model
        self.down_sample_rate = parameters['down_sampling_rate']
        self.min_num_seq = parameters['min_seq_len']
        self.collection_window_len = parameters['collection_window_after_trial_length']

        self.static_offset = parameters['static_trigger_offset']
        self.show_feedback = parameters['show_feedback']

        if self.show_feedback:
            self.feedback = VisualFeedback(self.window, self.parameters, self.experiment_clock)
Example #5
0
class RSVPCopyPhraseTask(Task):
    """RSVP Copy Phrase Task.

    Initializes and runs all needed code for executing a copy phrase task. A
        phrase is set in parameters and necessary objects (daq, display) are
        passed to this function. Certain Wrappers and Task Specific objects are
        executed here.

    Parameters
    ----------
        win : object,
            display window to present visual stimuli.
        daq : object,
            data acquisition object initialized for the desired protocol
        parameters : dict,
            configuration details regarding the experiment. See parameters.json
        file_save : str,
            path location of where to save data from the session
        signal_model : loaded pickle file,
            trained signal model.
        language_model: object,
            trained language model.
        fake : boolean, optional
            boolean to indicate whether this is a fake session or not.
    Returns
    -------
        file_save : str,
            path location of where to save data from the session
    """

    TASK_NAME = 'RSVP Copy Phrase Task'

    def __init__(
            self, win, daq, parameters, file_save, signal_model, language_model, fake):
        super(RSVPCopyPhraseTask, self).__init__()

        self.window = win
        self.frame_rate = self.window.getActualFrameRate()
        self.parameters = parameters
        self.daq = daq
        self.static_clock = core.StaticPeriod(screenHz=self.frame_rate)
        self.experiment_clock = core.Clock()
        self.buffer_val = parameters['task_buffer_len']
        self.alp = alphabet(parameters)
        self.rsvp = _init_copy_phrase_display(
            self.parameters, self.window, self.daq,
            self.static_clock, self.experiment_clock)
        self.file_save = file_save

        trigger_save_location = f"{self.file_save}/{parameters['triggers_file_name']}"
        self.trigger_file = open(trigger_save_location, 'w')
        self.session_save_location = f"{self.file_save}/{parameters['session_file_name']}"

        self.wait_screen_message = parameters['wait_screen_message']
        self.wait_screen_message_color = parameters[
            'wait_screen_message_color']

        self.num_sti = parameters['num_sti']
        self.len_sti = parameters['len_sti']
        self.time_cross = parameters['time_cross']
        self.time_target = parameters['time_target']
        self.time_flash = parameters['time_flash']
        self.timing = [self.time_target,
                       self.time_cross,
                       self.time_flash]

        self.color = [parameters['target_letter_color'],
                      parameters['fixation_color'],
                      parameters['stimuli_color']]

        self.task_info_color = parameters['task_color']

        self.stimuli_height = parameters['sti_height']

        self.is_txt_sti = parameters['is_txt_sti']
        self.eeg_buffer = parameters['eeg_buffer_len']
        self.copy_phrase = parameters['text_task']
        self.spelled_letters_count = int(
            parameters['spelled_letters_count'])
        if self.spelled_letters_count > len(self.copy_phrase):
            logging.debug("Already spelled letters exceeds phrase length.")
            self.spelled_letters_count = 0

        self.max_seq_length = parameters['max_seq_len']
        self.max_seconds = parameters['max_minutes'] * 60  # convert to seconds
        self.fake = fake
        self.language_model = language_model
        self.signal_model = signal_model
        self.down_sample_rate = parameters['down_sampling_rate']
        self.min_num_seq = parameters['min_seq_len']
        self.collection_window_len = parameters['collection_window_after_trial_length']

        self.static_offset = parameters['static_trigger_offset']
        self.show_feedback = parameters['show_feedback']

        if self.show_feedback:
            self.feedback = VisualFeedback(self.window, self.parameters, self.experiment_clock)

    def execute(self):
        self.logger.debug('Starting Copy Phrase Task!')
        text_task = str(self.copy_phrase[0:self.spelled_letters_count])
        task_list = [(str(self.copy_phrase),
                      str(self.copy_phrase[0:self.spelled_letters_count]))]

        # Try Initializing Copy Phrase Wrapper:
        copy_phrase_task = CopyPhraseWrapper(self.min_num_seq,
                                             self.max_seq_length,
                                             signal_model=self.signal_model,
                                             fs=self.daq.device_info.fs,
                                             k=2, alp=self.alp,
                                             task_list=task_list,
                                             lmodel=self.language_model,
                                             is_txt_sti=self.is_txt_sti,
                                             device_name=self.daq.device_info.name,
                                             device_channels=self.daq.device_info.channels,
                                             stimuli_timing=[self.time_cross, self.time_flash])

        # Set new epoch (whether to present a new epoch),
        #   run (whether to cont. session),
        #   sequence counter (how many seq have occured).
        #   epoch counter and index (what epoch, and how many sequences within it)
        new_epoch = True
        run = True
        seq_counter = 0
        epoch_counter = 0
        epoch_index = 0

        # Init session data and save before beginning
        data = {
            'session': self.file_save,
            'session_type': 'Copy Phrase',
            'paradigm': 'RSVP',
            'epochs': {},
            'total_time_spent': self.experiment_clock.getTime(),
            'total_number_epochs': 0,
        }

        # Save session data
        _save_session_related_data(self.session_save_location, data)

        # check user input to make sure we should be going
        if not get_user_input(self.rsvp, self.wait_screen_message,
                              self.wait_screen_message_color,
                              first_run=True):
            run = False

        # Start the Session!
        while run:

            # check user input to make sure we should be going
            if not get_user_input(self.rsvp, self.wait_screen_message,
                                  self.wait_screen_message_color):
                break

            if self.copy_phrase[0:len(text_task)] == text_task:
                target_letter = self.copy_phrase[len(text_task)]
            else:
                target_letter = '<'

            # Get sequence information
            if new_epoch:

                # Init an epoch, getting initial stimuli
                new_epoch, sti = copy_phrase_task.initialize_epoch()
                ele_sti = sti[0]
                timing_sti = sti[1]
                color_sti = sti[2]

                # Increase epoch number and reset epoch index
                epoch_counter += 1
                data['epochs'][epoch_counter] = {}
                epoch_index = 0
            else:
                epoch_index += 1

            # Update task state and reset the static
            self.rsvp.update_task_state(text=text_task, color_list=['white'])
            self.rsvp.draw_static()
            self.window.flip()

            # Setup the new Stimuli
            self.rsvp.stim_sequence = ele_sti[0]
            if self.is_txt_sti:
                self.rsvp.color_list_sti = color_sti[0]
            self.rsvp.time_list_sti = timing_sti[0]

            # Pause for a time
            core.wait(self.buffer_val)

            # Do the self.RSVP sequence!
            sequence_timing = self.rsvp.do_sequence()

            self.first_stim_time = self.rsvp.first_stim_time

            # Write triggers to file
            _write_triggers_from_sequence_copy_phrase(
                sequence_timing,
                self.trigger_file,
                self.copy_phrase,
                text_task)

            core.wait(self.buffer_val)

            if seq_counter == 0:
                del sequence_timing[0]

            # reshape the data and triggers as needed for later modules
            raw_data, triggers, target_info = \
                process_data_for_decision(
                    sequence_timing,
                    self.daq,
                    self.window,
                    self.parameters,
                    self.first_stim_time,
                    self.static_offset)

            # Uncomment this to turn off fake decisions, but use fake data.
            # self.fake = False
            if self.fake:
                # Construct Data Record
                data['epochs'][epoch_counter][epoch_index] = {
                    'stimuli': ele_sti,
                    'eeg_len': len(raw_data),
                    'timing_sti': timing_sti,
                    'triggers': triggers,
                    'target_info': target_info,
                    'target_letter': target_letter,
                    'current_text': text_task,
                    'copy_phrase': self.copy_phrase}

                # Evaluate this sequence
                (target_letter, text_task, run) = \
                    fake_copy_phrase_decision(self.copy_phrase,
                                              target_letter,
                                              text_task)

                # here we assume, in fake mode, all sequences result in a selection.
                last_selection = text_task[-1]
                new_epoch = True
                # Update next state for this record
                data['epochs'][
                    epoch_counter][
                    epoch_index][
                    'next_display_state'] = \
                    text_task

            else:
                # Evaluate this sequence, returning whether to gen a new
                #  epoch (seq) or stimuli to present
                new_epoch, sti = \
                    copy_phrase_task.evaluate_sequence(raw_data, triggers,
                                                       target_info, self.collection_window_len)

                # Construct Data Record
                data['epochs'][epoch_counter][epoch_index] = {
                    'stimuli': ele_sti,
                    'eeg_len': len(raw_data),
                    'timing_sti': timing_sti,
                    'triggers': triggers,
                    'target_info': target_info,
                    'current_text': text_task,
                    'copy_phrase': self.copy_phrase,
                    'next_display_state':
                        copy_phrase_task.decision_maker.displayed_state,
                    'lm_evidence': copy_phrase_task
                        .conjugator
                        .evidence_history['LM'][0]
                        .tolist(),
                    'eeg_evidence': copy_phrase_task
                        .conjugator
                        .evidence_history['ERP'][-1]
                        .tolist(),
                    'likelihood': copy_phrase_task
                        .conjugator.likelihood.tolist()
                }

                # If new_epoch is False, get the stimuli info returned
                if not new_epoch:
                    ele_sti = sti[0]
                    timing_sti = sti[1]
                    color_sti = sti[2]

                # Get the current task text from the decision maker
                text_task = copy_phrase_task.decision_maker.displayed_state
                last_selection = copy_phrase_task.decision_maker.last_selection

            # if a letter was selected and feedback enabled, show the chosen letter
            if new_epoch and self.show_feedback:
                self.feedback.administer(last_selection, message='Selected:')

            # Update time spent and save data
            data['total_time_spent'] = self.experiment_clock.getTime()
            data['total_number_epochs'] = epoch_counter
            _save_session_related_data(self.session_save_location, data)

            # Decide whether to keep the task going
            max_tries_exceeded = seq_counter >= self.max_seq_length
            max_time_exceeded = data['total_time_spent'] >= self.max_seconds
            if (text_task == self.copy_phrase or max_tries_exceeded or
                    max_time_exceeded):
                if max_tries_exceeded:
                    logging.debug("Max tries exceeded: to allow for more tries"
                                  " adjust the Maximum Sequence Length "
                                  "(max_seq_len) parameter.")
                if max_time_exceeded:
                    logging.debug("Max time exceeded. To allow for more time "
                                  "adjust the max_minutes parameter.")
                run = False

            # Increment sequence counter
            seq_counter += 1

        # Update task state and reset the static
        self.rsvp.update_task_state(text=text_task, color_list=['white'])

        # Say Goodbye!
        self.rsvp.text = trial_complete_message(self.window, self.parameters)
        self.rsvp.draw_static()
        self.window.flip()

        # Give the system time to process
        core.wait(self.buffer_val)

        if self.daq.is_calibrated:
            _write_triggers_from_sequence_copy_phrase(
                ['offset', self.daq.offset], self.trigger_file,
                self.copy_phrase, text_task, offset=True)

        # Close the trigger file for this session
        self.trigger_file.close()

        # Wait some time before exiting so there is trailing eeg data saved
        core.wait(self.eeg_buffer)

        return self.file_save

    def name(self):
        return self.TASK_NAME
Example #6
0
from bcipy.feedback.visual.visual_feedback import VisualFeedback
from psychopy import core
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()
# Start Visual Feedback
visual_feedback = VisualFeedback(display=display,
                                 parameters=parameters,
                                 clock=clock)
stimulus = 'A'
assertion = 'B'
message = 'Incorrect:'
visual_feedback.message_color = 'red'
timing = visual_feedback.administer(stimulus,
                                    compare_assertion=assertion,
                                    message=message)
print(timing)
print(visual_feedback._type())
Example #7
0
class TestVisualFeedback(unittest.TestCase):
    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 tearDown(self):
        # clean up by removing the data folder we used for testing
        unstub()

    def test_feedback_type(self):

        feedback_type = self.visual_feedback._type()
        self.assertEqual(feedback_type, 'Visual Feedback')

    def test_feedback_administer_text(self):
        test_stimulus = 'A'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:')

        self.assertTrue(isinstance(resp, list))

    def test_feedback_assertion_text(self):
        stimulus = 'B'
        assertion = 'A'
        self.visual_feedback.message_color = 'red'
        resp = self.visual_feedback.administer(stimulus,
                                               message='Incorrect:',
                                               compare_assertion=assertion)
        self.assertTrue(isinstance(resp, list))

    def test_feedback_administer_image(self):
        test_stimulus = 'bcipy/static/images/testing_images/white.png'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:')

        self.assertTrue(isinstance(resp, list))

    def test_feedback_assertion_images(self):
        test_stimulus = 'bcipy/static/images/testing_images/white.png'
        assertion = 'bcipy/static/images/testing_images/white.png'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:',
                                               compare_assertion=assertion)

        self.assertTrue(isinstance(resp, list))
Example #8
0
    def execute(self):
        self.logger.debug('Starting Icon to Icon Task!')
        image_array, timing_array = generate_icon_match_images(
            self.len_sti, self.image_path, self.num_sti, self.timing,
            self.is_word)

        # Get all png images in image path
        alp_image_array = glob.glob(self.image_path + '*.png')

        # Remove plus image from array
        for image in alp_image_array:
            if image.endswith('PLUS.png'):
                alp_image_array.remove(image)

        if self.is_word:
            image_name_array = glob.glob(self.image_path + '*.png')
            for image in image_name_array:
                image_name_array[image_name_array.index(
                    image)] = path.basename(image)
            alp_image_array.extend(image_name_array)

        for image in alp_image_array:
            alp_image_array[alp_image_array.index(image)] = image.split(
                '/')[-1].split('.')[0]

        self.alp = alp_image_array

        # Try Initializing Copy Phrase Wrapper:
        #       (sig_pro, decision maker, signal_model)
        try:
            copy_phrase_task = CopyPhraseWrapper(
                self.min_num_seq,
                self.max_seq_length,
                signal_model=self.signal_model,
                fs=self.daq.device_info.fs,
                k=2,
                alp=self.alp,
                task_list=['unnecessary_string', 'unnecessary_string'],
                lmodel=self.language_model,
                is_txt_sti=self.is_txt_sti,
                device_name=self.daq.device_info.name,
                device_channels=self.daq.device_info.channels)
        except Exception as e:
            self.logger.debug(f'Error Initializing Icon to Icon Task! {e}')
            raise e

        run = True
        new_epoch = True
        epoch_index = 0
        correct_trials = 0

        # Init session data and save before beginning
        data = {
            'session': self.file_save,
            'session_type': 'Icon to Icon Matching',
            'paradigm': 'RSVP',
            'epochs': {},
            'total_time_spent': self.experiment_clock.getTime(),
            'total_number_epochs': 0,
        }

        # Save session data
        _save_session_related_data(self.session_save_location, data)

        # Check user input to make sure we should be going
        if not get_user_input(self.rsvp,
                              self.wait_screen_message,
                              self.wait_screen_message_color,
                              first_run=True):
            run = False

        current_trial = 0
        while run:
            # check user input to make sure we should be going
            if not get_user_input(self.rsvp, self.wait_screen_message,
                                  self.wait_screen_message_color):
                break

            if new_epoch:
                # Init an epoch, getting initial stimuli
                new_epoch, sti = copy_phrase_task.initialize_epoch()

                # If correct decisions are being faked, make sure that we always
                # are starting a new epoch
                if self.fake:
                    new_epoch = True

                # Increase epoch number and reset epoch index
                data['epochs'][current_trial] = {}
                epoch_index = 0
            else:
                epoch_index += 1

            data['epochs'][current_trial][epoch_index] = {}

            if current_trial < len(image_array) or not new_epoch:
                self.rsvp.sti.height = self.stimuli_height

                self.rsvp.stim_sequence = image_array[current_trial]
                self.rsvp.time_list_sti = timing_array
                # Change size of target word if we are in word matching mode
                if self.is_word:
                    # Generate list whose length is the length of the stimuli sequence, filled with the stimuli height
                    self.rsvp.size_list_sti = list(
                        repeat(self.stimuli_height,
                               len(self.rsvp.stim_sequence) + 1))
                    # Set the target word font size to the font size defined in parameters
                    self.rsvp.size_list_sti[0] = self.word_matching_text_size

                core.wait(self.buffer_val)

                self.rsvp.update_task_state(self.rsvp.stim_sequence[0],
                                            self.task_height, 'yellow',
                                            self.rsvp.win.size, self.is_word)

                # Do the sequence
                sequence_timing = self.rsvp.do_sequence()

                self.first_stim_time = self.rsvp.first_stim_time

                # Write triggers to file
                _write_triggers_from_sequence_calibration(
                    sequence_timing, self.trigger_file)

                # Wait for a time
                core.wait(self.buffer_val)

                # reshape the data and triggers as needed for later modules
                raw_data, triggers, target_info = \
                    process_data_for_decision(sequence_timing, self.daq, self.window,
                                              self.parameters, self.first_stim_time)

                # self.fake = False

                display_stimulus = self.rsvp.stim_sequence[0]

                display_message = False
                if self.fake:
                    # Construct Data Record
                    data['epochs'][current_trial][epoch_index] = {
                        'stimuli': image_array[current_trial],
                        'eeg_len': len(raw_data),
                        'timing_sti': timing_array,
                        'triggers': triggers,
                        'target_info': target_info,
                        'target_letter': display_stimulus
                    }
                    correct_decision = True
                    display_message = True
                    message_color = 'green'
                    current_trial += 1
                    correct_trials += 1
                    new_epoch = True
                    if self.is_word:
                        display_stimulus = self.image_path + \
                            self.rsvp.stim_sequence[0] + '.png'
                else:
                    new_epoch, sti = \
                        copy_phrase_task.evaluate_sequence(raw_data, triggers,
                                                           target_info, self.collection_window_len)

                    # Construct Data Record
                    data['epochs'][current_trial][epoch_index] = {
                        'stimuli':
                        image_array[current_trial],
                        'eeg_len':
                        len(raw_data),
                        'timing_sti':
                        timing_array,
                        'triggers':
                        triggers,
                        'target_info':
                        target_info,
                        'lm_evidence':
                        copy_phrase_task.conjugator.evidence_history['LM']
                        [0].tolist(),
                        'eeg_evidence':
                        copy_phrase_task.conjugator.evidence_history['ERP']
                        [0].tolist(),
                        'likelihood':
                        copy_phrase_task.conjugator.likelihood.tolist()
                    }

                    # Test whether to display feedback message, and what color
                    # the message should be
                    if new_epoch:
                        if self.is_word:
                            decide_image_path = self.image_path + \
                                copy_phrase_task.decision_maker.last_selection + '.png'
                        else:
                            decide_image_path = copy_phrase_task.decision_maker.last_selection + '.png'
                        correct_decision = decide_image_path == self.rsvp.stim_sequence[
                            0]
                        display_stimulus = decide_image_path
                        current_trial += 1
                        if correct_decision:
                            message_color = 'green'
                            correct_trials += 1
                        else:
                            message_color = 'red'
                        display_message = True
                    else:
                        display_message = False

                if display_message:
                    # Display feedback about whether decision was correct
                    visual_feedback = VisualFeedback(
                        display=self.window,
                        parameters=self.parameters,
                        clock=self.experiment_clock)
                    stimulus = display_stimulus
                    visual_feedback.message_color = message_color
                    visual_feedback.administer(stimulus,
                                               compare_assertion=None,
                                               message='Decision:')

                # Update time spent and save data
                data['total_time_spent'] = self.experiment_clock.getTime()
                data['total_number_epochs'] = current_trial
                _save_session_related_data(self.session_save_location, data)

                # Decide whether to keep the task going
                max_tries_exceeded = current_trial >= self.max_seq_length
                max_time_exceeded = data['total_time_spent'] >= self.max_seconds
                if (max_tries_exceeded or max_time_exceeded):
                    if max_tries_exceeded:
                        logging.debug(
                            "Max tries exceeded: to allow for more tries"
                            " adjust the Maximum Sequence Length "
                            "(max_seq_len) parameter.")
                    if max_time_exceeded:
                        logging.debug(
                            "Max time exceeded. To allow for more time "
                            "adjust the max_minutes parameter.")
                    run = False

            else:
                run = False

        # Write trial data to icon_data.csv in file save location
        with open(f"{self.file_save}/icon_data.csv", 'w+') as icon_output_csv:
            icon_output_writer = csv.writer(icon_output_csv, delimiter=',')
            icon_output_writer.writerow([
                'Participant ID',
                dirname(self.file_save).replace(self.data_save_path, '')
            ])
            icon_output_writer.writerow(['Date/Time', datetime.datetime.now()])
            if self.auc_filename:
                icon_output_writer.writerow([
                    'Calibration AUC',
                    basename(self.auc_filename).replace('.pkl', '')
                ])
            temp_epoch_index = 1 if epoch_index == 0 else epoch_index
            temp_current_trial = 1 if current_trial == 0 else current_trial
            icon_output_writer.writerow([
                'Percentage of correctly selected icons',
                (correct_trials / (temp_current_trial * temp_epoch_index)) *
                100
            ])
            icon_output_writer.writerow([
                'Task type',
                ('Icon to word' if self.is_word else 'Icon to icon')
            ])

        # Say Goodbye!
        self.rsvp.text = trial_complete_message(self.window, self.parameters)
        self.rsvp.draw_static()
        self.window.flip()

        # Give the system time to process
        core.wait(self.buffer_val)

        # Close this sessions trigger file and return some data
        self.trigger_file.close()

        # Wait some time before exiting so there is trailing eeg data saved
        core.wait(self.eeg_buffer)

        return self.file_save
Example #9
0
class TestVisualFeedback(unittest.TestCase):
    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)

    def tearDown(self):
        # clean up by removing the data folder we used for testing
        unstub()

    def test_feedback_type(self):

        feedback_type = self.visual_feedback._type()
        self.assertEqual(feedback_type, 'Visual Feedback')

    def test_feedback_administer_text(self):
        test_stimulus = 'A'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:')

        self.assertTrue(isinstance(resp, list))

    def test_feedback_assertion_text(self):
        stimulus = 'B'
        assertion = 'A'
        self.visual_feedback.message_color = 'red'
        resp = self.visual_feedback.administer(stimulus,
                                               message='Incorrect:',
                                               compare_assertion=assertion)
        self.assertTrue(isinstance(resp, list))

    def test_feedback_administer_image(self):
        test_stimulus = 'bcipy/static/images/testing_images/white.png'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:')

        self.assertTrue(isinstance(resp, list))

    def test_feedback_assertion_images(self):
        test_stimulus = 'bcipy/static/images/testing_images/white.png'
        assertion = 'bcipy/static/images/testing_images/white.png'
        resp = self.visual_feedback.administer(test_stimulus,
                                               message='Correct:',
                                               compare_assertion=assertion)

        self.assertTrue(isinstance(resp, list))