You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
18 KiB

"""A wrapper for SaccadePursuit to track eye movements in addition
Author - Michael Tan (mtan30@uic.edu)
Adapted from: https://github.com/colinquirk/ChangeDetectionEyeTracking
Note: this code relies on the Colin Quirk templateexperiments module. You can get it from
https://github.com/colinquirk/templateexperiments and either put it in the same folder as this
code or give the path to psychopy in the preferences.
"""
from __future__ import division
from __future__ import print_function
import os
import random
import sys
import traceback
import subprocess
# Necesssary to access psychopy paths
import psychopy # noqa:F401
import eyelinker
import SaccadePursuit
# Experimental Parameters
monitor_name = 'testMonitor'
monitor_width = 41
distance_to_monitor = 74
monitor_px = [1440,900]
window_screen = 1
disableTracker = True # 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')
new_trial_sound = 'A'
# Saccade / Antisaccade Parameters
number_of_saccade_trials = 1
number_of_saccade_blocks = 1
saccade_distance = 2#15 # Degrees per direction
saccade_time = 3 # Maximum Time
stimulus_size = 0.3
stim_color = [1,-1,-1]
saccade_fixation_color = [255,255,255]
saccade_dot_color = [100,100,100]
antisaccade_instruct_file = os.path.join(image_directory,'AntiSaccadeInstruct2.tif')
antisaccade_file_scale = 1
# Pursuit Parameters
number_of_pursuit_trials = 1
number_of_pursuit_blocks = 1
pursuit_distance = 15
pursuit_frequencies = [0.4]#[0.1,0.2,0.4]
pursuit_time = [5]#[40,20,15]
# Necker Cube Parameters
number_of_necker_trials = 1
number_of_necker_blocks = 4
necker_time = 60
necker_color = [190,190,190]
necker_bg_color = [30,30,30]
necker_scale = 0.5
necker_file = os.path.join(image_directory,'Necker1.tif')
necker_response_box_file = os.path.join(image_directory,'ResponseBox5.tif')
necker_response_box_scale = 0.22
# Fixation Parameters
number_of_fixation_trials = 1
number_of_fixation_blocks = 1
fixation_size = stimulus_size*2
fixation_trial_time = 15
# Binocular Rivalry Parameters
number_of_rivalry_trials = 1
number_of_rivalry_blocks = 1
rivalry_time = 90
rivalry_scale = 2.5
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
data_fields = [
'Subject',
'Condition',
'Block',
'Trial',
'Timestamp',
'TrialType',
'Duration',
'Frequency',
'Locations',
]
# 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,
}
questionaire_order=[
'Subject Identifier',
'Age',
'Experimenter Initials',
'Gender',
'Run Fixation',
'Run Smooth Pursuit',
'Run Saccade',
'Run Anti-Saccade',
'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'
'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.'),
]
saccade_instruct_text = (
'For this experiment, we want to know how accurately your '
'eyes can move from one position to another. You will see '
'many gray dots and a cross on the screen. Please focus on '
'the fixation cross in the beginning. When the red dot '
'appears, move your eyes to the red dot as quickly as '
'possible and fixate on the red dot. When you hear a sound, '
'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.'
)
antisaccade_instruct_text = (
'For this experiment, we also want to know how accurately your '
'eyes can move from one point to another. Please focus on the '
'fixation cross. When the red dot appears, move your eyes to '
'the OPPOSITE direction of the target at approximately the '
'same distance from the fixation cross as the target as shown '
'by the arrow in the image below this text. The arrow will '
'not be present during the experiment. Move your eyes back '
'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.'
)
pursuit_instruct_text = (
'For this experiment, we want to know how well your eyes can '
'follow a target at different speeds. Please fixate the cross. '
'When the dot appears, follow the target with your eyes. You '
'will hear a sound when the trial is done. You may blink '
'until the next trial begins. A new trial will begin after '
'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.'
)
necker_instruct_text = (
'For this experiment, focus on the square. When you see the '
'square is pointing down and to the left, press the left '
'button. As soon as it switches to up and to the right, '
'press the right button. Refer to the image below this '
'text. The center button is not used during this experiment.\n\n'
'Respond at any time during the stimulus.\n\n'
'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.'
)
rivalry_instruct_text = (
'For this experiment, you will see different images in your '
'left and right eyes through the mirrors.\n\n'
'If you see a face, press the right button. If you see a '
'house, press the left button. If you perceive a mixture of '
'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.'
)
def convert_color_value(color):
"""Converts a list of 3 values from 0 to 255 to -1 to 1.
Parameters:
color -- A list of 3 ints between 0 and 255 to be converted.
"""
return [round(((n/127.5)-1), 2) for n in color]
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.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_pursuit_trials = number_of_pursuit_trials
self.number_of_pursuit_blocks = number_of_pursuit_blocks
self.number_of_necker_trials = number_of_necker_trials
self.number_of_necker_blocks = number_of_necker_blocks
self.number_of_fixation_trials = number_of_fixation_trials
self.number_of_fixation_blocks = number_of_fixation_blocks
self.number_of_rivalry_trials = number_of_rivalry_trials
self.number_of_rivalry_blocks = number_of_rivalry_blocks
self.response_box_file = response_box_file
self.necker_response_box_file = necker_response_box_file
self.necker_response_box_scale = necker_response_box_scale
self.response_box_scale = response_box_scale
self.antisaccade_instruct_file = antisaccade_instruct_file
self.antisaccade_file_scale = antisaccade_file_scale
super(EyeTrackingSaccadePursuit, self).__init__(**kwargs)
def quit_experiment(self):
self.quit = True
if self.experiment_window:
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.tracker.set_offline_mode()
self.tracker.close_edf()
self.tracker.transfer_edf()
self.tracker.close_connection()
subprocess.call(['edf2asc',fName])
super(EyeTrackingSaccadePursuit, self).quit_experiment()
def run(self):
self.chdir()
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,
field_order=questionaire_order)
if not ok:
print('Experiment has been terminated.')
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 Binocular Rivalry']:
conditions.append('Rivalry')
self.save_experiment_info()
self.open_csv_data_file()
self.open_window(screen=self.window_screen)
self.display_text_screen('Loading...', wait_for_input=False)
if not self.disableTracker:
self.tracker = eyelinker.EyeLinker(
self.experiment_window,
'ETSP' + self.experiment_info['Subject Identifier'] + '.edf',
'BOTH'
)
self.tracker.initialize_graphics()
self.tracker.open_edf()
self.tracker.send_command("add_file_preamble_text 'Saccade Pursuit Experiment Plus Fixation and Necker Cube'")
self.tracker.initialize_tracker()
self.tracker.send_calibration_settings()
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()
#random.shuffle(conditions)
condition_counter = 0
for condition in conditions:
condition_counter += 1
numBlocks = 1
numTrials = 1
if condition == 'Saccade':
self.display_text_screen(text=saccade_instruct_text)
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)
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)
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)
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)
numBlocks = self.number_of_rivalry_blocks
numTrials = self.number_of_rivalry_trials
else:
continue
for block_num in range(numBlocks):
block = self.make_block(condition, numTrials)
self.display_text_screen(text='Get ready...', wait_for_input=False)
psychopy.core.wait(2)
if condition == 'Saccade' or condition == 'AntiSaccade':
self.display_saccade_fixation(1)
else:
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)
)
if not self.disableTracker:
self.tracker.send_message('CONDITION %s' % condition)
self.tracker.send_message('BLOCK %d' % block_num)
self.tracker.send_message('TRIAL %d' % trial_num)
self.tracker.send_message('Location: %d,%d' % (trial['locations'][0],trial['locations'][1]))
status = '%s: Block %d, Trial %d' % (condition, block_num, trial_num)
self.tracker.send_status(status)
psychopy.sound.Sound('C').play()
self.tracker.start_recording()
data = self.run_trial(trial, block_num, trial_num, self.tracker)
self.tracker.stop_recording()
psychopy.sound.Sound('C').play()
else:
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)
if condition == 'Saccade' or condition == 'AntiSaccade':
self.display_saccade_fixation(1)
else:
self.display_fixation(0.5)
self.save_data_to_csv()
if block_num + 1 != numBlocks:
self.display_break()
if condition == 'Saccade':
self.display_text_screen(text='Remember:\n\n' + saccade_instruct_text)
elif condition=='AntiSaccade':
self.display_text_screen(text='Remember:\n\n' + antisaccade_instruct_text,
image_file = antisaccade_instruct_file,
image_scale = self.antisaccade_file_scale)
elif condition=='Fixation':
self.display_text_screen(text='Remember:\n\n' + fixation_instruct_text)
elif condition=='Pursuit':
self.display_text_screen(text='Remember:\n\n' + pursuit_instruct_text)
elif condition=='Necker':
self.display_text_screen(text='Remember:\n\n' + necker_instruct_text,
image_file=self.necker_response_box_file,
image_scale=self.necker_response_box_scale)
elif condition=='Rivalry':
self.display_text_screen(text='Remember:\n\n' + rivalry_instruct_text,
image_file=self.response_box_file,
image_scale=self.response_box_scale)
self.display_text_screen(
'The experiment is now over.',
bg_color=[50, 150, 50], text_color=[255, 255, 255],
wait_for_input=False)
psychopy.core.wait(5)
self.quit_experiment()
experiment = EyeTrackingSaccadePursuit(
experiment_name='ETSP',
data_fields=data_fields,
data_directory=data_directory,
instruct_text=instruct_text,
monitor_name=monitor_name,
monitor_width=monitor_width,
monitor_px=monitor_px,
monitor_distance=distance_to_monitor,
pursuit_time=pursuit_time, stim_color=stim_color,
pursuit_frequencies=pursuit_frequencies,
saccade_distance=saccade_distance,
saccade_time=saccade_time,
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,
fixation_size=fixation_size,
pursuit_distance=pursuit_distance,
necker_time=necker_time, necker_color=necker_color,
necker_scale=necker_scale,
necker_bg_color=convert_color_value(necker_bg_color),
fixation_trial_time=fixation_trial_time,
rivalry_time=rivalry_time,
rivalry_scale=rivalry_scale,
necker_file=necker_file,
rivalry_file1=rivalry_file1,
rivalry_file2=rivalry_file2,
rivalry_border_color=convert_color_value(rivalry_border_color),
disableTracker=disableTracker,
rivalry_border_width=rivalry_border_width,
rivalry_distance=rivalry_distance,
new_trial_sound = new_trial_sound
)
if __name__ == '__main__':
try:
experiment.run()
except Exception:
print(traceback.format_exc())
if not experiment.quit:
experiment.quit_experiment()