def present(self, feedback):
        self.generate_continue()
        size = (globalScreen.size[0] - self.config.textDisplayBorder,
                globalScreen.size[1] - self.config.textDisplayBorder)

        debug("presenting screen")
        feedback.registerHandler(self)
        debug("instruction <%s>" % self.instruction)
        text = WrappedText(
            text=self.instruction,
            color=(0.0, 0.0,
                   0.0),  # alpha is ignored (set with max_alpha_param)
            position=(self.config.textDisplayBorder,
                      globalScreen.size[1] - self.config.textDisplayBorder),
            size=size,
            font_size=self.config.textInstructionsSize,
            on=1,
            font_name=self.config.font)

        viewport = Viewport(screen=globalScreen,
                            size=size,
                            stimuli=[self._continue, text])

        self.updateDisplay(viewport)
        self.waitForResponse()
        feedback.detachHandler()

        viewport = Viewport(screen=globalScreen,
                            size=globalScreen.size,
                            stimuli=[])
        self.updateDisplay(viewport)
Esempio n. 2
0
    def __init__(self, sentence):
        self.stim = WrappedText(text=sentence,
                                font_size=50,
                                position=(xlim / 2, ylim / 2),
                                on=False)

        events = [
            Event(blank, start=0, duration=.3),
            Event(
                self.stim,
                start=.3,
                duration=1000,
                on_keypress=True,  # end the event with a keypress
                response=Response(label='instruct', limit='space'))
        ]
        Trial.__init__(self, events)
Esempio n. 3
0
class VisualSpellerVE(MainloopFeedback):
    """
    Visual Speller with six circles like the classical HexOSpell.
    """

    # Triggers: look in Marker
    END_LEVEL1, END_LEVEL2 = 244, 245  # end of hex levels
    COPYSPELLING_FINISHED = 246
    STIMULUS = [[11, 12, 13, 14, 15, 16], [21, 22, 23, 24, 25, 26]]
    RESPONSE = [[51, 52, 53, 54, 55, 56], [61, 62, 63, 64, 65, 66]]
    TARGET_ADD = 20
    ERROR_ADD = 100
    COUNTDOWN_STIMULI = 239
    ERROR_POTENTIAL = 96  # send if error potential is classified

    def init(self):
        """
        initialize parameters
        """
        self.log_filename = "VisualSpellerVE.log"

        self.geometry = [0, 0, 1280, 800]  ## size

        self.letterbox_size = (60, 60)
        self.osc_size = 40
        self.font_size_phrase = 60  # the spelled phrase at the top
        self.font_size_current_letter = 80  # the spelled phrase at the top
        self.font_size_countdown = 150  # number during countdown
        self.desired_phrase = ""

        ## colors:
        self.bg_color = (0.0, 0.0, 0.0)
        self.phrase_color = (0.2, 0.0, 1.0)
        self.current_letter_color = (1.0, 0.0, 0.0)
        self.countdown_color = (0.2, 0.0, 1.0)
        self.osc_color = (1, 1, 1)

        self.letter_set = [
            ["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", "_", ".", ",", "<"],
        ]
        self.fullscreen = False
        self.use_oscillator = True
        self.offline = True
        self.copy_spelling = True  # in copy-spelling mode, selection of the target symbol is forced
        self.debug = False
        self.nCountdown = 5
        self.nr_sequences = 6
        self.randomize_sequence = True  # set to False to present a fixed stimulus sequence
        self.min_dist = 2  # Min number of intermediate flashes bef. a flash is repeated twice

        self.stimulus_duration = 0.083  # 5 frames @60 Hz = 83ms flash
        self.interstimulus_duration = 0.1
        self.animation_time = 1
        self.wait_before_classify = 1.0
        self.feedback_duration = 1.0
        self.feedback_ErrP_duration = 1.0
        self.wait_after_start = 0.0

        # Countdown options
        self.do_animation = True
        self.synchronized_countdown = True
        if self.synchronized_countdown:
            self.do_animation = False

        self.countdown_level1 = True
        self.countdown_level2 = True

        self.countdown_shapes = {"circle": FilledCircle, "hexagon": FilledHexagon}
        self.countdown_shape_select = "hexagon"
        self.countdown_shape_color = (0.7, 0.7, 0.7)
        self.countdown_shape_on = True
        self.countdown_blinking_nr = 5  # number of pre-sequence stimuli(1 sec is approx. 5 frames at 60 Hz)

        self.wait_after_early_stopping = 3  # sec
        self.abort_trial = False
        self.output_per_stimulus = True
        self.use_ErrP_detection = False

        self.serialtrigger = False
        self.serialport = serialport.SerialPort(13)
        self.send_parallel_bak = self.send_parallel

        if self.debug:
            msg = "!!! YOU'RE IN DEBUG MODE! CLASSIFICATION WILL BE RANDOM OR KEYBOARD CONTROLLED !!!"
            self.logger.warning(msg)

    def pre_mainloop(self):

        ## logging
        assert len(self.log_filename) != 0  # 'log_filename' must not be empty string!
        logger.setLevel(logging.ERROR)
        handler = logging.FileHandler(self.log_filename, "w")
        handler.setLevel(logging.INFO)
        formatter = logging.Formatter("%(asctime)s: %(message)s")
        handler.setFormatter(formatter)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(handler)

        self._nr_elements = 6
        self._idx_backdoor = 5
        self._init_classifier_output()
        self._classified_element = -1
        self._classified_letter = -1
        for s in self.desired_phrase:
            assert s in [l for ls in self.letter_set for l in ls]  # invalid letters in desired phrase!
        self._spelled_phrase = ""
        self._spelled_letters = ""
        self._desired_letters = self.desired_phrase
        self._copyspelling_finished = False
        #        if self.offline:
        #            self.__idle()                # In offline mode: set the first to-be-spelled letter

        self._spellerHeight = self.geometry[3] - self.letterbox_size[1]
        self._centerPos = (self.geometry[2] / 2.0, self._spellerHeight / 2.0)

        self._nr_letters = 0
        for i in xrange(len(self.letter_set)):
            self._nr_letters += len(self.letter_set[i])

        self._current_level = 1  # Index of current level
        self._current_sequence = 0  # Index of current sequence
        self._current_stimulus = 0  # Index of current stimlus
        self._current_countdown = self.nCountdown
        self.random = random.Random(clock())
        self._debug_classified = None

        ## init states:
        self._state_countdown = True
        if not self.countdown_level1:
            self._state_countdown = False
            self._state_trial = True
        else:
            # self._state_countdown = not self.offline
            self._state_trial = False

        self._state_classify = False
        self._state_feedback = False
        self._state_abort = False

        ## init containers for VE elements:
        self._ve_elements = []

        ## oscillator state:
        if not self.use_oscillator:
            self.osc_color = self.bg_color
            self.osc_size = 0

        ## call subclass-specific pre_mainloop:
        self.prepare_mainloop()

        ## build screen elements:
        self.__init_screen()
        if self.offline:
            self.__idle()
        if self.abort_trial:
            """
            Start listener for abort_trial event eg.
            """

        ## delay after play (might be useful for filters...)
        pygame.time.wait(int(self.wait_after_start * 1000))
        self.logger.info("waiting %d seconds after play." % self.wait_after_start)

        ## send start trigger:
        self.send_parallel(marker.RUN_START)
        self.logger.info("[TRIGGER] %d" % marker.RUN_START)

        ## error potential classifier:
        self._ErrP_classifier = None

    def on_interaction_event(self, data):
        self.logger.debug("interaction event")
        serial = data.get("serialtrigger", None)
        if serial is None:
            return
        if serial:
            self.logger.debug("using serial port")
            self.send_parallel = self.serialport.send
        else:
            self.logger.debug("using parallel port")
            self.send_parallel = self.send_parallel_bak

    def post_mainloop(self):
        """
        Sends end marker to parallel port.
        """

        if self.abort_trial:
            """
            Stop listener for abort_trial event
           """
            pass

        pygame.time.wait(500)
        self.send_parallel(marker.RUN_END)
        self.logger.info("[TRIGGER] %d" % marker.RUN_END)
        pygame.time.wait(500)
        self._presentation.set(quit=True)
        self._screen.close()

    def __init_screen(self):
        ## create screen:
        if not self.fullscreen:
            os.environ["SDL_VIDEO_WINDOW_POS"] = "%d, %d" % (self.geometry[0], self.geometry[1])
        self._screen = Screen(
            size=(self.geometry[2], self.geometry[3]), fullscreen=self.fullscreen, bgcolor=self.bg_color, sync_swap=True
        )

        ## create letter box on top:
        self._ve_letterbox = Target2D(
            position=(self._centerPos[0], self.geometry[3] * (1 - 0.01) - self.letterbox_size[1] / 2.0),
            size=(self.letterbox_size[0], self.letterbox_size[1]),
            color=self.phrase_color,
        )
        self._ve_innerbox = Target2D(
            position=(self._centerPos[0], self.geometry[3] * (1 - 0.01) - self.letterbox_size[1] / 2.0),
            size=(self.letterbox_size[0] - 6, self.letterbox_size[1] - 6),
            color=self.bg_color,
        )

        self._current_letter_position = (
            self._centerPos[0],
            self.geometry[3] * (1 - 0.015) - self.letterbox_size[1] / 2.0,
        )
        self._ve_current_letter = Text(
            position=self._current_letter_position,
            text=(len(self._desired_letters[:1]) == 0 and " " or self._desired_letters[:1]),
            font_size=self.font_size_current_letter,
            color=self.current_letter_color,
            anchor="center",
        )

        self._ve_desired_letters = Text(
            position=(self._centerPos[0] + 5 + self.letterbox_size[0] / 2.0, self._current_letter_position[1]),
            text=(len(self._desired_letters[1:]) == 0 and " " or self._desired_letters[1:]),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor="left",
        )

        self._ve_spelled_phrase = Text(
            position=(self._centerPos[0] - 5 - self.letterbox_size[0] / 2.0, self._current_letter_position[1]),
            text=(len(self._spelled_phrase) == 0 and " " or self._spelled_phrase),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor="right",
        )

        # if we're in free spelling mode, we hide all text fields but
        # the _ve_spelled_phrase. we also need a multiline
        # _ve_spelled_phrase instead of the single lined one
        if self.offline == self.copy_spelling == False:
            self._spelled_phrase = "   "
            self._ve_spelled_phrase = WrappedText(
                position=(0, self._current_letter_position[1]),
                text=(len(self._spelled_phrase) == 0 and " " or self._spelled_phrase),
                font_size=self.font_size_phrase,
                color=self.phrase_color,
                size=(float(self.geometry[2]), float(self.geometry[3])),
            )
            for i in self._ve_letterbox, self._ve_innerbox, self._ve_current_letter, self._ve_desired_letters:
                i.set(on=False)

        ## add word box to elementlist:
        self._ve_elements.extend(
            [
                self._ve_letterbox,
                self._ve_innerbox,
                self._ve_current_letter,
                self._ve_desired_letters,
                self._ve_spelled_phrase,
            ]
        )

        ## create countdown:
        self._ve_countdown = Text(
            position=self._centerPos,
            text=" ",
            font_size=self.font_size_countdown,
            color=self.countdown_color,
            anchor="center",
            on=False,
        )

        ## create countdown shapes
        self._ve_countdown_shape = self.countdown_shapes[self.countdown_shape_select](
            radius=90, position=self._centerPos, color=self.countdown_shape_color, on=False
        )

        ## create oscillator circle:
        self._ve_oscillator = FilledCircle(
            position=(self.osc_size / 2 + 10, self.osc_size / 2 + 10),
            radius=self.osc_size / 2,
            color=self.osc_color,
            on=False,
        )

        ## create shapes and letters:
        self.init_screen_elements()

        ## add remaining elements to element list:
        self._ve_elements.extend([self._ve_countdown_shape, self._ve_countdown, self._ve_oscillator])

        ## add elements to viewport:
        self._viewport = Viewport(screen=self._screen, stimuli=self._ve_elements)
        self._presentation = Presentation(
            viewports=[self._viewport],
            handle_event_callbacks=[(pygame.KEYDOWN, self.keyboard_input), (pygame.QUIT, self.__stop)],
        )

    def play_tick(self):
        """
        called every loop, if in play mode.
        """

        self.pre_play_tick()

        if self._state_countdown:
            self.pre__countdown()
            self.__countdown()
            self.post__countdown()
        elif self._state_trial:
            self.pre__trial()
            self.__trial()
            self.post__trial()
        elif self._state_classify:
            self.pre__classify()
            self.__classify()
            self.post__classify()
        elif self._state_feedback:
            self.pre__feedback()
            self.__feedback()
            self.post__feedback()
        elif self._state_abort:
            self.__abort()
            self.post__abort()
        else:
            self.pre__idle()
            self.__idle()
            self.post__idle()
        self.post_play_tick()

    def __stop(self, *args):
        self.on_stop()

    def __idle(self):
        if self.offline and len(self._desired_letters) > 0:
            # add new letter:
            for e in xrange(len(self.letter_set)):
                for l in xrange(len(self.letter_set[e])):
                    if self._desired_letters[0] == self.letter_set[e][l]:
                        self._classified_element = e
                        self._classified_letter = l
            if self.countdown_level1:
                self._state_countdown = True
            else:
                self._state_trial = True
        else:
            ## otherwise just wait until a new letter is sent:
            self._presentation.set(go_duration=(0.1, "seconds"))
            self._presentation.go()

    def __countdown(self):
        def blink():
            i = 0
            while i < self.countdown_blinking_nr:
                self._ve_countdown_shape.set(on=True)
                self._ve_countdown.set(text="%d" % self._current_countdown, on=True)
                self.send_parallel(self.COUNTDOWN_STIMULI)
                self.logger.info("[TRIGGER] %d" % self.COUNTDOWN_STIMULI)
                self._presentation.set(go_duration=(self.stimulus_duration, "seconds"))
                self._presentation.go()
                self._ve_countdown_shape.set(on=False)
                self._ve_countdown.set(on=False)
                self._presentation.set(go_duration=(self.interstimulus_duration, "seconds"))
                self._presentation.go()
                i = i + 1

        if self._current_countdown == self.nCountdown:
            self.send_parallel(marker.COUNTDOWN_START)
            self.logger.info("[TRIGGER] %d" % marker.COUNTDOWN_START)
            self.set_countdown_screen()
            self._ve_countdown.set(on=True)
            self._ve_countdown_shape.set(on=self.countdown_shape_on)
            self._presentation.set(go_duration=(1, "seconds"))

        self._ve_countdown.set(text="%d" % self._current_countdown)
        self._presentation.go()
        self._current_countdown = (self._current_countdown - 1) % self.nCountdown

        if self.synchronized_countdown and self._current_countdown == 1:
            self.set_synchronized_countdown_screen()
            blink()
            self._current_countdown = self.nCountdown
            self.set_standard_screen()
            self._state_countdown = False
            self._state_trial = True
            self._ve_countdown.set(on=False)
            self._ve_countdown_shape.set(on=False)
            self._ve_countdown.set(color=self.countdown_color)

        if self._current_countdown == 0:
            # Executed only if self.synchronized_countdown = False
            self._current_countdown = self.nCountdown
            self.set_standard_screen()
            pygame.time.wait(10)
            self._state_countdown = False
            self._state_trial = True
            self._ve_countdown_shape.set(on=False)
            self._ve_countdown.set(on=False)
            self._ve_countdown.set(color=self.countdown_color)

    def __trial(self):

        if self._current_sequence == 0 and self._current_stimulus == 0:

            # level 1 animation when there is no countdown
            if self._current_level == 1 and not self.countdown_level1:
                if self.do_animation:
                    self.set_countdown_screen()
                self.set_standard_screen()
            # generate random sequences:
            if self.randomize_sequence:
                self.flash_sequence = []
                for _ in range(self.nr_sequences):
                    random_flash_sequence(
                        self, set=range(self._nr_elements), min_dist=self.min_dist, seq_len=self._nr_elements
                    )
            # or else use fixed sequence:
            else:
                self.flash_sequence = range(self._nr_elements)

        if self.randomize_sequence:
            currentStimulus = self.flash_sequence[self._current_sequence * self._nr_elements + self._current_stimulus]
        else:
            currentStimulus = self.flash_sequence[self._current_stimulus]
        # set stimulus:
        self.stimulus(currentStimulus, True)
        # self._ve_oscillator.set(on=True)

        if self.abort_trial and self.abort_trial_check():
            # restart trial on abort_trial event:
            self._state_trial = False
            self._state_abort = True
            return

        # check if current stimulus is target and then send trigger:
        target_add = 0
        if len(self._desired_letters) > 0:
            if self._current_level == 1:
                if self._desired_letters[:1] in self.letter_set[currentStimulus]:
                    # current stimulus is target group:
                    target_add = self.TARGET_ADD
            else:
                if currentStimulus == self._idx_backdoor:
                    # current stimulus is backdoor:
                    if not self._desired_letters[:1] in self.letter_set[self._classified_element]:
                        # we are in the wrong group. backdoor is target:
                        target_add = self.TARGET_ADD
                else:
                    # current stimulus is no backdoor:
                    if self._desired_letters[:1] == self.letter_set[self._classified_element][currentStimulus]:
                        # current stimulus is target symbol:
                        target_add = self.TARGET_ADD

        self.send_parallel(self.STIMULUS[self._current_level - 1][currentStimulus] + target_add)
        self.logger.info("[TRIGGER] %d" % (self.STIMULUS[self._current_level - 1][currentStimulus] + target_add))

        # present stimulus:
        self._presentation.set(go_duration=(self.stimulus_duration, "seconds"))
        self._presentation.go()

        # reset to normal:
        self._ve_oscillator.set(on=False)
        self.stimulus(currentStimulus, False)

        # present interstimulus:
        self._presentation.set(go_duration=(self.interstimulus_duration, "seconds"))
        self._presentation.go()

        if self.debug:
            self.on_control_event({"cl_output": (self.random.random(), currentStimulus + 1)})

        ## TODO: check here for classification !!!!
        if self.output_per_stimulus:
            # increase
            self._current_stimulus = (self._current_stimulus + 1) % self._nr_elements
            if self._current_stimulus == 0:
                self._current_sequence = (self._current_sequence + 1) % self.nr_sequences

            # check for end of trial:
            if self._current_sequence == 0 and self._current_stimulus == 0:

                # send trigger:
                if self._current_level == 1:
                    self.send_parallel(self.END_LEVEL1)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL1)
                else:
                    self.send_parallel(self.END_LEVEL2)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL2)

                # decide how to continue:
                self._state_trial = False
                self._state_classify = True
        else:
            # increase
            self._current_stimulus = (self._current_stimulus + 1) % self._nr_elements
            if self._current_stimulus == 0:
                self._current_sequence = (self._current_sequence + 1) % self.nr_sequences
                if self.check_classification(self._current_sequence + 1):
                    self._state_trial = False
                    self._state_classify = True
                    pygame.time.wait(self.wait_after_early_stopping * 1000)

            if self._current_sequence == 0 and self._current_stimulus == 0:

                # send trigger:
                if self._current_level == 1:
                    self.send_parallel(self.END_LEVEL1)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL1)
                else:
                    self.send_parallel(self.END_LEVEL2)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL2)

                # decide how to continue:
                self._state_trial = False
                self._state_classify = True

    def check_classification(self, nr):
        # print self._classifier_output
        means = [None] * self._nr_elements
        minimum = maxint
        classified = None
        for ii in range(self._nr_elements):
            means[ii] = sum(self._classifier_output[ii]) / nr
            if means[ii] < minimum:
                minimum = means[ii]
                classified = ii + 1

        print "\n**** Class: %d (mean=%f)\n" % (classified, means[classified - 1])
        return classified

    def __classify(self):

        ## wait until all classifier outputs are received:
        self._presentation.set(go_duration=(self.wait_before_classify, "seconds"))
        self._presentation.go()
        if self.offline:
            if self._current_level == 1:
                classified = self._classified_element
            else:
                classified = self._classified_letter
        elif not self._debug_classified == None:
            classified = self._debug_classified
            self._debug_classified = None
        else:
            if self.output_per_stimulus:
                nClassified = sum([len(self._classifier_output[i]) for i in xrange(self._nr_elements)])
                if nClassified < self._nr_elements * self.nr_sequences:
                    pygame.time.wait(20)
                    print "not enough classifier-outputs received! (something may be wrong)"
                    return

                ## classify and set output:
                means = [None] * self._nr_elements
                minimum = maxint
                classified = None
                for ii in range(self._nr_elements):
                    means[ii] = sum(self._classifier_output[ii]) / self.nr_sequences
                    if means[ii] < minimum:
                        minimum = means[ii]
                        classified = ii
                print "\n**** Class: %d (mean=%f)\n" % (classified + 1, means[classified])
            else:
                means = [None] * self._nr_elements
                minimum = maxint
                classified = None
                for ii in range(self._nr_elements):
                    means[ii] = sum(self._classifier_output[ii]) / self.nr_sequences
                    if means[ii] < minimum:
                        minimum = means[ii]
                        classified = ii
                print "\n**** Class: %d (mean=%f)\n" % (classified + 1, means[classified])

            ## Reset classifier output to empty lists
            self._init_classifier_output()

        error_add = 0
        ## evaluate classification:
        if self._current_level == 1:
            self._classified_element = classified
            if len(self._desired_letters) > 0 and not self._desired_letters[:1] in self.letter_set[classified]:
                # wrong group selected:
                error_add = self.ERROR_ADD
        else:
            self._classified_letter = classified
            if self._classified_letter == self._idx_backdoor:
                ## backdoor classified:
                if (
                    len(self._desired_letters) > 0
                    and self._desired_letters[:1] in self.letter_set[self._classified_element]
                ):
                    # backdoor selection wrong:
                    error_add = self.ERROR_ADD
            else:
                ## no backdoor classified:
                spelled_letter = self.letter_set[self._classified_element][self._classified_letter]
                if len(self._desired_letters) > 0 and spelled_letter != self._desired_letters[:1]:
                    # wrong letter spelled:
                    error_add = self.ERROR_ADD

        ## send response trigger:
        self.send_parallel(self.RESPONSE[self._current_level - 1][classified] + error_add)
        self.logger.info("[TRIGGER] %d" % (self.RESPONSE[self._current_level - 1][classified] + error_add))

        self._state_classify = False
        self._state_feedback = True

    def __feedback(self):
        self._state_feedback = False

        ## call subclass method:
        self.feedback()

        ## check ErrP classification:
        if self.use_ErrP_detection:
            t = 0
            while self._ErrP_classifier is None and t < 1000:
                t += 50
                pygame.time.wait(50)
            if self._ErrP_classifier is None:
                print "no ErrP classifier received! "
            if self._ErrP_classifier:
                self.send_parallel(self.ERROR_POTENTIAL)
                self.logger.info("[TRIGGER] %d" % (self.ERROR_POTENTIAL))

        ## call subclass method:
        if not self.countdown_level2:
            self.switch_level()

        ## update phrases:
        if (
            (self._current_level == 2)
            and (  # only update, if we are at the end of level 2,
                self._classified_letter != self._idx_backdoor or self.copy_spelling
            )
            and (  # if copyspelling off, we have no backdoor selected
                self._ErrP_classifier is None or not self._ErrP_classifier
            )
        ):  # no ErrP was detected (or ErrP detection is off)
            spelled_letter = ""
            if self.copy_spelling:
                ## in copy spelling we force the desired letter to be spelled
                if len(self._desired_letters) > 0:
                    spelled_letter = self._desired_letters[:1]
                else:
                    print "??? moved beyond desired phrase in copy spelling ???"
            else:
                spelled_letter = self.letter_set[self._classified_element][self._classified_letter]

            ## update desired phrase:
            if len(self._desired_letters) > 0:
                if spelled_letter == self._desired_letters[:1]:
                    # correct letter spelled:
                    self._desired_letters = self._desired_letters[1:]  # remove first letter
                else:
                    # wrong letter spelled:
                    if spelled_letter == "<":
                        self._desired_letters = self._spelled_phrase[-1:] + self._desired_letters
                    else:
                        self._desired_letters = "<" + self._desired_letters
                if len(self._desired_letters) == 0:
                    self._copyspelling_finished = True

            ## update spelled phrase:
            self._spelled_letters += spelled_letter
            if spelled_letter == "<":
                self._spelled_phrase = self._spelled_phrase[:-1]
            else:
                self._spelled_phrase += spelled_letter

            ## update screen phrases:
            self.logger.info("Current Phrase:")
            self.logger.info(self._spelled_phrase)
            self._ve_spelled_phrase.set(text=(len(self._spelled_phrase) == 0 and " " or self._spelled_phrase))
            self._ve_current_letter.set(text=(len(self._desired_letters[:1]) == 0 and " " or self._desired_letters[:1]))
            self._ve_desired_letters.set(
                text=(len(self._desired_letters[1:]) == 0 and " " or self._desired_letters[1:])
            )

        if self.use_ErrP_detection and self._ErrP_classifier:
            self._state_trial = True

        else:
            if self._current_level == 1:
                # continue with level2 trial:
                if self.countdown_level2:
                    self._state_countdown = True
                else:
                    self._state_trial = True

            elif not self.offline:
                # start countdown
                if self.countdown_level1:
                    self._state_countdown = True
                else:
                    self._state_trial = True

            # set new level:
            self._current_level = 3 - self._current_level

        ## reset ErrP_classifier:
        self._ErrP_classifier = None

        # check copyspelling:
        if self._copyspelling_finished:
            self._copyspelling_finished = False
            self.on_control_event({"print": 0})  # print desired phrase
            self.on_control_event({"print": 1})  # print spelled phrase
            self.on_control_event({"print": 2})  # print all spelled letters
            self.send_parallel(self.COPYSPELLING_FINISHED)
            self.logger.info("[TRIGGER] %d" % (self.COPYSPELLING_FINISHED))
            pygame.time.wait(50)

    def __abort(self):

        # play warning sound

        def sine_array_onecycle(hz, peak, sample_rate):
            length = sample_rate / float(hz)
            omega = NP.pi * 2 / length
            xvalues = NP.arange(int(length)) * omega
            return peak * NP.sin(xvalues)

        def sine_array(hz, peak, samples_rate):
            return NP.resize(sine_array_onecycle(hz, peak, sample_rate), (sample_rate,))

        sample_rate = 44100
        pygame.mixer.init(sample_rate, -16, 2)  # 44.1kHz, 16-bit signed, stereo
        f = sine_array(8000, 1, sample_rate)
        f = NP.array(zip(f, f))
        sound = pygame.sndarray.make_sound(f)
        channel = sound.play(-1)
        channel.set_volume(0.2, 0.2)
        pygame.time.delay(1000)
        sound.stop()

        if self._current_level == 1 and self.countdown_level1:
            self._state_countdown = True
        elif self._current_level == 2 and self.countdown_level2:
            self._state_countdown = True
        else:
            self._state_trial = True

        self._init_classifier_output()

    def _init_classifier_output(self):
        ## Empty lists
        self._classifier_output = [list() for _ in xrange(self._nr_elements)]

    def abort_trial_check(self):
        """
        Check if event is an abort trial event
        """
        return False

    def keyboard_input(self, event):
        if event.key == pygame.K_ESCAPE:
            self.on_stop()
        elif event.key == pygame.K_KP_ENTER:
            self.on_control_event({"print": 0})  # print desired phrase
            self.on_control_event({"print": 1})  # print spelled phrase
            self.on_control_event({"print": 2})  # print all spelled letters
        elif event.key == pygame.K_DELETE:
            # The DELETE key empties the spelled text shown this works
            # only in free spelling mode (i.e. offline and copy_spelling
            # are set to False)
            if self.offline == self.copy_spelling == False:
                self.logger.info("Clearing Text.")
                self._spelled_phrase = "   "
        elif self.debug:
            if (
                (event.key >= pygame.K_a and event.key <= pygame.K_z)
                or (event.key == pygame.K_LESS)
                or (event.key == pygame.K_PERIOD)
                or (event.key == pygame.K_COMMA)
            ):
                self.on_control_event({"new_letter": chr(event.key).upper()})
            elif event.key == pygame.K_MINUS:
                self.on_control_event({"new_letter": chr(pygame.K_UNDERSCORE)})
            elif event.key == pygame.K_BACKSPACE:
                self.on_control_event({"new_letter": chr(pygame.K_LESS)})
            elif event.key == pygame.K_SPACE:
                self.on_control_event({"new_letter": chr(pygame.K_UNDERSCORE)})
            elif event.key == pygame.K_UP and self.use_ErrP_detection:
                self.on_control_event({"cl_output": (1, 7)})
            elif event.key == pygame.K_DOWN and self.use_ErrP_detection:
                self.on_control_event({"cl_output": (0, 7)})
            if not self.offline:
                if event.key >= pygame.K_0 and event.key <= pygame.K_5:
                    self._debug_classified = int(chr(event.key))
                elif event.key >= pygame.K_KP0 and event.key <= pygame.K_KP5:
                    self._debug_classified = int(chr(event.key - 208))

    def on_control_event(self, data):
        self.logger.info("[CONTROL_EVENT] %s" % str(data))
        if data.has_key(u"cl_output"):
            # classification output was sent:
            score_data = data[u"cl_output"]
            cl_out = score_data[0]
            iSubstim = int(score_data[1])  # evt auch "Subtrial"
            if iSubstim in range(1, 7):
                self._classifier_output[iSubstim - 1].append(cl_out)
            elif self.use_ErrP_detection:
                self._ErrP_classifier = cl_out
        elif data.has_key("new_letter"):
            # get new letter to spell:
            self._desired_letters += data["new_letter"]
            self._ve_current_letter.set(text=(len(self._desired_letters[:1]) == 0 and " " or self._desired_letters[:1]))
            self._ve_desired_letters.set(
                text=(len(self._desired_letters[1:]) == 0 and " " or self._desired_letters[1:])
            )

        elif data.has_key(u"print"):
            if data[u"print"] == 0:
                self.logger.info("[DESIRED_PHRASE] %s" % self.desired_phrase)
            elif data[u"print"] == 1:
                self.logger.info("[SPELLED_PHRASE] %s" % self._spelled_phrase)
            elif data[u"print"] == 2:
                self.logger.info("[SPELLED_LETTERS] %s" % self._spelled_letters)

    """
    ==========================
    == METHODS TO OVERLOAD: ==
    ==========================
    """

    def init_screen_elements(self):
        """
        overwrite this function in subclass.
        """
        pass

    def prepare_mainloop(self):
        """
        overwrite this function in subclass.
        """
        pass

    def set_countdown_screen(self):
        """
        set screen how it should look during countdown.
        overwrite this function in subclass.
        """
        pass

    def set_standard_screen(self):
        """
        set screen elements to standard state.
        overwrite this function in subclass.
        """
        pass

    def set_synchronized_countdown_screen(self):
        """
        set screen elements to for the synchronized countdown.
        overwrite this function in subclass.
        """
        pass

    def stimulus(self, i_element, on=True):
        """
        turn on/off the stimulus elements and turn off/on the normal elements.
        overwrite this function in subclass.
        """
        pass

    def feedback(self):
        """
        set screen how it should look during feedback presentation.
        overwrite this function in subclass.
        """
        pass

    def switch_level(self):
        """
        overwrite this function in subclass.
        """
        pass

    def pre_play_tick(self):
        pass

    def post_play_tick(self):
        pass

    def pre__countdown(self):
        pass

    def post__countdown(self):
        pass

    def pre__trial(self):
        pass

    def post__trial(self):
        pass

    def pre__classify(self):
        pass

    def post__classify(self):
        pass

    def pre__feedback(self):
        pass

    def post__feedback(self):
        pass

    def pre__idle(self):
        pass

    def post__idle(self):
        pass

    def post__abort(self):
        pass
Esempio n. 4
0
    def __init_screen(self):
        ## create screen:
        if not self.fullscreen:
            os.environ["SDL_VIDEO_WINDOW_POS"] = "%d, %d" % (self.geometry[0], self.geometry[1])
        self._screen = Screen(
            size=(self.geometry[2], self.geometry[3]), fullscreen=self.fullscreen, bgcolor=self.bg_color, sync_swap=True
        )

        ## create letter box on top:
        self._ve_letterbox = Target2D(
            position=(self._centerPos[0], self.geometry[3] * (1 - 0.01) - self.letterbox_size[1] / 2.0),
            size=(self.letterbox_size[0], self.letterbox_size[1]),
            color=self.phrase_color,
        )
        self._ve_innerbox = Target2D(
            position=(self._centerPos[0], self.geometry[3] * (1 - 0.01) - self.letterbox_size[1] / 2.0),
            size=(self.letterbox_size[0] - 6, self.letterbox_size[1] - 6),
            color=self.bg_color,
        )

        self._current_letter_position = (
            self._centerPos[0],
            self.geometry[3] * (1 - 0.015) - self.letterbox_size[1] / 2.0,
        )
        self._ve_current_letter = Text(
            position=self._current_letter_position,
            text=(len(self._desired_letters[:1]) == 0 and " " or self._desired_letters[:1]),
            font_size=self.font_size_current_letter,
            color=self.current_letter_color,
            anchor="center",
        )

        self._ve_desired_letters = Text(
            position=(self._centerPos[0] + 5 + self.letterbox_size[0] / 2.0, self._current_letter_position[1]),
            text=(len(self._desired_letters[1:]) == 0 and " " or self._desired_letters[1:]),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor="left",
        )

        self._ve_spelled_phrase = Text(
            position=(self._centerPos[0] - 5 - self.letterbox_size[0] / 2.0, self._current_letter_position[1]),
            text=(len(self._spelled_phrase) == 0 and " " or self._spelled_phrase),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor="right",
        )

        # if we're in free spelling mode, we hide all text fields but
        # the _ve_spelled_phrase. we also need a multiline
        # _ve_spelled_phrase instead of the single lined one
        if self.offline == self.copy_spelling == False:
            self._spelled_phrase = "   "
            self._ve_spelled_phrase = WrappedText(
                position=(0, self._current_letter_position[1]),
                text=(len(self._spelled_phrase) == 0 and " " or self._spelled_phrase),
                font_size=self.font_size_phrase,
                color=self.phrase_color,
                size=(float(self.geometry[2]), float(self.geometry[3])),
            )
            for i in self._ve_letterbox, self._ve_innerbox, self._ve_current_letter, self._ve_desired_letters:
                i.set(on=False)

        ## add word box to elementlist:
        self._ve_elements.extend(
            [
                self._ve_letterbox,
                self._ve_innerbox,
                self._ve_current_letter,
                self._ve_desired_letters,
                self._ve_spelled_phrase,
            ]
        )

        ## create countdown:
        self._ve_countdown = Text(
            position=self._centerPos,
            text=" ",
            font_size=self.font_size_countdown,
            color=self.countdown_color,
            anchor="center",
            on=False,
        )

        ## create countdown shapes
        self._ve_countdown_shape = self.countdown_shapes[self.countdown_shape_select](
            radius=90, position=self._centerPos, color=self.countdown_shape_color, on=False
        )

        ## create oscillator circle:
        self._ve_oscillator = FilledCircle(
            position=(self.osc_size / 2 + 10, self.osc_size / 2 + 10),
            radius=self.osc_size / 2,
            color=self.osc_color,
            on=False,
        )

        ## create shapes and letters:
        self.init_screen_elements()

        ## add remaining elements to element list:
        self._ve_elements.extend([self._ve_countdown_shape, self._ve_countdown, self._ve_oscillator])

        ## add elements to viewport:
        self._viewport = Viewport(screen=self._screen, stimuli=self._ve_elements)
        self._presentation = Presentation(
            viewports=[self._viewport],
            handle_event_callbacks=[(pygame.KEYDOWN, self.keyboard_input), (pygame.QUIT, self.__stop)],
        )
Esempio n. 5
0
class VisualSpellerVE(MainloopFeedback):
    '''
    Visual Speller with six circles like the classical HexOSpell.
    '''

    # Triggers: look in Marker
    END_LEVEL1, END_LEVEL2 = 244, 245  # end of hex levels
    COPYSPELLING_FINISHED = 246
    STIMULUS = [[11, 12, 13, 14, 15, 16], [21, 22, 23, 24, 25, 26]]
    RESPONSE = [[51, 52, 53, 54, 55, 56], [61, 62, 63, 64, 65, 66]]
    TARGET_ADD = 20
    ERROR_ADD = 100
    COUNTDOWN_STIMULI = 239
    ERROR_POTENTIAL = 96  # send if error potential is classified

    def init(self):
        '''
        initialize parameters
        '''
        self.log_filename = 'VisualSpellerVE.log'

        self.geometry = [0, 0, 1280, 800]  ## size

        self.letterbox_size = (60, 60)
        self.osc_size = 40
        self.font_size_phrase = 60  # the spelled phrase at the top
        self.font_size_current_letter = 80  # the spelled phrase at the top
        self.font_size_countdown = 150  # number during countdown
        self.desired_phrase = ""

        ## colors:
        self.bg_color = (0., 0., 0.)
        self.phrase_color = (0.2, 0.0, 1.0)
        self.current_letter_color = (1.0, 0.0, 0.0)
        self.countdown_color = (0.2, 0.0, 1.0)
        self.osc_color = (1, 1, 1)

        self.letter_set = [['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','_','.',',','<']]
        self.fullscreen = False
        self.use_oscillator = True
        self.offline = True
        self.copy_spelling = True  # in copy-spelling mode, selection of the target symbol is forced
        self.debug = False
        self.nCountdown = 5
        self.nr_sequences = 6
        self.randomize_sequence = True  # set to False to present a fixed stimulus sequence
        self.min_dist = 2  # Min number of intermediate flashes bef. a flash is repeated twice

        self.stimulus_duration = 0.083  # 5 frames @60 Hz = 83ms flash
        self.interstimulus_duration = 0.1
        self.animation_time = 1
        self.wait_before_classify = 1.
        self.feedback_duration = 1.
        self.feedback_ErrP_duration = 1.0
        self.wait_after_start = 0.

        # Countdown options
        self.do_animation = True
        self.synchronized_countdown = True
        if (self.synchronized_countdown):
            self.do_animation = False

        self.countdown_level1 = True
        self.countdown_level2 = True

        self.countdown_shapes = {
            'circle': FilledCircle,
            'hexagon': FilledHexagon
        }
        self.countdown_shape_select = 'hexagon'
        self.countdown_shape_color = (0.7, 0.7, 0.7)
        self.countdown_shape_on = True
        self.countdown_blinking_nr = 5  #number of pre-sequence stimuli(1 sec is approx. 5 frames at 60 Hz)

        self.wait_after_early_stopping = 3  #sec
        self.abort_trial = False
        self.output_per_stimulus = True
        self.use_ErrP_detection = False

        self.serialtrigger = False

        # FIXME: this should be fixed properly
        try:
            self.serialport = serialport.SerialPort(13)
        except:
            self.serialport = None
        self.send_parallel_bak = self.send_parallel

        if self.debug:
            msg = "!!! YOU\'RE IN DEBUG MODE! CLASSIFICATION WILL BE RANDOM OR KEYBOARD CONTROLLED !!!"
            self.logger.warning(msg)

    def pre_mainloop(self):

        ## logging
        assert (len(self.log_filename) != 0
                )  # 'log_filename' must not be empty string!
        logger.setLevel(logging.ERROR)
        handler = logging.FileHandler(self.log_filename, 'w')
        handler.setLevel(logging.INFO)
        formatter = logging.Formatter("%(asctime)s: %(message)s")
        handler.setFormatter(formatter)
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(handler)

        self._nr_elements = 6
        self._idx_backdoor = 5
        self._init_classifier_output()
        self._classified_element = -1
        self._classified_letter = -1
        for s in self.desired_phrase:
            assert s in [l for ls in self.letter_set
                         for l in ls]  # invalid letters in desired phrase!
        self._spelled_phrase = ""
        self._spelled_letters = ""
        self._desired_letters = self.desired_phrase
        self._copyspelling_finished = False
        #        if self.offline:
        #            self.__idle()                # In offline mode: set the first to-be-spelled letter

        self._spellerHeight = self.geometry[3] - self.letterbox_size[1]
        self._centerPos = (self.geometry[2] / 2., self._spellerHeight / 2.)

        self._nr_letters = 0
        for i in xrange(len(self.letter_set)):
            self._nr_letters += len(self.letter_set[i])

        self._current_level = 1  # Index of current level
        self._current_sequence = 0  # Index of current sequence
        self._current_stimulus = 0  # Index of current stimlus
        self._current_countdown = self.nCountdown
        self.random = random.Random(clock())
        self._debug_classified = None

        ## init states:
        self._state_countdown = True
        if not self.countdown_level1:
            self._state_countdown = False
            self._state_trial = True
        else:
            #self._state_countdown = not self.offline
            self._state_trial = False

        self._state_classify = False
        self._state_feedback = False
        self._state_abort = False

        ## init containers for VE elements:
        self._ve_elements = []

        ## oscillator state:
        if not self.use_oscillator:
            self.osc_color = self.bg_color
            self.osc_size = 0

        ## call subclass-specific pre_mainloop:
        self.prepare_mainloop()

        ## build screen elements:
        self.__init_screen()
        if self.offline:
            self.__idle()
        if self.abort_trial:
            '''
            Start listener for abort_trial event eg.
            '''

        ## delay after play (might be useful for filters...)
        pygame.time.wait(int(self.wait_after_start * 1000))
        self.logger.info("waiting %d seconds after play." %
                         self.wait_after_start)

        ## send start trigger:
        self.send_parallel(marker.RUN_START)
        self.logger.info("[TRIGGER] %d" % marker.RUN_START)

        ## error potential classifier:
        self._ErrP_classifier = None

    def on_interaction_event(self, data):
        self.logger.debug('interaction event')
        serial = data.get('serialtrigger', None)
        if serial is None:
            return
        if serial:
            self.logger.debug('using serial port')
            self.send_parallel = self.serialport.send
        else:
            self.logger.debug('using parallel port')
            self.send_parallel = self.send_parallel_bak

    def post_mainloop(self):
        """
        Sends end marker to parallel port.
        """

        if self.abort_trial:
            '''
            Stop listener for abort_trial event
           '''
            pass

        pygame.time.wait(500)
        self.send_parallel(marker.RUN_END)
        self.logger.info("[TRIGGER] %d" % marker.RUN_END)
        pygame.time.wait(500)
        self._presentation.set(quit=True)
        self._screen.close()

    def __init_screen(self):
        ## create screen:
        if not self.fullscreen:
            os.environ['SDL_VIDEO_WINDOW_POS'] = '%d, %d' % (self.geometry[0],
                                                             self.geometry[1])
        self._screen = Screen(size=(self.geometry[2], self.geometry[3]),
                              fullscreen=self.fullscreen,
                              bgcolor=self.bg_color,
                              sync_swap=True)

        ## create letter box on top:
        self._ve_letterbox = Target2D(position=(self._centerPos[0],
                                                self.geometry[3] * (1 - 0.01) -
                                                self.letterbox_size[1] / 2.),
                                      size=(self.letterbox_size[0],
                                            self.letterbox_size[1]),
                                      color=self.phrase_color)
        self._ve_innerbox = Target2D(position=(self._centerPos[0],
                                               self.geometry[3] * (1 - 0.01) -
                                               self.letterbox_size[1] / 2.),
                                     size=(self.letterbox_size[0] - 6,
                                           self.letterbox_size[1] - 6),
                                     color=self.bg_color)

        self._current_letter_position = (self._centerPos[0],
                                         self.geometry[3] * (1 - 0.015) -
                                         self.letterbox_size[1] / 2.)
        self._ve_current_letter = Text(
            position=self._current_letter_position,
            text=(len(self._desired_letters[:1]) == 0 and " "
                  or self._desired_letters[:1]),
            font_size=self.font_size_current_letter,
            color=self.current_letter_color,
            anchor='center')

        self._ve_desired_letters = Text(
            position=(self._centerPos[0] + 5 + self.letterbox_size[0] / 2.,
                      self._current_letter_position[1]),
            text=(len(self._desired_letters[1:]) == 0 and " "
                  or self._desired_letters[1:]),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor='left')

        self._ve_spelled_phrase = Text(
            position=(self._centerPos[0] - 5 - self.letterbox_size[0] / 2.,
                      self._current_letter_position[1]),
            text=(len(self._spelled_phrase) == 0 and " "
                  or self._spelled_phrase),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor='right')

        # if we're in free spelling mode, we hide all text fields but
        # the _ve_spelled_phrase. we also need a multiline
        # _ve_spelled_phrase instead of the single lined one
        if self.offline == self.copy_spelling == False:
            self._spelled_phrase = "   "
            self._ve_spelled_phrase = WrappedText(
                position=(0, self._current_letter_position[1]),
                text=(len(self._spelled_phrase) == 0 and " "
                      or self._spelled_phrase),
                font_size=self.font_size_phrase,
                color=self.phrase_color,
                size=(float(self.geometry[2]), float(self.geometry[3])))
            for i in self._ve_letterbox, self._ve_innerbox, self._ve_current_letter, self._ve_desired_letters:
                i.set(on=False)

        ## add word box to elementlist:
        self._ve_elements.extend([
            self._ve_letterbox, self._ve_innerbox, self._ve_current_letter,
            self._ve_desired_letters, self._ve_spelled_phrase
        ])

        ## create countdown:
        self._ve_countdown = Text(position=self._centerPos,
                                  text=" ",
                                  font_size=self.font_size_countdown,
                                  color=self.countdown_color,
                                  anchor='center',
                                  on=False)

        ## create countdown shapes
        self._ve_countdown_shape = self.countdown_shapes[
            self.countdown_shape_select](radius=90,
                                         position=self._centerPos,
                                         color=self.countdown_shape_color,
                                         on=False)

        ## create oscillator circle:
        self._ve_oscillator = FilledCircle(position=(self.osc_size / 2 + 10,
                                                     self.osc_size / 2 + 10),
                                           radius=self.osc_size / 2,
                                           color=self.osc_color,
                                           on=False)

        ## create shapes and letters:
        self.init_screen_elements()

        ## add remaining elements to element list:
        self._ve_elements.extend([
            self._ve_countdown_shape, self._ve_countdown, self._ve_oscillator
        ])

        ## add elements to viewport:
        self._viewport = Viewport(screen=self._screen,
                                  stimuli=self._ve_elements)
        self._presentation = Presentation(viewports=[self._viewport],
                                          handle_event_callbacks=[
                                              (pygame.KEYDOWN,
                                               self.keyboard_input),
                                              (pygame.QUIT, self.__stop)
                                          ])

    def play_tick(self):
        """
        called every loop, if in play mode.
        """

        self.pre_play_tick()

        if self._state_countdown:
            self.pre__countdown()
            self.__countdown()
            self.post__countdown()
        elif self._state_trial:
            self.pre__trial()
            self.__trial()
            self.post__trial()
        elif self._state_classify:
            self.pre__classify()
            self.__classify()
            self.post__classify()
        elif self._state_feedback:
            self.pre__feedback()
            self.__feedback()
            self.post__feedback()
        elif self._state_abort:
            self.__abort()
            self.post__abort()
        else:
            self.pre__idle()
            self.__idle()
            self.post__idle()
        self.post_play_tick()

    def __stop(self, *args):
        self.on_stop()

    def __idle(self):
        if self.offline and len(self._desired_letters) > 0:
            # add new letter:
            for e in xrange(len(self.letter_set)):
                for l in xrange(len(self.letter_set[e])):
                    if self._desired_letters[0] == self.letter_set[e][l]:
                        self._classified_element = e
                        self._classified_letter = l
            if self.countdown_level1:
                self._state_countdown = True
            else:
                self._state_trial = True
        else:
            ## otherwise just wait until a new letter is sent:
            self._presentation.set(go_duration=(0.1, 'seconds'))
            self._presentation.go()

    def __countdown(self):
        def blink():
            i = 0
            while (i < self.countdown_blinking_nr):
                self._ve_countdown_shape.set(on=True)
                self._ve_countdown.set(text="%d" % self._current_countdown,
                                       on=True)
                self.send_parallel(self.COUNTDOWN_STIMULI)
                self.logger.info("[TRIGGER] %d" % self.COUNTDOWN_STIMULI)
                self._presentation.set(go_duration=(self.stimulus_duration,
                                                    'seconds'))
                self._presentation.go()
                self._ve_countdown_shape.set(on=False)
                self._ve_countdown.set(on=False)
                self._presentation.set(
                    go_duration=(self.interstimulus_duration, 'seconds'))
                self._presentation.go()
                i = i + 1

        if self._current_countdown == self.nCountdown:
            self.send_parallel(marker.COUNTDOWN_START)
            self.logger.info("[TRIGGER] %d" % marker.COUNTDOWN_START)
            self.set_countdown_screen()
            self._ve_countdown.set(on=True)
            self._ve_countdown_shape.set(on=self.countdown_shape_on)
            self._presentation.set(go_duration=(1, 'seconds'))

        self._ve_countdown.set(text="%d" % self._current_countdown)
        self._presentation.go()
        self._current_countdown = (self._current_countdown -
                                   1) % self.nCountdown

        if self.synchronized_countdown and self._current_countdown == 1:
            self.set_synchronized_countdown_screen()
            blink()
            self._current_countdown = self.nCountdown
            self.set_standard_screen()
            self._state_countdown = False
            self._state_trial = True
            self._ve_countdown.set(on=False)
            self._ve_countdown_shape.set(on=False)
            self._ve_countdown.set(color=self.countdown_color)

        if self._current_countdown == 0:
            # Executed only if self.synchronized_countdown = False
            self._current_countdown = self.nCountdown
            self.set_standard_screen()
            pygame.time.wait(10)
            self._state_countdown = False
            self._state_trial = True
            self._ve_countdown_shape.set(on=False)
            self._ve_countdown.set(on=False)
            self._ve_countdown.set(color=self.countdown_color)

    def __trial(self):

        if self._current_sequence == 0 and self._current_stimulus == 0:

            #level 1 animation when there is no countdown
            if self._current_level == 1 and not self.countdown_level1:
                if self.do_animation:
                    self.set_countdown_screen()
                self.set_standard_screen()
            # generate random sequences:
            if self.randomize_sequence:
                self.flash_sequence = []
                for _ in range(self.nr_sequences):
                    random_flash_sequence(self,
                                          set=range(self._nr_elements),
                                          min_dist=self.min_dist,
                                          seq_len=self._nr_elements)
            # or else use fixed sequence:
            else:
                self.flash_sequence = range(self._nr_elements)

        if self.randomize_sequence:
            currentStimulus = self.flash_sequence[self._current_sequence *
                                                  self._nr_elements +
                                                  self._current_stimulus]
        else:
            currentStimulus = self.flash_sequence[self._current_stimulus]
        # set stimulus:
        self.stimulus(currentStimulus, True)
        #self._ve_oscillator.set(on=True)

        if self.abort_trial and self.abort_trial_check():
            # restart trial on abort_trial event:
            self._state_trial = False
            self._state_abort = True
            return

        # check if current stimulus is target and then send trigger:
        target_add = 0
        if len(self._desired_letters) > 0:
            if self._current_level == 1:
                if self._desired_letters[:1] in self.letter_set[
                        currentStimulus]:
                    # current stimulus is target group:
                    target_add = self.TARGET_ADD
            else:
                if currentStimulus == self._idx_backdoor:
                    # current stimulus is backdoor:
                    if not self._desired_letters[:1] in self.letter_set[
                            self._classified_element]:
                        # we are in the wrong group. backdoor is target:
                        target_add = self.TARGET_ADD
                else:
                    # current stimulus is no backdoor:
                    if self._desired_letters[:1] == self.letter_set[
                            self._classified_element][currentStimulus]:
                        # current stimulus is target symbol:
                        target_add = self.TARGET_ADD

        self.send_parallel(self.STIMULUS[self._current_level -
                                         1][currentStimulus] + target_add)
        self.logger.info(
            "[TRIGGER] %d" %
            (self.STIMULUS[self._current_level - 1][currentStimulus] +
             target_add))

        # present stimulus:
        self._presentation.set(go_duration=(self.stimulus_duration, 'seconds'))
        self._presentation.go()

        # reset to normal:
        self._ve_oscillator.set(on=False)
        self.stimulus(currentStimulus, False)

        # present interstimulus:
        self._presentation.set(go_duration=(self.interstimulus_duration,
                                            'seconds'))
        self._presentation.go()

        if self.debug:
            self.on_control_event(
                {'cl_output': (self.random.random(), currentStimulus + 1)})

        ## TODO: check here for classification !!!!
        if self.output_per_stimulus:
            # increase
            self._current_stimulus = (self._current_stimulus +
                                      1) % self._nr_elements
            if self._current_stimulus == 0:
                self._current_sequence = (self._current_sequence +
                                          1) % self.nr_sequences

            # check for end of trial:
            if self._current_sequence == 0 and self._current_stimulus == 0:

                # send trigger:
                if self._current_level == 1:
                    self.send_parallel(self.END_LEVEL1)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL1)
                else:
                    self.send_parallel(self.END_LEVEL2)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL2)

                # decide how to continue:
                self._state_trial = False
                self._state_classify = True
        else:
            # increase
            self._current_stimulus = (self._current_stimulus +
                                      1) % self._nr_elements
            if self._current_stimulus == 0:
                self._current_sequence = (self._current_sequence +
                                          1) % self.nr_sequences
                if self.check_classification(self._current_sequence + 1):
                    self._state_trial = False
                    self._state_classify = True
                    pygame.time.wait(self.wait_after_early_stopping * 1000)

            if self._current_sequence == 0 and self._current_stimulus == 0:

                # send trigger:
                if self._current_level == 1:
                    self.send_parallel(self.END_LEVEL1)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL1)
                else:
                    self.send_parallel(self.END_LEVEL2)
                    self.logger.info("[TRIGGER] %d" % self.END_LEVEL2)

                # decide how to continue:
                self._state_trial = False
                self._state_classify = True

    def check_classification(self, nr):
        #print self._classifier_output
        means = [None] * self._nr_elements
        minimum = maxint
        classified = None
        for ii in range(self._nr_elements):
            means[ii] = sum(self._classifier_output[ii]) / nr
            if means[ii] < minimum:
                minimum = means[ii]
                classified = ii + 1

        print "\n**** Class: %d (mean=%f)\n" % (classified,
                                                means[classified - 1])
        return classified

    def __classify(self):

        ## wait until all classifier outputs are received:
        self._presentation.set(go_duration=(self.wait_before_classify,
                                            'seconds'))
        self._presentation.go()
        if self.offline:
            if self._current_level == 1:
                classified = self._classified_element
            else:
                classified = self._classified_letter
        elif not self._debug_classified == None:
            classified = self._debug_classified
            self._debug_classified = None
        else:
            if self.output_per_stimulus:
                nClassified = sum([
                    len(self._classifier_output[i])
                    for i in xrange(self._nr_elements)
                ])
                if nClassified < self._nr_elements * self.nr_sequences:
                    pygame.time.wait(20)
                    print 'not enough classifier-outputs received! (something may be wrong)'
                    return

                ## classify and set output:
                means = [None] * self._nr_elements
                minimum = maxint
                classified = None
                for ii in range(self._nr_elements):
                    means[ii] = sum(
                        self._classifier_output[ii]) / self.nr_sequences
                    if means[ii] < minimum:
                        minimum = means[ii]
                        classified = ii
                print "\n**** Class: %d (mean=%f)\n" % (classified + 1,
                                                        means[classified])
            else:
                means = [None] * self._nr_elements
                minimum = maxint
                classified = None
                for ii in range(self._nr_elements):
                    means[ii] = sum(
                        self._classifier_output[ii]) / self.nr_sequences
                    if means[ii] < minimum:
                        minimum = means[ii]
                        classified = ii
                print "\n**** Class: %d (mean=%f)\n" % (classified + 1,
                                                        means[classified])

            ## Reset classifier output to empty lists
            self._init_classifier_output()

        error_add = 0
        ## evaluate classification:
        if self._current_level == 1:
            self._classified_element = classified
            if len(self._desired_letters
                   ) > 0 and not self._desired_letters[:1] in self.letter_set[
                       classified]:
                # wrong group selected:
                error_add = self.ERROR_ADD
        else:
            self._classified_letter = classified
            if self._classified_letter == self._idx_backdoor:
                ## backdoor classified:
                if len(self._desired_letters
                       ) > 0 and self._desired_letters[:1] in self.letter_set[
                           self._classified_element]:
                    # backdoor selection wrong:
                    error_add = self.ERROR_ADD
            else:
                ## no backdoor classified:
                spelled_letter = self.letter_set[self._classified_element][
                    self._classified_letter]
                if len(self._desired_letters
                       ) > 0 and spelled_letter != self._desired_letters[:1]:
                    # wrong letter spelled:
                    error_add = self.ERROR_ADD

        ## send response trigger:
        self.send_parallel(self.RESPONSE[self._current_level - 1][classified] +
                           error_add)
        self.logger.info(
            "[TRIGGER] %d" %
            (self.RESPONSE[self._current_level - 1][classified] + error_add))

        self._state_classify = False
        self._state_feedback = True

    def __feedback(self):
        self._state_feedback = False

        ## call subclass method:
        self.feedback()

        ## check ErrP classification:
        if self.use_ErrP_detection:
            t = 0
            while self._ErrP_classifier is None and t < 1000:
                t += 50
                pygame.time.wait(50)
            if self._ErrP_classifier is None:
                print "no ErrP classifier received! "
            if self._ErrP_classifier:
                self.send_parallel(self.ERROR_POTENTIAL)
                self.logger.info("[TRIGGER] %d" % (self.ERROR_POTENTIAL))

        ## call subclass method:
        if not self.countdown_level2:
            self.switch_level()

        ## update phrases:
        if ((self._current_level == 2)
                and  # only update, if we are at the end of level 2,
            (self._classified_letter != self._idx_backdoor
             or self.copy_spelling)
                and  # if copyspelling off, we have no backdoor selected
            (self._ErrP_classifier is None or not self._ErrP_classifier)
            ):  # no ErrP was detected (or ErrP detection is off)
            spelled_letter = ""
            if self.copy_spelling:
                ## in copy spelling we force the desired letter to be spelled
                if len(self._desired_letters) > 0:
                    spelled_letter = self._desired_letters[:1]
                else:
                    print "??? moved beyond desired phrase in copy spelling ???"
            else:
                spelled_letter = self.letter_set[self._classified_element][
                    self._classified_letter]

            ## update desired phrase:
            if len(self._desired_letters) > 0:
                if spelled_letter == self._desired_letters[:1]:
                    # correct letter spelled:
                    self._desired_letters = self._desired_letters[
                        1:]  # remove first letter
                else:
                    # wrong letter spelled:
                    if spelled_letter == "<":
                        self._desired_letters = self._spelled_phrase[
                            -1:] + self._desired_letters
                    else:
                        self._desired_letters = "<" + self._desired_letters
                if len(self._desired_letters) == 0:
                    self._copyspelling_finished = True

            ## update spelled phrase:
            self._spelled_letters += spelled_letter
            if spelled_letter == "<":
                self._spelled_phrase = self._spelled_phrase[:-1]
            else:
                self._spelled_phrase += spelled_letter

            ## update screen phrases:
            self.logger.info('Current Phrase:')
            self.logger.info(self._spelled_phrase)
            self._ve_spelled_phrase.set(
                text=(len(self._spelled_phrase) == 0 and " "
                      or self._spelled_phrase))
            self._ve_current_letter.set(
                text=(len(self._desired_letters[:1]) == 0 and " "
                      or self._desired_letters[:1]))
            self._ve_desired_letters.set(
                text=(len(self._desired_letters[1:]) == 0 and " "
                      or self._desired_letters[1:]))

        if self.use_ErrP_detection and self._ErrP_classifier:
            self._state_trial = True

        else:
            if self._current_level == 1:
                # continue with level2 trial:
                if self.countdown_level2:
                    self._state_countdown = True
                else:
                    self._state_trial = True

            elif not self.offline:
                # start countdown
                if self.countdown_level1:
                    self._state_countdown = True
                else:
                    self._state_trial = True

            # set new level:
            self._current_level = 3 - self._current_level

        ## reset ErrP_classifier:
        self._ErrP_classifier = None

        # check copyspelling:
        if self._copyspelling_finished:
            self._copyspelling_finished = False
            self.on_control_event({'print': 0})  # print desired phrase
            self.on_control_event({'print': 1})  # print spelled phrase
            self.on_control_event({'print': 2})  # print all spelled letters
            self.send_parallel(self.COPYSPELLING_FINISHED)
            self.logger.info("[TRIGGER] %d" % (self.COPYSPELLING_FINISHED))
            pygame.time.wait(50)

    def __abort(self):

        # play warning sound

        def sine_array_onecycle(hz, peak, sample_rate):
            length = sample_rate / float(hz)
            omega = NP.pi * 2 / length
            xvalues = NP.arange(int(length)) * omega
            return (peak * NP.sin(xvalues))

        def sine_array(hz, peak, samples_rate):
            return NP.resize(sine_array_onecycle(hz, peak, sample_rate),
                             (sample_rate, ))

        sample_rate = 44100
        pygame.mixer.init(sample_rate, -16,
                          2)  # 44.1kHz, 16-bit signed, stereo
        f = sine_array(8000, 1, sample_rate)
        f = NP.array(zip(f, f))
        sound = pygame.sndarray.make_sound(f)
        channel = sound.play(-1)
        channel.set_volume(0.2, 0.2)
        pygame.time.delay(1000)
        sound.stop()

        if self._current_level == 1 and self.countdown_level1:
            self._state_countdown = True
        elif self._current_level == 2 and self.countdown_level2:
            self._state_countdown = True
        else:
            self._state_trial = True

        self._init_classifier_output()

    def _init_classifier_output(self):
        ## Empty lists
        self._classifier_output = [list() for _ in xrange(self._nr_elements)]

    def abort_trial_check(self):
        '''
        Check if event is an abort trial event
        '''
        return False

    def keyboard_input(self, event):
        if event.key == pygame.K_ESCAPE:
            self.on_stop()
        elif event.key == pygame.K_KP_ENTER:
            self.on_control_event({'print': 0})  # print desired phrase
            self.on_control_event({'print': 1})  # print spelled phrase
            self.on_control_event({'print': 2})  # print all spelled letters
        elif event.key == pygame.K_DELETE:
            # The DELETE key empties the spelled text shown this works
            # only in free spelling mode (i.e. offline and copy_spelling
            # are set to False)
            if self.offline == self.copy_spelling == False:
                self.logger.info('Clearing Text.')
                self._spelled_phrase = "   "
        elif self.debug:
            if ((event.key >= pygame.K_a and event.key <= pygame.K_z)
                    or (event.key == pygame.K_LESS)
                    or (event.key == pygame.K_PERIOD)
                    or (event.key == pygame.K_COMMA)):
                self.on_control_event({'new_letter': chr(event.key).upper()})
            elif event.key == pygame.K_MINUS:
                self.on_control_event({'new_letter': chr(pygame.K_UNDERSCORE)})
            elif event.key == pygame.K_BACKSPACE:
                self.on_control_event({'new_letter': chr(pygame.K_LESS)})
            elif event.key == pygame.K_SPACE:
                self.on_control_event({'new_letter': chr(pygame.K_UNDERSCORE)})
            elif event.key == pygame.K_UP and self.use_ErrP_detection:
                self.on_control_event({'cl_output': (1, 7)})
            elif event.key == pygame.K_DOWN and self.use_ErrP_detection:
                self.on_control_event({'cl_output': (0, 7)})
            if not self.offline:
                if (event.key >= pygame.K_0 and event.key <= pygame.K_5):
                    self._debug_classified = int(chr(event.key))
                elif (event.key >= pygame.K_KP0 and event.key <= pygame.K_KP5):
                    self._debug_classified = int(chr(event.key - 208))

    def on_control_event(self, data):
        self.logger.info("[CONTROL_EVENT] %s" % str(data))
        if data.has_key(u'cl_output'):
            # classification output was sent:
            score_data = data[u'cl_output']
            cl_out = score_data[0]
            iSubstim = int(score_data[1])  # evt auch "Subtrial"
            if iSubstim in range(1, 7):
                self._classifier_output[iSubstim - 1].append(cl_out)
            elif self.use_ErrP_detection:
                self._ErrP_classifier = cl_out
        elif data.has_key('new_letter'):
            # get new letter to spell:
            self._desired_letters += data['new_letter']
            self._ve_current_letter.set(
                text=(len(self._desired_letters[:1]) == 0 and " "
                      or self._desired_letters[:1]))
            self._ve_desired_letters.set(
                text=(len(self._desired_letters[1:]) == 0 and " "
                      or self._desired_letters[1:]))

        elif data.has_key(u'print'):
            if data[u'print'] == 0:
                self.logger.info("[DESIRED_PHRASE] %s" % self.desired_phrase)
            elif data[u'print'] == 1:
                self.logger.info("[SPELLED_PHRASE] %s" % self._spelled_phrase)
            elif data[u'print'] == 2:
                self.logger.info("[SPELLED_LETTERS] %s" %
                                 self._spelled_letters)

    '''
    ==========================
    == METHODS TO OVERLOAD: ==
    ==========================
    '''

    def init_screen_elements(self):
        '''
        overwrite this function in subclass.
        '''
        pass

    def prepare_mainloop(self):
        '''
        overwrite this function in subclass.
        '''
        pass

    def set_countdown_screen(self):
        '''
        set screen how it should look during countdown.
        overwrite this function in subclass.
        '''
        pass

    def set_standard_screen(self):
        '''
        set screen elements to standard state.
        overwrite this function in subclass.
        '''
        pass

    def set_synchronized_countdown_screen(self):
        '''
        set screen elements to for the synchronized countdown.
        overwrite this function in subclass.
        '''
        pass

    def stimulus(self, i_element, on=True):
        '''
        turn on/off the stimulus elements and turn off/on the normal elements.
        overwrite this function in subclass.
        '''
        pass

    def feedback(self):
        '''
        set screen how it should look during feedback presentation.
        overwrite this function in subclass.
        '''
        pass

    def switch_level(self):
        '''
        overwrite this function in subclass.
        '''
        pass

    def pre_play_tick(self):
        pass

    def post_play_tick(self):
        pass

    def pre__countdown(self):
        pass

    def post__countdown(self):
        pass

    def pre__trial(self):
        pass

    def post__trial(self):
        pass

    def pre__classify(self):
        pass

    def post__classify(self):
        pass

    def pre__feedback(self):
        pass

    def post__feedback(self):
        pass

    def pre__idle(self):
        pass

    def post__idle(self):
        pass

    def post__abort(self):
        pass
Esempio n. 6
0
    def __init_screen(self):
        ## create screen:
        if not self.fullscreen:
            os.environ['SDL_VIDEO_WINDOW_POS'] = '%d, %d' % (self.geometry[0],
                                                             self.geometry[1])
        self._screen = Screen(size=(self.geometry[2], self.geometry[3]),
                              fullscreen=self.fullscreen,
                              bgcolor=self.bg_color,
                              sync_swap=True)

        ## create letter box on top:
        self._ve_letterbox = Target2D(position=(self._centerPos[0],
                                                self.geometry[3] * (1 - 0.01) -
                                                self.letterbox_size[1] / 2.),
                                      size=(self.letterbox_size[0],
                                            self.letterbox_size[1]),
                                      color=self.phrase_color)
        self._ve_innerbox = Target2D(position=(self._centerPos[0],
                                               self.geometry[3] * (1 - 0.01) -
                                               self.letterbox_size[1] / 2.),
                                     size=(self.letterbox_size[0] - 6,
                                           self.letterbox_size[1] - 6),
                                     color=self.bg_color)

        self._current_letter_position = (self._centerPos[0],
                                         self.geometry[3] * (1 - 0.015) -
                                         self.letterbox_size[1] / 2.)
        self._ve_current_letter = Text(
            position=self._current_letter_position,
            text=(len(self._desired_letters[:1]) == 0 and " "
                  or self._desired_letters[:1]),
            font_size=self.font_size_current_letter,
            color=self.current_letter_color,
            anchor='center')

        self._ve_desired_letters = Text(
            position=(self._centerPos[0] + 5 + self.letterbox_size[0] / 2.,
                      self._current_letter_position[1]),
            text=(len(self._desired_letters[1:]) == 0 and " "
                  or self._desired_letters[1:]),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor='left')

        self._ve_spelled_phrase = Text(
            position=(self._centerPos[0] - 5 - self.letterbox_size[0] / 2.,
                      self._current_letter_position[1]),
            text=(len(self._spelled_phrase) == 0 and " "
                  or self._spelled_phrase),
            font_size=self.font_size_phrase,
            color=self.phrase_color,
            anchor='right')

        # if we're in free spelling mode, we hide all text fields but
        # the _ve_spelled_phrase. we also need a multiline
        # _ve_spelled_phrase instead of the single lined one
        if self.offline == self.copy_spelling == False:
            self._spelled_phrase = "   "
            self._ve_spelled_phrase = WrappedText(
                position=(0, self._current_letter_position[1]),
                text=(len(self._spelled_phrase) == 0 and " "
                      or self._spelled_phrase),
                font_size=self.font_size_phrase,
                color=self.phrase_color,
                size=(float(self.geometry[2]), float(self.geometry[3])))
            for i in self._ve_letterbox, self._ve_innerbox, self._ve_current_letter, self._ve_desired_letters:
                i.set(on=False)

        ## add word box to elementlist:
        self._ve_elements.extend([
            self._ve_letterbox, self._ve_innerbox, self._ve_current_letter,
            self._ve_desired_letters, self._ve_spelled_phrase
        ])

        ## create countdown:
        self._ve_countdown = Text(position=self._centerPos,
                                  text=" ",
                                  font_size=self.font_size_countdown,
                                  color=self.countdown_color,
                                  anchor='center',
                                  on=False)

        ## create countdown shapes
        self._ve_countdown_shape = self.countdown_shapes[
            self.countdown_shape_select](radius=90,
                                         position=self._centerPos,
                                         color=self.countdown_shape_color,
                                         on=False)

        ## create oscillator circle:
        self._ve_oscillator = FilledCircle(position=(self.osc_size / 2 + 10,
                                                     self.osc_size / 2 + 10),
                                           radius=self.osc_size / 2,
                                           color=self.osc_color,
                                           on=False)

        ## create shapes and letters:
        self.init_screen_elements()

        ## add remaining elements to element list:
        self._ve_elements.extend([
            self._ve_countdown_shape, self._ve_countdown, self._ve_oscillator
        ])

        ## add elements to viewport:
        self._viewport = Viewport(screen=self._screen,
                                  stimuli=self._ve_elements)
        self._presentation = Presentation(viewports=[self._viewport],
                                          handle_event_callbacks=[
                                              (pygame.KEYDOWN,
                                               self.keyboard_input),
                                              (pygame.QUIT, self.__stop)
                                          ])
Esempio n. 7
0
import VisionEgg
VisionEgg.start_default_logging()
VisionEgg.watch_exceptions()

from VisionEgg.Core import get_default_screen, Viewport, swap_buffers, \
     FrameTimer
from VisionEgg.Text import Text
from VisionEgg.WrappedText import WrappedText
import pygame
from pygame.locals import QUIT, KEYDOWN, MOUSEBUTTONDOWN

screen = get_default_screen()
screen.parameters.bgcolor = (0.0, 0.0, 1.0)  # background blue (RGB)

text = WrappedText(
    text=
    """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac magna nibh. Cras ac volutpat purus. Suspendisse potenti. Vestibulum accumsan erat ac massa hendrerit semper. Ut a laoreet magna. Suspendisse risus odio, porttitor nec sodales nec, laoreet sit amet tellus. Maecenas condimentum orci id magna laoreet tincidunt. Sed porta velit a ligula ullamcorper accumsan. Fusce a felis tortor, vel hendrerit mauris.""",
    position=(0, screen.size[1]))

viewport = Viewport(screen=screen, size=screen.size, stimuli=[text])
frame_timer = FrameTimer()
quit_now = 0
print "press any key or click the mouse to stop"
while not quit_now:
    for event in pygame.event.get():
        if event.type in (QUIT, KEYDOWN, MOUSEBUTTONDOWN):
            quit_now = 1
    screen.clear()
    viewport.draw()
    swap_buffers()
    frame_timer.tick()
frame_timer.log_histogram()