예제 #1
0
    def __init__(self,
                 window: visual.Window,
                 static_clock,
                 experiment_clock: core.Clock,
                 marker_writer: Optional[MarkerWriter] = None,
                 task_color: List[str] = ['white'],
                 task_font: str = 'Times',
                 task_pos: Tuple[float, float] = (-.8, .9),
                 task_height: float = 0.2,
                 task_text: str = '1/100',
                 info_color: List[str] = ['white'],
                 info_text: List[str] = ['Information Text'],
                 info_font: List[str] = ['Times'],
                 info_pos=[(.8, .9)],
                 info_height=[0.2],
                 stim_font='Times',
                 stim_pos=(-.8, .9),
                 stim_height=0.2,
                 stim_sequence: List[str] = ['a'] * 10,
                 stim_colors: List[str] = ['white'] * 10,
                 stim_timing: List[float] = [1] * 10,
                 is_txt_stim: bool = True,
                 static_time: float = .05,
                 trigger_type: str = 'image',
                 space_char: SPACE_CHAR = SPACE_CHAR):
        """Initialize RSVP window parameters and objects.

        PARAMETERS:
        ----------
        # Experiment
        window(visual.Window): PsychoPy Window
        static_clock(TODO): no idea
        experiment_clock(core.Clock): Clock used to timestamp experiment
        marker_writer(MarkerWriter): object used to write triggers to
            the daq stream.

        # Task
        task_color(list[string]): Color of the task string. Shares the
            length of the task_text. If of length 1 the entire task
            bar shares the same color.
        task_font(string): Font of task string
        task_pos(tuple): position of task string
        task_height(float): height for task string
        task_text(string): text of the task bar

        # Info
        info_text(list[string]): Text list for information texts
        info_color(list[string]): Color of the information text string
        info_font(list[string]): Font of the information text string
        info_pos(list[tuple]): Position of the information text string
        info_height(list[float]): Height of the information text string

        # Stimuli
        stim_height(float): height of the stimuli object
        stim_pos(tuple): position of stimuli
        stim_font(string): font of the stimuli
        stim_sequence(list[string]): list of elements to flash
        stim_colors(list[string]): list of colors for stimuli
        stim_timing(list[float]): timing for each letter flash
        """
        self.window = window
        self.refresh_rate = window.getActualFrameRate()

        self.logger = logging.getLogger(__name__)

        self.stimuli_sequence = stim_sequence
        self.stimuli_colors = stim_colors
        self.stimuli_timing = stim_timing

        self.is_txt_stim = is_txt_stim
        self.staticPeriod = static_clock
        self.static_time = static_time
        self.experiment_clock = experiment_clock
        self.timing_clock = core.Clock()

        # Used to handle writing the marker stimulus
        self.marker_writer = marker_writer or NullMarkerWriter()

        # Length of the stimuli (number of flashes)
        self.stim_length = len(stim_sequence)

        # Informational Parameters
        self.info_text = info_text

        # Stim parameters
        self.stimuli_font = stim_font
        self.stimuli_height = stim_height
        self.stimuli_pos = stim_pos

        # Trigger Items
        self.first_run = True
        self.trigger_type = trigger_type
        self.trigger_callback = TriggerCallback()

        # Callback used on presentation of first stimulus.
        self.first_stim_callback = lambda _sti: None
        self.size_list_sti = []

        self.space_char = space_char

        self.task = visual.TextStim(win=self.window,
                                    color=task_color[0],
                                    height=task_height,
                                    text=task_text,
                                    font=task_font,
                                    pos=task_pos,
                                    wrapWidth=None,
                                    colorSpace='rgb',
                                    opacity=1,
                                    depth=-6.0)

        # Create multiple text objects based on input
        self.text = []
        for idx in range(len(self.info_text)):
            self.text.append(
                visual.TextStim(win=self.window,
                                color=info_color[idx],
                                height=info_height[idx],
                                text=self.info_text[idx],
                                font=info_font[idx],
                                pos=info_pos[idx],
                                wrapWidth=None,
                                colorSpace='rgb',
                                opacity=1,
                                depth=-6.0))

        # Create Stimuli Object
        if self.is_txt_stim:
            self.sti = visual.TextStim(win=self.window,
                                       color='white',
                                       height=self.stimuli_height,
                                       text='+',
                                       font=self.stimuli_font,
                                       pos=self.stimuli_pos,
                                       wrapWidth=None,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)
        else:
            self.sti = visual.ImageStim(win=self.window,
                                        image=None,
                                        mask=None,
                                        pos=self.stimuli_pos,
                                        ori=0.0)
예제 #2
0
    def __init__(self,
                 window,
                 static_period,
                 experiment_clock,
                 marker_writer=None,
                 color_task=['white'],
                 font_task='Times',
                 pos_task=(-.8, .9),
                 task_height=0.2,
                 text_task='1/100',
                 color_text=['white'],
                 text_text=['Information Text'],
                 font_text=['Times'],
                 pos_text=[(.8, .9)],
                 height_text=[0.2],
                 font_sti='Times',
                 pos_sti=(-.8, .9),
                 sti_height=0.2,
                 stim_sequence=['a'] * 10,
                 color_list_sti=['white'] * 10,
                 time_list_sti=[1] * 10,
                 is_txt_sti=True,
                 static_period_time=.05,
                 trigger_type='image',
                 space_char=SPACE_CHAR):
        """Initialize RSVP window parameters and objects.

        Args:
                window(visual_window): Window in computer
                marker_writer(MarkerWriter): object used to write triggers to
                    the daq stream.
                color_task(list[string]): Color of the task string. Shares the
                    length of the text_task. If of length 1 the entire task
                    bar shares the same color.
                font_task(string): Font of task string
                pos_task(tuple): position of task string
                task_height(float): height for task string
                text_task(string): text of the task bar

                text_text(list[string]): text list for information texts
                color_text(list[string]): Color of the text string
                font_text(list[string]): Font of text string
                pos_text(list[tuple]): position of text string
                task_height(list[float]): height for text string

                sti_height(float): height of the stimuli object
                pos_sti(tuple): position of stimuli
                font_sti(string): font of the stimuli
                stim_sequence(list[string]): list of elements to flash
                color_list_sti(list[string]): list of colors for stimuli
                time_list_sti(list[float]): timing for each letter flash
        """
        self.win = window
        self.refresh_rate = window.getActualFrameRate()

        self.logger = logging.getLogger(__name__)

        self.stim_sequence = stim_sequence
        self.color_list_sti = color_list_sti
        self.time_list_sti = time_list_sti

        self.is_txt_sti = is_txt_sti

        self.staticPeriod = static_period
        self.static_period_time = static_period_time
        self.experiment_clock = experiment_clock
        self.timing_clock = core.Clock()

        # Used to handle writing the marker stimulus
        self.marker_writer = marker_writer or NullMarkerWriter()

        # Length of the stimuli (number of flashes)
        self.len_sti = len(stim_sequence)

        # Stim parameters
        self.font_stim = font_sti
        self.height_stim = sti_height
        self.pos_sti = pos_sti

        self.first_run = True
        self.trigger_type = trigger_type
        self.trigger_callback = TriggerCallback()
        # Callback used on presentation of first stimulus.
        self.first_stim_callback = lambda _sti: None
        self.size_list_sti = []

        self.space_char = space_char

        # Check if task text is multicolored
        if len(color_task) == 1:
            self.task = visual.TextStim(win=window,
                                        color=color_task[0],
                                        height=task_height,
                                        text=text_task,
                                        font=font_task,
                                        pos=pos_task,
                                        wrapWidth=None,
                                        colorSpace='rgb',
                                        opacity=1,
                                        depth=-6.0)
        else:
            self.task = MultiColorText(win=window,
                                       list_color=color_task,
                                       height=task_height,
                                       text=text_task,
                                       font=font_task,
                                       pos=pos_task,
                                       opacity=1,
                                       depth=-6.0)

        # Create multiple text objects based on input
        self.text = []
        for idx in range(len(text_text)):
            self.text.append(
                visual.TextStim(win=window,
                                color=color_text[idx],
                                height=height_text[idx],
                                text=text_text[idx],
                                font=font_text[idx],
                                pos=pos_text[idx],
                                wrapWidth=None,
                                colorSpace='rgb',
                                opacity=1,
                                depth=-6.0))

        # Create Stimuli Object
        if self.is_txt_sti:
            self.sti = visual.TextStim(win=window,
                                       color='white',
                                       height=sti_height,
                                       text='+',
                                       font=font_sti,
                                       pos=pos_sti,
                                       wrapWidth=None,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)
        else:
            self.sti = visual.ImageStim(win=window,
                                        image=None,
                                        mask=None,
                                        pos=pos_sti,
                                        ori=0.0)
예제 #3
0
class RSVPDisplay(object):
    """RSVP Display Object for Sequence Presentation.

    Animates a sequence in RSVP. Mode should be determined outside.
    """
    def __init__(self,
                 window: visual.Window,
                 static_clock,
                 experiment_clock: core.Clock,
                 marker_writer: Optional[MarkerWriter] = None,
                 task_color: List[str] = ['white'],
                 task_font: str = 'Times',
                 task_pos: Tuple[float, float] = (-.8, .9),
                 task_height: float = 0.2,
                 task_text: str = '1/100',
                 info_color: List[str] = ['white'],
                 info_text: List[str] = ['Information Text'],
                 info_font: List[str] = ['Times'],
                 info_pos=[(.8, .9)],
                 info_height=[0.2],
                 stim_font='Times',
                 stim_pos=(-.8, .9),
                 stim_height=0.2,
                 stim_sequence: List[str] = ['a'] * 10,
                 stim_colors: List[str] = ['white'] * 10,
                 stim_timing: List[float] = [1] * 10,
                 is_txt_stim: bool = True,
                 static_time: float = .05,
                 trigger_type: str = 'image',
                 space_char: SPACE_CHAR = SPACE_CHAR):
        """Initialize RSVP window parameters and objects.

        PARAMETERS:
        ----------
        # Experiment
        window(visual.Window): PsychoPy Window
        static_clock(TODO): no idea
        experiment_clock(core.Clock): Clock used to timestamp experiment
        marker_writer(MarkerWriter): object used to write triggers to
            the daq stream.

        # Task
        task_color(list[string]): Color of the task string. Shares the
            length of the task_text. If of length 1 the entire task
            bar shares the same color.
        task_font(string): Font of task string
        task_pos(tuple): position of task string
        task_height(float): height for task string
        task_text(string): text of the task bar

        # Info
        info_text(list[string]): Text list for information texts
        info_color(list[string]): Color of the information text string
        info_font(list[string]): Font of the information text string
        info_pos(list[tuple]): Position of the information text string
        info_height(list[float]): Height of the information text string

        # Stimuli
        stim_height(float): height of the stimuli object
        stim_pos(tuple): position of stimuli
        stim_font(string): font of the stimuli
        stim_sequence(list[string]): list of elements to flash
        stim_colors(list[string]): list of colors for stimuli
        stim_timing(list[float]): timing for each letter flash
        """
        self.window = window
        self.refresh_rate = window.getActualFrameRate()

        self.logger = logging.getLogger(__name__)

        self.stimuli_sequence = stim_sequence
        self.stimuli_colors = stim_colors
        self.stimuli_timing = stim_timing

        self.is_txt_stim = is_txt_stim
        self.staticPeriod = static_clock
        self.static_time = static_time
        self.experiment_clock = experiment_clock
        self.timing_clock = core.Clock()

        # Used to handle writing the marker stimulus
        self.marker_writer = marker_writer or NullMarkerWriter()

        # Length of the stimuli (number of flashes)
        self.stim_length = len(stim_sequence)

        # Informational Parameters
        self.info_text = info_text

        # Stim parameters
        self.stimuli_font = stim_font
        self.stimuli_height = stim_height
        self.stimuli_pos = stim_pos

        # Trigger Items
        self.first_run = True
        self.trigger_type = trigger_type
        self.trigger_callback = TriggerCallback()

        # Callback used on presentation of first stimulus.
        self.first_stim_callback = lambda _sti: None
        self.size_list_sti = []

        self.space_char = space_char

        self.task = visual.TextStim(win=self.window,
                                    color=task_color[0],
                                    height=task_height,
                                    text=task_text,
                                    font=task_font,
                                    pos=task_pos,
                                    wrapWidth=None,
                                    colorSpace='rgb',
                                    opacity=1,
                                    depth=-6.0)

        # Create multiple text objects based on input
        self.text = []
        for idx in range(len(self.info_text)):
            self.text.append(
                visual.TextStim(win=self.window,
                                color=info_color[idx],
                                height=info_height[idx],
                                text=self.info_text[idx],
                                font=info_font[idx],
                                pos=info_pos[idx],
                                wrapWidth=None,
                                colorSpace='rgb',
                                opacity=1,
                                depth=-6.0))

        # Create Stimuli Object
        if self.is_txt_stim:
            self.sti = visual.TextStim(win=self.window,
                                       color='white',
                                       height=self.stimuli_height,
                                       text='+',
                                       font=self.stimuli_font,
                                       pos=self.stimuli_pos,
                                       wrapWidth=None,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)
        else:
            self.sti = visual.ImageStim(win=self.window,
                                        image=None,
                                        mask=None,
                                        pos=self.stimuli_pos,
                                        ori=0.0)

    def draw_static(self):
        """Draw static elements in a stimulus."""
        self.task.draw()
        for idx in range(len(self.text)):
            self.text[idx].draw()

    def schedule_to(self, ele_list=[], time_list=[], color_list=[]):
        """Schedule stimuli elements (works as a buffer).

        Args:
                ele_list(list[string]): list of elements of stimuli
                time_list(list[float]): list of timings of stimuli
                color_list(list[string]): colors of elements of stimuli
        """
        self.stimuli_sequence = ele_list
        self.stimuli_timing = time_list
        self.stimuli_colors = color_list

    def update_task(self, text: str, color_list: List[str], pos: Tuple[float]):
        """Update Task Object.

        PARAMETERS:
        -----------
        text: text for task
        color_list: list of the colors for each char
        pos: position of task
        """
        self.task.text = text
        self.task.color = color_list[0]
        self.task.pos = pos

    def do_sequence(self):
        """Do Sequence.

        Animates a sequence of flashing letters to achieve RSVP.
        """

        # init an array for timing information
        timing = []

        if self.first_run:
            # play a sequence start sound to help orient triggers
            first_stim_timing = _calibration_trigger(
                self.experiment_clock,
                trigger_type=self.trigger_type,
                display=self.window,
                on_trigger=self.marker_writer.push_marker)

            timing.append(first_stim_timing)

            self.first_stim_time = first_stim_timing[-1]
            self.first_run = False

        # generate a sequence (list of stimuli with meta information)
        sequence = self._generate_sequence()

        # do the sequence
        for idx in range(len(sequence)):

            self.is_first_stim = (idx == 0)

            # set a static period to do all our stim setting.
            #   will warn if ISI value is violated.
            self.staticPeriod.name = 'Stimulus Draw Period'
            self.staticPeriod.start(self.stimuli_timing[idx])

            # Reset the timing clock to start presenting
            self.window.callOnFlip(self.trigger_callback.callback,
                                   self.experiment_clock,
                                   sequence[idx]['sti_label'])
            self.window.callOnFlip(self.marker_writer.push_marker,
                                   sequence[idx]['sti_label'])

            if idx == 0 and callable(self.first_stim_callback):
                self.first_stim_callback(sequence[idx]['sti'])

            # Draw stimulus for n frames
            sequence[idx]['sti'].draw()
            self.draw_static()
            self.window.flip()
            core.wait(
                (sequence[idx]['time_to_present'] - 1) / self.refresh_rate)

            # End static period
            self.staticPeriod.complete()

            # append timing information
            if self.is_txt_stim:
                timing.append(self.trigger_callback.timing)
            else:
                timing.append(self.trigger_callback.timing)

            self.trigger_callback.reset()

        # draw in static and flip once more
        self.draw_static()
        self.window.flip()

        return timing

    def _generate_sequence(self):
        """Generate Sequence.

        Generate stimuli for next RSVP sequence.
        """
        stim_info = []
        for idx in range(len(self.stimuli_sequence)):
            current_stim = {}

            # turn ms timing into frames! Much more accurate!
            current_stim['time_to_present'] = int(self.stimuli_timing[idx] *
                                                  self.refresh_rate)

            # check if stimulus needs to use a non-default size
            if self.size_list_sti:
                this_stimuli_size = self.size_list_sti[idx]
            else:
                this_stimuli_size = self.stimuli_height

            # Set the Stimuli attrs
            if self.stimuli_sequence[idx].endswith('.png'):
                current_stim['sti'] = self.create_stimulus(
                    mode='image', height_int=this_stimuli_size)
                current_stim['sti'].image = self.stimuli_sequence[idx]
                current_stim['sti'].size = resize_image(
                    current_stim['sti'].image, current_stim['sti'].win.size,
                    this_stimuli_size)
                current_stim['sti_label'] = path.splitext(
                    path.basename(self.stimuli_sequence[idx]))[0]
            else:
                # text stimulus
                current_stim['sti'] = self.create_stimulus(
                    mode='text', height_int=this_stimuli_size)
                txt = self.stimuli_sequence[idx]
                # customize presentation of space char.
                current_stim[
                    'sti'].text = txt if txt != SPACE_CHAR else self.space_char
                current_stim['sti'].color = self.stimuli_colors[idx]
                current_stim['sti_label'] = txt

                # test whether the word will be too big for the screen
                text_width = current_stim['sti'].boundingBox[0]
                if text_width > self.window.size[0]:
                    info = get_system_info()
                    text_height = current_stim['sti'].boundingBox[1]
                    # If we are in full-screen, text size in Psychopy norm units
                    # is monitor width/monitor height
                    if self.window.size[0] == info['RESOLUTION'][0]:
                        new_text_width = info['RESOLUTION'][0] / \
                            info['RESOLUTION'][1]
                    else:
                        # If not, text width is calculated relative to both
                        # monitor size and window size
                        new_text_width = (
                            self.window.size[1] / info['RESOLUTION'][1]) * (
                                info['RESOLUTION'][0] / info['RESOLUTION'][1])
                    new_text_height = (text_height *
                                       new_text_width) / text_width
                    current_stim['sti'].height = new_text_height
            stim_info.append(current_stim)
        return stim_info

    def update_task_state(self, text: str, color_list: List[str]) -> None:
        """Update task state.

        Removes letters or appends to the right.
        Args:
                text(string): new text for task state
                color_list(list[string]): list of colors for each
        """
        task_state_text = visual.TextStim(win=self.window,
                                          font=self.task.font,
                                          text=text)
        x_task_position = task_state_text.boundingBox[0] / \
            self.window.size[0] - 1
        task_pos = (x_task_position, 1 - self.task.height)

        self.update_task(text=text, color_list=color_list, pos=task_pos)

    def wait_screen(self, message, color):
        """Wait Screen.

        Args:
            message(string): message to be displayed while waiting
        """

        # Construct the wait message
        wait_message = visual.TextStim(win=self.window,
                                       font=self.stimuli_font,
                                       text=message,
                                       height=.1,
                                       color=color,
                                       pos=(0, -.5),
                                       wrapWidth=2,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)

        # Try adding our BCI logo. Pass if not found.
        try:
            wait_logo = visual.ImageStim(
                self.window,
                image='bcipy/static/images/gui_images/bci_cas_logo.png',
                pos=(0, .5),
                mask=None,
                ori=0.0)
            wait_logo.size = resize_image(
                'bcipy/static/images/gui_images/bci_cas_logo.png',
                self.window.size, 1)
            wait_logo.draw()

        except Exception:
            self.logger.debug('Cannot load logo image')
            pass

        # Draw and flip the screen.
        wait_message.draw()
        self.window.flip()

    def create_stimulus(self, height_int: int, mode: str = 'text'):
        """Create Stimulus.

        Returns a TextStim or ImageStim object.
            Args:
            height_int: The height of the stimulus
            mode: "text" or "image", determines which to return
        """
        if mode == 'text':
            return visual.TextStim(win=self.window,
                                   color='white',
                                   height=height_int,
                                   text='+',
                                   font=self.stimuli_font,
                                   pos=self.stimuli_pos,
                                   wrapWidth=None,
                                   colorSpace='rgb',
                                   opacity=1,
                                   depth=-6.0)
        if mode == 'image':
            return visual.ImageStim(win=self.window,
                                    image=None,
                                    mask=None,
                                    units='',
                                    pos=self.stimuli_pos,
                                    size=(height_int, height_int),
                                    ori=0.0)
예제 #4
0
class RSVPDisplay(object):
    """RSVP Display Object for Sequence Presentation.

    Animates a sequence in RSVP. Mode should be determined outside.
    """
    def __init__(self,
                 window,
                 static_period,
                 experiment_clock,
                 marker_writer=None,
                 color_task=['white'],
                 font_task='Times',
                 pos_task=(-.8, .9),
                 task_height=0.2,
                 text_task='1/100',
                 color_text=['white'],
                 text_text=['Information Text'],
                 font_text=['Times'],
                 pos_text=[(.8, .9)],
                 height_text=[0.2],
                 font_sti='Times',
                 pos_sti=(-.8, .9),
                 sti_height=0.2,
                 stim_sequence=['a'] * 10,
                 color_list_sti=['white'] * 10,
                 time_list_sti=[1] * 10,
                 is_txt_sti=True,
                 static_period_time=.05,
                 trigger_type='image',
                 space_char=SPACE_CHAR):
        """Initialize RSVP window parameters and objects.

        Args:
                window(visual_window): Window in computer
                marker_writer(MarkerWriter): object used to write triggers to
                    the daq stream.
                color_task(list[string]): Color of the task string. Shares the
                    length of the text_task. If of length 1 the entire task
                    bar shares the same color.
                font_task(string): Font of task string
                pos_task(tuple): position of task string
                task_height(float): height for task string
                text_task(string): text of the task bar

                text_text(list[string]): text list for information texts
                color_text(list[string]): Color of the text string
                font_text(list[string]): Font of text string
                pos_text(list[tuple]): position of text string
                task_height(list[float]): height for text string

                sti_height(float): height of the stimuli object
                pos_sti(tuple): position of stimuli
                font_sti(string): font of the stimuli
                stim_sequence(list[string]): list of elements to flash
                color_list_sti(list[string]): list of colors for stimuli
                time_list_sti(list[float]): timing for each letter flash
        """
        self.win = window
        self.refresh_rate = window.getActualFrameRate()

        self.logger = logging.getLogger(__name__)

        self.stim_sequence = stim_sequence
        self.color_list_sti = color_list_sti
        self.time_list_sti = time_list_sti

        self.is_txt_sti = is_txt_sti

        self.staticPeriod = static_period
        self.static_period_time = static_period_time
        self.experiment_clock = experiment_clock
        self.timing_clock = core.Clock()

        # Used to handle writing the marker stimulus
        self.marker_writer = marker_writer or NullMarkerWriter()

        # Length of the stimuli (number of flashes)
        self.len_sti = len(stim_sequence)

        # Stim parameters
        self.font_stim = font_sti
        self.height_stim = sti_height
        self.pos_sti = pos_sti

        self.first_run = True
        self.trigger_type = trigger_type
        self.trigger_callback = TriggerCallback()
        # Callback used on presentation of first stimulus.
        self.first_stim_callback = lambda _sti: None
        self.size_list_sti = []

        self.space_char = space_char

        # Check if task text is multicolored
        if len(color_task) == 1:
            self.task = visual.TextStim(win=window,
                                        color=color_task[0],
                                        height=task_height,
                                        text=text_task,
                                        font=font_task,
                                        pos=pos_task,
                                        wrapWidth=None,
                                        colorSpace='rgb',
                                        opacity=1,
                                        depth=-6.0)
        else:
            self.task = MultiColorText(win=window,
                                       list_color=color_task,
                                       height=task_height,
                                       text=text_task,
                                       font=font_task,
                                       pos=pos_task,
                                       opacity=1,
                                       depth=-6.0)

        # Create multiple text objects based on input
        self.text = []
        for idx in range(len(text_text)):
            self.text.append(
                visual.TextStim(win=window,
                                color=color_text[idx],
                                height=height_text[idx],
                                text=text_text[idx],
                                font=font_text[idx],
                                pos=pos_text[idx],
                                wrapWidth=None,
                                colorSpace='rgb',
                                opacity=1,
                                depth=-6.0))

        # Create Stimuli Object
        if self.is_txt_sti:
            self.sti = visual.TextStim(win=window,
                                       color='white',
                                       height=sti_height,
                                       text='+',
                                       font=font_sti,
                                       pos=pos_sti,
                                       wrapWidth=None,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)
        else:
            self.sti = visual.ImageStim(win=window,
                                        image=None,
                                        mask=None,
                                        pos=pos_sti,
                                        ori=0.0)

    def draw_static(self):
        """Draw static elements in a stimulus."""
        self.task.draw()
        for idx in range(len(self.text)):
            self.text[idx].draw()

    def schedule_to(self, ele_list=[], time_list=[], color_list=[]):
        """Schedule stimuli elements (works as a buffer).

        Args:
                ele_list(list[string]): list of elements of stimuli
                time_list(list[float]): list of timings of stimuli
                color_list(list[string]): colors of elements of stimuli
        """
        self.stim_sequence = ele_list
        self.time_list_sti = time_list
        self.color_list_sti = color_list

    def update_task(self, text, color_list, pos):
        """Update Task Object.
        Args:
                text(string): text for task
                color_list(list[string]): list of the colors for each char
                pos(tuple): position of task
        """
        if len(color_list) == 1:
            self.task.text = text
            self.task.color = color_list[0]
            self.task.pos = pos
        else:
            self.task.update(text=text, color_list=color_list, pos=pos)

    def do_sequence(self):
        """Do Sequence.

        Animates a sequence of flashing letters to achieve RSVP.
        """

        # init an array for timing information
        timing = []

        if self.first_run:
            # play a sequence start sound to help orient triggers
            stim_timing = _calibration_trigger(
                self.experiment_clock,
                trigger_type=self.trigger_type,
                display=self.win,
                on_trigger=self.marker_writer.push_marker)

            timing.append(stim_timing)

            self.first_stim_time = stim_timing[-1]
            self.first_run = False

        # do the sequence
        for idx in range(len(self.stim_sequence)):

            # set a static period to do all our stim setting.
            #   will warn if ISI value is violated.
            self.staticPeriod.start(self.static_period_time)

            # turn ms timing into frames! Much more accurate!
            self.time_to_present = int(self.time_list_sti[idx] *
                                       self.refresh_rate)

            # check if stimulus needs to use a non-default size
            if self.size_list_sti:
                this_stimuli_size = self.size_list_sti[idx]
            else:
                this_stimuli_size = self.height_stim

            # Set the Stimuli attrs
            if self.stim_sequence[idx].endswith('.png'):
                self.sti = self.create_stimulus(mode='image',
                                                height_int=this_stimuli_size)
                self.sti.image = self.stim_sequence[idx]
                self.sti.size = resize_image(self.sti.image, self.sti.win.size,
                                             this_stimuli_size)
                sti_label = path.splitext(
                    path.basename(self.stim_sequence[idx]))[0]
            else:
                # text stimulus
                self.sti = self.create_stimulus(mode='text',
                                                height_int=this_stimuli_size)
                txt = self.stim_sequence[idx]
                # customize presentation of space char.
                self.sti.text = txt if txt != SPACE_CHAR else self.space_char
                self.sti.color = self.color_list_sti[idx]
                sti_label = txt

                # test whether the word will be too big for the screen
                text_width = self.sti.boundingBox[0]
                if text_width > self.win.size[0]:
                    info = get_system_info()
                    text_height = self.sti.boundingBox[1]
                    # If we are in full-screen, text size in Psychopy norm units
                    # is monitor width/monitor height
                    if self.win.size[0] == info['RESOLUTION'][0]:
                        new_text_width = info['RESOLUTION'][0] / info[
                            'RESOLUTION'][1]
                    else:
                        # If not, text width is calculated relative to both
                        # monitor size and window size
                        new_text_width = (
                            self.win.size[1] / info['RESOLUTION'][1]) * (
                                info['RESOLUTION'][0] / info['RESOLUTION'][1])
                    new_text_height = (text_height *
                                       new_text_width) / text_width
                    self.sti.height = new_text_height

            # End static period
            self.staticPeriod.complete()

            # Reset the timing clock to start presenting
            self.win.callOnFlip(self.trigger_callback.callback,
                                self.experiment_clock, sti_label)
            self.win.callOnFlip(self.marker_writer.push_marker, sti_label)

            if idx == 0 and callable(self.first_stim_callback):
                self.first_stim_callback(self.sti)

            # Draw stimulus for n frames
            for _n_frames in range(self.time_to_present):
                self.sti.draw()
                self.draw_static()
                self.win.flip()

            # append timing information
            if self.is_txt_sti:
                timing.append(self.trigger_callback.timing)
            else:
                timing.append(self.trigger_callback.timing)

            self.trigger_callback.reset()

        # draw in static and flip once more
        self.draw_static()
        self.win.flip()

        return timing

    def update_task_state(self, text: str, color_list: List[str]) -> None:
        """Update task state.

        Removes letters or appends to the right.
        Args:
                text(string): new text for task state
                color_list(list[string]): list of colors for each
        """
        tmp = visual.TextStim(win=self.win, font=self.task.font, text=text)
        x_pos_task = tmp.boundingBox[0] / self.win.size[0] - 1
        pos_task = (x_pos_task, 1 - self.task.height)

        self.update_task(text=text, color_list=color_list, pos=pos_task)

    def wait_screen(self, message, color):
        """Wait Screen.

        Args:
            message(string): message to be displayed while waiting
        """

        # Construct the wait message
        wait_message = visual.TextStim(win=self.win,
                                       font=self.font_stim,
                                       text=message,
                                       height=.1,
                                       color=color,
                                       pos=(0, -.5),
                                       wrapWidth=2,
                                       colorSpace='rgb',
                                       opacity=1,
                                       depth=-6.0)

        # Try adding our BCI logo. Pass if not found.
        try:
            wait_logo = visual.ImageStim(
                self.win,
                image='bcipy/static/images/gui_images/bci_cas_logo.png',
                pos=(0, .5),
                mask=None,
                ori=0.0)
            wait_logo.size = resize_image(
                'bcipy/static/images/gui_images/bci_cas_logo.png',
                self.win.size, 1)
            wait_logo.draw()

        except Exception:
            self.logger.debug("Cannot load logo image")
            pass

        # Draw and flip the screen.
        wait_message.draw()
        self.win.flip()

    def create_stimulus(self, height_int: int, mode: str = "text"):
        """Create Stimulus.

        Returns a TextStim or ImageStim object.
            Args:
            height_int: The height of the stimulus
            mode: "text" or "image", determines which to return
        """
        if mode == "text":
            return visual.TextStim(win=self.win,
                                   color='white',
                                   height=height_int,
                                   text='+',
                                   font=self.font_stim,
                                   pos=self.pos_sti,
                                   wrapWidth=None,
                                   colorSpace='rgb',
                                   opacity=1,
                                   depth=-6.0)
        if mode == "image":
            return visual.ImageStim(win=self.win,
                                    image=None,
                                    mask=None,
                                    units='',
                                    pos=self.pos_sti,
                                    size=(height_int, height_int),
                                    ori=0.0)