From fd6ae585dfd341fe34ca17691105693551eaf1c5 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 26 Feb 2020 15:00:20 -0600 Subject: [PATCH] Updated a number of settings. Forgot to commit for a while. -MT --- SaccadePursuit.py | 106 +++++---- SaccadePursuitEyeTracking.py | 430 ++++++++++++++++++++++++++++------- eyelinker.py | 0 template.py | 28 ++- 4 files changed, 433 insertions(+), 131 deletions(-) mode change 100644 => 100755 SaccadePursuit.py mode change 100644 => 100755 SaccadePursuitEyeTracking.py mode change 100644 => 100755 eyelinker.py mode change 100644 => 100755 template.py diff --git a/SaccadePursuit.py b/SaccadePursuit.py old mode 100644 new mode 100755 index db094e9..087a09f --- a/SaccadePursuit.py +++ b/SaccadePursuit.py @@ -71,12 +71,14 @@ class SPtask(template.BaseExperiment): stim_color, pursuit_frequencies, saccade_distance, saccade_time, stimulus_size, saccade_fixation_color, + number_of_saccade_lights, fixation_size, pursuit_distance, necker_time, necker_color, necker_scale, necker_bg_color, necker_file, rivalry_file1, rivalry_file2, fixation_trial_time, rivalry_time, - rivalry_scale,rivalry_border_color, + rivalry_height, rivalry_width, + rivalry_border_color, rivalry_border_width, rivalry_distance, new_trial_sound, questionaire_dict=None, @@ -84,33 +86,35 @@ class SPtask(template.BaseExperiment): **kwargs): self.new_trial_sound = new_trial_sound - + # Pursuit self.pursuit_time = pursuit_time self.pursuit_distance = pursuit_distance self.pursuit_frequencies = pursuit_frequencies - + # Saccade self.stim_color = stim_color self.saccade_time = saccade_time self.stimulus_size = stimulus_size self.saccade_distance = saccade_distance self.saccade_fixation_color = saccade_fixation_color - + self.number_of_saccade_lights = number_of_saccade_lights + # Fixation self.fixation_size = fixation_size self.fixation_trial_time = fixation_trial_time - + # Necker self.necker_time = necker_time self.necker_color = necker_color self.necker_scale = necker_scale self.necker_bg_color = necker_bg_color self.necker_file = necker_file - + # Rivalry self.rivalry_time = rivalry_time - self.rivalry_scale = rivalry_scale + self.rivalry_height = rivalry_height + self.rivalry_width = rivalry_width self.rivalry_file1 = rivalry_file1 self.rivalry_file2 = rivalry_file2 self.rivalry_border_color = rivalry_border_color @@ -118,7 +122,7 @@ class SPtask(template.BaseExperiment): self.rivalry_distance = rivalry_distance self.questionaire_dict = questionaire_dict - + super(SPtask, self).__init__(**kwargs) def chdir(self): @@ -140,12 +144,16 @@ class SPtask(template.BaseExperiment): """ trial_list = [] - - saccade_locations = list(range(-self.saccade_distance,0,1)) - for x in range(self.saccade_distance): - saccade_locations.append(x+1) - random.shuffle(saccade_locations) - + + #saccade_locations = list(range(-self.saccade_distance,0,1)) + #for x in range(self.saccade_distance): + # saccade_locations.append(x+1) + #random.shuffle(saccade_locations) + + temp1 = random.sample(range(1,self.saccade_distance+1),self.number_of_saccade_lights) + temp2 = random.sample(range(1,self.saccade_distance+1),self.number_of_saccade_lights) + saccade_locations = [x*-1 for x in temp1] + temp2 + counter = 0 for num_trials in range(numTrials): if condition == 'Pursuit': @@ -184,7 +192,7 @@ class SPtask(template.BaseExperiment): trial_type -- Whether this trial is same or different. """ locs = [pursuit_frequency,0] - + trial = { 'locations': locs, 'trial_time': trial_time, @@ -201,18 +209,21 @@ class SPtask(template.BaseExperiment): break_text = 'Please take a short break. Press any key to continue.' self.display_text_screen(text=break_text, bg_color=[0, 0, 0]) - def display_fixation(self, wait_time, necker_bg_color=[-1,-1,-1]): + def display_fixation(self, wait_time, tracker, necker_bg_color=[-1,-1,-1]): """Displays a fixation cross. A helper function for self.run_trial. Parameters: wait_time -- The amount of time the fixation should be displayed for. """ - + self.experiment_window.color = necker_bg_color - + stim = psychopy.visual.TextStim( self.experiment_window, text='+', color=self.stim_color, height=self.fixation_size, units='deg') + if tracker: + tracker.send_message('Start Fixation') + for frameN in range(int(round(wait_time*60))): stim.draw() self.experiment_window.flip() @@ -225,9 +236,9 @@ class SPtask(template.BaseExperiment): should be displayed. colors -- A list of colors describing what should be drawn at each coordinate. """ - + color = self.stim_color - + stim = psychopy.visual.Circle( self.experiment_window, radius=self.stimulus_size/2, pos=coordinates, fillColor=color, @@ -235,7 +246,7 @@ class SPtask(template.BaseExperiment): fixation = psychopy.visual.TextStim( self.experiment_window, text='+', color=self.saccade_fixation_color, height=self.fixation_size, units='deg') - + stimList = [] temp = list(range(-self.saccade_distance,self.saccade_distance,1)) for circleN in temp: @@ -243,14 +254,14 @@ class SPtask(template.BaseExperiment): tempCoord = circleN else: tempCoord = circleN+1 - + tempStim = psychopy.visual.Circle( self.experiment_window, radius=self.stimulus_size/2, pos=[tempCoord,0], fillColor=self.saccade_dot_color, lineColor=self.saccade_dot_color, units='deg') stimList.append(tempStim) - - tracker.send_message('Start Stim') + if tracker: + tracker.send_message('Start Stim') for frameN in range(int(round(stim_time*60))): for s in stimList: s.draw() @@ -258,7 +269,8 @@ class SPtask(template.BaseExperiment): stim.draw() self.experiment_window.flip() #print(psychopy.tools.monitorunittools.deg2pix(stim.pos, self.experiment_monitor)) - tracker.send_message('End Stim') + if tracker: + tracker.send_message('End Stim') def display_saccade_fixation(self, stim_time): """Displays the stimuli. A helper function for self.run_saccade_trial. @@ -268,11 +280,11 @@ class SPtask(template.BaseExperiment): should be displayed. colors -- A list of colors describing what should be drawn at each coordinate. """ - + fixation = psychopy.visual.TextStim( self.experiment_window, text='+', color=self.stim_color, height=self.fixation_size, units='deg') - + stimList = [] temp = list(range(-self.saccade_distance,self.saccade_distance,1)) for circleN in temp: @@ -285,14 +297,14 @@ class SPtask(template.BaseExperiment): pos=[tempCoord,0], fillColor=self.saccade_dot_color, lineColor=self.saccade_dot_color, units='deg') stimList.append(tempStim) - + for frameN in range(int(round(stim_time*60))): for s in stimList: s.draw() fixation.draw() self.experiment_window.flip() - def display_pursuit(self, stim_frequency, stim_time): + def display_pursuit(self, stim_frequency, stim_time, tracker): """Displays a pursuit stimululus. A helper function for self.run_pursuit_trial. Parameters: @@ -300,9 +312,9 @@ class SPtask(template.BaseExperiment): point of the stimulus. color -- A color describing what should be drawn. """ - + color = self.stim_color - + Xposition = [0] num_frames_per_second = 60 counter = 0 @@ -315,7 +327,7 @@ class SPtask(template.BaseExperiment): for time in range(stim_frames): Xposition.append(math.sin(freq*math.radians((time+1)*360/num_frames_per_second))*self.pursuit_distance) counter += 1 - + stim = psychopy.visual.Circle( self.experiment_window, radius=self.stimulus_size/2, pos=(0,0), fillColor=color, @@ -325,6 +337,9 @@ class SPtask(template.BaseExperiment): stim.draw() self.experiment_window.flip() + if tracker: + tracker.send_message('Start Stim') + for Xpos in Xposition: stim.pos = (Xpos,0) stim.draw() @@ -344,6 +359,8 @@ class SPtask(template.BaseExperiment): stim.size *= self.necker_scale stim.setColor(self.necker_color, 'rgb255') responses = [] + if tracker: + tracker.send_message('Start Stim') for frameN in range(stim_time*60): response = psychopy.event.getKeys() if response: @@ -365,16 +382,17 @@ class SPtask(template.BaseExperiment): def display_rivalry(self, stim_time, tracker): color = self.rivalry_border_color lineSize = self.rivalry_border_width - imDist = self.rivalry_distance + stimPos = self.experiment_window.size[0]/4 + stimPos = psychopy.tools.monitorunittools.pix2deg(stimPos,self.experiment_monitor) stim1 = psychopy.visual.ImageStim( self.experiment_window, - image=self.rivalry_file1, pos=(-imDist,0)) - stim1.size *= self.rivalry_scale + image=self.rivalry_file1, pos=(-stimPos,0)) + stim1.size = (self.rivalry_width, self.rivalry_height) stim2 = psychopy.visual.ImageStim( self.experiment_window, - image=self.rivalry_file2, pos=(imDist,0)) - stim2.size *= self.rivalry_scale - + image=self.rivalry_file2, pos=(stimPos,0)) + stim2.size = (self.rivalry_width, self.rivalry_height) + borderScale = stim1.size[0]*0.4 borderWidth1 = stim1.size[0]+borderScale borderHeight1 = stim1.size[1]+borderScale @@ -408,6 +426,8 @@ class SPtask(template.BaseExperiment): lineColor=color, lineWidth=lineSize) responses = [] + if tracker: + tracker.send_message('Start Stim') for frameN in range(stim_time*60): response = psychopy.event.getKeys() if response: @@ -447,19 +467,19 @@ class SPtask(template.BaseExperiment): if trial['trial_type']=='Necker': freq = self.display_necker(trial['trial_time'], tracker) elif trial['trial_type']=='Pursuit': - self.display_pursuit(trial['pursuit_frequency'],trial['trial_time']) + self.display_pursuit(trial['pursuit_frequency'],trial['trial_time'],tracker) psychopy.sound.Sound(self.new_trial_sound).play() freq = trial['pursuit_frequency'] elif trial['trial_type']=='Fixation': - self.display_fixation(trial['trial_time']) + self.display_fixation(trial['trial_time'],tracker) elif trial['trial_type']=='Rivalry': freq = self.display_rivalry(trial['trial_time'], tracker) else: self.display_saccade(trial['locations'], trial['trial_time'], tracker) psychopy.sound.Sound(self.new_trial_sound).play() - + data = { - 'Subject': self.experiment_info['Subject Identifier'], + 'Subject': self.experiment_info['Subject ID'], 'Block': block_num, 'Trial': trial_num, 'Timestamp': psychopy.core.getAbsTime(), @@ -475,6 +495,6 @@ class SPtask(template.BaseExperiment): # If you call this script directly, directs you to correct file if __name__ == '__main__': warnings.showwarning( - 'Please run SaccadePursuitEyeTracking.py', + 'Please run SaccadePursuitEyeTracking.py', Warning,'SaccadePursuit.py',414) warnings.formatwarning = warning_on_one_line diff --git a/SaccadePursuitEyeTracking.py b/SaccadePursuitEyeTracking.py old mode 100644 new mode 100755 index 12c7746..5180bc0 --- a/SaccadePursuitEyeTracking.py +++ b/SaccadePursuitEyeTracking.py @@ -16,6 +16,8 @@ import random import sys import traceback import subprocess +import math +import numpy # Necesssary to access psychopy paths import psychopy # noqa:F401 @@ -26,22 +28,26 @@ import SaccadePursuit # Experimental Parameters monitor_name = 'testMonitor' -monitor_width = 41 +monitor_width = 41 #25.8 height distance_to_monitor = 74 monitor_px = [1440,900] window_screen = 1 -disableTracker = True # For Debugging +disableTracker = False # For Debugging isi_time = 2 # Interstimulus Interval data_directory = os.path.join( os.path.expanduser('~'), 'Desktop', 'ExperimentalData', 'SaccadePursuitEyeTracking') -image_directory = os.path.join(os.getcwd(),'Images') +# image_directory = os.path.join(os.getcwd(),'Images') +image_directory = os.path.join( + os.path.expanduser('~'), 'Desktop','SaccadePursuitExperiment','Images') new_trial_sound = 'A' +instHeight = 6 # Saccade / Antisaccade Parameters number_of_saccade_trials = 1 number_of_saccade_blocks = 1 saccade_distance = 15 # Degrees per direction +number_of_saccade_lights = 7 # Number of active lights per direction saccade_time = 3 # Maximum Time stimulus_size = 0.3 stim_color = [1,-1,-1] @@ -54,8 +60,8 @@ antisaccade_file_scale = 1 number_of_pursuit_trials = 1 number_of_pursuit_blocks = 1 pursuit_distance = 15 -pursuit_frequencies = [0.1,0.2,0.4] -pursuit_time = [40,20,15] +pursuit_frequencies = [0.1,0.2,0.3] +pursuit_time = [40,20,13.33] # Necker Cube Parameters number_of_necker_trials = 1 @@ -78,12 +84,14 @@ fixation_trial_time = 15 number_of_rivalry_trials = 1 number_of_rivalry_blocks = 1 rivalry_time = 90 -rivalry_scale = 2.5 +#rivalry_scale = 2.5 +rivalry_height = 1.5 +rivalry_width = 1 +rivalry_distance = 3 rivalry_file1 = os.path.join(image_directory,'house4n_11-160.tif') rivalry_file2 = os.path.join(image_directory,'face2nS_11-160.tif') rivalry_border_color = [190,190,190] rivalry_border_width = 5 -rivalry_distance = 4 response_box_file = os.path.join(image_directory,'ResponseBox3.tif') response_box_scale = 0.22 @@ -101,43 +109,42 @@ data_fields = [ # Add additional questions here questionaire_dict = { - 'Run Fixation': True, 'Run Smooth Pursuit': True, 'Run Saccade': True, 'Run Anti-Saccade': True, - 'Run Necker Cube': True, - 'Run Binocular Rivalry': False, +# 'Run Necker Cube': False, + 'Run Binocular Rivalry': True, } questionaire_order=[ - 'Subject Identifier', - 'Age', + 'Subject ID', + 'Session', + 'Timepoint', 'Experimenter Initials', 'Gender', - 'Run Fixation', 'Run Smooth Pursuit', 'Run Saccade', 'Run Anti-Saccade', - 'Run Necker Cube', +# 'Run Necker Cube', 'Run Binocular Rivalry', ] instruct_text = [ - ('Welcome to the experiment. Press any key to continue.'), - ('In this experiment you will be following targets.\n\n' - 'Each trial will start with a fixation cross. ' - 'Focus on the fixation cross until a stimulus appears.\n\n' - 'In the first three phases, you will follow the stimuli. ' - 'In the fourth phase, you will focus on the ' - 'opposite side of center cross at an ' - 'equal distance as the stimulus. In the fifth ' - 'phase, you will see a cube, and are to respond as ' - 'indicated.' - '\n\n' + ('Welcome to the experiment'), + (#'In this experiment you will be following targets.\n\n' +# 'Each trial will start with a fixation cross. ' +# 'Focus on the fixation cross until a stimulus appears.\n\n' +# 'In the first three phases, you will follow the stimuli. ' +# 'In the fourth phase, you will focus on the ' +# 'opposite side of center cross at an ' +# 'equal distance as the stimulus. In the fifth ' +# 'phase, you will see a cube, and are to respond as ' +# 'indicated.' +# '\n\n' 'Do not move your head during the trials of this ' - 'experiment. Move only your eyes to follow the targets.' - 'You will be offered breaks in between sections.\n\n' - 'Press any key to continue.'), + 'experiment.\n\nMove only your eyes.\n'), +# 'You will be offered rests between sections.\n\n' +# 'Press any key to continue.'), ] saccade_instruct_text = ( @@ -150,7 +157,7 @@ saccade_instruct_text = ( 'move your eyes back to the fixation cross.\n\n' 'Try not to blink while a red dot is displayed.\n\n' 'You can then blink or close your eyes to rest for a few seconds.\n\n' - 'Press any key to continue.' +# 'Press any key to continue.' ) antisaccade_instruct_text = ( @@ -164,7 +171,7 @@ antisaccade_instruct_text = ( 'to the fixation point after each target disappears.\n\n' 'Try not to blink while a red dot is displayed.\n\n' 'You can then blink or close your eyes to rest for a few seconds.\n\n' - 'Press any key to continue.' +# 'Press any key to continue.' ) pursuit_instruct_text = ( @@ -176,17 +183,7 @@ pursuit_instruct_text = ( 'two seconds.\n\n' 'Try not to blink while the dot is moving.\n\n' 'You can then blink or close your eyes to rest for a few seconds.\n\n' - 'Press any key to continue.' -) - -fixation_instruct_text = ( - 'For this experiment, we want to know how well you can keep ' - 'your eyes fixed on a target without moving. When the cross ' - 'appears, please fixate on it.\n\n' - 'Try not to move your eyes from the cross or blink until it ' - 'disappears.\n\n' - 'You can then blink or close your eyes to rest for a few seconds.\n\n' - 'Press any key to continue.' +# 'Press any key to continue.' ) necker_instruct_text = ( @@ -199,7 +196,7 @@ necker_instruct_text = ( 'Try not to blink after the cube appears.\n\n' 'You can blink or close your eyes to rest after the cube ' 'disappears.\n\n' - 'Press any key to continue.' +# 'Press any key to continue.' ) rivalry_instruct_text = ( @@ -210,7 +207,7 @@ rivalry_instruct_text = ( 'the face and house, press the center button.\n\n' 'You can blink or close your eyes to rest for a few seconds ' 'after the pictures disappear.\n\n' - 'Press any key to continue.' +# 'Press any key to continue.' ) def convert_color_value(color): @@ -226,12 +223,13 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): def __init__(self, **kwargs): self.quit = False # Needed because eyetracker must shut down self.tracker = None - self.disable_tracker = disableTracker + self.disableTracker = disableTracker self.window_screen = window_screen self.monitor_px = monitor_px - + self.number_of_saccade_trials = number_of_saccade_trials self.number_of_saccade_blocks = number_of_saccade_blocks + self.number_of_saccade_lights = number_of_saccade_lights self.number_of_pursuit_trials = number_of_pursuit_trials self.number_of_pursuit_blocks = number_of_pursuit_blocks self.number_of_necker_trials = number_of_necker_trials @@ -246,6 +244,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): self.response_box_scale = response_box_scale self.antisaccade_instruct_file = antisaccade_instruct_file self.antisaccade_file_scale = antisaccade_file_scale + self.instHeight = instHeight super(EyeTrackingSaccadePursuit, self).__init__(**kwargs) @@ -255,12 +254,18 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): self.display_text_screen('Quiting...', wait_for_input=False) if self.tracker: fName = os.path.join(self.data_directory, - 'ETSP' + self.experiment_info['Subject Identifier'] + '.edf') + self.experiment_info['Subject ID'] + self.experiment_info['Timepoint'] + '.edf') + fName2 = os.path.join(self.data_directory, + 'ETSP_' + self.experiment_info['Subject ID'] + self.experiment_info['Session'] \ + + self.experiment_info['Timepoint'] + '.edf') self.tracker.set_offline_mode() self.tracker.close_edf() self.tracker.transfer_edf() self.tracker.close_connection() - subprocess.call(['edf2asc',fName]) + #subprocess.call(['rename',fName, fName2]) + os.rename(fName, fName2) + subprocess.call(['edf2asc',fName2]) + super(EyeTrackingSaccadePursuit, self).quit_experiment() def run(self): @@ -268,7 +273,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): print('Note: EDF file will be overwritten if identical subject identifiers are used!') ok = self.get_experiment_info_from_dialog( - additional_fields_dict=questionaire_dict, + additional_fields_dict=questionaire_dict, field_order=questionaire_order) if not ok: @@ -276,16 +281,14 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): sys.exit(1) conditions = [] - if self.experiment_info['Run Fixation']: - conditions.append('Fixation') if self.experiment_info['Run Smooth Pursuit']: conditions.append('Pursuit') if self.experiment_info['Run Saccade']: conditions.append('Saccade') if self.experiment_info['Run Anti-Saccade']: conditions.append('AntiSaccade') - if self.experiment_info['Run Necker Cube']: - conditions.append('Necker') +# if self.experiment_info['Run Necker Cube']: +# conditions.append('Necker') if self.experiment_info['Run Binocular Rivalry']: conditions.append('Rivalry') @@ -297,7 +300,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): if not self.disableTracker: self.tracker = eyelinker.EyeLinker( self.experiment_window, - 'ETSP' + self.experiment_info['Subject Identifier'] + '.edf', + self.experiment_info['Subject ID'] + self.experiment_info['Timepoint'] + '.edf', 'BOTH' ) self.tracker.initialize_graphics() @@ -310,12 +313,12 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): for instruction in self.instruct_text: self.display_text_screen(text=instruction) - + if not self.disableTracker: self.tracker.display_eyetracking_instructions() self.tracker.setup_tracker() self.tracker.send_command("track_search_limits=NO") - + #random.shuffle(conditions) condition_counter = 0 @@ -324,33 +327,38 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): numBlocks = 1 numTrials = 1 if condition == 'Saccade': - self.display_text_screen(text=saccade_instruct_text) +# self.display_text_screen(text=saccade_instruct_text) + self.display_saccade_instructions() numBlocks = self.number_of_saccade_blocks numTrials = self.number_of_saccade_trials elif condition=='AntiSaccade': - self.display_text_screen(text=antisaccade_instruct_text, - image_file = antisaccade_instruct_file, - image_scale = self.antisaccade_file_scale) +# self.display_text_screen(text=antisaccade_instruct_text, +# image_file = antisaccade_instruct_file, +# image_scale = self.antisaccade_file_scale) + self.display_saccade_instructions(anti=True) numBlocks = self.number_of_saccade_blocks numTrials = self.number_of_saccade_trials elif condition=='Fixation': - self.display_text_screen(text=fixation_instruct_text) numBlocks = self.number_of_fixation_blocks numTrials = self.number_of_fixation_trials elif condition=='Pursuit': - self.display_text_screen(text=pursuit_instruct_text) +# self.display_text_screen(text=pursuit_instruct_text) + self.display_pursuit_instructions() numBlocks = self.number_of_pursuit_blocks numTrials = self.number_of_pursuit_trials elif condition=='Necker': - self.display_text_screen(text=necker_instruct_text, - image_file=self.necker_response_box_file, - image_scale=self.necker_response_box_scale) + self.display_text_screen(text=necker_instruct_text, + image_file=self.necker_response_box_file, + image_scale=self.necker_response_box_scale) numBlocks = self.number_of_necker_blocks numTrials = self.number_of_necker_trials elif condition=='Rivalry': - self.display_text_screen(text=rivalry_instruct_text, - image_file=self.response_box_file, - image_scale=self.response_box_scale) +# self.display_text_screen(text=rivalry_instruct_text, +# image_file=self.response_box_file, +# image_scale=self.response_box_scale) + self.display_rivalry_instructions( + image_file=self.response_box_file, + image_scale=self.response_box_scale) numBlocks = self.number_of_rivalry_blocks numTrials = self.number_of_rivalry_trials else: @@ -363,14 +371,18 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): if condition == 'Saccade' or condition == 'AntiSaccade': self.display_saccade_fixation(1) else: - self.display_fixation(0.5) - + self.display_fixation(0.5,[]) + for trial_num, trial in enumerate(block): - print( - "Condition: ",condition,"(",condition_counter,"/",len(conditions),")", - "Block ",block_num+1,"/",numBlocks, - " Trial ",trial_num+1,"/",len(block) - ) + printString = ("Condition: %s (%d/%d) Block: %d/%d Trial %d/%d" + % (condition,condition_counter,len(conditions), + block_num+1,numBlocks,trial_num+1,len(block))) + if condition == 'Saccade' or condition == 'AntiSaccade': + if trial['locations'][0]>0: + printString = printString + " (Right)" + else: + printString = printString + " (Left)" + print(printString) if not self.disableTracker: self.tracker.send_message('CONDITION %s' % condition) self.tracker.send_message('BLOCK %d' % block_num) @@ -380,22 +392,26 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): self.tracker.send_status(status) #psychopy.sound.Sound('C').play() self.tracker.start_recording() + if condition == 'Pursuit' and trial_num==0: + self.display_fixation(self.fixation_trial_time,self.tracker) data = self.run_trial(trial, block_num, trial_num, self.tracker) self.tracker.stop_recording() #psychopy.sound.Sound('C').play() else: + if condition == 'Pursuit' and trial_num==0: + self.display_fixation(self.fixation_trial_time,self.tracker) data = self.run_trial(trial, block_num, trial_num) data.update({'Condition': condition}) self.send_data(data) if condition == 'Saccade' or condition=='AntiSaccade': self.display_saccade_fixation(self.isi_time) else: - self.display_fixation(self.isi_time) + self.display_fixation(self.isi_time,[]) if condition == 'Saccade' or condition == 'AntiSaccade': self.display_saccade_fixation(1) else: - self.display_fixation(0.5) + self.display_fixation(0.5,[]) self.save_data_to_csv() if block_num + 1 != numBlocks: @@ -429,6 +445,264 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask): self.quit_experiment() + def display_pursuit_instructions( + self, bg_color=[0,0,0], wait_for_input=True, **kwargs): + + bg_color = convert_color_value(bg_color) + fg_color = convert_color_value([255,255,255]) + + backgroundRect = psychopy.visual.Rect( + self.experiment_window, fillColor=bg_color, units='norm', width=2, + height=2) + backgroundRect.draw() + + borderFrame = psychopy.visual.Rect( + self.experiment_window, lineColor=fg_color, units='deg', + width=self.pursuit_distance*2.1, height=1.5, pos=(0,self.instHeight)) + + textObject = psychopy.visual.TextStim( + self.experiment_window, text='** Smooth Pursuit Example **', color=fg_color, units='deg', + height=1, pos=(0,self.instHeight+2), **kwargs) + + color = self.stim_color + + Xposition = [0] + num_frames_per_second = 60 + counter = 0 + stim_frequency = [0.1] + stim_time = [10] + for freq in stim_frequency: + stim_frames = int(round(stim_time[counter]*num_frames_per_second)) + for time in range(stim_frames): + Xposition.append(math.sin(freq*math.radians((time+1)*360/num_frames_per_second))*self.pursuit_distance) + counter += 1 + + stim = psychopy.visual.Circle( + self.experiment_window, radius=self.stimulus_size/2, + pos=(0,0), fillColor=color, + lineColor=color, units='deg') + + keys = None + while keys != ['space']: + for Xpos in Xposition: + textObject.draw() + borderFrame.draw() + stim.pos = (Xpos,self.instHeight) + stim.draw() + self.experiment_window.flip() + keys = psychopy.event.getKeys() + if keys==['space']: + break + + + return keys + + def display_saccade_instructions( + self, anti=False, bg_color=[0,0,0], wait_for_input=True, **kwargs): + + bg_color = convert_color_value(bg_color) + fg_color = convert_color_value([255,255,255]) + + backgroundRect = psychopy.visual.Rect( + self.experiment_window, fillColor=bg_color, units='norm', width=2, + height=2) + backgroundRect.draw() + + borderFrame = psychopy.visual.Rect( + self.experiment_window, lineColor=fg_color, units='deg', + width=self.pursuit_distance*2.1, height=3, pos=(0,self.instHeight )) + + if anti: + dispText = '** Anti-Saccade Example **' + else: + dispText = '** Saccade Example **' + + textObject = psychopy.visual.TextStim( + self.experiment_window, text=dispText, color=fg_color, units='deg', + height=1, pos=(0,self.instHeight+3), **kwargs) + + color = self.stim_color + + stim = psychopy.visual.Circle( + self.experiment_window, radius=self.stimulus_size/2, + pos=(1,0), fillColor=color, + lineColor=color, units='deg') + + #arrowVert = [(-0.8,0.1),(-0.8,-0.1),(-0.4,-0.1),(-0.4,-0.2),(0,0),(-0.4,0.2),(-0.4,0.1)] + arrowVert = numpy.array([(-.25,-2),(.25,-2),(.25,-.75),(.5,-.75),(0,0),(-.5,-.75),(-.25,-.75),(-.25,-2)]) + arrowVert = arrowVert + (0,self.instHeight + 5.25) + arrowStim = psychopy.visual.ShapeStim( + self.experiment_window, vertices=arrowVert, fillColor=color, size=0.5, lineColor=color, units='deg') + + fixation = psychopy.visual.TextStim( + self.experiment_window, text='+', color=self.saccade_fixation_color, + height=self.fixation_size, units='deg', pos=[0,self.instHeight]) + + stimList = [] + temp = list(range(-self.saccade_distance,self.saccade_distance,1)) + for circleN in temp: + if circleN < 0: + tempCoord = circleN + else: + tempCoord = circleN+1 + + tempStim = psychopy.visual.Circle( + self.experiment_window, radius=self.stimulus_size/2, + pos=[tempCoord,self.instHeight], fillColor=self.saccade_dot_color, + lineColor=self.saccade_dot_color, units='deg') + stimList.append(tempStim) + + temp1 = random.sample(range(1,self.saccade_distance+1),self.number_of_saccade_lights) + temp2 = random.sample(range(1,self.saccade_distance+1),self.number_of_saccade_lights) + saccade_locations = [x*-1 for x in temp1] + temp2 + + keys = None + while keys != ['space']: + for ii in range(4): + if anti: + stim_time = 5; + else: + stim_time = round(random.uniform(1,self.saccade_time),3) + for frameN in range(90): + textObject.draw() + borderFrame.draw() + for s in stimList: + s.draw() + fixation.color = color + fixation.draw() + self.experiment_window.flip() + keys = psychopy.event.getKeys() + if keys==['space']: + break + if keys==['space']: + break + Xpos = random.randint(1,15)*random.choice([-1,1]) + for frameN in range(int(round(stim_time*60))): + textObject.draw() + borderFrame.draw() + for s in stimList: + s.draw() + stim.pos = (Xpos,self.instHeight) + stim.draw() + fixation.color=self.saccade_fixation_color + fixation.draw() + if anti and frameN > stim_time*30: + arrowVert2 = arrowVert + (-Xpos*2,0) + arrowStim.vertices = arrowVert2 + arrowStim.draw() + self.experiment_window.flip() + keys = psychopy.event.getKeys() + if keys==['space']: + break + if keys==['space']: + break + + if keys==['space']: + break + if anti: + textObject.draw() + borderFrame.draw() + for s in stimList: + s.draw() + fixation.color = color + fixation.draw() + self.experiment_window.flip() + keys = psychopy.event.waitKeys(); + + return keys + + def display_rivalry_instructions( + self, anti=False, bg_color=[0,0,0], wait_for_input=True, + image_file=[], image_scale=0.25, **kwargs): + + fg_color = convert_color_value([255,255,255]) + bg_color = convert_color_value(bg_color) + + backgroundRect = psychopy.visual.Rect( + self.experiment_window, fillColor=bg_color, units='norm', width=2, + height=2) + backgroundRect.draw() + + textObject = psychopy.visual.TextStim( + self.experiment_window, text='** Binocular Rivalry Example **', color=fg_color, units='deg', + height=1, pos=(0,self.instHeight+2), **kwargs) + + color = self.rivalry_border_color + lineSize = self.rivalry_border_width + + stimPos = self.experiment_window.size[0]/4 + stimPos = psychopy.tools.monitorunittools.pix2deg(stimPos,self.experiment_monitor) + stim1 = psychopy.visual.ImageStim( + self.experiment_window, + image=self.rivalry_file1, pos=(-stimPos,0)) + stim1.size = (self.rivalry_width, self.rivalry_height) + stim2 = psychopy.visual.ImageStim( + self.experiment_window, + image=self.rivalry_file2, pos=(stimPos,0)) + stim2.size = (self.rivalry_width, self.rivalry_height) + + borderScale = stim1.size[0]*0.4 + borderWidth1 = stim1.size[0]+borderScale + borderHeight1 = stim1.size[1]+borderScale + borderWidth2 = stim2.size[0]+borderScale + borderHeight2 = stim2.size[1]+borderScale + stimBorder1 = psychopy.visual.Rect( + self.experiment_window, width=borderWidth1, + height=borderHeight1, + pos=stim1.pos, lineWidth=lineSize, + lineColor=color, units='deg') + stimBorder2 = psychopy.visual.Rect( + self.experiment_window, width=borderWidth2, + height=borderHeight2, + pos=stim2.pos, lineWidth=lineSize, + lineColor=color, units='deg') + stimFix1_top = psychopy.visual.Line( + self.experiment_window, start=(stim1.pos[0],stim1.pos[1]+borderHeight1/2), + end=(stim1.pos[0],stim1.pos[1]+stim1.size[1]/1.9), units='deg', + lineColor=color, lineWidth=lineSize) + stimFix1_left = psychopy.visual.Line( + self.experiment_window, start=(stim1.pos[0]-borderWidth1/2,stim1.pos[1]), + end=(stim1.pos[0]-stim1.size[0]/1.9,stim1.pos[1]), units='deg', + lineColor=color, lineWidth=lineSize) + stimFix2_bottom = psychopy.visual.Line( + self.experiment_window, start=(stim2.pos[0],stim2.pos[1]-borderHeight2/2), + end=(stim2.pos[0],stim2.pos[1]-stim2.size[1]/1.9), units='deg', + lineColor=color, lineWidth=lineSize) + stimFix2_right = psychopy.visual.Line( + self.experiment_window, start=(stim2.pos[0]+borderWidth2/2,stim2.pos[1]), + end=(stim2.pos[0]+stim2.size[0]/1.9,stim2.pos[1]), units='deg', + lineColor=color, lineWidth=lineSize) + + textObject.draw() + stim1.draw() + stim2.draw() + stimBorder1.draw() + stimBorder2.draw() + stimFix1_top.draw() + stimFix1_left.draw() + stimFix2_bottom.draw() + stimFix2_right.draw() + + imageObject = psychopy.visual.ImageStim( + self.experiment_window, units='pix', + image=image_file) + sizex = int(round(imageObject.size[0])*image_scale) + sizey = int(round(imageObject.size[1])*image_scale) + bottomOfScreen = int(math.floor(-self.experiment_window.size[1]/2+sizey/2))+5 + imageObject.size = [sizex,sizey] + imageObject.pos = (0,bottomOfScreen) + imageObject.draw() + + self.experiment_window.flip() + + keys = None + if wait_for_input: + psychopy.core.wait(.2) # Prevents accidental key presses + keys = psychopy.event.waitKeys() + while keys != ['space']: + keys = psychopy.event.waitKeys() + self.experiment_window.flip() + experiment = EyeTrackingSaccadePursuit( experiment_name='ETSP', @@ -443,6 +717,7 @@ experiment = EyeTrackingSaccadePursuit( pursuit_frequencies=pursuit_frequencies, saccade_distance=saccade_distance, saccade_time=saccade_time, + number_of_saccade_lights=number_of_saccade_lights, saccade_fixation_color=convert_color_value(saccade_fixation_color), saccade_dot_color = convert_color_value(saccade_dot_color), isi_time=isi_time, stimulus_size=stimulus_size, @@ -453,7 +728,8 @@ experiment = EyeTrackingSaccadePursuit( necker_bg_color=convert_color_value(necker_bg_color), fixation_trial_time=fixation_trial_time, rivalry_time=rivalry_time, - rivalry_scale=rivalry_scale, + rivalry_height=rivalry_height, + rivalry_width=rivalry_width, necker_file=necker_file, rivalry_file1=rivalry_file1, rivalry_file2=rivalry_file2, diff --git a/eyelinker.py b/eyelinker.py old mode 100644 new mode 100755 diff --git a/template.py b/template.py old mode 100644 new mode 100755 index f44439a..4b35412 --- a/template.py +++ b/template.py @@ -13,6 +13,7 @@ convert_color_value -- Converts a list of 3 values from 0 to 255 to -1 to 1. from __future__ import division from __future__ import print_function +from datetime import date import os import pickle @@ -135,8 +136,9 @@ class BaseExperiment(object): fields for the dialog box and output dictionary. """ - self.experiment_info = {'Subject Identifier': 'XX00', - 'Age': '0', + self.experiment_info = {'Subject ID': '0000', + 'Session': 'A', + 'Timepoint': '0', 'Experimenter Initials': 'MT', 'Gender': self.gender_options, } @@ -144,8 +146,9 @@ class BaseExperiment(object): if additional_fields_dict is not None: self.experiment_info.update(additional_fields_dict) if field_order is None: - field_order=['Subject Identifier', - 'Age', + field_order=['Subject ID', + 'Session', + 'Timepoint', 'Experimenter Initials', 'Gender', ] @@ -167,9 +170,11 @@ class BaseExperiment(object): """ if filename is None: - filename = (self.experiment_name + '_' + - self.experiment_info['Subject Identifier'] + - '_info') + filename = ('ETSP_' + self.experiment_info['Subject ID'] + + self.experiment_info['Session'] + + self.experiment_info['Timepoint'] + + '_info') + elif filename[-4:] == '.txt': filename = filename[:-4] @@ -190,7 +195,7 @@ class BaseExperiment(object): with open(filename, 'w') as info_file: for key, value in self.experiment_info.items(): info_file.write(key + ':' + str(value) + '\n') - info_file.write('\n') + info_file.write('Date: ' + date.today().strftime('%m/%d/%Y') + '\n\n') def open_csv_data_file(self, data_filename=None): """Opens the csv file and writes the header. @@ -201,8 +206,9 @@ class BaseExperiment(object): """ if data_filename is None: - data_filename = (self.experiment_name + '_' + - self.experiment_info['Subject Identifier']) + data_filename = ('ETSP_' + self.experiment_info['Subject ID'] + + self.experiment_info['Session'] + + self.experiment_info['Timepoint']) elif data_filename[-4:] == '.csv': data_filename = data_filename[:-4] @@ -355,7 +361,7 @@ class BaseExperiment(object): wrapWidth=round(.8*self.experiment_window.size[0]), **kwargs) #textObject.draw() - if image_file: # Display image immediately below text. + if image_file: # Display image at bottom of screen. textObject.pos = (0,self.experiment_window.size[1]/2-5) textObject.alignVert = 'top' textObject.draw()