def pre_mainloop(self):
     self._init_pygame()
     # Create visual elements
     star_dist = 20
     textsize, textcolor = 30, (255, 255, 255)
     size2, color2 = 60, (100, 100, 200)
     font = pygame.font.Font(None, textsize)
     self.cross = font.render("+", False, textcolor)
     font = pygame.font.Font(None, size2)
     star = font.render("*", False, color2)
     self.objects_im, self.objects_rect = [], []
     # Add 5 stars and their rectangles
     for i in range(5):
         self.objects_im.append(star)
     self.objects_rect.append(star.get_rect(center=(star_dist, star_dist)))
     self.objects_rect.append(
         star.get_rect(center=(star_dist, self.screenHeight - star_dist)))
     self.objects_rect.append(
         star.get_rect(center=(self.screenWidth - star_dist, star_dist)))
     self.objects_rect.append(
         star.get_rect(center=(self.screenWidth - star_dist,
                               self.screenHeight - star_dist)))
     self.objects_rect.append(
         star.get_rect(center=(self.screenWidth / 2,
                               self.screenHeight / 2)))
     self.screen.blit(self.background, self.background_rect)
     pygame.display.flip()
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
class EyeTrackerTestCase(unittest.TestCase):
    
    def setUp(self):
        self.et = EyeTracker()
        self.et.start()
        self.etserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        time.sleep(0.1)
        try:
            self.etserver.connect(("", 1111))
        except:
            # We pretend everything is working when no eyetracker is available
            # for testing purposes
            self.etserver = None
        
    def tearDown(self):
        self.et.stop()
        if self.etserver is None:
            return
        self.etserver.close()
    
    def testParser(self):
        """EyeTracker should correctly parse valid data from the eyetracker."""
        if self.etserver is None:
            return
        self.etserver.send(r"17\48\56\437:607:322:F:F:180\r\n")
        time.sleep(0.1)
        self.assertEqual(self.et.time_h, 17)
        self.assertEqual(self.et.time_m, 48)
        self.assertEqual(self.et.time_s, 56)
        self.assertEqual(self.et.time_ms, 437)
        self.assertEqual(self.et.x, 607)
        self.assertEqual(self.et.y, 322)
        self.assertEqual(self.et.duration, 180)
 def setUp(self):
     self.et = EyeTracker()
     self.et.start()
     self.etserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     time.sleep(0.1)
     try:
         self.etserver.connect(("", 1111))
     except:
         # We pretend everything is working when no eyetracker is available
         # for testing purposes
         self.etserver = None
class EyetrackerRawdata(MainloopFeedback):
    def pre_mainloop(self):
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    def play_tick(self):
        print self.et.x, self.et.y, self.et.duration

    def post_mainloop(self):
        # Stop eyetracker
        self.et.stop()
Ejemplo n.º 5
0
class EyetrackerRawdata(MainloopFeedback):

    def pre_mainloop(self):
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()
        
    def play_tick(self):
        print self.et.x, self.et.y, self.et.duration

    def post_mainloop(self):
        # Stop eyetracker
        self.et.stop()
Ejemplo n.º 6
0
 def pre_mainloop(self):
     self._init_pygame()
     # Create visual elements 
     star_dist = 20
     textsize, textcolor = 30, (255, 255, 255)
     size2, color2 = 60, (100, 100, 200)
     font = pygame.font.Font(None, textsize)
     self.cross = font.render("+", False, textcolor);
     font = pygame.font.Font(None, size2)
     star = font.render("*", False, color2);
     self.objects_im, self.objects_rect = [], []
     # Add 5 stars and their rectangles
     for i in range(5): self.objects_im.append(star)
     self.objects_rect.append(star.get_rect(center=(star_dist, star_dist)))
     self.objects_rect.append(star.get_rect(center=(star_dist, self.screenHeight - star_dist)))
     self.objects_rect.append(star.get_rect(center=(self.screenWidth - star_dist, star_dist)))
     self.objects_rect.append(star.get_rect(center=(self.screenWidth - star_dist, self.screenHeight - star_dist)))
     self.objects_rect.append(star.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2)))
     self.screen.blit(self.background, self.background_rect)
     pygame.display.flip()
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
Ejemplo n.º 7
0
class ERPHex(VisualP300):
    
    DEFAULT_DISPLAY_RADIUS = 220        
    DEFAULT_NR_ELEMENTS = 6
    
    # Pre codes
    PRE_WORD = 0
    PRE_COUNTDOWN = 1
    PRE_WAIT = 2
    PREP_TRIAL = 3
    
    # Trigger
    START_TRIAL_LV1 = 100         # Start trial level 1
    START_TRIAL_LV2 = 101         # Start trial level 2
    INVALID = 66               # invalid trial
    HEX_CENTRAL_FIX = 82# central fixation condition
    HEX_TARGET_FIX = 83    # target fixation condition
    
    def init(self):
        VisualP300.init(self)
        self.display_radius = self.DEFAULT_DISPLAY_RADIUS
        self.nr_elements = self.DEFAULT_NR_ELEMENTS   
        # Graphical settings
        #self.speller_fade_time = 1500   # How long speller is faded in/out
        self.pygame_info = False
        self.bgcolor = 0, 0, 0
        self.screenWidth, self.screenHeight = 1280, 1024
        self.canvasWidth, self.canvasHeight = 660, 820
        self.fullscreen = True        
        # Hex speller settings
        # Hex level can be 0 (group level) or 1 (element level)
        self.hex_time = 100     # Time for each circle takes to enter the stage
        # Arrange letters so that the gap ' ' is always pointing to the center of the display 
        self.hex_letters = ("CDE AB", "GHIJ F", "KLMNO ", " PQRST", "Y UVWX", ".< Z,_")      
        # Words (chosen such that each letter in English alphabet at least one time)
        self.words = ["WINKT", "FJORD", "LUXUS", "SPHINX", "QUARZ", "VODKA", "YACHT", "GEBOT", "MEMME"]
        # Overwritten members of VisualP300
        self.word_time = 50            # How long word is shown (# fps)
        self.word_time_ms = 2500        # word time in ms
        self.min_dist = 2           # Min number of intermediate flashes bef. a flash is repeated twice 
        self.flash_duration = 3   # 5 frames @60 Hz = 83ms flash
        self.soa = 5
        self.hex_countdown = [4, 0]  # How many seconds countdown before level 1 and level 2
        self.nr_sequences = 10
        self.trial_nr = 1
        # Timing
        self.animation_time = 20        # Length of animation in #frames
        # Triggers for Level 1 (letter groups) and Level 2 (individual letters)  
        self.hex_group_triggers = [ [11, 12, 13, 14, 15, 16] , [21, 22, 23, 24, 25, 26] ]
        # If a certain group is a target, this value will be added to the trigger  
        self.trigger_target_add = 20
        
        # For data logging (-> the data file is opened in pre_mainloop)
        self.datafilename = "Feedbacks\ERP\data\datafile_hex.txt"
        self.datafile = None

        self.fps = 30
        
        # Eye tracker
        self.use_eyetracker = False
        self.et_fixate_center = True   # Whether center or fixdot has to be fixated   
        self.et_currentxy = (0, 0)      # Current fixation
        self.et_duration = 100
        self.et_targetxy = (100, 100)       # Target coordinates
        self.et_range = 100        # Maximum acceptable distance between target and actual fixation
        self.et_range_time = 200    # maximum acceptable fixation off the designated point 


    def before_mainloop(self):
        """
        Get a matrix layout, add circle elements and add groups according
        to rows and columns. 
        """
        # There are 7 hex displays, one for the group level and six for the subgroups
        self.hex_displays = [None] * 7
        self.hex_groups = [None] * 7
        # Get layout & elements
        self.layout = CircularLayout(nr_elements=self.nr_elements, radius=self.display_radius)
        colors = ((255, 255, 255), (255, 100, 255), (255, 255, 100), (255, 100, 100), (100, 100, 255), (100, 255, 255))
        textcolors = ((255, 0, 0) , (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 20, 160), (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 100, 200))
        self.hex_textcolor = (255, 0, 0)
        color , radius = (255, 255, 255), 60
        # Create the top-level display
        for i in range(self.nr_elements):
            e = Circle(nr_states=3, color=color, radius=radius, text=self.hex_letters[i], textcolor=self.hex_textcolor, colorkey=(0, 0, 0), circular_layout=True, circular_offset= - math.pi / 2)
            e.set_states(0, {"textsize":45, "radius":74})
            e.set_states(1, {"textsize":70, "radius":100})
            # Also add a blank version (for animation)
            e.set_states(2, {"textsize":25, "radius":74, "text":"" , "circular_layout":False})
              
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Get groups and add them
        for i in range(self.nr_elements):
            self.add_group(i)
        # Add fixation dot if desire
        if self.et_fixate_center:
            dotcolor, dotradius = (160, 160, 255), 5
            dot = Circle(radius=dotradius, color=dotcolor)
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 22)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update()
        
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Save group display as first display
        self.hex_displays[0] = self.elements
        self.hex_groups[0] = self.groups
        
        for j in range(6):
            # Empty elements bin and start again
            self.elements, self.groups = [], []
            # Create the element-level displays
            lettergroup = self.hex_letters[j]      # Add empty space element
            for i in range(self.nr_elements):
                e = Circle(color=color, radius=radius, text=lettergroup[i], textcolor=self.hex_textcolor, textsize=50, colorkey=(0, 0, 0))
                e.set_states(0, {"textsize":110, "radius":74, "color":(255, 255, 255) })
                e.set_states(1, {"textsize":160, "radius":100, "color":(255, 255, 255) })
                self.add_element(e)
                e.refresh()
                e.update(0)
    
            # Get groups and add them
            for i in range(self.nr_elements):
                self.add_group(i)
                       
            # Save element display and groups
            self.hex_displays[j + 1] = self.elements
            self.hex_groups[j + 1] = self.groups
        # Groups for entering and leaving
        self.enter_group = pygame.sprite.RenderUpdates()
        self.leave_group = pygame.sprite.RenderUpdates()
        # Sounds
        self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Open file for logging data
        if self.datafilename != "":
            try: 
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()
        # Init other variables
        self.group_trigger = None           # Set trigger before each trial
        self.hex_level = 0
        self.current_word = 0           # Index of current word
        self.current_letter = 0         # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0          # Set whether trial is valid or not
        if self.et_fixate_center:
            self.send_parallel(self.HEX_CENTRAL_FIX)
        else:
            self.send_parallel(self.HEX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    
    def pre_trial(self):
        # Countdown,prepare 
        if self.pre_mode == self.PRE_WORD: self.new_word()
        elif self.pre_mode == self.PREP_TRIAL: self.prep_trial()
        elif self.pre_mode == self.PRE_COUNTDOWN: self.pre_countdown()
        else: self.wait()

    def new_word(self):
        # If we just started a new word: present it
        if self.hex_level == 0 and self.current_letter == 0 and self.word_time > 0:
            self.sound_new_word.play()
            self.current_tick += 1
            word = self.words[self.current_word]
            font = pygame.font.Font(None, self.textsize)
            next_word_image = font.render("Next word: " + word, True, self.textcolor);
            next_word_rect = next_word_image.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2))
            # Paint it
            self.screen.blit(self.all_background, self.all_background_rect)
            pygame.display.flip()
            self.screen.blit(self.all_background, self.all_background_rect)
            self.screen.blit(next_word_image, next_word_rect)
            pygame.display.flip()
            pygame.time.wait(self.word_time_ms)

            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
            
        else:
            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
    
    def pre_countdown(self):
        if self.hex_countdown[self.hex_level] > 0:
            if self.current_countdown == 0:
                self.screen.blit(self.all_background, self.all_background_rect)
                self.all_elements_group.draw(self.screen)
                if len(self.deco) > 0: self.deco_group.draw(self.screen)
                pygame.display.flip()
            self.current_countdown += 1
            if self.current_countdown == self.hex_countdown[self.hex_level]:
                self.pre_mode = self.PRE_WAIT
            self.sound_countdown.play()
            pygame.time.wait(1000)
        else: self.pre_mode = self.PRE_WAIT
                
    def wait(self):
        self.screen.blit(self.all_background, self.all_background_rect)
        self.all_elements_group.draw(self.screen)
        if len(self.deco) > 0: self.deco_group.draw(self.screen)
        pygame.display.flip()
        if self.hex_level == 0:
            self.send_parallel(self.START_TRIAL_LV1)
        else: self.send_parallel(self.START_TRIAL_LV2)
        pygame.time.wait(1000)
        self.state_finished = True

    def prep_trial(self):
        # reset variables
        self.invalid_trial = 0
        self.current_countdown = 0
        # get current word
        word = self.words[self.current_word]
        if self.hex_level == 0:
            self.elements = self.hex_displays[0] 
            self.groups = self.hex_groups[0] 
        else:
            # Second level: search the right hex and prepare it
            # Check to which level-2 group current letter belongs 
            hex_nr = None
            letter = word[self.current_letter]
            for i in range(len(self.hex_letters)):
                if letter in self.hex_letters[i]:
                    hex_nr = i
                    break
            self.elements = self.hex_displays[hex_nr + 1]  
            self.groups = self.hex_groups[hex_nr + 1]   

        self.textrow.text = word # Set new word
        self.textrow.highlight = [self.current_letter]  # highlight current letter
        self.textrow.refresh()
        # Delete old flash sequence & make new random sequence 
        self.flash_sequence = []
        # Prequel flashes, allowing repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=6, repetition=True)
        # Experimental sequences
        for i in range(self.nr_sequences):
            random_flash_sequence(self, min_dist=self.min_dist)
        # Sequel, again with repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=6, repetition=True)
        # Determine target group  set triggers & get a copy of that list
        self.group_trigger = self.hex_group_triggers[self.hex_level][:]
        # Determine target hex
        letter = self.words[self.current_word][self.current_letter]
        hex_nr, target = 0, 0
        while letter not in self.hex_letters[hex_nr]: hex_nr += 1
        if self.hex_level == 0:  
            target = hex_nr 
        else:   # hex level 1
            target = self.hex_letters[hex_nr].index(letter)
        # Modify trigget of target by adding a value
        self.group_trigger[target] += self.trigger_target_add
        # Step to next pre_mode
        self.pre_mode = self.PRE_COUNTDOWN
        # For logfile , if new trial is started
        if self.hex_level == 0: self.datalines = []
        # Flash count
        self.flashcount = 0
        # Determine whether fixate center or fixate target
        if self.et_fixate_center:
            # Fixate center
            self.et_targetxy = (self.screenWidth / 2, self.screenHeight / 2)
        else:
            # Fixate target
            self.et_targetxy = self.elements[target].pos    
        

    def pre_stimulus(self):
        # Control eye tracker
        if self.use_eyetracker:
            if self.et.x is None:
                self.logger.error("[ERP Hex] No eyetracker data received!")
                self.on_stop()
                self.state_finished = True
                return
            self.et_currentxy = (self.et.x, self.et.y)
            self.et_duration = self.et.duration
            tx, ty = self.et_targetxy[0], self.et_targetxy[1]
            cx, cy = self.et_currentxy[0], self.et_currentxy[1]
            dist = math.sqrt(math.pow(tx - cx, 2) + math.pow(ty - cy, 2))
            #self.et_outside = 0
            # Check if current fixation is outside the accepted range 
            if dist > self.et_range:
                #self.et_outside = 1
                # Check if the off-fixation is beyond temporal limits
                if self.et_duration > self.et_range_time:
                    self.invalid_trial = 1
                    # Break off current trial !!
                    self.state_finished = True
                    self.sound_invalid.play()
                    show_message(self, "Bad fixation, we have to restart...")
                    wait_for_key()
                    self.screen.blit(self.background, self.background_rect)
                    # Send break-off trigger
                    #print " self INVALID"
                    self.send_parallel(self.INVALID)
        if self.invalid_trial == 0 and (self.stim_state == self.STIM_START_FLASH) and self.group_trigger is not None:
            #print "GROUP TRIGGER"
            self.send_parallel(self.group_trigger[self.flash_sequence[self.current_flash]])
            self.log_data()

                
    def post_trial(self):
        if self.invalid_trial == 1:
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            self.state_finished = True
            self.hex_level = 0          # Reset hex level
            self.flush_data()
            self.trial_nr += 1
        elif self.hex_level == 0:
#            print "Level-0>> "+str(self.hex_level)
            if self.current_tick == 0:    # startup animation
                # Prepare animation, find current hex
                self.hex_nr, self.letter = 0, self.words[self.current_word][self.current_letter]
                while self.letter not in self.hex_letters[self.hex_nr]: self.hex_nr += 1
                element = self.elements[self.hex_nr]       # Reference for convenience
                self.text = self.hex_letters[self.hex_nr]
                self.all_elements_group.update(2) # Set elements into 'empty' state
                # Copy start and end positions of the elements
                self.start_pos = None
                self.end_pos = None
                (elx, ely) = element.rect.left, element.rect.top
                self.start_pos = element.letter_pos[:]
                self.end_pos = self.layout.positions[:]
                # Start size and end size of letters

                self.start_size = 40
                self.end_size = 110
                self.color = element.textcolor
                " Start positions of letters are rel. to the surface they are on, not rel to screen "
                for i in range(len(self.start_pos)):
                    x, y = self.start_pos[i]
                    self.start_pos[i] = (x + elx, y + ely)
                    # End positions of letters are centered on zero
                    x, y = self.end_pos[i]
                    self.end_pos[i] = (x + self.screenWidth / 2, y + self.screenHeight / 2)
                self.current_tick += 1
            elif self.current_tick <= self.animation_time:
                # Animate
                self.animate_letters(self.current_tick)
                self.clock.tick(self.fps)    # Assure that it's not going too fast
                self.current_tick += 1
            else:        
                # Finished, change hex level
                self.hex_level = (self.hex_level + 1) % 2
                self.state_finished = True
                self.pre_mode = self.PRE_WORD
                self.current_tick = 0
        elif self.hex_level == 1:
            # Get count from participant
            self.prompt_count()
            self.flush_data()
            self.trial_nr += 1

            # Check if we reached the final letter
            if self.current_letter == len(self.words[self.current_word]) - 1:
                self.current_letter = 0
                self.current_word += 1
                # Check if we reached the final word
                if self.current_word > len(self.words) - 1:
                    # We reached the end
                    self.on_stop()
            else:
                # Next letter
                self.current_letter += 1
            # Change hex level
            self.hex_level = (self.hex_level + 1) % 2
            # Post trial is finished
            self.state_finished = True
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            # Give 1 second time
            pygame.time.delay(1000)

    def after_mainloop(self):
        self.color = None
        self.text = None
        self.start_pos = None
        self.end_pos = None
        self.et_targetxy = None
        self.datalines = None
        self.enter_group = None
        self.leave_group = None
        self.sound_new_word = None
        self.sound_countdown = None
        self.sound_invalid = None
        self.hex_displays = None
        self.hex_groups = None
        self.textrow = None
        self.countrow = None
        # Stop eyetracker
        self.et.stop()

    def prompt_count(self):
        pygame.event.clear()
        text, ready = "", False
        while not ready:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    k = event.key
                    if k == pygame.K_BACKSPACE:
                        if len(text) > 0: text = text[0: - 1]   # Delete last number
                    elif len(text) < 2:
                        if k == pygame.K_0: text = text + "0"
                        elif k == pygame.K_1: text = text + "1"
                        elif k == pygame.K_2: text = text + "2"
                        elif k == pygame.K_3: text = text + "3"
                        elif k == pygame.K_4: text = text + "4"
                        elif k == pygame.K_5: text = text + "5"
                        elif k == pygame.K_6: text = text + "6"
                        elif k == pygame.K_7: text = text + "7"
                        elif k == pygame.K_8: text = text + "8"
                        elif k == pygame.K_9: text = text + "9"
                    elif k == pygame.K_RETURN: ready = True
            self.countrow.text = text
            self.countrow.refresh()
            self.countrow.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.screen.blit(self.countrow.image, self.countrow.rect)
            pygame.display.flip()
            pygame.time.wait(100)
        self.flashcount = int(text)

        
    def hex_enter(self):
        # Erase and draw on both fore and back screen using flip
        self.screen.blit(self.background, self.background_rect)
        self.deco_group.draw(self.screen)
        pygame.display.flip()
        self.screen.blit(self.background, self.background_rect)
        self.deco_group.draw(self.screen)
        # Makes the hex elements enter the screen one for one
        self.enter_group.empty()
        for i in range(self.nr_elements):
            self.enter_group.add(self.elements[i])
            self.enter_group.update(0)
            self.enter_group.draw(self.screen)
            pygame.display.flip()
            pygame.time.wait(self.hex_time)
        # Draw latest version also onto back screen
        self.deco_group.draw(self.screen)
        self.enter_group.draw(self.screen)
        
    def hex_leave(self, reverse=False):
        """
        Makes the hex elements leave the screen one for one
        If reverse=True then they leave in reverse order 
        """
        self.leave_group.empty()
        self.leave_group.add(self.elements)
        for i in range(self.nr_elements):
            if reverse: ind = self.nr_elements - i - 1
            else: ind = i
            self.leave_group.remove(self.elements[ind])
            self.leave_group.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.leave_group.draw(self.screen)
            self.deco_group.draw(self.screen)
            pygame.display.flip()
            pygame.time.wait(self.hex_time)

    def animate_letters(self, t, reverse=False):
        " Moves the letters from the chosen hex to their destinations "
        " Set elements to 'empty' states and get letter positions "
        " 'reverse' performs the reverse motion pattern "
        " Get current position "
        p = t / float(self.animation_time)     # Position parameters
        font = pygame.font.Font(None, int(round(self.end_size * p + self.start_size * (1 - p))))
        # paint everything 
        self.screen.blit(self.background, self.background_rect)
        self.all_elements_group.draw(self.screen)
        if len(self.deco) > 0: self.deco_group.draw(self.screen)
        for i in range(len(self.text)):
            xs, ys = self.start_pos[i]
            xe, ye = self.end_pos[i]
            x, y = xe * p + xs * (1 - p), ye * p + ys * (1 - p)
            surf = font.render(self.text[i], False, self.color)
            rect = surf.get_rect(center=(x, y))
            self.screen.blit(surf, rect)
        pygame.display.flip()

    def log_data(self):
        """
        Structure of logfile
        Word Letter TrialNr Speller Fix_condition Trigger Time targetx targety currentx currenty Duration Invalid(-> added at flush)
        """
        #print self.et.x, self.et.y, self.et.duration
        if self.state == self.STIM_START_FLASH:
            word = self.words[self.current_word]
            items = []
            items.append(word)
            items.append(word[self.current_letter])
            items.append(str(self.trial_nr))
            items.append("hex")
            if self.et_fixate_center:
                items.append("center")
            else:
                items.append("target")
            items.append(str(self.flash_sequence[self.current_flash]))
            items.append(str(pygame.time.get_ticks()))
            if self.use_eyetracker:
                items.append(str(self.et_targetxy[0]))
                items.append(str(self.et_targetxy[1]))
                items.append(str(self.et_currentxy[0]))
                items.append(str(self.et_currentxy[1]))
                items.append(str(self.et_duration))
                #items.append(str(self.et_outside))
            line = "\t".join(items)
            self.datalines.append(line) 
    
    def flush_data(self):
        # Writes the data into the data logfile
        for line in self.datalines:
            line2 = line + "\t" + str(self.flashcount) + "\t" + str(self.invalid_trial) + "\n"
            if self.datafile is not None:
                try: self.datafile.write(line2)
                except IOError:
                    self.logger.warn("Could not write to datafile")
Ejemplo n.º 8
0
 def before_mainloop(self):
     """
     Get a matrix layout, add circle elements and add groups according
     to rows and columns. 
     """
     # There are 7 hex displays, one for the group level and six for the subgroups
     self.hex_displays = [None] * 7
     self.hex_groups = [None] * 7
     # Get layout & elements
     self.layout = CircularLayout(nr_elements=self.nr_elements, radius=self.display_radius)
     colors = ((255, 255, 255), (255, 100, 255), (255, 255, 100), (255, 100, 100), (100, 100, 255), (100, 255, 255))
     textcolors = ((255, 0, 0) , (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 20, 160), (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 100, 200))
     self.hex_textcolor = (255, 0, 0)
     color , radius = (255, 255, 255), 60
     # Create the top-level display
     for i in range(self.nr_elements):
         e = Circle(nr_states=3, color=color, radius=radius, text=self.hex_letters[i], textcolor=self.hex_textcolor, colorkey=(0, 0, 0), circular_layout=True, circular_offset= - math.pi / 2)
         e.set_states(0, {"textsize":45, "radius":74})
         e.set_states(1, {"textsize":70, "radius":100})
         # Also add a blank version (for animation)
         e.set_states(2, {"textsize":25, "radius":74, "text":"" , "circular_layout":False})
           
         self.add_element(e)
         e.refresh()
         e.update(0)
     # Get groups and add them
     for i in range(self.nr_elements):
         self.add_group(i)
     # Add fixation dot if desire
     if self.et_fixate_center:
         dotcolor, dotradius = (160, 160, 255), 5
         dot = Circle(radius=dotradius, color=dotcolor)
         dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
         dot.refresh()
         dot.update()
         self.deco.append(dot)
     # Add text row
     self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
     self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 22)
     self.textrow.refresh()
     self.textrow.update()
     self.deco.append(self.textrow)
     # Add count row (where count is entered by participant)
     self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
     self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
     self.countrow.refresh()
     self.countrow.update()
     
     # Add deco to deco group
     if len(self.deco) > 0:
         self.deco_group = pygame.sprite.RenderUpdates(self.deco)
     # Save group display as first display
     self.hex_displays[0] = self.elements
     self.hex_groups[0] = self.groups
     
     for j in range(6):
         # Empty elements bin and start again
         self.elements, self.groups = [], []
         # Create the element-level displays
         lettergroup = self.hex_letters[j]      # Add empty space element
         for i in range(self.nr_elements):
             e = Circle(color=color, radius=radius, text=lettergroup[i], textcolor=self.hex_textcolor, textsize=50, colorkey=(0, 0, 0))
             e.set_states(0, {"textsize":110, "radius":74, "color":(255, 255, 255) })
             e.set_states(1, {"textsize":160, "radius":100, "color":(255, 255, 255) })
             self.add_element(e)
             e.refresh()
             e.update(0)
 
         # Get groups and add them
         for i in range(self.nr_elements):
             self.add_group(i)
                    
         # Save element display and groups
         self.hex_displays[j + 1] = self.elements
         self.hex_groups[j + 1] = self.groups
     # Groups for entering and leaving
     self.enter_group = pygame.sprite.RenderUpdates()
     self.leave_group = pygame.sprite.RenderUpdates()
     # Sounds
     self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
     self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
     self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
     # Open file for logging data
     if self.datafilename != "":
         try: 
             self.datafile = open(self.datafilename, 'a')
         except IOError:
             print "Cannot open datafile"
             self.datafile = None
             self.on_quit()
     # Init other variables
     self.group_trigger = None           # Set trigger before each trial
     self.hex_level = 0
     self.current_word = 0           # Index of current word
     self.current_letter = 0         # Index of current letter
     self.pre_mode = self.PRE_WORD
     self.current_tick = 0
     self.invalid_trial = 0          # Set whether trial is valid or not
     if self.et_fixate_center:
         self.send_parallel(self.HEX_CENTRAL_FIX)
     else:
         self.send_parallel(self.HEX_TARGET_FIX)
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
class EyetrackerFeedback(MainloopFeedback):

    DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT = 1280, 1024
    DEFAULT_FULLSCREEN = True
    DEFAULT_BGCOLOR = 0, 0, 0  # Default background color

    " Settings for textmessages via the function show_message() "
    DEFAULT_TEXTSIZE = 40
    DEFAULT_TEXTCOLOR = 255, 255, 255

    " Settings for pygame "
    DEFAULT_VIDEO_DRIVER = 'directx'

    " *** Overwritten MainloopFeedback methods *** "

    def init(self):
        self.window_title = "Intelligaze Feedback"
        self.screenWidth, self.screenHeight = self.DEFAULT_SCREEN_WIDTH, self.DEFAULT_SCREEN_HEIGHT
        """ Canvas: The part of the screen which is used for painting!
        That's more efficient than repainting the whole of the screen 
        """
        self.canvasWidth, self.canvasHeight = self.screenWidth, self.screenHeight
        self.fullscreen = self.DEFAULT_FULLSCREEN
        self.bgcolor = self.DEFAULT_BGCOLOR
        self.textsize = self.DEFAULT_TEXTSIZE
        self.textcolor = self.DEFAULT_TEXTCOLOR
        # Random number generator
        self.random = random.Random()  # Get random generator

        self.video_driver = self.DEFAULT_VIDEO_DRIVER
        # Timing
        self.fps = 30

    def pre_mainloop(self):
        self._init_pygame()
        # Create visual elements
        star_dist = 20
        textsize, textcolor = 30, (255, 255, 255)
        size2, color2 = 60, (100, 100, 200)
        font = pygame.font.Font(None, textsize)
        self.cross = font.render("+", False, textcolor)
        font = pygame.font.Font(None, size2)
        star = font.render("*", False, color2)
        self.objects_im, self.objects_rect = [], []
        # Add 5 stars and their rectangles
        for i in range(5):
            self.objects_im.append(star)
        self.objects_rect.append(star.get_rect(center=(star_dist, star_dist)))
        self.objects_rect.append(
            star.get_rect(center=(star_dist, self.screenHeight - star_dist)))
        self.objects_rect.append(
            star.get_rect(center=(self.screenWidth - star_dist, star_dist)))
        self.objects_rect.append(
            star.get_rect(center=(self.screenWidth - star_dist,
                                  self.screenHeight - star_dist)))
        self.objects_rect.append(
            star.get_rect(center=(self.screenWidth / 2,
                                  self.screenHeight / 2)))
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    def _init_pygame(self):
        # Initialize pygame, open screen and fill screen with background color
        os.environ['SDL_VIDEODRIVER'] = self.video_driver  # Set video driver
        pygame.init()
        self.clock = pygame.time.Clock()
        if self.fullscreen:
            opts = pygame.FULLSCREEN | pygame.DOUBLEBUF  #|pygame.HWSURFACE
            self.screen = pygame.display.set_mode(
                (self.screenWidth, self.screenHeight), opts)
        else:
            self.screen = pygame.display.set_mode(
                (self.screenWidth, self.screenHeight))
        self.background = pygame.Surface((self.canvasWidth, self.canvasHeight))
        self.background.fill(self.bgcolor)
        self.background_rect = self.background.get_rect(
            center=(self.screenWidth / 2, self.screenHeight / 2))
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        self.font = pygame.font.Font(None, self.textsize)

    def post_mainloop(self):
        # Un-initialize all references to pygame objects before shutting down pygame
        self.background = None
        self.background_rect = None
        self.cross = None
        self.objects_im = None
        self.objects_rect = None
        self.screen = None
        self.clock = None
        self.font = None
        pygame.mixer.quit()
        pygame.quit()
        # Stop eyetracker
        self.et.stop()

    def tick(self):
        # Check event cue
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.on_stop()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.on_stop()

    def pause_tick(self):
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        pygame.time.delay(100)

    def play_tick(self):
        self.screen.blit(self.background, self.background_rect)
        # Cross
        x, y, dur = self.et.x, self.et.y, self.et.duration
        if x is not None:
            rect = self.cross.get_rect(center=(x, y))
            self.screen.blit(self.cross, rect)
        # Stars
        for i in range(5):
            self.screen.blit(self.objects_im[i], self.objects_rect[i])
        # Display info

        txt = self.font.render(
            str(x) + " / " + str(y) + " / " + str(dur), True, self.textcolor)
        txt_rect = txt.get_rect(center=(100, 50))
        self.screen.blit(txt, txt_rect)
        pygame.display.flip()
        self.clock.tick(self.fps)
Ejemplo n.º 10
0
class ERPHex(VisualP300):
    
    DEFAULT_DISPLAY_RADIUS = 220        
    DEFAULT_NR_ELEMENTS = 6
    
    # Pre codes
    PRE_WORD = 0
    PRE_COUNTDOWN = 1
    PRE_WAIT = 2
    PREP_TRIAL = 3
    
    # Trigger
    START_TRIAL_LV1 = 100         # Start trial level 1
    START_TRIAL_LV2 = 101         # Start trial level 2
    INVALID = 66               # invalid trial
    HEX_CENTRAL_FIX = 82# central fixation condition
    HEX_TARGET_FIX = 83    # target fixation condition
    
    def init(self):
        VisualP300.init(self)
        self.display_radius = self.DEFAULT_DISPLAY_RADIUS
        self.nr_elements = self.DEFAULT_NR_ELEMENTS   
        # Graphical settings
        #self.speller_fade_time = 1500   # How long speller is faded in/out
        self.pygame_info = False
        self.bgcolor = 0, 0, 0
        self.screenWidth, self.screenHeight = 1280, 1024
        self.canvasWidth, self.canvasHeight = 660, 820
        self.fullscreen = True        
        # Hex speller settings
        # Hex level can be 0 (group level) or 1 (element level)
        self.hex_time = 100     # Time for each circle takes to enter the stage
        # Arrange letters so that the gap ' ' is always pointing to the center of the display 
        self.hex_letters = ("CDE AB", "GHIJ F", "KLMNO ", " PQRST", "Y UVWX", ".< Z,_")      
        # Words (chosen such that each letter in English alphabet at least one time)
        self.words = ["WINKT", "FJORD", "LUXUS", "SPHINX", "QUARZ", "VODKA", "YACHT", "GEBOT", "MEMME"]
        # Overwritten members of VisualP300
        self.word_time = 50            # How long word is shown (# fps)
        self.word_time_ms = 2500        # word time in ms
        self.min_dist = 2           # Min number of intermediate flashes bef. a flash is repeated twice 
        self.flash_duration = 3   # 5 frames @60 Hz = 83ms flash
        self.soa = 5
        self.hex_countdown = [4, 0]  # How many seconds countdown before level 1 and level 2
        self.nr_sequences = 10
        self.trial_nr = 1
        # Timing
        self.animation_time = 20        # Length of animation in #frames
        # Triggers for Level 1 (letter groups) and Level 2 (individual letters)  
        self.hex_group_triggers = [ [11, 12, 13, 14, 15, 16] , [21, 22, 23, 24, 25, 26] ]
        # If a certain group is a target, this value will be added to the trigger  
        self.trigger_target_add = 20
        
        # For data logging (-> the data file is opened in pre_mainloop)
        self.datafilename = "Feedbacks\ERP\data\datafile_hex.txt"
        self.datafile = None

        self.fps = 30
        
        # Eye tracker
        self.use_eyetracker = False
        self.et_fixate_center = True   # Whether center or fixdot has to be fixated   
        self.et_currentxy = (0, 0)      # Current fixation
        self.et_duration = 100
        self.et_targetxy = (100, 100)       # Target coordinates
        self.et_range = 100        # Maximum acceptable distance between target and actual fixation
        self.et_range_time = 200    # maximum acceptable fixation off the designated point 


    def before_mainloop(self):
        """
        Get a matrix layout, add circle elements and add groups according
        to rows and columns. 
        """
        # There are 7 hex displays, one for the group level and six for the subgroups
        self.hex_displays = [None] * 7
        self.hex_groups = [None] * 7
        # Get layout & elements
        self.layout = CircularLayout(nr_elements=self.nr_elements, radius=self.display_radius)
        colors = ((255, 255, 255), (255, 100, 255), (255, 255, 100), (255, 100, 100), (100, 100, 255), (100, 255, 255))
        textcolors = ((255, 0, 0) , (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 20, 160), (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 100, 200))
        self.hex_textcolor = (255, 0, 0)
        color , radius = (255, 255, 255), 60
        # Create the top-level display
        for i in range(self.nr_elements):
            e = Circle(nr_states=3, color=color, radius=radius, text=self.hex_letters[i], textcolor=self.hex_textcolor, colorkey=(0, 0, 0), circular_layout=True, circular_offset= - math.pi / 2)
            e.set_states(0, {"textsize":45, "radius":74})
            e.set_states(1, {"textsize":70, "radius":100})
            # Also add a blank version (for animation)
            e.set_states(2, {"textsize":25, "radius":74, "text":"" , "circular_layout":False})
              
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Get groups and add them
        for i in range(self.nr_elements):
            self.add_group(i)
        # Add fixation dot if desire
        if self.et_fixate_center:
            dotcolor, dotradius = (160, 160, 255), 5
            dot = Circle(radius=dotradius, color=dotcolor)
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 22)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update()
        
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Save group display as first display
        self.hex_displays[0] = self.elements
        self.hex_groups[0] = self.groups
        
        for j in range(6):
            # Empty elements bin and start again
            self.elements, self.groups = [], []
            # Create the element-level displays
            lettergroup = self.hex_letters[j]      # Add empty space element
            for i in range(self.nr_elements):
                e = Circle(color=color, radius=radius, text=lettergroup[i], textcolor=self.hex_textcolor, textsize=50, colorkey=(0, 0, 0))
                e.set_states(0, {"textsize":110, "radius":74, "color":(255, 255, 255) })
                e.set_states(1, {"textsize":160, "radius":100, "color":(255, 255, 255) })
                self.add_element(e)
                e.refresh()
                e.update(0)
    
            # Get groups and add them
            for i in range(self.nr_elements):
                self.add_group(i)
                       
            # Save element display and groups
            self.hex_displays[j + 1] = self.elements
            self.hex_groups[j + 1] = self.groups
        # Groups for entering and leaving
        self.enter_group = pygame.sprite.RenderUpdates()
        self.leave_group = pygame.sprite.RenderUpdates()
        # Sounds
        self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Open file for logging data
        if self.datafilename != "":
            try: 
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()
        # Init other variables
        self.group_trigger = None           # Set trigger before each trial
        self.hex_level = 0
        self.current_word = 0           # Index of current word
        self.current_letter = 0         # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0          # Set whether trial is valid or not
        if self.et_fixate_center:
            self.send_parallel(self.HEX_CENTRAL_FIX)
        else:
            self.send_parallel(self.HEX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    
    def pre_trial(self):
        # Countdown,prepare 
        if self.pre_mode == self.PRE_WORD: self.new_word()
        elif self.pre_mode == self.PREP_TRIAL: self.prep_trial()
        elif self.pre_mode == self.PRE_COUNTDOWN: self.pre_countdown()
        else: self.wait()

    def new_word(self):
        # If we just started a new word: present it
        if self.hex_level == 0 and self.current_letter == 0 and self.word_time > 0:
            self.sound_new_word.play()
            self.current_tick += 1
            word = self.words[self.current_word]
            font = pygame.font.Font(None, self.textsize)
            next_word_image = font.render("Next word: " + word, True, self.textcolor);
            next_word_rect = next_word_image.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2))
            # Paint it
            self.screen.blit(self.all_background, self.all_background_rect)
            pygame.display.flip()
            self.screen.blit(self.all_background, self.all_background_rect)
            self.screen.blit(next_word_image, next_word_rect)
            pygame.display.flip()
            pygame.time.wait(self.word_time_ms)

            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
            
        else:
            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
    
    def pre_countdown(self):
        if self.hex_countdown[self.hex_level] > 0:
            if self.current_countdown == 0:
                self.screen.blit(self.all_background, self.all_background_rect)
                self.all_elements_group.draw(self.screen)
                if len(self.deco) > 0: self.deco_group.draw(self.screen)
                pygame.display.flip()
            self.current_countdown += 1
            if self.current_countdown == self.hex_countdown[self.hex_level]:
                self.pre_mode = self.PRE_WAIT
            self.sound_countdown.play()
            pygame.time.wait(1000)
        else: self.pre_mode = self.PRE_WAIT
                
    def wait(self):
        self.screen.blit(self.all_background, self.all_background_rect)
        self.all_elements_group.draw(self.screen)
        if len(self.deco) > 0: self.deco_group.draw(self.screen)
        pygame.display.flip()
        if self.hex_level == 0:
            self.send_parallel(self.START_TRIAL_LV1)
        else: self.send_parallel(self.START_TRIAL_LV2)
        pygame.time.wait(1000)
        self.state_finished = True

    def prep_trial(self):
        # reset variables
        self.invalid_trial = 0
        self.current_countdown = 0
        # get current word
        word = self.words[self.current_word]
        if self.hex_level == 0:
            self.elements = self.hex_displays[0] 
            self.groups = self.hex_groups[0] 
        else:
            # Second level: search the right hex and prepare it
            # Check to which level-2 group current letter belongs 
            hex_nr = None
            letter = word[self.current_letter]
            for i in range(len(self.hex_letters)):
                if letter in self.hex_letters[i]:
                    hex_nr = i
                    break
            self.elements = self.hex_displays[hex_nr + 1]  
            self.groups = self.hex_groups[hex_nr + 1]   

        self.textrow.text = word # Set new word
        self.textrow.highlight = [self.current_letter]  # highlight current letter
        self.textrow.refresh()
        # Delete old flash sequence & make new random sequence 
        self.flash_sequence = []
        # Prequel flashes, allowing repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=6, repetition=True)
        # Experimental sequences
        for i in range(self.nr_sequences):
            random_flash_sequence(self, min_dist=self.min_dist)
        # Sequel, again with repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=6, repetition=True)
        # Determine target group  set triggers & get a copy of that list
        self.group_trigger = self.hex_group_triggers[self.hex_level][:]
        # Determine target hex
        letter = self.words[self.current_word][self.current_letter]
        hex_nr, target = 0, 0
        while letter not in self.hex_letters[hex_nr]: hex_nr += 1
        if self.hex_level == 0:  
            target = hex_nr 
        else:   # hex level 1
            target = self.hex_letters[hex_nr].index(letter)
        # Modify trigget of target by adding a value
        self.group_trigger[target] += self.trigger_target_add
        # Step to next pre_mode
        self.pre_mode = self.PRE_COUNTDOWN
        # For logfile , if new trial is started
        if self.hex_level == 0: self.datalines = []
        # Flash count
        self.flashcount = 0
        # Determine whether fixate center or fixate target
        if self.et_fixate_center:
            # Fixate center
            self.et_targetxy = (self.screenWidth / 2, self.screenHeight / 2)
        else:
            # Fixate target
            self.et_targetxy = self.elements[target].pos    
        

    def pre_stimulus(self):
        # Control eye tracker
        if self.use_eyetracker:
            if self.et.x is None:
                self.logger.error("[ERP Hex] No eyetracker data received!")
                self.on_stop()
                self.state_finished = True
                return
            self.et_currentxy = (self.et.x, self.et.y)
            self.et_duration = self.et.duration
            tx, ty = self.et_targetxy[0], self.et_targetxy[1]
            cx, cy = self.et_currentxy[0], self.et_currentxy[1]
            dist = math.sqrt(math.pow(tx - cx, 2) + math.pow(ty - cy, 2))
            #self.et_outside = 0
            # Check if current fixation is outside the accepted range 
            if dist > self.et_range:
                #self.et_outside = 1
                # Check if the off-fixation is beyond temporal limits
                if self.et_duration > self.et_range_time:
                    self.invalid_trial = 1
                    # Break off current trial !!
                    self.state_finished = True
                    self.sound_invalid.play()
                    show_message(self, "Bad fixation, we have to restart...")
                    wait_for_key()
                    self.screen.blit(self.background, self.background_rect)
                    # Send break-off trigger
                    #print " self INVALID"
                    self.send_parallel(self.INVALID)
        if self.invalid_trial == 0 and (self.stim_state == self.STIM_START_FLASH) and self.group_trigger is not None:
            #print "GROUP TRIGGER"
            self.send_parallel(self.group_trigger[self.flash_sequence[self.current_flash]])
            self.log_data()

                
    def post_trial(self):
        if self.invalid_trial == 1:
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            self.state_finished = True
            self.hex_level = 0          # Reset hex level
            self.flush_data()
            self.trial_nr += 1
        elif self.hex_level == 0:
#            print "Level-0>> "+str(self.hex_level)
            if self.current_tick == 0:    # startup animation
                # Prepare animation, find current hex
                self.hex_nr, self.letter = 0, self.words[self.current_word][self.current_letter]
                while self.letter not in self.hex_letters[self.hex_nr]: self.hex_nr += 1
                element = self.elements[self.hex_nr]       # Reference for convenience
                self.text = self.hex_letters[self.hex_nr]
                self.all_elements_group.update(2) # Set elements into 'empty' state
                # Copy start and end positions of the elements
                self.start_pos = None
                self.end_pos = None
                (elx, ely) = element.rect.left, element.rect.top
                self.start_pos = element.letter_pos[:]
                self.end_pos = self.layout.positions[:]
                # Start size and end size of letters

                self.start_size = 40
                self.end_size = 110
                self.color = element.textcolor
                " Start positions of letters are rel. to the surface they are on, not rel to screen "
                for i in range(len(self.start_pos)):
                    x, y = self.start_pos[i]
                    self.start_pos[i] = (x + elx, y + ely)
                    # End positions of letters are centered on zero
                    x, y = self.end_pos[i]
                    self.end_pos[i] = (x + self.screenWidth / 2, y + self.screenHeight / 2)
                self.current_tick += 1
            elif self.current_tick <= self.animation_time:
                # Animate
                self.animate_letters(self.current_tick)
                self.clock.tick(self.fps)    # Assure that it's not going too fast
                self.current_tick += 1
            else:        
                # Finished, change hex level
                self.hex_level = (self.hex_level + 1) % 2
                self.state_finished = True
                self.pre_mode = self.PRE_WORD
                self.current_tick = 0
        elif self.hex_level == 1:
            # Get count from participant
            self.prompt_count()
            self.flush_data()
            self.trial_nr += 1

            # Check if we reached the final letter
            if self.current_letter == len(self.words[self.current_word]) - 1:
                self.current_letter = 0
                self.current_word += 1
                # Check if we reached the final word
                if self.current_word > len(self.words) - 1:
                    # We reached the end
                    self.on_stop()
            else:
                # Next letter
                self.current_letter += 1
            # Change hex level
            self.hex_level = (self.hex_level + 1) % 2
            # Post trial is finished
            self.state_finished = True
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            # Give 1 second time
            pygame.time.delay(1000)

    def after_mainloop(self):
        self.color = None
        self.text = None
        self.start_pos = None
        self.end_pos = None
        self.et_targetxy = None
        self.datalines = None
        self.enter_group = None
        self.leave_group = None
        self.sound_new_word = None
        self.sound_countdown = None
        self.sound_invalid = None
        self.hex_displays = None
        self.hex_groups = None
        self.textrow = None
        self.countrow = None
        # Stop eyetracker
        self.et.stop()

    def prompt_count(self):
        pygame.event.clear()
        text, ready = "", False
        while not ready:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    k = event.key
                    if k == pygame.K_BACKSPACE:
                        if len(text) > 0: text = text[0: - 1]   # Delete last number
                    elif len(text) < 2:
                        if k == pygame.K_0: text = text + "0"
                        elif k == pygame.K_1: text = text + "1"
                        elif k == pygame.K_2: text = text + "2"
                        elif k == pygame.K_3: text = text + "3"
                        elif k == pygame.K_4: text = text + "4"
                        elif k == pygame.K_5: text = text + "5"
                        elif k == pygame.K_6: text = text + "6"
                        elif k == pygame.K_7: text = text + "7"
                        elif k == pygame.K_8: text = text + "8"
                        elif k == pygame.K_9: text = text + "9"
                    elif k == pygame.K_RETURN: ready = True
            self.countrow.text = text
            self.countrow.refresh()
            self.countrow.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.screen.blit(self.countrow.image, self.countrow.rect)
            pygame.display.flip()
            pygame.time.wait(100)
        self.flashcount = int(text)

        
    def hex_enter(self):
        # Erase and draw on both fore and back screen using flip
        self.screen.blit(self.background, self.background_rect)
        self.deco_group.draw(self.screen)
        pygame.display.flip()
        self.screen.blit(self.background, self.background_rect)
        self.deco_group.draw(self.screen)
        # Makes the hex elements enter the screen one for one
        self.enter_group.empty()
        for i in range(self.nr_elements):
            self.enter_group.add(self.elements[i])
            self.enter_group.update(0)
            self.enter_group.draw(self.screen)
            pygame.display.flip()
            pygame.time.wait(self.hex_time)
        # Draw latest version also onto back screen
        self.deco_group.draw(self.screen)
        self.enter_group.draw(self.screen)
        
    def hex_leave(self, reverse=False):
        """
        Makes the hex elements leave the screen one for one
        If reverse=True then they leave in reverse order 
        """
        self.leave_group.empty()
        self.leave_group.add(self.elements)
        for i in range(self.nr_elements):
            if reverse: ind = self.nr_elements - i - 1
            else: ind = i
            self.leave_group.remove(self.elements[ind])
            self.leave_group.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.leave_group.draw(self.screen)
            self.deco_group.draw(self.screen)
            pygame.display.flip()
            pygame.time.wait(self.hex_time)

    def animate_letters(self, t, reverse=False):
        " Moves the letters from the chosen hex to their destinations "
        " Set elements to 'empty' states and get letter positions "
        " 'reverse' performs the reverse motion pattern "
        " Get current position "
        p = t / float(self.animation_time)     # Position parameters
        font = pygame.font.Font(None, int(round(self.end_size * p + self.start_size * (1 - p))))
        # paint everything 
        self.screen.blit(self.background, self.background_rect)
        self.all_elements_group.draw(self.screen)
        if len(self.deco) > 0: self.deco_group.draw(self.screen)
        for i in range(len(self.text)):
            xs, ys = self.start_pos[i]
            xe, ye = self.end_pos[i]
            x, y = xe * p + xs * (1 - p), ye * p + ys * (1 - p)
            surf = font.render(self.text[i], False, self.color)
            rect = surf.get_rect(center=(x, y))
            self.screen.blit(surf, rect)
        pygame.display.flip()

    def log_data(self):
        """
        Structure of logfile
        Word Letter TrialNr Speller Fix_condition Trigger Time targetx targety currentx currenty Duration Invalid(-> added at flush)
        """
        #print self.et.x, self.et.y, self.et.duration
        if self.state == self.STIM_START_FLASH:
            word = self.words[self.current_word]
            items = []
            items.append(word)
            items.append(word[self.current_letter])
            items.append(str(self.trial_nr))
            items.append("hex")
            if self.et_fixate_center:
                items.append("center")
            else:
                items.append("target")
            items.append(str(self.flash_sequence[self.current_flash]))
            items.append(str(pygame.time.get_ticks()))
            if self.use_eyetracker:
                items.append(str(self.et_targetxy[0]))
                items.append(str(self.et_targetxy[1]))
                items.append(str(self.et_currentxy[0]))
                items.append(str(self.et_currentxy[1]))
                items.append(str(self.et_duration))
                #items.append(str(self.et_outside))
            line = "\t".join(items)
            self.datalines.append(line) 
    
    def flush_data(self):
        # Writes the data into the data logfile
        for line in self.datalines:
            line2 = line + "\t" + str(self.flashcount) + "\t" + str(self.invalid_trial) + "\n"
            if self.datafile is not None:
                try: self.datafile.write(line2)
                except IOError:
                    self.logger.warn("Could not write to datafile")
Ejemplo n.º 11
0
 def before_mainloop(self):
     """
     Get a matrix layout, add circle elements and add groups according
     to rows and columns. 
     """
     # There are 7 hex displays, one for the group level and six for the subgroups
     self.hex_displays = [None] * 7
     self.hex_groups = [None] * 7
     # Get layout & elements
     self.layout = CircularLayout(nr_elements=self.nr_elements, radius=self.display_radius)
     colors = ((255, 255, 255), (255, 100, 255), (255, 255, 100), (255, 100, 100), (100, 100, 255), (100, 255, 255))
     textcolors = ((255, 0, 0) , (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 20, 160), (0, 255, 0) , (0, 0, 255) , (0, 255, 255) , (255, 155, 0), (0, 100, 200))
     self.hex_textcolor = (255, 0, 0)
     color , radius = (255, 255, 255), 60
     # Create the top-level display
     for i in range(self.nr_elements):
         e = Circle(nr_states=3, color=color, radius=radius, text=self.hex_letters[i], textcolor=self.hex_textcolor, colorkey=(0, 0, 0), circular_layout=True, circular_offset= - math.pi / 2)
         e.set_states(0, {"textsize":45, "radius":74})
         e.set_states(1, {"textsize":70, "radius":100})
         # Also add a blank version (for animation)
         e.set_states(2, {"textsize":25, "radius":74, "text":"" , "circular_layout":False})
           
         self.add_element(e)
         e.refresh()
         e.update(0)
     # Get groups and add them
     for i in range(self.nr_elements):
         self.add_group(i)
     # Add fixation dot if desire
     if self.et_fixate_center:
         dotcolor, dotradius = (160, 160, 255), 5
         dot = Circle(radius=dotradius, color=dotcolor)
         dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
         dot.refresh()
         dot.update()
         self.deco.append(dot)
     # Add text row
     self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
     self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 22)
     self.textrow.refresh()
     self.textrow.update()
     self.deco.append(self.textrow)
     # Add count row (where count is entered by participant)
     self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
     self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
     self.countrow.refresh()
     self.countrow.update()
     
     # Add deco to deco group
     if len(self.deco) > 0:
         self.deco_group = pygame.sprite.RenderUpdates(self.deco)
     # Save group display as first display
     self.hex_displays[0] = self.elements
     self.hex_groups[0] = self.groups
     
     for j in range(6):
         # Empty elements bin and start again
         self.elements, self.groups = [], []
         # Create the element-level displays
         lettergroup = self.hex_letters[j]      # Add empty space element
         for i in range(self.nr_elements):
             e = Circle(color=color, radius=radius, text=lettergroup[i], textcolor=self.hex_textcolor, textsize=50, colorkey=(0, 0, 0))
             e.set_states(0, {"textsize":110, "radius":74, "color":(255, 255, 255) })
             e.set_states(1, {"textsize":160, "radius":100, "color":(255, 255, 255) })
             self.add_element(e)
             e.refresh()
             e.update(0)
 
         # Get groups and add them
         for i in range(self.nr_elements):
             self.add_group(i)
                    
         # Save element display and groups
         self.hex_displays[j + 1] = self.elements
         self.hex_groups[j + 1] = self.groups
     # Groups for entering and leaving
     self.enter_group = pygame.sprite.RenderUpdates()
     self.leave_group = pygame.sprite.RenderUpdates()
     # Sounds
     self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
     self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
     self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
     # Open file for logging data
     if self.datafilename != "":
         try: 
             self.datafile = open(self.datafilename, 'a')
         except IOError:
             print "Cannot open datafile"
             self.datafile = None
             self.on_quit()
     # Init other variables
     self.group_trigger = None           # Set trigger before each trial
     self.hex_level = 0
     self.current_word = 0           # Index of current word
     self.current_letter = 0         # Index of current letter
     self.pre_mode = self.PRE_WORD
     self.current_tick = 0
     self.invalid_trial = 0          # Set whether trial is valid or not
     if self.et_fixate_center:
         self.send_parallel(self.HEX_CENTRAL_FIX)
     else:
         self.send_parallel(self.HEX_TARGET_FIX)
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
Ejemplo n.º 12
0
class ERPMatrix(VisualP300):
    
    # Dimensions of the matrix
    DEFAULT_ROWS = 6
    DEFAULT_COLS = 5
    DEFAULT_MATRIX_WIDTH = 500
    DEFAULT_MATRIX_HEIGHT = 500

    # Pre codes
    PRE_WORD = 0
    PRE_COUNTDOWN = 1
    PRE_WAIT = 2
    PREP_TRIAL = 3

    # Trigger
    START_TRIAL = 100           # Start trial level
    INVALID = 66               # invalid trial
    MATRIX_CENTRAL_FIX = 80# central fixation condition
    MATRIX_TARGET_FIX = 81   # target fixation condition
    
    def init(self):
        VisualP300.init(self)
        # Graphical settings
        self.pygame_info = False
        self.bgcolor = 0, 0, 0
        self.screenWidth, self.screenHeight = 1280, 1024
        self.canvasWidth, self.canvasHeight = 600, 820
        self.fullscreen = True
        # Words
        #self.words = ["WINKT","FJORD","HYBRID","LUXUS","SPHINX","QUARZ","VODKA","YACHT","GEBOT","MEMME"]
        self.words = ["XBCI"]
        self.current_word = 0           # Index of current word
        self.current_letter = 0         # Index of current letter

        # Speller settings
        self.rows = self.DEFAULT_ROWS
        self.cols = self.DEFAULT_COLS
        self.matrix_width = self.DEFAULT_MATRIX_WIDTH 
        self.matrix_height = self.DEFAULT_MATRIX_HEIGHT
        self.letters = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ_,.<")
        self.countdown = 2          # countdown length in seconds
        # Overwritten members of VisualP300
        self.word_time = 50            # How long word is shown (#fps)
        self.word_time_ms = 2500        # word time in ms
        self.min_dist = 2           # Min number of intermediate flashes bef. a flash is repeated twice 
        self.flash_duration = 3
        self.soa = 5
        self.countdown = 4
        self.nr_sequences = 10
        self.trial_nr = 1

        self.fps = 30       
        # Triggers for Level 1 (letter groups) and Level 2 (individual letters)  
        self.matrix_trigger = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
        # If a certain group is a target, this value will be added to the trigger  
        self.trigger_target_add = 20
        
        # For data logging (-> the data file is opened in pre_mainloop)
        self.datafilename = "Feedbacks\ERP\data\datafile_matrix.txt"       
        self.datafile = None
        # Eye tracker
        self.use_eyetracker = False
        self.et_fixate_center = True   # Whether center or fixdot has to be fixated   
        self.et_currentxy = (0, 0)      # Current fixation
        self.et_duration = 100
        self.et_targetxy = (0, 0)       # Target coordinates
        self.et_range = 100        # Maximal acceptable distance between target and actual fixation
        self.et_range_time = 200    # maximum acceptable fixation off the designated point 
        #self.et_outside = 0          # Whether current fixation is inside designated area

    def before_mainloop(self):
        """
        Instantiate a matrix layout, add text elements and add 
        groups according to rows and columns. 
        """
        # Get layout & add elements
        self.layout = MatrixLayout(size=(self.matrix_width, self.matrix_height), rows=self.rows, cols=self.cols)
        nr_elements = self.rows * self.cols
        for i in range(nr_elements):
            e = Text(text=self.letters[i], color=(255, 255, 255), size=40)
            e.set_states(0, {"size":40})
            e.set_states(1, {"size":65})
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Determine groups & add them
        rows_cols = self.layout.get_rows_cols()
        for group in rows_cols:
            self.add_group(group)
        # Add fixation dot
        if self.et_fixate_center:
            dot = Circle(radius=3, color=(160, 160, 255))
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 21)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update(0)
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Open file for logging data
        if self.datafilename != "":
            try: 
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()

        # Sounds
        self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Variables
        self.group_trigger = None           # Set triggers before each trial
        self.current_word = 0           # Index of current word
        self.current_letter = 0         # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0          # Set whether trial is valid or not
        # Send a trigger
        if self.et_fixate_center:
            self.send_parallel(self.MATRIX_CENTRAL_FIX)
        else:
            self.send_parallel(self.MATRIX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    
    def pre_trial(self):
        # Countdown,prepare
        if self.pre_mode == self.PRE_WORD: self.new_word()
        elif self.pre_mode == self.PREP_TRIAL: self.prep_trial()
        elif self.pre_mode == self.PRE_COUNTDOWN: self.pre_countdown()
        else: self.wait()
        
    def new_word(self):
        # If we just started a new word: present it
        if self.current_letter == 0 and self.word_time > 0:
            self.sound_new_word.play()
            self.current_tick += 1
            word = self.words[self.current_word]
            font = pygame.font.Font(None, self.textsize)
            self.next_word_image = font.render("Next word: " + word, True, self.textcolor);
            self.next_word_rect = self.next_word_image.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2))
            # Paint it
            self.screen.blit(self.all_background, self.all_background_rect)
            pygame.display.flip()
            self.screen.blit(self.all_background, self.all_background_rect)
            self.screen.blit(self.next_word_image, self.next_word_rect)
            pygame.display.flip()
            pygame.time.wait(self.word_time_ms)

            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
        else:
            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
            
    def pre_countdown(self):
        if self.countdown > 0:
            if self.current_countdown == 0:
                self.screen.blit(self.all_background, self.all_background_rect)
                self.all_elements_group.draw(self.screen)
                if len(self.deco) > 0: self.deco_group.draw(self.screen)
                pygame.display.flip()
            self.current_countdown += 1
            if self.current_countdown == self.countdown:
                self.pre_mode = self.PRE_WAIT
            self.sound_countdown.play()
            pygame.time.wait(1000)
        else: self.pre_mode = self.PRE_WAIT

            
    def wait(self):
        " Send trigger & wait 1 second"
        self.send_parallel(self.START_TRIAL)
        pygame.time.delay(1000)
        self.state_finished = True

            
    def prep_trial(self):
        # reset variables
        self.invalid_trial = 0
        self.current_countdown = 0
        # get current word
        word = self.words[self.current_word]
        self.textrow.text = word # Set new word
        self.textrow.highlight = [self.current_letter]  # highlight current letter
        self.textrow.refresh()
        # Delete old flash sequence & make new random sequence 
        self.flash_sequence = []
        # Prequel flashes, allowing repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=11, repetition=True)
        # Experimental sequences
        for i in range(self.nr_sequences):
            random_flash_sequence(self, min_dist=self.min_dist)
        # Sequel, again with repetitions
        random_flash_sequence(self, min_dist=self.min_dist, seq_len=11, repetition=True)
        # Get indices of elements in rows and cols to find target index
        indices = self.layout.get_rows_cols()
        letter = word[self.current_letter]
        ind = self.letters.index(letter)            # Find index of letter
        target_row = 0                              # Two targets: first is row
        target_col = self.rows                      # second is column, starting after the rows
        # Find target row and column
        while ind not in indices[target_row]: target_row += 1  
        while ind not in indices[target_col]: target_col += 1  
        self.group_trigger = self.matrix_trigger[:]
        # Modify trigget of target by adding a value
        self.group_trigger[target_row] += self.trigger_target_add
        self.group_trigger[target_col] += self.trigger_target_add
        # Step to next pre_mode
        self.pre_mode = self.PRE_COUNTDOWN
        # For logfile
        self.datalines = []
        # Flash count
        self.flashcount = 0
        # Determine whether fixate center or fixate target
        if self.et_fixate_center:
            # Fixate center
            self.et_targetxy = (self.screenWidth / 2, self.screenHeight / 2)
        else:
            # Fixate target
            self.et_targetxy = self.elements[ind].pos    
 
    def pre_stimulus(self):
        # Control eye tracker
        if self.use_eyetracker:
            if self.et.x is None:
                self.logger.error("[ERP Matrix] No eyetracker data received!")
                self.on_stop()
                self.state_finished = True
                return
            self.et_currentxy = (self.et.x, self.et.y)
            self.et_duration = self.et.duration
            tx, ty = self.et_targetxy[0], self.et_targetxy[1]
            cx, cy = self.et_currentxy[0], self.et_currentxy[1]
            dist = math.sqrt(math.pow(tx - cx, 2) + math.pow(ty - cy, 2))
            #self.et_outside = 0
            # Check if current fixation is outside the accepted range 
            if dist > self.et_range:
                #self.et_outside = 1
                # Check if the off-fixation is beyond temporal limits
                if self.et_duration > self.et_range_time:
                    self.invalid_trial = 1
                    # Break off current trial !!
                    self.state_finished = True
                    self.sound_invalid.play()
                    show_message(self, "Bad fixation, we have to restart ...")
                    self.screen.blit(self.background, self.background_rect)
                    wait_for_key()
                    # Send break-off trigger
                    self.send_parallel(self.INVALID)
        if self.invalid_trial == 0 and (self.stim_state == self.STIM_START_FLASH) and self.group_trigger is not None:
            self.send_parallel(self.group_trigger[self.flash_sequence[self.current_flash]])
            self.log_data()
            
    def post_trial(self):
        if self.invalid_trial == 1:
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            self.state_finished = True
            self.flush_data()
            self.trial_nr += 1
        else:
            # Get count from participant
            self.prompt_count()
            self.flush_data()
            self.trial_nr += 1
            # Check if we reached the final letter
            if self.current_letter == len(self.words[self.current_word]) - 1:
                self.current_letter = 0
                self.current_word += 1
                # Check if we reached the final word
                if self.current_word > len(self.words) - 1:
                    # We reached the end
                    self.on_stop()
            else:
                # Next letter
                self.current_letter += 1
            # Post trial is finished
            self.state_finished = True
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            # Give 1 second time
            pygame.time.delay(1000)

    def after_mainloop(self):
        self.sound_new_word = None
        self.sound_countdown = None
        self.sound_invalid = None
        self.countrow = None
        self.textrow = None
        # Stop eyetracker
        self.et.stop()

    def prompt_count(self):
        pygame.event.clear()
        text, ready = "", False
        while not ready:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    k = event.key
                    if k == pygame.K_BACKSPACE:
                        if len(text) > 0: text = text[0: - 1]   # Delete last number
                    elif len(text) < 2:
                        if k == pygame.K_0: text = text + "0"
                        elif k == pygame.K_1: text = text + "1"
                        elif k == pygame.K_2: text = text + "2"
                        elif k == pygame.K_3: text = text + "3"
                        elif k == pygame.K_4: text = text + "4"
                        elif k == pygame.K_5: text = text + "5"
                        elif k == pygame.K_6: text = text + "6"
                        elif k == pygame.K_7: text = text + "7"
                        elif k == pygame.K_8: text = text + "8"
                        elif k == pygame.K_9: text = text + "9"
                    elif k == pygame.K_RETURN: ready = True
            self.countrow.text = text
            self.countrow.refresh()
            self.countrow.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.screen.blit(self.countrow.image, self.countrow.rect)
            pygame.display.flip()
            pygame.time.wait(100)
        self.flashcount = int(text)
            
            
    def log_data(self):
        """
        Structure of logfile
        Word Letter Trial Speller Fix_condition Trigger Time targetx targety currentx currenty Duration FlashCount Invalid(->added at flush)
        """
        #print self.et.x, self.et.y, self.et.duration
        if self.state == self.STIM_START_FLASH:
            word = self.words[self.current_word]
            items = []
            items.append(word)
            items.append(word[self.current_letter])
            items.append(str(self.trial_nr))
            items.append("matrix")
            if self.et_fixate_center:
                items.append("center")
            else:
                items.append("target")
            items.append(str(self.flash_sequence[self.current_flash]))
            items.append(str(pygame.time.get_ticks()))
            if self.use_eyetracker:
                items.append(str(self.et_targetxy[0]))
                items.append(str(self.et_targetxy[1]))
                items.append(str(self.et_currentxy[0]))
                items.append(str(self.et_currentxy[1]))
                items.append(str(self.et_duration))
                #items.append(str(self.et_outside))
            line = "\t".join(items)
            self.datalines.append(line) 
    
    def flush_data(self):
        # Writes the data into the data logfile
        for line in self.datalines:
            line2 = line + "\t" + str(self.flashcount) + "\t" + str(self.invalid_trial) + "\n"
            if self.datafile is not None:
                try: self.datafile.write(line2)
                except IOError:
                    self.logger.warn("Could not write to datafile")
 def pre_mainloop(self):
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
Ejemplo n.º 14
0
class EyetrackerFeedback(MainloopFeedback):
    
    DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT = 1280, 1024
    DEFAULT_FULLSCREEN = True
    DEFAULT_BGCOLOR = 0, 0, 0                 # Default background color

    " Settings for textmessages via the function show_message() "
    DEFAULT_TEXTSIZE = 40
    DEFAULT_TEXTCOLOR = 255, 255, 255
    
    " Settings for pygame "
    DEFAULT_VIDEO_DRIVER = 'directx' 
    
    " *** Overwritten MainloopFeedback methods *** "
    def init(self):
        self.window_title = "Intelligaze Feedback"
        self.screenWidth, self.screenHeight = self.DEFAULT_SCREEN_WIDTH, self.DEFAULT_SCREEN_HEIGHT
        """ Canvas: The part of the screen which is used for painting!
        That's more efficient than repainting the whole of the screen 
        """
        self.canvasWidth, self.canvasHeight = self.screenWidth, self.screenHeight
        self.fullscreen = self.DEFAULT_FULLSCREEN
        self.bgcolor = self.DEFAULT_BGCOLOR
        self.textsize = self.DEFAULT_TEXTSIZE 
        self.textcolor = self.DEFAULT_TEXTCOLOR
        # Random number generator
        self.random = random.Random()           # Get random generator
        
        self.video_driver = self.DEFAULT_VIDEO_DRIVER
        # Timing
        self.fps = 30
        
    def pre_mainloop(self):
        self._init_pygame()
        # Create visual elements 
        star_dist = 20
        textsize, textcolor = 30, (255, 255, 255)
        size2, color2 = 60, (100, 100, 200)
        font = pygame.font.Font(None, textsize)
        self.cross = font.render("+", False, textcolor);
        font = pygame.font.Font(None, size2)
        star = font.render("*", False, color2);
        self.objects_im, self.objects_rect = [], []
        # Add 5 stars and their rectangles
        for i in range(5): self.objects_im.append(star)
        self.objects_rect.append(star.get_rect(center=(star_dist, star_dist)))
        self.objects_rect.append(star.get_rect(center=(star_dist, self.screenHeight - star_dist)))
        self.objects_rect.append(star.get_rect(center=(self.screenWidth - star_dist, star_dist)))
        self.objects_rect.append(star.get_rect(center=(self.screenWidth - star_dist, self.screenHeight - star_dist)))
        self.objects_rect.append(star.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2)))
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    def _init_pygame(self):
        # Initialize pygame, open screen and fill screen with background color
        os.environ['SDL_VIDEODRIVER'] = self.video_driver   # Set video driver
        pygame.init()
        self.clock = pygame.time.Clock()
        if self.fullscreen:
            opts = pygame.FULLSCREEN | pygame.DOUBLEBUF#|pygame.HWSURFACE
            self.screen = pygame.display.set_mode((self.screenWidth, self.screenHeight), opts)
        else:
            self.screen = pygame.display.set_mode((self.screenWidth, self.screenHeight))
        self.background = pygame.Surface((self.canvasWidth, self.canvasHeight)) 
        self.background.fill(self.bgcolor)
        self.background_rect = self.background.get_rect(center=(self.screenWidth / 2, self.screenHeight / 2))
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        self.font = pygame.font.Font(None, self.textsize)
            
    def post_mainloop(self):
        # Un-initialize all references to pygame objects before shutting down pygame
        self.background = None
        self.background_rect = None
        self.cross = None
        self.objects_im = None
        self.objects_rect = None
        self.screen = None
        self.clock = None
        self.font = None
        pygame.mixer.quit() 
        pygame.quit()
        # Stop eyetracker
        self.et.stop()

    def tick(self):
        # Check event cue
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.on_stop()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.on_stop() 
            
    def pause_tick(self):
        self.screen.blit(self.background, self.background_rect)
        pygame.display.flip()
        pygame.time.delay(100)
        
    def play_tick(self):
        self.screen.blit(self.background, self.background_rect)
        # Cross
        x, y, dur = self.et.x, self.et.y, self.et.duration
        if x is not None:
            rect = self.cross.get_rect(center=(x, y)) 
            self.screen.blit(self.cross, rect)
        # Stars
        for i in range(5):
            self.screen.blit(self.objects_im[i], self.objects_rect[i])
        # Display info
        
        txt = self.font.render(str(x) + " / " + str(y) + " / " + str(dur), True, self.textcolor)
        txt_rect = txt.get_rect(center=(100, 50))
        self.screen.blit(txt, txt_rect)      
        pygame.display.flip()
        self.clock.tick(self.fps)
Ejemplo n.º 15
0
    def before_mainloop(self):
        """
        Instantiate a matrix layout, add text elements and add 
        groups according to rows and columns. 
        """
        # Get layout & add elements
        self.layout = MatrixLayout(size=(self.matrix_width,
                                         self.matrix_height),
                                   rows=self.rows,
                                   cols=self.cols)
        nr_elements = self.rows * self.cols
        for i in range(nr_elements):
            e = Text(text=self.letters[i], color=(255, 255, 255), size=40)
            e.set_states(0, {"size": 40})
            e.set_states(1, {"size": 65})
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Determine groups & add them
        rows_cols = self.layout.get_rows_cols()
        for group in rows_cols:
            self.add_group(group)
        # Add fixation dot
        if self.et_fixate_center:
            dot = Circle(radius=3, color=(160, 160, 255))
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="",
                               textsize=42,
                               color=(255, 255, 255),
                               size=(450, 42),
                               edgecolor=(55, 100, 255),
                               antialias=True,
                               colorkey=(0, 0, 0),
                               highlight=[1],
                               highlight_color=(255, 0, 0),
                               highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2,
                            (self.screenHeight - self.canvasHeight) / 2 + 21)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="",
                                textsize=60,
                                color=(150, 150, 255),
                                size=(100, 60),
                                edgecolor=(255, 255, 255),
                                antialias=True,
                                colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update(0)
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Open file for logging data
        if self.datafilename != "":
            try:
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()

        # Sounds
        self.sound_new_word = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Variables
        self.group_trigger = None  # Set triggers before each trial
        self.current_word = 0  # Index of current word
        self.current_letter = 0  # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0  # Set whether trial is valid or not
        # Send a trigger
        if self.et_fixate_center:
            self.send_parallel(self.MATRIX_CENTRAL_FIX)
        else:
            self.send_parallel(self.MATRIX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()
Ejemplo n.º 16
0
 def pre_mainloop(self):
     # Start eye tracker
     self.et = EyeTracker()
     self.et.start()
Ejemplo n.º 17
0
class ERPMatrix(VisualP300):

    # Dimensions of the matrix
    DEFAULT_ROWS = 6
    DEFAULT_COLS = 5
    DEFAULT_MATRIX_WIDTH = 500
    DEFAULT_MATRIX_HEIGHT = 500

    # Pre codes
    PRE_WORD = 0
    PRE_COUNTDOWN = 1
    PRE_WAIT = 2
    PREP_TRIAL = 3

    # Trigger
    START_TRIAL = 100  # Start trial level
    INVALID = 66  # invalid trial
    MATRIX_CENTRAL_FIX = 80  # central fixation condition
    MATRIX_TARGET_FIX = 81  # target fixation condition

    def init(self):
        VisualP300.init(self)
        # Graphical settings
        self.pygame_info = False
        self.bgcolor = 0, 0, 0
        self.screenWidth, self.screenHeight = 1280, 1024
        self.canvasWidth, self.canvasHeight = 600, 820
        self.fullscreen = True
        # Words
        #self.words = ["WINKT","FJORD","HYBRID","LUXUS","SPHINX","QUARZ","VODKA","YACHT","GEBOT","MEMME"]
        self.words = ["XBCI"]
        self.current_word = 0  # Index of current word
        self.current_letter = 0  # Index of current letter

        # Speller settings
        self.rows = self.DEFAULT_ROWS
        self.cols = self.DEFAULT_COLS
        self.matrix_width = self.DEFAULT_MATRIX_WIDTH
        self.matrix_height = self.DEFAULT_MATRIX_HEIGHT
        self.letters = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ_,.<")
        self.countdown = 2  # countdown length in seconds
        # Overwritten members of VisualP300
        self.word_time = 50  # How long word is shown (#fps)
        self.word_time_ms = 2500  # word time in ms
        self.min_dist = 2  # Min number of intermediate flashes bef. a flash is repeated twice
        self.flash_duration = 3
        self.soa = 5
        self.countdown = 4
        self.nr_sequences = 10
        self.trial_nr = 1

        self.fps = 30
        # Triggers for Level 1 (letter groups) and Level 2 (individual letters)
        self.matrix_trigger = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
        # If a certain group is a target, this value will be added to the trigger
        self.trigger_target_add = 20

        # For data logging (-> the data file is opened in pre_mainloop)
        self.datafilename = "Feedbacks\ERP\data\datafile_matrix.txt"
        self.datafile = None
        # Eye tracker
        self.use_eyetracker = False
        self.et_fixate_center = True  # Whether center or fixdot has to be fixated
        self.et_currentxy = (0, 0)  # Current fixation
        self.et_duration = 100
        self.et_targetxy = (0, 0)  # Target coordinates
        self.et_range = 100  # Maximal acceptable distance between target and actual fixation
        self.et_range_time = 200  # maximum acceptable fixation off the designated point
        #self.et_outside = 0          # Whether current fixation is inside designated area

    def before_mainloop(self):
        """
        Instantiate a matrix layout, add text elements and add 
        groups according to rows and columns. 
        """
        # Get layout & add elements
        self.layout = MatrixLayout(size=(self.matrix_width,
                                         self.matrix_height),
                                   rows=self.rows,
                                   cols=self.cols)
        nr_elements = self.rows * self.cols
        for i in range(nr_elements):
            e = Text(text=self.letters[i], color=(255, 255, 255), size=40)
            e.set_states(0, {"size": 40})
            e.set_states(1, {"size": 65})
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Determine groups & add them
        rows_cols = self.layout.get_rows_cols()
        for group in rows_cols:
            self.add_group(group)
        # Add fixation dot
        if self.et_fixate_center:
            dot = Circle(radius=3, color=(160, 160, 255))
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="",
                               textsize=42,
                               color=(255, 255, 255),
                               size=(450, 42),
                               edgecolor=(55, 100, 255),
                               antialias=True,
                               colorkey=(0, 0, 0),
                               highlight=[1],
                               highlight_color=(255, 0, 0),
                               highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2,
                            (self.screenHeight - self.canvasHeight) / 2 + 21)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="",
                                textsize=60,
                                color=(150, 150, 255),
                                size=(100, 60),
                                edgecolor=(255, 255, 255),
                                antialias=True,
                                colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update(0)
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Open file for logging data
        if self.datafilename != "":
            try:
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()

        # Sounds
        self.sound_new_word = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound(
            "Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Variables
        self.group_trigger = None  # Set triggers before each trial
        self.current_word = 0  # Index of current word
        self.current_letter = 0  # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0  # Set whether trial is valid or not
        # Send a trigger
        if self.et_fixate_center:
            self.send_parallel(self.MATRIX_CENTRAL_FIX)
        else:
            self.send_parallel(self.MATRIX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()

    def pre_trial(self):
        # Countdown,prepare
        if self.pre_mode == self.PRE_WORD: self.new_word()
        elif self.pre_mode == self.PREP_TRIAL: self.prep_trial()
        elif self.pre_mode == self.PRE_COUNTDOWN: self.pre_countdown()
        else: self.wait()

    def new_word(self):
        # If we just started a new word: present it
        if self.current_letter == 0 and self.word_time > 0:
            self.sound_new_word.play()
            self.current_tick += 1
            word = self.words[self.current_word]
            font = pygame.font.Font(None, self.textsize)
            self.next_word_image = font.render("Next word: " + word, True,
                                               self.textcolor)
            self.next_word_rect = self.next_word_image.get_rect(
                center=(self.screenWidth / 2, self.screenHeight / 2))
            # Paint it
            self.screen.blit(self.all_background, self.all_background_rect)
            pygame.display.flip()
            self.screen.blit(self.all_background, self.all_background_rect)
            self.screen.blit(self.next_word_image, self.next_word_rect)
            pygame.display.flip()
            pygame.time.wait(self.word_time_ms)

            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0
        else:
            self.pre_mode = self.PREP_TRIAL
            self.current_tick = 0
            self.current_countdown = 0

    def pre_countdown(self):
        if self.countdown > 0:
            if self.current_countdown == 0:
                self.screen.blit(self.all_background, self.all_background_rect)
                self.all_elements_group.draw(self.screen)
                if len(self.deco) > 0: self.deco_group.draw(self.screen)
                pygame.display.flip()
            self.current_countdown += 1
            if self.current_countdown == self.countdown:
                self.pre_mode = self.PRE_WAIT
            self.sound_countdown.play()
            pygame.time.wait(1000)
        else:
            self.pre_mode = self.PRE_WAIT

    def wait(self):
        " Send trigger & wait 1 second"
        self.send_parallel(self.START_TRIAL)
        pygame.time.delay(1000)
        self.state_finished = True

    def prep_trial(self):
        # reset variables
        self.invalid_trial = 0
        self.current_countdown = 0
        # get current word
        word = self.words[self.current_word]
        self.textrow.text = word  # Set new word
        self.textrow.highlight = [self.current_letter
                                  ]  # highlight current letter
        self.textrow.refresh()
        # Delete old flash sequence & make new random sequence
        self.flash_sequence = []
        # Prequel flashes, allowing repetitions
        random_flash_sequence(self,
                              min_dist=self.min_dist,
                              seq_len=11,
                              repetition=True)
        # Experimental sequences
        for i in range(self.nr_sequences):
            random_flash_sequence(self, min_dist=self.min_dist)
        # Sequel, again with repetitions
        random_flash_sequence(self,
                              min_dist=self.min_dist,
                              seq_len=11,
                              repetition=True)
        # Get indices of elements in rows and cols to find target index
        indices = self.layout.get_rows_cols()
        letter = word[self.current_letter]
        ind = self.letters.index(letter)  # Find index of letter
        target_row = 0  # Two targets: first is row
        target_col = self.rows  # second is column, starting after the rows
        # Find target row and column
        while ind not in indices[target_row]:
            target_row += 1
        while ind not in indices[target_col]:
            target_col += 1
        self.group_trigger = self.matrix_trigger[:]
        # Modify trigget of target by adding a value
        self.group_trigger[target_row] += self.trigger_target_add
        self.group_trigger[target_col] += self.trigger_target_add
        # Step to next pre_mode
        self.pre_mode = self.PRE_COUNTDOWN
        # For logfile
        self.datalines = []
        # Flash count
        self.flashcount = 0
        # Determine whether fixate center or fixate target
        if self.et_fixate_center:
            # Fixate center
            self.et_targetxy = (self.screenWidth / 2, self.screenHeight / 2)
        else:
            # Fixate target
            self.et_targetxy = self.elements[ind].pos

    def pre_stimulus(self):
        # Control eye tracker
        if self.use_eyetracker:
            if self.et.x is None:
                self.logger.error("[ERP Matrix] No eyetracker data received!")
                self.on_stop()
                self.state_finished = True
                return
            self.et_currentxy = (self.et.x, self.et.y)
            self.et_duration = self.et.duration
            tx, ty = self.et_targetxy[0], self.et_targetxy[1]
            cx, cy = self.et_currentxy[0], self.et_currentxy[1]
            dist = math.sqrt(math.pow(tx - cx, 2) + math.pow(ty - cy, 2))
            #self.et_outside = 0
            # Check if current fixation is outside the accepted range
            if dist > self.et_range:
                #self.et_outside = 1
                # Check if the off-fixation is beyond temporal limits
                if self.et_duration > self.et_range_time:
                    self.invalid_trial = 1
                    # Break off current trial !!
                    self.state_finished = True
                    self.sound_invalid.play()
                    show_message(self, "Bad fixation, we have to restart ...")
                    self.screen.blit(self.background, self.background_rect)
                    wait_for_key()
                    # Send break-off trigger
                    self.send_parallel(self.INVALID)
        if self.invalid_trial == 0 and (
                self.stim_state
                == self.STIM_START_FLASH) and self.group_trigger is not None:
            self.send_parallel(
                self.group_trigger[self.flash_sequence[self.current_flash]])
            self.log_data()

    def post_trial(self):
        if self.invalid_trial == 1:
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            self.state_finished = True
            self.flush_data()
            self.trial_nr += 1
        else:
            # Get count from participant
            self.prompt_count()
            self.flush_data()
            self.trial_nr += 1
            # Check if we reached the final letter
            if self.current_letter == len(self.words[self.current_word]) - 1:
                self.current_letter = 0
                self.current_word += 1
                # Check if we reached the final word
                if self.current_word > len(self.words) - 1:
                    # We reached the end
                    self.on_stop()
            else:
                # Next letter
                self.current_letter += 1
            # Post trial is finished
            self.state_finished = True
            self.current_tick = 0
            self.pre_mode = self.PRE_WORD
            # Give 1 second time
            pygame.time.delay(1000)

    def after_mainloop(self):
        self.sound_new_word = None
        self.sound_countdown = None
        self.sound_invalid = None
        self.countrow = None
        self.textrow = None
        # Stop eyetracker
        self.et.stop()

    def prompt_count(self):
        pygame.event.clear()
        text, ready = "", False
        while not ready:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    k = event.key
                    if k == pygame.K_BACKSPACE:
                        if len(text) > 0:
                            text = text[0:-1]  # Delete last number
                    elif len(text) < 2:
                        if k == pygame.K_0: text = text + "0"
                        elif k == pygame.K_1: text = text + "1"
                        elif k == pygame.K_2: text = text + "2"
                        elif k == pygame.K_3: text = text + "3"
                        elif k == pygame.K_4: text = text + "4"
                        elif k == pygame.K_5: text = text + "5"
                        elif k == pygame.K_6: text = text + "6"
                        elif k == pygame.K_7: text = text + "7"
                        elif k == pygame.K_8: text = text + "8"
                        elif k == pygame.K_9: text = text + "9"
                    elif k == pygame.K_RETURN: ready = True
            self.countrow.text = text
            self.countrow.refresh()
            self.countrow.update(0)
            self.screen.blit(self.background, self.background_rect)
            self.screen.blit(self.countrow.image, self.countrow.rect)
            pygame.display.flip()
            pygame.time.wait(100)
        self.flashcount = int(text)

    def log_data(self):
        """
        Structure of logfile
        Word Letter Trial Speller Fix_condition Trigger Time targetx targety currentx currenty Duration FlashCount Invalid(->added at flush)
        """
        #print self.et.x, self.et.y, self.et.duration
        if self.state == self.STIM_START_FLASH:
            word = self.words[self.current_word]
            items = []
            items.append(word)
            items.append(word[self.current_letter])
            items.append(str(self.trial_nr))
            items.append("matrix")
            if self.et_fixate_center:
                items.append("center")
            else:
                items.append("target")
            items.append(str(self.flash_sequence[self.current_flash]))
            items.append(str(pygame.time.get_ticks()))
            if self.use_eyetracker:
                items.append(str(self.et_targetxy[0]))
                items.append(str(self.et_targetxy[1]))
                items.append(str(self.et_currentxy[0]))
                items.append(str(self.et_currentxy[1]))
                items.append(str(self.et_duration))
                #items.append(str(self.et_outside))
            line = "\t".join(items)
            self.datalines.append(line)

    def flush_data(self):
        # Writes the data into the data logfile
        for line in self.datalines:
            line2 = line + "\t" + str(self.flashcount) + "\t" + str(
                self.invalid_trial) + "\n"
            if self.datafile is not None:
                try:
                    self.datafile.write(line2)
                except IOError:
                    self.logger.warn("Could not write to datafile")
Ejemplo n.º 18
0
    def before_mainloop(self):
        """
        Instantiate a matrix layout, add text elements and add 
        groups according to rows and columns. 
        """
        # Get layout & add elements
        self.layout = MatrixLayout(size=(self.matrix_width, self.matrix_height), rows=self.rows, cols=self.cols)
        nr_elements = self.rows * self.cols
        for i in range(nr_elements):
            e = Text(text=self.letters[i], color=(255, 255, 255), size=40)
            e.set_states(0, {"size":40})
            e.set_states(1, {"size":65})
            self.add_element(e)
            e.refresh()
            e.update(0)
        # Determine groups & add them
        rows_cols = self.layout.get_rows_cols()
        for group in rows_cols:
            self.add_group(group)
        # Add fixation dot
        if self.et_fixate_center:
            dot = Circle(radius=3, color=(160, 160, 255))
            dot.pos = (self.screenWidth / 2, self.screenHeight / 2)
            dot.refresh()
            dot.update()
            self.deco.append(dot)
        # Add text row
        self.textrow = Textrow(text="", textsize=42, color=(255, 255, 255), size=(450, 42), edgecolor=(55, 100, 255), antialias=True, colorkey=(0, 0, 0), highlight=[1], highlight_color=(255, 0, 0), highlight_size=62)
        self.textrow.pos = (self.screenWidth / 2, (self.screenHeight - self.canvasHeight) / 2 + 21)
        self.textrow.refresh()
        self.textrow.update()
        self.deco.append(self.textrow)
        # Add count row (where count is entered by participant)
        self.countrow = Textrow(text="", textsize=60, color=(150, 150, 255), size=(100, 60), edgecolor=(255, 255, 255), antialias=True, colorkey=(0, 0, 0))
        self.countrow.pos = (self.screenWidth / 2, self.screenHeight / 2)
        self.countrow.refresh()
        self.countrow.update(0)
        # Add deco to deco group
        if len(self.deco) > 0:
            self.deco_group = pygame.sprite.RenderUpdates(self.deco)
        # Open file for logging data
        if self.datafilename != "":
            try: 
                self.datafile = open(self.datafilename, 'a')
            except IOError:
                print "Cannot open datafile"
                self.datafile = None
                self.on_quit()

        # Sounds
        self.sound_new_word = pygame.mixer.Sound("Feedbacks\ERP-speller\windaVinciSysStart.wav")
        self.sound_countdown = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceDefault.wav")
        self.sound_invalid = pygame.mixer.Sound("Feedbacks\ERP-speller\winSpaceCritStop.wav")
        # Variables
        self.group_trigger = None           # Set triggers before each trial
        self.current_word = 0           # Index of current word
        self.current_letter = 0         # Index of current letter
        self.pre_mode = self.PRE_WORD
        self.current_tick = 0
        self.invalid_trial = 0          # Set whether trial is valid or not
        # Send a trigger
        if self.et_fixate_center:
            self.send_parallel(self.MATRIX_CENTRAL_FIX)
        else:
            self.send_parallel(self.MATRIX_TARGET_FIX)
        # Start eye tracker
        self.et = EyeTracker()
        self.et.start()