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.

369 lines
14 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 = 59.5 #41
distance_to_monitor = 70
monitor_px = [2560,1440] #[1440,900]
window_screen = 0 #1
disableTracker = True # For Debugging
#conditions = ['Fixation', 'Pursuit', 'Saccade', 'AntiSaccade', 'Necker'] #, 'Rivalry']
conditions = ['Pursuit']
isi_time = 1 # Interstimulus Interval
data_directory = os.path.join(
os.path.expanduser('~'), 'Desktop', 'ExperimentalData', 'SaccadePursuitEyeTracking')
image_directory = os.path.join(os.getcwd(),'Images')
# Saccade / Antisaccade Parameters
number_of_saccade_trials = 1
number_of_saccade_blocks = 1
saccade_distance = 15 #15
saccade_time = 3 #3
stimulus_size = 0.3
stim_color = [1,-1,-1]
saccade_fixation_color = [.5,.5,.5]
# Pursuit Parameters
number_of_pursuit_trials = 1
number_of_pursuit_blocks = 1
pursuit_distance = 15
pursuit_frequencies = [0.1,0.2,0.4]
pursuit_time = [5,5,5] #[40,20,15]
# Necker Cube Parameters
number_of_necker_trials = 1
number_of_necker_blocks = 4
necker_time = 60
necker_color = [191,191,191]
necker_bg_color = [-0.5,-0.5,-0.5]
necker_scale = 0.5
necker_file = os.path.join(image_directory,'Necker1.tif')
# 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 = [0.5,0.5,0.5]
rivalry_border_width = 5
rivalry_distance = 4
data_fields = [
'Subject',
'Condition',
'Block',
'Trial',
'Timestamp',
'TrialType',
'Duration',
'Frequency',
'Locations',
]
instruct_text = [
('Welcome to the experiment. Press space 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 get breaks in between blocks.\n\n'
'Press space to continue.'),
]
saccade_instruct_text = (
'For these trials, focus on the fixation cross. When the target '
'appears, move your gaze to the target. Move your gaze back to '
'the fixation point after the target disappears.\n\n'
'Try not to blink after the target appears.\n\n'
'Press space to continue.'
)
antisaccade_instruct_text = (
'For these trials, focus on the fixation cross. When the target '
'appears, move your gaze to the OPPOSITE direction of the target '
'at approximately the same distance from the fixation cross as '
'the target. Move your gaze back to the fixation point after '
'the target disappears.\n\n'
'Try not to blink after the target appears.\n\n'
'Press space to continue.'
)
pursuit_instruct_text = (
'For these trials, when the circle target appears, follow the '
'target with your eyes.\n\n'
'Try not to blink while the circle is moving.\n\n'
'Press space to continue.'
)
fixation_instruct_text = (
'For these trials, when the cross appears, fixate on it.\n\n'
'Try not to move your eyes from the cross while it '
'remains visible.\n\n'
'Press space to continue.'
)
necker_instruct_text = (
'For these trials, focus on the square. When your '
'perception is that 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.\n\n'
'Respond at any time during the stimulus.\n\n'
'Press space to continue.'
)
rivalry_instruct_text = (
'For these trials, the experimenter will move a '
'system in front of your field of view. Look through '
'the mirrors to see a different image in each eye.\n\n'
'If you perceive a face, press the right button. If '
'you perceive a house, press the left button. If '
'you perceive a combination of the face and house, '
'press the middle button.\n\n'
'Respond at any time during the stimuli.\n\n'
'Press space to continue.'
)
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.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
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(self.questionaire_dict)
if not ok:
print('Experiment has been terminated.')
sys.exit(1)
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.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(self.conditions)
condition_counter = 0
for condition in self.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)
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)
numBlocks = self.number_of_necker_blocks
numTrials = self.number_of_necker_trials
elif condition=='Rivalry':
self.display_text_screen(text=rivalry_instruct_text)
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(self.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)
status = '%s: Block %d, Trial %d' % (condition, block_num, trial_num)
self.tracker.send_status(status)
self.tracker.start_recording()
data = self.run_trial(trial, block_num, trial_num, self.tracker)
self.tracker.stop_recording()
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)
else:
self.display_text_screen(text='Remember:\n\n' + pursuit_instruct_text)
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,
conditions=conditions,
pursuit_time=pursuit_time, stim_color=stim_color,
pursuit_frequencies=pursuit_frequencies,
saccade_distance=saccade_distance,
saccade_time=saccade_time,
saccade_fixation_color=saccade_fixation_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=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=rivalry_border_color,
disableTracker=disableTracker,
rivalry_border_width=rivalry_border_width,
rivalry_distance=rivalry_distance,
)
if __name__ == '__main__':
try:
experiment.run()
except Exception:
print(traceback.format_exc())
if not experiment.quit:
experiment.quit_experiment()