def define_stimuli(self): """ Creates standard and deviant stimuli. """ # The stimului. This can be anything compatible with VisionEgg dev = FilledCircle( color=(0, 1.0, 0), position=[self.geometry[2] / 2, self.geometry[3] / 2], radius=100) std1 = Target2D(color=(0, 0, 1.0), position=[self.geometry[2] / 2, self.geometry[3] / 2], size=(200, 200)) std2 = Target2D(color=(1.0, 0, 0), position=[self.geometry[2] / 2, self.geometry[3] / 2], size=(200, 200)) #dev = Text(text='Deviant', font_size=72, position=(300, 200), anchor='center') #std1 = Text(text='Standard I', font_size=72, position=(300, 200), anchor='center') #std2 = Text(text='Standard II', font_size=72, position=(300, 200), anchor='center') return [std1, std2], [dev]
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
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) ])
class CenterSpellerVE(VisualSpellerVE): ''' classdocs ''' def init(self): ''' initialize parameters ''' VisualSpellerVE.init(self) ## sizes: self.letter_radius = 40 self.speller_radius = 250 self.font_size_level1 = 45 # letters in level 1 self.font_size_level2 = 130 # letters in level 2 self.feedbackbox_size = 200.0 self.fixationpoint_size = 4.0 self.font_size_feedback_ErrP = 300 ## stimulus types: self.stimulustype_color = True self.shape_on=True self.stimulus_duration = 0.083 # 5 frames @60 Hz = 83ms flash self.interstimulus_duration = 0.1 ## feedback type: self.feedback_show_shape = True self.feedback_show_shape_at_center =True ## level 2 appearance: self.level_2_shapes = True self.level_2_letter_colors = False self.level_2_animation = True self.backdoor_symbol = "^" ## colors: self.shape_color = (1.0, 1.0, 1.0) self.stimuli_colors = [[0.0,0.0,1.0], [0.0,0.53,0.006], [1.0,0.0,0.0], [1.0,1.0,0.0], [0.86,0.0,0.86], [0.95,0.95,0.95]] self.letter_color = (.5, .5, .5) self.feedback_color = (0.9, 0.9, 0.9) self.fixationpoint_color = (1.0, 1.0, 1.0) self.feedback_ErrP_color = (0.7, 0.1, 0.1) ## register possible shapes: self.registered_shapes = {'circle':FilledCircle, 'cross' : FilledCross, 'hexagon':FilledHexagon, 'hourglass':FilledHourglass, 'triangle':FilledTriangle, 'rectangle':Target2D} ## define shapes in the form ['name', {parameters}]: # where parameters can be VisionEgg parameters eg. 'radius', 'size', 'orientation' etc. self.shapes = [ [ "triangle", {"innerColor":self.bg_color, "innerSize": 60, "size": 200}], [ "hourglass", {"size": 100 }], [ "cross", {"orientation": 45, "size": [30,180], "innerColor":self.bg_color}], [ "triangle", {"innerColor":self.bg_color, "innerSize": 60, "orientation": 180, "size": 200}], [ "hourglass", {"orientation": 90, "size": 100}], [ "cross", {"size": [30,180], "innerColor":self.bg_color }] ] # If False only shapes are shown in that level but not group symbols(Letters) self.level_1_symbols=True self.level_2_symbols=True def prepare_mainloop(self): ''' called in pre_mainloop of superclass. ''' if not self.do_animation: self.level_2_animation = False ## init containers for VE elements: self._ve_shapes = [] self._ve_letters = [] self._letter_positions = [] self._countdown_shape_positions = [] self._countdown_letter_positions = [] self._countdown_screen = False if self.stimulustype_color: assert len(self.stimuli_colors)==self._nr_elements self.shape_color = self.stimuli_colors else: self.shape_color = [self.shape_color]*self._nr_elements if not self.feedback_show_shape: self.feedback_show_shape_at_center = False if not self.shape_on: self.shapes=[['triangle', {'size':200}], ['triangle', {'size':200}], ['triangle', {'size':200}], ['triangle', {'size':200}], ['triangle', {'size':200}], ['triangle', {'size':200}]] def init_screen_elements(self): ''' Initializing screen elements ''' ## create shapes: if self.do_animation: for i in xrange(self._nr_elements): self._ve_shapes.append(self.registered_shapes[self.shapes[i][0]]( position=self._centerPos, color=self.shape_color[i], on=False, **self.shapes[i][1])) ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append((self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append((self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append((self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append(self._centerPos) self._countdown_letter_positions.append(self._countdown_shape_positions[i]) self._ve_letters.append(Text(position=self._centerPos, text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text(position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) ##################### IF NOT DO ANIMATION ######################### else: ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): self._ve_shapes.append(self.registered_shapes[self.shapes[i][0]]( position=(self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1]), color=self.shape_color[i], on=False, **self.shapes[i][1])) for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append((self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append((self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append((self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append(Text(position=(self._letter_layout.positions[j][0] + self._centerPos[0]+ circle_layout.positions[i][0], self._letter_layout.positions[j][1] + self._centerPos[1]+circle_layout.positions[i][1]), text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append((self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1])) self._countdown_letter_positions.append((self._letter_layout.positions[i][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[i][1] + self._countdown_shape_positions[-1][1])) self._ve_letters.append(Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0]+ circle_layout.positions[i][0], self._letter_layout.positions[i][1] + self._centerPos[1]+circle_layout.positions[i][1]), text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text(position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) # put letters in container: self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_feedback_ErrP) self._ve_elements.append(self._ve_fixationpoint) def set_countdown_screen(self): ''' set screen how it should look during countdown. ''' if self._countdown_screen: return self._countdown_screen = True is_level1 = self._current_level==1 ## turn on visible elements: for i in xrange(self._nr_elements): # shapes self._ve_shapes[i].set(on=is_level1 or self.level_2_shapes) for i in xrange(self._nr_letters): # level 1 letters self._ve_letters[i].set(on=is_level1 ) for i in xrange(len(self.letter_set[self._classified_element])): # level 2 letters self._ve_letters[self._nr_letters + i].set(on=not is_level1, text=(is_level1 and " " or self.letter_set[self._classified_element][i])) self._ve_letters[self._nr_letters + self._nr_elements-1].set(on=not is_level1, text=(is_level1 and " " or self.backdoor_symbol)) ## move all elements with their letters to countdown position: def update(t): dt = t/self.animation_time for i in xrange(self._nr_elements): pos = animate_sigmoid(self._centerPos, self._countdown_shape_positions[i], dt) self._ve_shapes[i].set(position=pos) # shapes self._ve_letters[self._nr_letters + i].set(position=pos) # level 2 letters for i in xrange(self._nr_letters): pos = animate_sigmoid(self._letter_positions[i], self._countdown_letter_positions[i], dt) self._ve_letters[i].set(position=pos) # level 1 letters def update2(t): # if not do animation for i in xrange(self._nr_elements): self._ve_shapes[i].set(position=self._countdown_shape_positions[i])# shapes self._ve_letters[self._nr_letters + i].set(position=self._countdown_shape_positions[i])# level 2 letters for i in xrange(self._nr_letters): self._ve_letters[i].set(position=self._countdown_letter_positions[i])# level 1 letters if self.do_animation: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) else: self._presentation.set(go_duration=(0,'seconds')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update2)) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) for i in xrange(self._nr_elements): self._ve_shapes[i].set(position=self._countdown_shape_positions[i]) self._ve_letters[self._nr_letters + i].set(position=self._countdown_shape_positions[i]) for i in xrange(self._nr_letters): self._ve_letters[i].set(position=self._countdown_letter_positions[i]) def set_synchronized_countdown_screen(self): for i in xrange(self._nr_elements): self._ve_shapes[i].set(on=False) if self._current_level==1: for j in xrange(self._nr_letters): self._ve_letters[j].set(on=False) else: for j in xrange(self._nr_elements+1): self._ve_letters[-j].set(on=False) def set_standard_screen(self): ''' set screen elements to standard state. ''' self._countdown_screen = False # move all elements with their letters to standard position: def update(t): dt = t/self.animation_time for i in xrange(self._nr_elements): pos = animate_sigmoid(self._countdown_shape_positions[i], self._centerPos, dt) self._ve_shapes[i].set(position=pos) # shapes self._ve_letters[self._nr_letters + i].set(position=pos) # level 2 letters for i in xrange(self._nr_letters): pos = animate_sigmoid(self._countdown_letter_positions[i], self._letter_positions[i], dt) self._ve_letters[i].set(position=pos) # level 1 letters if self.do_animation: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) else: self._presentation.set(go_duration=(0,'seconds')) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) for i in xrange(self._nr_elements): self._ve_shapes[i].set(position=self._centerPos, on=False) self._ve_letters[self._nr_letters + i].set(position=self._centerPos, on=False) for i in xrange(self._nr_letters): self._ve_letters[i].set(position=self._letter_positions[i], on=False) self._ve_countdown.set(on=False, text=" ") def stimulus(self, i_element, on=True): ''' turn on/off the stimulus elements and turn off/on the normal elements. ''' if self._current_level==1: self._ve_shapes[i_element].set(on=on) for i in xrange(len(self.letter_set[i_element])): self._ve_letters[(self._nr_elements-1) * i_element + i].set(on=on and self.level_1_symbols) else: self._ve_shapes[i_element].set(on=(on and self.level_2_shapes)) if i_element < len(self.letter_set[self._classified_element]): self._ve_letters[self._nr_letters + i_element].set(on=on and self.level_2_symbols, text=(on and self.letter_set[self._classified_element][i_element] or " ")) else: self._ve_letters[self._nr_letters + self._nr_elements-1].set(on=on and self.level_2_symbols, text=(on and self.backdoor_symbol or " ")) def feedback(self): ''' Show classified element / letter(s). ''' self._show_feedback(True) ## present: self._presentation.set(go_duration=(self.feedback_duration, 'seconds')) self._presentation.go() self._show_feedback(False) def _show_feedback(self, on=True): ## turn on/off feedback box: if not self.feedback_show_shape_at_center: self._ve_feedback_box.set(on=on) if self._current_level == 1: ## turn on/off center letter group: if not self.feedback_show_shape_at_center: for i in xrange(self._nr_elements): self._ve_feedback_letters[i].set(on=on, text=(i<self._nr_elements-1 and self.letter_set[self._classified_element][i] or self.backdoor_symbol)) if self.feedback_show_shape: if self.feedback_show_shape_at_center or not on: pos = self._centerPos else: pos = self._countdown_shape_positions[self._classified_element] ## turn on/off selected element: self._ve_shapes[self._classified_element].set(on=on, position=pos) ## turn on/off letters of selected element: idx_start = self._classified_element*(self._nr_elements-1) idx_end = idx_start + self._nr_elements-1 for i in xrange(idx_start, idx_end): self._ve_letters[i].set(on=on, position=(on and list(NP.add(pos, self._letter_layout.positions[i % (self._nr_elements-1)])) or self._letter_positions[i])) else: ### level 2: ## check if backdoor classified: if self._classified_letter >= len(self.letter_set[self._classified_element]): text = self.backdoor_symbol else: text = self.letter_set[self._classified_element][self._classified_letter] ## turn on/off letter: if self.offline or not self.feedback_show_shape_at_center: self._ve_feedback_letters[-1].set(on=on, text=text, color=(self.level_2_letter_colors and self.stimuli_colors[self._classified_letter] or self.letter_color)) if self.feedback_show_shape: if self.feedback_show_shape_at_center: pos = self._centerPos else: pos = self._countdown_shape_positions[self._classified_element] ## turn on/off current element: self._ve_shapes[self._classified_letter].set(on=(on and self.level_2_shapes), position=(on and pos or self._centerPos)) ## turn on/off letter of current element: idx = self._nr_letters + self._classified_letter self._ve_letters[idx].set(on=on, text=text, position=(on and pos or self._letter_positions[idx])) def switch_level(self): if self.use_ErrP_detection and self._ErrP_classifier: self._ve_feedback_ErrP.set(on=True) self._show_feedback(True) self._presentation.set(go_duration=(self.feedback_ErrP_duration, 'seconds')) self._presentation.go() self._ve_feedback_ErrP.set(on=False) self._show_feedback(False) return if self._current_level==1: '''level 1: move classified letters to circles ''' if self.level_2_animation: ## turn on all elements: for i in xrange(self._nr_elements): self._ve_shapes[i].set(on=self.level_2_shapes, position=self._countdown_shape_positions[i]) ## animate letters: def update(t): dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements)] feedback_letters = [] for i in xrange(self._nr_elements): pos = (animate_sigmoid(NP.add(self._letter_layout.positions[i], self._centerPos), self._countdown_shape_positions[i], dt)) font_size = int(round(animate(self.font_size_level1, self.font_size_level2, dt))) color = (self.level_2_letter_colors and list(animate(self.letter_color, self.stimuli_colors[i], dt)) or self.letter_color) text = (i==self._nr_elements-1 and self.backdoor_symbol or self.letter_set[self._classified_element][i]) feedback_letters.append(Text(position=pos, color=color, font_size=font_size, text=text, anchor="center")) self._viewport.parameters.stimuli.extend(feedback_letters) if self.feedback_show_shape_at_center: pos = animate_sigmoid(self._centerPos, self._countdown_shape_positions[self._classified_element], dt) self._ve_shapes[self._classified_element].set(position=pos) # send to screen: self._viewport.parameters.stimuli.extend([None]*(self._nr_elements)) self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements)] ## turn on level 2 letters: for i in xrange(self._nr_elements): text = (i==self._nr_elements-1 and self.backdoor_symbol or self.letter_set[self._classified_element][i]) self._ve_letters[self._nr_letters + i].set(on=True, text=text, position=self._countdown_letter_positions[self._nr_letters + i]) else: ## turn on all elements: self.set_standard_screen() else: ''' level 2: move classified letter to wordbox ''' ## check if backdoor classified: if self._classified_letter >= len(self.letter_set[self._classified_element]): text = self.backdoor_symbol else: text = self.letter_set[self._classified_element][self._classified_letter] ## animate letter, but not if backdoor classified: if self._classified_letter < len(self.letter_set[self._classified_element]): def update(t): dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] pos = animate_sigmoid(self._centerPos, self._current_letter_position, dt) color = (self.level_2_letter_colors and list(animate_sigmoid(self.stimuli_colors[self._classified_letter], self.current_letter_color, dt)) or list(animate_sigmoid(self.letter_color, self.current_letter_color, dt))) font_size = int(round(animate(self.font_size_level2, self.font_size_current_letter, dt))) self._viewport.parameters.stimuli.append(Text(position=pos, color=color, font_size=font_size, text=text, anchor='center')) # send to screen: self._viewport.parameters.stimuli.append(None) self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] else: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.go() ## turn off feedback box: self._ve_feedback_box.set(on=False) def pre__classify(self): self._ve_fixationpoint.set(on=True) def post__classify(self): self._ve_fixationpoint.set(on=False)
def init_screen_elements(self): ''' Initializing screen elements ''' ## create shapes: if self.do_animation: for i in xrange(self._nr_elements): self._ve_shapes.append(self.registered_shapes[self.shapes[i][0]]( position=self._centerPos, color=self.shape_color[i], on=False, **self.shapes[i][1])) ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append((self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append((self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append((self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append(self._centerPos) self._countdown_letter_positions.append(self._countdown_shape_positions[i]) self._ve_letters.append(Text(position=self._centerPos, text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text(position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) ##################### IF NOT DO ANIMATION ######################### else: ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): self._ve_shapes.append(self.registered_shapes[self.shapes[i][0]]( position=(self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1]), color=self.shape_color[i], on=False, **self.shapes[i][1])) for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append((self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append((self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append((self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append(Text(position=(self._letter_layout.positions[j][0] + self._centerPos[0]+ circle_layout.positions[i][0], self._letter_layout.positions[j][1] + self._centerPos[1]+circle_layout.positions[i][1]), text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append((self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1])) self._countdown_letter_positions.append((self._letter_layout.positions[i][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[i][1] + self._countdown_shape_positions[-1][1])) self._ve_letters.append(Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0]+ circle_layout.positions[i][0], self._letter_layout.positions[i][1] + self._centerPos[1]+circle_layout.positions[i][1]), text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text(position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) # put letters in container: self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_feedback_ErrP) self._ve_elements.append(self._ve_fixationpoint)
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') ## 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 init_screen_elements(self): ''' Initialize screen elements ''' self._letter_positions = [] ## create triangles: self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) self._letter_layout.positions.reverse() a = self.speller_radius / 2. b = a * NP.sqrt(3) / 3. self._shape_positions = [(self._centerPos[0], self._centerPos[1] + 2*b), (self._centerPos[0] + a, self._centerPos[1] + b), (self._centerPos[0] + a, self._centerPos[1] - b), (self._centerPos[0], self._centerPos[1] - 2*b), (self._centerPos[0] - a, self._centerPos[1] - b), (self._centerPos[0] - a, self._centerPos[1] + b)] orientaion = [180., 0., 180., 0., 180., 0.] for i in xrange(self._nr_elements): self._ve_edges.append(FilledTriangle(size=self.speller_radius, position=self._shape_positions[i], orientation=orientaion[i], color=self.edge_color)) self._ve_shapes.append(FilledTriangle(size=self.speller_radius - self.edge_size, position=self._shape_positions[i], orientation=orientaion[i], color=self.shape_color)) ## add the letters of level 1: for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((self._letter_layout.positions[j][0]+self._shape_positions[i][0], self._letter_layout.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) ## add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements-1): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint)
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
class HexoSpellerVE(VisualSpellerVE): ''' Visual Speller with six circles like the classical HexOSpell. ''' def init(self): ''' initialize parameters ''' VisualSpellerVE.init(self) ## sizes: self.circle_radius = 100 self.edge_size = 2 self.speller_radius = 260 self.fixationpoint_size = 4.0 self.font_size_level1 = 70 # letters in level 1 self.font_size_level2 = 130 # letters in level 2 self.feedbackbox_size = 200.0 ## colors: self.shape_color = (0.0, 0.0, 0.0) self.edge_color = (1.0, 1.0, 1.0) self.stimuli_colors = [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (1.0, 1.0, 0.0), (1.0, 0.0, 1.0), (0.0, 1.0, 1.0)] self.letter_color = (1.0, 1.0, 1.0) self.letter_stimulus_color = (0.0, 0.0, 0.0) self.fixationpoint_color = (1.0, 1.0, 1.0) self.feedback_color = (0.7, 0.7, 0.7) ## stimulus types: self.stimulustype_color = True self.stimulustype_flash = True self.flash_size_factor = 1.2 self.countdown_shape_select='circle' def prepare_mainloop(self): ''' called in pre_mainloop of superclass. ''' if not self.stimulustype_flash: self.flash_size_factor = 1.0 if self.stimulustype_color: assert len(self.stimuli_colors)==self._nr_elements else: self.stimuli_colors = [self.shape_color]*self._nr_elements ## init containers for VE elements: self._ve_shapes = [] self._ve_edges = [] self._ve_letters = [] self._edge_radius = self.circle_radius self._circle_radius = self.circle_radius - self.edge_size def init_screen_elements(self): ''' Initializing screen elements ''' self._letter_positions = [] ## create and place the circles and letters: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.circle_radius*0.65, start=NP.pi/6.*5) self._letter_layout.positions.reverse() self._shape_positions = [(x+self.geometry[2]/2, y+self._spellerHeight/2) for (x,y) in circle_layout.positions] # add the standard elements: for i in xrange(self._nr_elements): self._ve_edges.append(FilledCircle(radius=self._edge_radius, position=self._shape_positions[i], color=self.edge_color)) self._ve_shapes.append(FilledCircle(radius=self._circle_radius, position=self._shape_positions[i], color=self.shape_color)) # add the letters of level 1: for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((self._letter_layout.positions[j][0]+self._shape_positions[i][0], self._letter_layout.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) # add the stimuli letters of level 1: letter_layout2 = CircularLayout(nr_elements=self._nr_elements, radius=self.circle_radius*0.65*self.flash_size_factor, start=NP.pi/6.*5) letter_layout2.positions.reverse() for i in xrange(self._nr_elements): for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((letter_layout2.positions[j][0]+self._shape_positions[i][0], letter_layout2.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=int(NP.floor(self.font_size_level1*self.flash_size_factor)), color=self.letter_stimulus_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) # add stimuli letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=int(NP.floor(self.font_size_level2*self.flash_size_factor)), color=self.letter_stimulus_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements-1): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.extend([self._ve_feedback_box]) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint) def set_countdown_screen(self): ''' set screen how it should look during countdown. ''' self.set_standard_screen(False) def set_standard_screen(self, std=True): ''' set screen elements to standard state. ''' is_level1 = self._current_level==1 for i in xrange(self._nr_elements): self._ve_edges[i].set(radius=(std and self._edge_radius or self._edge_radius * self.flash_size_factor)) self._ve_shapes[i].set(color=(std and self.shape_color or self.stimuli_colors[i]), radius=(std and self._circle_radius or self._circle_radius * self.flash_size_factor)) self._ve_letters[self._nr_letters*2 + i].set(on=(std and (not is_level1))) self._ve_letters[self._nr_letters*2 + self._nr_elements + i].set(on=((not std) and (not is_level1))) for i in xrange(self._nr_letters): self._ve_letters[i].set(on=(std and is_level1)) self._ve_letters[self._nr_letters + i].set(on=((not std) and is_level1)) self._ve_fixationpoint.set(on=std) def set_synchronized_countdown_screen(self): self.set_standard_screen(True) def stimulus(self, i_element, on=True): ''' turn on/off the stimulus elements and turn off/on the normal elements. ''' self._ve_shapes[i_element].set(color=(on and self.stimuli_colors[i_element] or self.shape_color), radius=self._circle_radius*(on and self.flash_size_factor or 1)) self._ve_edges[i_element].set(radius=self._edge_radius*(on and self.flash_size_factor or 1)) if self._current_level==1: for i in xrange(len(self.letter_set[i_element])): i_normal = (self._nr_elements-1) * i_element + i i_stimulus = self._nr_letters + i_normal self._ve_letters[i_normal].set(on=not on) self._ve_letters[i_stimulus].set(on=on) else: i_normal = 2*self._nr_letters + i_element i_stimulus = self._nr_elements + i_normal self._ve_letters[i_normal].set(on=not on) self._ve_letters[i_stimulus].set(on=on) def feedback(self): # turn on feedback box and turn off fixationpoint: self._ve_feedback_box.set(on=True) self._ve_fixationpoint.set(on=False) if self._current_level == 1: '''level 1: present classified letter group in center and move letters to circles ''' ## display letter group: for i in xrange(self._nr_elements-1): self._ve_feedback_letters[i].set(on=True, text=self.letter_set[self._classified_element][i]) ## turn on current element: self.stimulus(self._classified_element, True) ## turn off other letters: idx_start = self._classified_element*(self._nr_elements-1) idx_end = idx_start + self._nr_elements-1 for i in xrange(idx_start): self._ve_letters[i].set(on=False) for i in xrange(idx_end, self._nr_letters): self._ve_letters[i].set(on=False) ## present: self._presentation.set(go_duration=(1, 'seconds')) self._presentation.go() ## turn off current element: self.stimulus(self._classified_element, False) for i in xrange(idx_start, idx_end): self._ve_letters[i].set(on=False) for i in xrange(self._nr_elements-1): self._ve_feedback_letters[i].set(on=False) ## animate letters: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.extend([None]*(self._nr_elements-1)) def update(t): dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements-1)] feedback_letters = [] for i in xrange(self._nr_elements-1): pos = animate_sigmoid(NP.add(self._letter_layout.positions[i], self._centerPos), self._shape_positions[i], dt) font_size = int(round(animate(self.font_size_level1, self.font_size_level2, dt))) feedback_letters.append(Text(position=pos, color=self.letter_color, font_size=font_size, text=self.letter_set[self._classified_element][i], anchor="center")) self._viewport.parameters.stimuli.extend(feedback_letters) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements-1)] ## turn on level 2 letters: for i in xrange(len(self.letter_set[self._classified_element])): self._ve_letters[self._nr_letters*2 + i].set(on=True, text=self.letter_set[self._classified_element][i]) self._ve_letters[self._nr_letters*2 + self._nr_elements + i].set(text=self.letter_set[self._classified_element][i]) else: ''' level 2: present classified letter and move it to wordbox ''' ## check if backdoor classified: if self._classified_letter >= len(self.letter_set[self._classified_element]): text = ' ' else: text = self.letter_set[self._classified_element][self._classified_letter] ## display letter: self._ve_feedback_letters[-1].set(on=True, text=text) ## turn on current element stimulusw: if not self.offline: self.stimulus(self._classified_letter, True) ## turn off other letters: for i in xrange(self._classified_letter): self._ve_letters[self._nr_letters*2 + i].set(on=False) for i in xrange(self._classified_letter+1, self._nr_elements): self._ve_letters[self._nr_letters*2 + i].set(on=False) ## present: self._presentation.set(go_duration=(self.feedback_duration, 'seconds')) self._presentation.go() ## turn off current element stimulus: if not self.offline: self.stimulus(self._classified_letter, False) self._ve_letters[self._nr_letters*2 + self._classified_letter].set(on=False) self._ve_feedback_letters[-1].set(on=False) ## animate letter, but not if backdoor classified: if self._classified_letter < len(self.letter_set[self._classified_element]): self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.append(None) def update(t): # Add one screen refresh (otherwise animation lags by 1 frame) dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] pos = animate_sigmoid(self._centerPos, self._current_letter_position, dt) color = animate_sigmoid(self.letter_color, self.current_letter_color, dt) font_size = int(round(animate(self.font_size_level2, self.font_size_current_letter, dt))) self._viewport.parameters.stimuli.append(Text(position=pos, color=color, font_size=font_size, text=text, anchor='center')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] else: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.go() ## turn on level 1 letters: for i in xrange(self._nr_letters): self._ve_letters[i].set(on=True) ## turn off feedback box and turn on fixationpoint: self._ve_feedback_box.set(on=False) self._ve_fixationpoint.set(on=True)
class CenterSpellerVE(VisualSpellerVE): ''' classdocs ''' def init(self): ''' initialize parameters ''' VisualSpellerVE.init(self) ## sizes: self.letter_radius = 40 self.speller_radius = 250 self.font_size_level1 = 45 # letters in level 1 self.font_size_level2 = 130 # letters in level 2 self.feedbackbox_size = 200.0 self.fixationpoint_size = 4.0 self.font_size_feedback_ErrP = 300 ## stimulus types: self.stimulustype_color = True self.shape_on = True self.stimulus_duration = 0.083 # 5 frames @60 Hz = 83ms flash self.interstimulus_duration = 0.1 ## feedback type: self.feedback_show_shape = True self.feedback_show_shape_at_center = True ## level 2 appearance: self.level_2_shapes = True self.level_2_letter_colors = False self.level_2_animation = True self.backdoor_symbol = "^" ## colors: self.shape_color = (1.0, 1.0, 1.0) self.stimuli_colors = [[0.0, 0.0, 1.0], [0.0, 0.53, 0.006], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.86, 0.0, 0.86], [0.95, 0.95, 0.95]] self.letter_color = (.5, .5, .5) self.feedback_color = (0.9, 0.9, 0.9) self.fixationpoint_color = (1.0, 1.0, 1.0) self.feedback_ErrP_color = (0.7, 0.1, 0.1) ## register possible shapes: self.registered_shapes = { 'circle': FilledCircle, 'cross': FilledCross, 'hexagon': FilledHexagon, 'hourglass': FilledHourglass, 'triangle': FilledTriangle, 'rectangle': Target2D } ## define shapes in the form ['name', {parameters}]: # where parameters can be VisionEgg parameters eg. 'radius', 'size', 'orientation' etc. self.shapes = [[ "triangle", { "innerColor": self.bg_color, "innerSize": 60, "size": 200 } ], ["hourglass", { "size": 100 }], [ "cross", { "orientation": 45, "size": [30, 180], "innerColor": self.bg_color } ], [ "triangle", { "innerColor": self.bg_color, "innerSize": 60, "orientation": 180, "size": 200 } ], ["hourglass", { "orientation": 90, "size": 100 }], [ "cross", { "size": [30, 180], "innerColor": self.bg_color } ]] # If False only shapes are shown in that level but not group symbols(Letters) self.level_1_symbols = True self.level_2_symbols = True def prepare_mainloop(self): ''' called in pre_mainloop of superclass. ''' if not self.do_animation: self.level_2_animation = False ## init containers for VE elements: self._ve_shapes = [] self._ve_letters = [] self._letter_positions = [] self._countdown_shape_positions = [] self._countdown_letter_positions = [] self._countdown_screen = False if self.stimulustype_color: assert len(self.stimuli_colors) == self._nr_elements self.shape_color = self.stimuli_colors else: self.shape_color = [self.shape_color] * self._nr_elements if not self.feedback_show_shape: self.feedback_show_shape_at_center = False if not self.shape_on: self.shapes = [['triangle', { 'size': 200 }], ['triangle', { 'size': 200 }], ['triangle', { 'size': 200 }], ['triangle', { 'size': 200 }], ['triangle', { 'size': 200 }], ['triangle', { 'size': 200 }]] def init_screen_elements(self): ''' Initializing screen elements ''' ## create shapes: if self.do_animation: for i in xrange(self._nr_elements): self._ve_shapes.append( self.registered_shapes[self.shapes[i][0]]( position=self._centerPos, color=self.shape_color[i], on=False, **self.shapes[i][1])) ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi / 6. * 5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append( (self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append( (self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append( (self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append( Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append(self._centerPos) self._countdown_letter_positions.append( self._countdown_shape_positions[i]) self._ve_letters.append( Text(position=self._centerPos, text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text( position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle( radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) ##################### IF NOT DO ANIMATION ######################### else: ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi / 6. * 5) self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) circle_layout.positions.reverse() self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): self._ve_shapes.append( self.registered_shapes[self.shapes[i][0]]( position=(self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1]), color=self.shape_color[i], on=False, **self.shapes[i][1])) for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append( (self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append( (self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append( (self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append( Text(position=(self._letter_layout.positions[j][0] + self._centerPos[0] + circle_layout.positions[i][0], self._letter_layout.positions[j][1] + self._centerPos[1] + circle_layout.positions[i][1]), text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append( (self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1])) self._countdown_letter_positions.append( (self._letter_layout.positions[i][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[i][1] + self._countdown_shape_positions[-1][1])) self._ve_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0] + circle_layout.positions[i][0], self._letter_layout.positions[i][1] + self._centerPos[1] + circle_layout.positions[i][1]), text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text( position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle( radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) # put letters in container: self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_feedback_ErrP) self._ve_elements.append(self._ve_fixationpoint) def set_countdown_screen(self): ''' set screen how it should look during countdown. ''' if self._countdown_screen: return self._countdown_screen = True is_level1 = self._current_level == 1 ## turn on visible elements: for i in xrange(self._nr_elements): # shapes self._ve_shapes[i].set(on=is_level1 or self.level_2_shapes) for i in xrange(self._nr_letters): # level 1 letters self._ve_letters[i].set(on=is_level1) for i in xrange(len( self.letter_set[self._classified_element])): # level 2 letters self._ve_letters[self._nr_letters + i].set( on=not is_level1, text=(is_level1 and " " or self.letter_set[self._classified_element][i])) self._ve_letters[self._nr_letters + self._nr_elements - 1].set( on=not is_level1, text=(is_level1 and " " or self.backdoor_symbol)) ## move all elements with their letters to countdown position: def update(t): dt = t / self.animation_time for i in xrange(self._nr_elements): pos = animate_sigmoid(self._centerPos, self._countdown_shape_positions[i], dt) self._ve_shapes[i].set(position=pos) # shapes self._ve_letters[self._nr_letters + i].set( position=pos) # level 2 letters for i in xrange(self._nr_letters): pos = animate_sigmoid(self._letter_positions[i], self._countdown_letter_positions[i], dt) self._ve_letters[i].set(position=pos) # level 1 letters def update2(t): # if not do animation for i in xrange(self._nr_elements): self._ve_shapes[i].set( position=self._countdown_shape_positions[i]) # shapes self._ve_letters[self._nr_letters + i].set( position=self._countdown_shape_positions[i] ) # level 2 letters for i in xrange(self._nr_letters): self._ve_letters[i].set( position=self._countdown_letter_positions[i] ) # level 1 letters if self.do_animation: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) else: self._presentation.set(go_duration=(0, 'seconds')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update2)) # send to screen: self._presentation.go() self._presentation.remove_controller(None, None, None) for i in xrange(self._nr_elements): self._ve_shapes[i].set(position=self._countdown_shape_positions[i]) self._ve_letters[self._nr_letters + i].set( position=self._countdown_shape_positions[i]) for i in xrange(self._nr_letters): self._ve_letters[i].set( position=self._countdown_letter_positions[i]) def set_synchronized_countdown_screen(self): for i in xrange(self._nr_elements): self._ve_shapes[i].set(on=False) if self._current_level == 1: for j in xrange(self._nr_letters): self._ve_letters[j].set(on=False) else: for j in xrange(self._nr_elements + 1): self._ve_letters[-j].set(on=False) def set_standard_screen(self): ''' set screen elements to standard state. ''' self._countdown_screen = False # move all elements with their letters to standard position: def update(t): dt = t / self.animation_time for i in xrange(self._nr_elements): pos = animate_sigmoid(self._countdown_shape_positions[i], self._centerPos, dt) self._ve_shapes[i].set(position=pos) # shapes self._ve_letters[self._nr_letters + i].set( position=pos) # level 2 letters for i in xrange(self._nr_letters): pos = animate_sigmoid(self._countdown_letter_positions[i], self._letter_positions[i], dt) self._ve_letters[i].set(position=pos) # level 1 letters if self.do_animation: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) else: self._presentation.set(go_duration=(0, 'seconds')) # send to screen: self._presentation.go() self._presentation.remove_controller(None, None, None) for i in xrange(self._nr_elements): self._ve_shapes[i].set(position=self._centerPos, on=False) self._ve_letters[self._nr_letters + i].set( position=self._centerPos, on=False) for i in xrange(self._nr_letters): self._ve_letters[i].set(position=self._letter_positions[i], on=False) self._ve_countdown.set(on=False, text=" ") def stimulus(self, i_element, on=True): ''' turn on/off the stimulus elements and turn off/on the normal elements. ''' if self._current_level == 1: self._ve_shapes[i_element].set(on=on) for i in xrange(len(self.letter_set[i_element])): self._ve_letters[(self._nr_elements - 1) * i_element + i].set(on=on and self.level_1_symbols) else: self._ve_shapes[i_element].set(on=(on and self.level_2_shapes)) if i_element < len(self.letter_set[self._classified_element]): self._ve_letters[self._nr_letters + i_element].set( on=on and self.level_2_symbols, text=(on and self.letter_set[self._classified_element][i_element] or " ")) else: self._ve_letters[self._nr_letters + self._nr_elements - 1].set( on=on and self.level_2_symbols, text=(on and self.backdoor_symbol or " ")) def feedback(self): ''' Show classified element / letter(s). ''' self._show_feedback(True) ## present: self._presentation.set(go_duration=(self.feedback_duration, 'seconds')) self._presentation.go() self._show_feedback(False) def _show_feedback(self, on=True): ## turn on/off feedback box: if not self.feedback_show_shape_at_center: self._ve_feedback_box.set(on=on) if self._current_level == 1: ## turn on/off center letter group: if not self.feedback_show_shape_at_center: for i in xrange(self._nr_elements): self._ve_feedback_letters[i].set( on=on, text=(i < self._nr_elements - 1 and self.letter_set[self._classified_element][i] or self.backdoor_symbol)) if self.feedback_show_shape: if self.feedback_show_shape_at_center or not on: pos = self._centerPos else: pos = self._countdown_shape_positions[ self._classified_element] ## turn on/off selected element: self._ve_shapes[self._classified_element].set(on=on, position=pos) ## turn on/off letters of selected element: idx_start = self._classified_element * (self._nr_elements - 1) idx_end = idx_start + self._nr_elements - 1 for i in xrange(idx_start, idx_end): self._ve_letters[i].set( on=on, position=(on and list( NP.add( pos, self._letter_layout.positions[ i % (self._nr_elements - 1)])) or self._letter_positions[i])) else: ### level 2: ## check if backdoor classified: if self._classified_letter >= len( self.letter_set[self._classified_element]): text = self.backdoor_symbol else: text = self.letter_set[self._classified_element][ self._classified_letter] ## turn on/off letter: if self.offline or not self.feedback_show_shape_at_center: self._ve_feedback_letters[-1].set( on=on, text=text, color=(self.level_2_letter_colors and self.stimuli_colors[self._classified_letter] or self.letter_color)) if self.feedback_show_shape: if self.feedback_show_shape_at_center: pos = self._centerPos else: pos = self._countdown_shape_positions[ self._classified_element] ## turn on/off current element: self._ve_shapes[self._classified_letter].set( on=(on and self.level_2_shapes), position=(on and pos or self._centerPos)) ## turn on/off letter of current element: idx = self._nr_letters + self._classified_letter self._ve_letters[idx].set( on=on, text=text, position=(on and pos or self._letter_positions[idx])) def switch_level(self): if self.use_ErrP_detection and self._ErrP_classifier: self._ve_feedback_ErrP.set(on=True) self._show_feedback(True) self._presentation.set(go_duration=(self.feedback_ErrP_duration, 'seconds')) self._presentation.go() self._ve_feedback_ErrP.set(on=False) self._show_feedback(False) return if self._current_level == 1: '''level 1: move classified letters to circles ''' if self.level_2_animation: ## turn on all elements: for i in xrange(self._nr_elements): self._ve_shapes[i].set( on=self.level_2_shapes, position=self._countdown_shape_positions[i]) ## animate letters: def update(t): dt = t / self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-( self._nr_elements)] feedback_letters = [] for i in xrange(self._nr_elements): pos = (animate_sigmoid( NP.add(self._letter_layout.positions[i], self._centerPos), self._countdown_shape_positions[i], dt)) font_size = int( round( animate(self.font_size_level1, self.font_size_level2, dt))) color = (self.level_2_letter_colors and list( animate(self.letter_color, self.stimuli_colors[i], dt)) or self.letter_color) text = ( i == self._nr_elements - 1 and self.backdoor_symbol or self.letter_set[self._classified_element][i]) feedback_letters.append( Text(position=pos, color=color, font_size=font_size, text=text, anchor="center")) self._viewport.parameters.stimuli.extend( feedback_letters) if self.feedback_show_shape_at_center: pos = animate_sigmoid( self._centerPos, self._countdown_shape_positions[ self._classified_element], dt) self._ve_shapes[self._classified_element].set( position=pos) # send to screen: self._viewport.parameters.stimuli.extend([None] * (self._nr_elements)) self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) self._presentation.go() self._presentation.remove_controller(None, None, None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-( self._nr_elements)] ## turn on level 2 letters: for i in xrange(self._nr_elements): text = (i == self._nr_elements - 1 and self.backdoor_symbol or self.letter_set[self._classified_element][i]) self._ve_letters[self._nr_letters + i].set( on=True, text=text, position=self._countdown_letter_positions[ self._nr_letters + i]) else: ## turn on all elements: self.set_standard_screen() else: ''' level 2: move classified letter to wordbox ''' ## check if backdoor classified: if self._classified_letter >= len( self.letter_set[self._classified_element]): text = self.backdoor_symbol else: text = self.letter_set[self._classified_element][ self._classified_letter] ## animate letter, but not if backdoor classified: if self._classified_letter < len( self.letter_set[self._classified_element]): def update(t): dt = t / self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[: -1] pos = animate_sigmoid(self._centerPos, self._current_letter_position, dt) color = (self.level_2_letter_colors and list( animate_sigmoid( self.stimuli_colors[self._classified_letter], self.current_letter_color, dt)) or list( animate_sigmoid(self.letter_color, self.current_letter_color, dt))) font_size = int( round( animate(self.font_size_level2, self.font_size_current_letter, dt))) self._viewport.parameters.stimuli.append( Text(position=pos, color=color, font_size=font_size, text=text, anchor='center')) # send to screen: self._viewport.parameters.stimuli.append(None) self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) self._presentation.go() self._presentation.remove_controller(None, None, None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[: -1] else: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.go() ## turn off feedback box: self._ve_feedback_box.set(on=False) def pre__classify(self): self._ve_fixationpoint.set(on=True) def post__classify(self): self._ve_fixationpoint.set(on=False)
def init_screen_elements(self): ''' Initializing screen elements ''' ## create shapes: if self.do_animation: for i in xrange(self._nr_elements): self._ve_shapes.append( self.registered_shapes[self.shapes[i][0]]( position=self._centerPos, color=self.shape_color[i], on=False, **self.shapes[i][1])) ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi / 6. * 5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append( (self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append( (self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append( (self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append( Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append(self._centerPos) self._countdown_letter_positions.append( self._countdown_shape_positions[i]) self._ve_letters.append( Text(position=self._centerPos, text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text( position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle( radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) ##################### IF NOT DO ANIMATION ######################### else: ## add letters of level 1: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi / 6. * 5) self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) circle_layout.positions.reverse() self._letter_layout.positions.reverse() for i in xrange(self._nr_elements): self._ve_shapes.append( self.registered_shapes[self.shapes[i][0]]( position=(self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1]), color=self.shape_color[i], on=False, **self.shapes[i][1])) for i in xrange(self._nr_elements): # store countdown position: self._countdown_shape_positions.append( (self._centerPos[0] + circle_layout.positions[i][0], self._centerPos[1] + circle_layout.positions[i][1])) # put shape in container: self._ve_elements.append(self._ve_shapes[i]) for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! # store position: self._letter_positions.append( (self._letter_layout.positions[j][0] + self._centerPos[0], self._letter_layout.positions[j][1] + self._centerPos[1])) # store countdown position: self._countdown_letter_positions.append( (self._letter_layout.positions[j][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[j][1] + self._countdown_shape_positions[-1][1])) # add letter: self._ve_letters.append( Text(position=(self._letter_layout.positions[j][0] + self._centerPos[0] + circle_layout.positions[i][0], self._letter_layout.positions[j][1] + self._centerPos[1] + circle_layout.positions[i][1]), text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._letter_positions.append( (self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1])) self._countdown_letter_positions.append( (self._letter_layout.positions[i][0] + self._countdown_shape_positions[-1][0], self._letter_layout.positions[i][1] + self._countdown_shape_positions[-1][1])) self._ve_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0] + circle_layout.positions[i][0], self._letter_layout.positions[i][1] + self._centerPos[1] + circle_layout.positions[i][1]), text=" ", font_size=self.font_size_level2, color=(self.level_2_letter_colors and self.stimuli_colors[i] or self.letter_color), anchor='center', on=False)) # put letters in container: self._ve_elements.extend(self._ve_letters) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## add feedback note (whether or not there was an ErrP detected): self._ve_feedback_ErrP = Text( position=self._centerPos, color=self.feedback_ErrP_color, text="X", font_size=self.font_size_feedback_ErrP, anchor='center', on=False) ## add fixation point: self._ve_fixationpoint = FilledCircle( radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color, on=False) # put letters in container: self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_feedback_ErrP) self._ve_elements.append(self._ve_fixationpoint)
def init_screen_elements(self): ''' Initialize screen elements ''' self._letter_positions = [] ## create triangles: self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) self._letter_layout.positions.reverse() a = self.speller_radius / 2. b = a * NP.sqrt(3) / 3. self._shape_positions = [ (self._centerPos[0], self._centerPos[1] + 2 * b), (self._centerPos[0] + a, self._centerPos[1] + b), (self._centerPos[0] + a, self._centerPos[1] - b), (self._centerPos[0], self._centerPos[1] - 2 * b), (self._centerPos[0] - a, self._centerPos[1] - b), (self._centerPos[0] - a, self._centerPos[1] + b) ] orientaion = [180., 0., 180., 0., 180., 0.] for i in xrange(self._nr_elements): self._ve_edges.append( FilledTriangle(size=self.speller_radius, position=self._shape_positions[i], orientation=orientaion[i], color=self.edge_color)) self._ve_shapes.append( FilledTriangle(size=self.speller_radius - self.edge_size, position=self._shape_positions[i], orientation=orientaion[i], color=self.shape_color)) ## add the letters of level 1: for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append( (self._letter_layout.positions[j][0] + self._shape_positions[i][0], self._letter_layout.positions[j][1] + self._shape_positions[i][1])) self._ve_letters.append( Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) ## add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append( Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements - 1): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint)
class CakeSpellerVE(VisualSpellerVE): ''' Visual speller as a cake with six pieces. ''' def init(self): ''' initialize parameters ''' VisualSpellerVE.init(self) ## sizes: self.letter_radius = 70 self.speller_radius = 380 self.fixationpoint_size = 5.0 self.edge_size = 5 self.font_size_level1 = 70 # letters in level 1 self.font_size_level2 = 130 # letters in level 2 self.feedbackbox_size = 200.0 ## colors: self.shape_color = (0.0, 0.0, 0.0) self.edge_color = (1.0, 1.0, 1.0) self.stimuli_colors = [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (1.0, 1.0, 0.0), (1.0, 0.0, 1.0), (0.0, 1.0, 1.0)] self.letter_color = (1.0, 1.0, 1.0) self.letter_stimulus_color = (0.0, 0.0, 0.0) self.fixationpoint_color = (1.0, 1.0, 1.0) self.feedback_color = (0.7, 0.7, 0.7) self.countdown_color = self.bg_color def prepare_mainloop(self): ''' called in pre_mainloop of superclass. ''' assert len(self.stimuli_colors) == self._nr_elements ## init containers for VE elements: self._ve_shapes = [] self._ve_edges = [] self._ve_letters = [] def init_screen_elements(self): ''' Initialize screen elements ''' self._letter_positions = [] ## create triangles: self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi / 6. * 5) self._letter_layout.positions.reverse() a = self.speller_radius / 2. b = a * NP.sqrt(3) / 3. self._shape_positions = [ (self._centerPos[0], self._centerPos[1] + 2 * b), (self._centerPos[0] + a, self._centerPos[1] + b), (self._centerPos[0] + a, self._centerPos[1] - b), (self._centerPos[0], self._centerPos[1] - 2 * b), (self._centerPos[0] - a, self._centerPos[1] - b), (self._centerPos[0] - a, self._centerPos[1] + b) ] orientaion = [180., 0., 180., 0., 180., 0.] for i in xrange(self._nr_elements): self._ve_edges.append( FilledTriangle(size=self.speller_radius, position=self._shape_positions[i], orientation=orientaion[i], color=self.edge_color)) self._ve_shapes.append( FilledTriangle(size=self.speller_radius - self.edge_size, position=self._shape_positions[i], orientation=orientaion[i], color=self.shape_color)) ## add the letters of level 1: for j in xrange( len(self.letter_set[i]) ): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append( (self._letter_layout.positions[j][0] + self._shape_positions[i][0], self._letter_layout.positions[j][1] + self._shape_positions[i][1])) self._ve_letters.append( Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) ## add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append( Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements - 1): self._ve_feedback_letters.append( Text(position=(self._letter_layout.positions[i][0] + self._centerPos[0], self._letter_layout.positions[i][1] + self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append( Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint) def set_countdown_screen(self): ''' set screen elements to countdown state ''' self.set_standard_screen(False) def set_standard_screen(self, std=True): ''' set screen elements to standard state. ''' is_level1 = self._current_level == 1 for i in xrange(self._nr_elements): self._ve_shapes[i].set(color=(std and self.shape_color or self.stimuli_colors[i]), on=True) self._ve_letters[self._nr_letters + i].set( on=not is_level1, color=(std and self.letter_color or self.letter_stimulus_color)) for i in xrange(self._nr_letters): self._ve_letters[i].set(on=is_level1, color=(std and self.letter_color or self.letter_stimulus_color)) def stimulus(self, i_element, on=True): ''' turn on/off the stimulus elements and turn off/on the normal elements. ''' self._ve_shapes[i_element].set( color=(on and self.stimuli_colors[i_element] or self.shape_color)) if self._current_level == 1: for i in xrange(len(self.letter_set[i_element])): self._ve_letters[(self._nr_elements - 1) * i_element + i].set( color=(on and self.letter_stimulus_color or self.letter_color)) else: self._ve_letters[self._nr_letters + i_element].set( color=(on and self.letter_stimulus_color or self.letter_color)) def fixation(self, state=True): """ turn on/off the fixation elements. """ self._ve_fixationpoint.set(on=state) def feedback(self): # turn on feedback box and turn off fixationpoint: self._ve_feedback_box.set(on=True) self.fixation(False) if self._current_level == 1: '''level 1: present classified letter group in center and move letters to circles ''' ## display letter group: for i in xrange(self._nr_elements - 1): self._ve_feedback_letters[i].set( on=True, text=self.letter_set[self._classified_element][i]) ## turn on current element: self.stimulus(self._classified_element, True) ## turn off other letters: idx_start = self._classified_element * (self._nr_elements - 1) idx_end = idx_start + self._nr_elements - 1 for i in xrange(idx_start): self._ve_letters[i].set(on=False) for i in xrange(idx_end, self._nr_letters): self._ve_letters[i].set(on=False) ## present: self.stimulus(self._classified_element, False) self._presentation.set(go_duration=(self.feedback_duration, 'seconds')) self._presentation.go() ## turn off current element: for i in xrange(idx_start, idx_end): self._ve_letters[i].set(on=False) for i in xrange(self._nr_elements - 1): self._ve_feedback_letters[i].set(on=False) ## animate letters: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.extend([None] * (self._nr_elements - 1)) def update(t): dt = t / self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-( self._nr_elements - 1)] feedback_letters = [] for i in xrange(self._nr_elements - 1): pos = animate_sigmoid( NP.add(self._letter_layout.positions[i], self._centerPos), self._shape_positions[i], dt) font_size = int( round( animate(self.font_size_level1, self.font_size_level2, dt))) feedback_letters.append( Text(position=pos, color=self.letter_color, font_size=font_size, text=self.letter_set[self._classified_element][i], anchor="center")) self._viewport.parameters.stimuli.extend(feedback_letters) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None, None, None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-( self._nr_elements - 1)] ## turn on level 2 letters: for i in xrange(len(self.letter_set[self._classified_element])): self._ve_letters[self._nr_letters + i].set( on=True, text=self.letter_set[self._classified_element][i]) else: ''' level 2: present classified letter and move it to wordbox ''' ## check if backdoor classified: if self._classified_letter >= len( self.letter_set[self._classified_element]): text = ' ' else: text = self.letter_set[self._classified_element][ self._classified_letter] ## display letter: self._ve_feedback_letters[-1].set(on=True, text=text) ## turn on current element stimulusw: if not self.offline: self.stimulus(self._classified_letter, True) ## turn off other letters: for i in xrange(self._classified_letter): self._ve_letters[self._nr_letters + i].set(on=False) for i in xrange(self._classified_letter + 1, self._nr_elements): self._ve_letters[self._nr_letters + i].set(on=False) ## present: self._presentation.set(go_duration=(1, 'seconds')) self._presentation.go() ## turn off current element stimulus: if not self.offline: self.stimulus(self._classified_letter, False) self._ve_letters[self._nr_letters + self._classified_letter].set(on=False) self._ve_feedback_letters[-1].set(on=False) ## animate letter, but not if backdoor classified: if self._classified_letter < len( self.letter_set[self._classified_element]): self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.append(None) def update(t): dt = t / self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[: -1] pos = animate_sigmoid(self._centerPos, self._current_letter_position, dt) color = animate_sigmoid(self.letter_color, self.current_letter_color, dt) font_size = int( round( animate(self.font_size_level2, self.font_size_current_letter, dt))) self._viewport.parameters.stimuli.append( Text(position=pos, color=color, font_size=font_size, text=text, anchor='center')) self._presentation.add_controller( None, None, FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None, None, None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[: -1] else: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.go() ## turn on level 1 letters: for i in xrange(self._nr_letters): self._ve_letters[i].set(on=True) ## turn off feedback box and turn on fixationpoint: self._ve_feedback_box.set(on=False) self.fixation()
def __init__(self, position, radius=2.0): FilledCircle.__init__(self, color=self.grey, position=position, radius=radius)
def init_screen_elements(self): ''' Initializing screen elements ''' self._letter_positions = [] ## create and place the circles and letters: circle_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.speller_radius, start=NP.pi/6.*5) circle_layout.positions.reverse() self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.circle_radius*0.65, start=NP.pi/6.*5) self._letter_layout.positions.reverse() self._shape_positions = [(x+self.geometry[2]/2, y+self._spellerHeight/2) for (x,y) in circle_layout.positions] # add the standard elements: for i in xrange(self._nr_elements): self._ve_edges.append(FilledCircle(radius=self._edge_radius, position=self._shape_positions[i], color=self.edge_color)) self._ve_shapes.append(FilledCircle(radius=self._circle_radius, position=self._shape_positions[i], color=self.shape_color)) # add the letters of level 1: for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((self._letter_layout.positions[j][0]+self._shape_positions[i][0], self._letter_layout.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) # add the stimuli letters of level 1: letter_layout2 = CircularLayout(nr_elements=self._nr_elements, radius=self.circle_radius*0.65*self.flash_size_factor, start=NP.pi/6.*5) letter_layout2.positions.reverse() for i in xrange(self._nr_elements): for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((letter_layout2.positions[j][0]+self._shape_positions[i][0], letter_layout2.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=int(NP.floor(self.font_size_level1*self.flash_size_factor)), color=self.letter_stimulus_color, anchor='center', on=False)) # add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) # add stimuli letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=int(NP.floor(self.font_size_level2*self.flash_size_factor)), color=self.letter_stimulus_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements-1): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.extend([self._ve_feedback_box]) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint)
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 __init__(self): self.color = [random.random() for i in (1,2,3)] self.type = 'circle' FilledCircle.__init__(self, color = self.color, radius = 50, **std_params)
class CakeSpellerVE(VisualSpellerVE): ''' Visual speller as a cake with six pieces. ''' def init(self): ''' initialize parameters ''' VisualSpellerVE.init(self) ## sizes: self.letter_radius = 70 self.speller_radius = 380 self.fixationpoint_size = 5.0 self.edge_size = 5 self.font_size_level1 = 70 # letters in level 1 self.font_size_level2 = 130 # letters in level 2 self.feedbackbox_size = 200.0 ## colors: self.shape_color = (0.0, 0.0, 0.0) self.edge_color = (1.0, 1.0, 1.0) self.stimuli_colors = [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (1.0, 1.0, 0.0), (1.0, 0.0, 1.0), (0.0, 1.0, 1.0)] self.letter_color = (1.0, 1.0, 1.0) self.letter_stimulus_color = (0.0, 0.0, 0.0) self.fixationpoint_color = (1.0, 1.0, 1.0) self.feedback_color = (0.7, 0.7, 0.7) self.countdown_color =self.bg_color def prepare_mainloop(self): ''' called in pre_mainloop of superclass. ''' assert len(self.stimuli_colors)==self._nr_elements ## init containers for VE elements: self._ve_shapes = [] self._ve_edges = [] self._ve_letters = [] def init_screen_elements(self): ''' Initialize screen elements ''' self._letter_positions = [] ## create triangles: self._letter_layout = CircularLayout(nr_elements=self._nr_elements, radius=self.letter_radius, start=NP.pi/6.*5) self._letter_layout.positions.reverse() a = self.speller_radius / 2. b = a * NP.sqrt(3) / 3. self._shape_positions = [(self._centerPos[0], self._centerPos[1] + 2*b), (self._centerPos[0] + a, self._centerPos[1] + b), (self._centerPos[0] + a, self._centerPos[1] - b), (self._centerPos[0], self._centerPos[1] - 2*b), (self._centerPos[0] - a, self._centerPos[1] - b), (self._centerPos[0] - a, self._centerPos[1] + b)] orientaion = [180., 0., 180., 0., 180., 0.] for i in xrange(self._nr_elements): self._ve_edges.append(FilledTriangle(size=self.speller_radius, position=self._shape_positions[i], orientation=orientaion[i], color=self.edge_color)) self._ve_shapes.append(FilledTriangle(size=self.speller_radius - self.edge_size, position=self._shape_positions[i], orientation=orientaion[i], color=self.shape_color)) ## add the letters of level 1: for j in xrange(len(self.letter_set[i])): # warning: self.letter_set must be at least of length self._nr_elements!!! self._letter_positions.append((self._letter_layout.positions[j][0]+self._shape_positions[i][0], self._letter_layout.positions[j][1]+self._shape_positions[i][1])) self._ve_letters.append(Text(position=self._letter_positions[-1], text=self.letter_set[i][j], font_size=self.font_size_level1, color=self.letter_color, anchor='center')) ## add letters of level 2: for i in xrange(self._nr_elements): self._ve_letters.append(Text(position=self._shape_positions[i], text=" ", font_size=self.font_size_level2, color=self.letter_color, anchor='center', on=False)) ## add fixation point: self._ve_fixationpoint = FilledCircle(radius=self.fixationpoint_size, position=self._centerPos, color=self.fixationpoint_color) ## create feedback box: self._ve_feedback_box = Target2D(position=self._centerPos, size=(self.feedbackbox_size, self.feedbackbox_size), color=self.feedback_color, on=False) ## add feedback letters: self._ve_feedback_letters = [] for i in xrange(self._nr_elements-1): self._ve_feedback_letters.append(Text(position=(self._letter_layout.positions[i][0]+self._centerPos[0], self._letter_layout.positions[i][1]+self._centerPos[1]), color=self.letter_color, font_size=self.font_size_level1, text=" ", on=False, anchor="center")) self._ve_feedback_letters.append(Text(position=self._centerPos, color=self.letter_color, font_size=self.font_size_level2, text=" ", anchor='center', on=False)) ## put all in elements container: self._ve_elements.extend(self._ve_edges) self._ve_elements.extend(self._ve_shapes) self._ve_elements.extend(self._ve_letters) self._ve_elements.append(self._ve_feedback_box) self._ve_elements.extend(self._ve_feedback_letters) self._ve_elements.append(self._ve_fixationpoint) def set_countdown_screen(self): ''' set screen elements to countdown state ''' self.set_standard_screen(False) def set_standard_screen(self, std=True): ''' set screen elements to standard state. ''' is_level1 = self._current_level==1 for i in xrange(self._nr_elements): self._ve_shapes[i].set(color=(std and self.shape_color or self.stimuli_colors[i]), on=True) self._ve_letters[self._nr_letters + i].set(on=not is_level1, color=(std and self.letter_color or self.letter_stimulus_color)) for i in xrange(self._nr_letters): self._ve_letters[i].set(on=is_level1, color=(std and self.letter_color or self.letter_stimulus_color)) def stimulus(self, i_element, on=True): ''' turn on/off the stimulus elements and turn off/on the normal elements. ''' self._ve_shapes[i_element].set(color=(on and self.stimuli_colors[i_element] or self.shape_color)) if self._current_level==1: for i in xrange(len(self.letter_set[i_element])): self._ve_letters[(self._nr_elements-1)*i_element + i].set(color=(on and self.letter_stimulus_color or self.letter_color)) else: self._ve_letters[self._nr_letters + i_element].set(color=(on and self.letter_stimulus_color or self.letter_color)) def fixation(self, state=True): """ turn on/off the fixation elements. """ self._ve_fixationpoint.set(on=state) def feedback(self): # turn on feedback box and turn off fixationpoint: self._ve_feedback_box.set(on=True) self.fixation(False) if self._current_level == 1: '''level 1: present classified letter group in center and move letters to circles ''' ## display letter group: for i in xrange(self._nr_elements-1): self._ve_feedback_letters[i].set(on=True, text=self.letter_set[self._classified_element][i]) ## turn on current element: self.stimulus(self._classified_element, True) ## turn off other letters: idx_start = self._classified_element*(self._nr_elements-1) idx_end = idx_start + self._nr_elements-1 for i in xrange(idx_start): self._ve_letters[i].set(on=False) for i in xrange(idx_end, self._nr_letters): self._ve_letters[i].set(on=False) ## present: self.stimulus(self._classified_element, False) self._presentation.set(go_duration=(self.feedback_duration, 'seconds')) self._presentation.go() ## turn off current element: for i in xrange(idx_start, idx_end): self._ve_letters[i].set(on=False) for i in xrange(self._nr_elements-1): self._ve_feedback_letters[i].set(on=False) ## animate letters: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.extend([None]*(self._nr_elements-1)) def update(t): dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements-1)] feedback_letters = [] for i in xrange(self._nr_elements-1): pos = animate_sigmoid(NP.add(self._letter_layout.positions[i], self._centerPos), self._shape_positions[i], dt) font_size = int(round(animate(self.font_size_level1, self.font_size_level2, dt))) feedback_letters.append(Text(position=pos, color=self.letter_color, font_size=font_size, text=self.letter_set[self._classified_element][i], anchor="center")) self._viewport.parameters.stimuli.extend(feedback_letters) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-(self._nr_elements-1)] ## turn on level 2 letters: for i in xrange(len(self.letter_set[self._classified_element])): self._ve_letters[self._nr_letters + i].set(on=True, text=self.letter_set[self._classified_element][i]) else: ''' level 2: present classified letter and move it to wordbox ''' ## check if backdoor classified: if self._classified_letter >= len(self.letter_set[self._classified_element]): text = ' ' else: text = self.letter_set[self._classified_element][self._classified_letter] ## display letter: self._ve_feedback_letters[-1].set(on=True, text=text) ## turn on current element stimulusw: if not self.offline: self.stimulus(self._classified_letter, True) ## turn off other letters: for i in xrange(self._classified_letter): self._ve_letters[self._nr_letters + i].set(on=False) for i in xrange(self._classified_letter+1, self._nr_elements): self._ve_letters[self._nr_letters + i].set(on=False) ## present: self._presentation.set(go_duration=(1, 'seconds')) self._presentation.go() ## turn off current element stimulus: if not self.offline: self.stimulus(self._classified_letter, False) self._ve_letters[self._nr_letters + self._classified_letter].set(on=False) self._ve_feedback_letters[-1].set(on=False) ## animate letter, but not if backdoor classified: if self._classified_letter < len(self.letter_set[self._classified_element]): self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._viewport.parameters.stimuli.append(None) def update(t): dt = t/self.animation_time self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] pos = animate_sigmoid(self._centerPos, self._current_letter_position, dt) color = animate_sigmoid(self.letter_color, self.current_letter_color, dt) font_size = int(round(animate(self.font_size_level2, self.font_size_current_letter, dt))) self._viewport.parameters.stimuli.append(Text(position=pos, color=color, font_size=font_size, text=text, anchor='center')) self._presentation.add_controller(None,None,FunctionController(during_go_func=update)) # send to screen: self._presentation.go() self._presentation.remove_controller(None,None,None) self._viewport.parameters.stimuli = self._viewport.parameters.stimuli[:-1] else: self._presentation.set(go_duration=(self.animation_time, 'seconds')) self._presentation.go() ## turn on level 1 letters: for i in xrange(self._nr_letters): self._ve_letters[i].set(on=True) ## turn off feedback box and turn on fixationpoint: self._ve_feedback_box.set(on=False) self.fixation()