8 changed files with 1289 additions and 306 deletions
Binary file not shown.
Binary file not shown.
@ -0,0 +1,849 @@ |
|||||||
|
"""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 |
||||||
|
import math |
||||||
|
import numpy |
||||||
|
|
||||||
|
# Necesssary to access psychopy paths |
||||||
|
import psychopy # noqa:F401 |
||||||
|
|
||||||
|
import eyelinker |
||||||
|
|
||||||
|
import SaccadePursuit |
||||||
|
|
||||||
|
disableTracker = False # For Debugging |
||||||
|
mikeComp = False # Also for debugging |
||||||
|
|
||||||
|
# Experimental Parameters |
||||||
|
monitor_name = 'testMonitor' |
||||||
|
distance_to_monitor = 74 |
||||||
|
|
||||||
|
if mikeComp: |
||||||
|
window_screen = 0 |
||||||
|
monitor_px = [2560,1440] |
||||||
|
monitor_width = 59 |
||||||
|
else: |
||||||
|
window_screen = 1 |
||||||
|
monitor_px = [1440, 900] |
||||||
|
monitor_width = 41 # 25.8 height |
||||||
|
|
||||||
|
isi_time = 2 # Interstimulus Interval |
||||||
|
data_directory = os.path.join( |
||||||
|
os.path.expanduser('~'), 'Desktop', 'ExperimentalData', 'Screening') |
||||||
|
if mikeComp: |
||||||
|
image_directory = os.path.join(os.getcwd(),'Images') |
||||||
|
else: |
||||||
|
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 = 2 # Number of active lights 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 = [50, 50, 50] |
||||||
|
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.2] |
||||||
|
pursuit_time = [10] |
||||||
|
|
||||||
|
# Necker Cube Parameters |
||||||
|
number_of_necker_trials = 1 |
||||||
|
number_of_necker_blocks = 1 |
||||||
|
necker_time = 10 |
||||||
|
necker_color = [190, 190, 190] |
||||||
|
necker_bg_color = [30, 30, 30] |
||||||
|
necker_scale = 0.25 |
||||||
|
necker_file = os.path.join(image_directory, 'Necker1.tif') |
||||||
|
necker_response_box_file = os.path.join(image_directory, 'neckerInstruction.tif') |
||||||
|
necker_response_box_scale = 0.307 |
||||||
|
|
||||||
|
# Fixation Parameters |
||||||
|
number_of_fixation_trials = 1 |
||||||
|
number_of_fixation_blocks = 1 |
||||||
|
fixation_size = stimulus_size*2 |
||||||
|
fixation_trial_time = 5 |
||||||
|
|
||||||
|
# Binocular Rivalry Parameters |
||||||
|
number_of_rivalry_trials = 1 |
||||||
|
number_of_rivalry_blocks = 1 |
||||||
|
rivalry_time = 10 |
||||||
|
#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 |
||||||
|
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 Target Fixation': True, |
||||||
|
'Run Smooth Pursuit': True, |
||||||
|
'Run Saccade': True, |
||||||
|
'Run Anti-Saccade': True, |
||||||
|
'Run Necker Cube': True, |
||||||
|
'Run Binocular Rivalry': False, |
||||||
|
} |
||||||
|
|
||||||
|
questionaire_order = [ |
||||||
|
'Subject ID', |
||||||
|
'Session', |
||||||
|
'Timepoint', |
||||||
|
'Experimenter Initials', |
||||||
|
'Gender', |
||||||
|
'Run Target Fixation', |
||||||
|
'Run Smooth Pursuit', |
||||||
|
'Run Saccade', |
||||||
|
'Run Anti-Saccade', |
||||||
|
'Run Necker Cube', |
||||||
|
'Run Binocular Rivalry', |
||||||
|
] |
||||||
|
|
||||||
|
instruct_text = [ |
||||||
|
('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.\n\nMove only your eyes.\n' |
||||||
|
'Remember: Please keep your head still with your forehead\n' |
||||||
|
'against the bar and your chin rested on the pad.'), |
||||||
|
# 'You will be offered rests 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.' |
||||||
|
) |
||||||
|
|
||||||
|
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.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 |
||||||
|
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 |
||||||
|
self.instHeight = instHeight |
||||||
|
|
||||||
|
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: |
||||||
|
self.tracker.set_offline_mode() |
||||||
|
self.tracker.close_edf() |
||||||
|
self.tracker.transfer_edf() |
||||||
|
self.tracker.close_connection() |
||||||
|
fName = os.path.join(self.data_directory, |
||||||
|
self.experiment_info['Subject ID'] + |
||||||
|
self.experiment_info['Timepoint'] + '.edf') |
||||||
|
if os.path.exists(fName): |
||||||
|
baseName = os.path.join(self.data_directory, |
||||||
|
'ETSP_' + |
||||||
|
self.experiment_info['Subject ID'] + |
||||||
|
self.experiment_info['Session'] + |
||||||
|
self.experiment_info['Timepoint']) |
||||||
|
fName2 = baseName + '.edf' |
||||||
|
if os.path.exists(fName2): |
||||||
|
ii = 1 |
||||||
|
newName = baseName+'('+str(ii)+')'+'.edf' |
||||||
|
while os.path.exists(newName): |
||||||
|
ii+=1 |
||||||
|
newName = baseName+'('+str(ii)+')'+'.edf' |
||||||
|
fName2 = newName |
||||||
|
os.rename(fName, fName2) |
||||||
|
subprocess.call(['edf2asc', fName2]) |
||||||
|
|
||||||
|
super(EyeTrackingSaccadePursuit, self).quit_experiment() |
||||||
|
|
||||||
|
def run(self): |
||||||
|
self.chdir() |
||||||
|
|
||||||
|
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 Target 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, |
||||||
|
self.experiment_info['Subject ID'] + |
||||||
|
self.experiment_info['Timepoint'] + '.edf', |
||||||
|
'BOTH' |
||||||
|
) |
||||||
|
self.tracker.initialize_graphics() |
||||||
|
self.tracker.open_edf() |
||||||
|
self.tracker.initialize_tracker() |
||||||
|
self.tracker.send_calibration_settings( |
||||||
|
settings={ |
||||||
|
'preamble_text': 'Saccade Pursuit Experiment Plus Fixation and Necker Cube', } |
||||||
|
) |
||||||
|
|
||||||
|
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 |
||||||
|
for condition in conditions: |
||||||
|
condition_counter += 1 |
||||||
|
numBlocks = 1 |
||||||
|
numTrials = 1 |
||||||
|
if condition == 'Saccade': |
||||||
|
self.display_saccade_instructions() |
||||||
|
numBlocks = self.number_of_saccade_blocks |
||||||
|
numTrials = self.number_of_saccade_trials |
||||||
|
elif condition == 'AntiSaccade': |
||||||
|
self.display_saccade_instructions(anti=True) |
||||||
|
numBlocks = self.number_of_saccade_blocks |
||||||
|
numTrials = self.number_of_saccade_trials |
||||||
|
elif condition == 'Fixation': |
||||||
|
self.display_fixation_instructions() |
||||||
|
numBlocks = self.number_of_fixation_blocks |
||||||
|
numTrials = self.number_of_fixation_trials |
||||||
|
elif condition == 'Pursuit': |
||||||
|
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) |
||||||
|
numBlocks = self.number_of_necker_blocks |
||||||
|
numTrials = self.number_of_necker_trials |
||||||
|
elif condition == 'Rivalry': |
||||||
|
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: |
||||||
|
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): |
||||||
|
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) |
||||||
|
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() |
||||||
|
# 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, []) |
||||||
|
|
||||||
|
if condition == 'Saccade' or condition == 'AntiSaccade': |
||||||
|
self.display_saccade_fixation(1) |
||||||
|
else: |
||||||
|
self.display_fixation(0.5, []) |
||||||
|
self.save_data_to_csv() |
||||||
|
|
||||||
|
# Display text to prepare for next block |
||||||
|
if block_num + 1 != numBlocks: |
||||||
|
self.display_break() |
||||||
|
|
||||||
|
if condition == 'Saccade': |
||||||
|
self.display_saccade_instructions() |
||||||
|
elif condition == 'AntiSaccade': |
||||||
|
self.display_saccade_instructions(anti=True) |
||||||
|
elif condition == 'Fixation': |
||||||
|
self.display_text_screen( |
||||||
|
text='Prepare for next trial.') |
||||||
|
elif condition == 'Pursuit': |
||||||
|
self.display_pursuit_instructions() |
||||||
|
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_rivalry_instructions( |
||||||
|
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() |
||||||
|
|
||||||
|
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_fixation_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)) |
||||||
|
|
||||||
|
dispText = '** Target Fixation Example **' |
||||||
|
|
||||||
|
textObject = psychopy.visual.TextStim( |
||||||
|
self.experiment_window, text=dispText, color=fg_color, units='deg', |
||||||
|
height=1, pos=(0, self.instHeight+3), **kwargs) |
||||||
|
|
||||||
|
fixation = psychopy.visual.TextStim( |
||||||
|
self.experiment_window, text='+', color=self.stim_color, |
||||||
|
height=self.fixation_size, units='deg', pos=[0, self.instHeight]) |
||||||
|
|
||||||
|
borderFrame.draw() |
||||||
|
textObject.draw() |
||||||
|
fixation.draw() |
||||||
|
self.experiment_window.flip() |
||||||
|
|
||||||
|
keys = None |
||||||
|
while keys != ['space']: |
||||||
|
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 |
||||||
|
showImages = False |
||||||
|
if wait_for_input: |
||||||
|
psychopy.core.wait(.2) # Prevents accidental key presses |
||||||
|
keys = psychopy.event.waitKeys() |
||||||
|
while keys != ['space']: |
||||||
|
showImages = not showImages |
||||||
|
if showImages: |
||||||
|
textObject.draw() |
||||||
|
stim1.draw() |
||||||
|
stim2.draw() |
||||||
|
stimBorder1.draw() |
||||||
|
stimBorder2.draw() |
||||||
|
stimFix1_top.draw() |
||||||
|
stimFix1_left.draw() |
||||||
|
stimFix2_bottom.draw() |
||||||
|
stimFix2_right.draw() |
||||||
|
imageObject.draw() |
||||||
|
else: |
||||||
|
textObject.draw() |
||||||
|
# stim1.draw() |
||||||
|
# stim2.draw() |
||||||
|
stimBorder1.draw() |
||||||
|
stimBorder2.draw() |
||||||
|
stimFix1_top.draw() |
||||||
|
stimFix1_left.draw() |
||||||
|
stimFix2_bottom.draw() |
||||||
|
stimFix2_right.draw() |
||||||
|
imageObject.draw() |
||||||
|
self.experiment_window.flip() |
||||||
|
keys = psychopy.event.waitKeys() |
||||||
|
self.experiment_window.flip() |
||||||
|
|
||||||
|
|
||||||
|
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, |
||||||
|
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, |
||||||
|
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_height=rivalry_height, |
||||||
|
rivalry_width=rivalry_width, |
||||||
|
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() |
@ -0,0 +1,63 @@ |
|||||||
|
from __future__ import division |
||||||
|
from __future__ import print_function |
||||||
|
|
||||||
|
import os |
||||||
|
import random |
||||||
|
import sys |
||||||
|
import traceback |
||||||
|
import subprocess |
||||||
|
import math |
||||||
|
import numpy |
||||||
|
|
||||||
|
# Necesssary to access psychopy paths |
||||||
|
import psychopy # noqa:F401 |
||||||
|
|
||||||
|
import eyelinker |
||||||
|
|
||||||
|
import SaccadePursuit |
||||||
|
|
||||||
|
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] |
||||||
|
|
||||||
|
|
||||||
|
#myPR655 = PR655(5) |
||||||
|
#print(myPR655) |
||||||
|
#myPR655.getLum() |
||||||
|
#nm, power = myPR655.getLastSpectrum() |
||||||
|
|
||||||
|
#print(nm) |
||||||
|
#print(power) |
||||||
|
|
||||||
|
#phot = hardware.findPhotometer() |
||||||
|
#print(phot) |
||||||
|
#print(phot.measure()) |
||||||
|
#print(phot.getLum()) |
||||||
|
|
||||||
|
#bg_color = convert_color_value(bg_color) |
||||||
|
fg_color = convert_color_value([0,0,255]) |
||||||
|
|
||||||
|
monitor_px = [1440,900] |
||||||
|
window_screen = 1 |
||||||
|
|
||||||
|
experiment_monitor = psychopy.monitors.Monitor( |
||||||
|
'testMonitor', width=41, |
||||||
|
distance=74) |
||||||
|
experiment_monitor.setSizePix(monitor_px) |
||||||
|
|
||||||
|
experiment_window = psychopy.visual.Window(monitor_px, |
||||||
|
monitor=experiment_monitor, fullscr=True, color=fg_color, |
||||||
|
colorSpace='rgb', units='deg', allowGUI=False, screen=1) |
||||||
|
|
||||||
|
keys = psychopy.event.waitKeys() |
||||||
|
|
||||||
|
#backgroundRect = psychopy.visual.Rect( |
||||||
|
# experiment_window, fillColor=fg_color, units='norm', width=2, |
||||||
|
# height=2) |
||||||
|
#backgroundRect.draw() |
||||||
|
|
@ -1,273 +1,273 @@ |
|||||||
from __future__ import print_function |
from __future__ import print_function |
||||||
from __future__ import division |
from __future__ import division |
||||||
|
|
||||||
import os |
import os |
||||||
import sys |
import sys |
||||||
import time |
import time |
||||||
|
|
||||||
import pylink as pl |
import pylink as pl |
||||||
#from PsychoPyCustomDisplay import PsychoPyCustomDisplay |
#from PsychoPyCustomDisplay import PsychoPyCustomDisplay |
||||||
from EyeLinkCoreGraphicsPsychoPy import EyeLinkCoreGraphicsPsychoPy |
from EyeLinkCoreGraphicsPsychoPy import EyeLinkCoreGraphicsPsychoPy |
||||||
|
|
||||||
import psychopy.event |
import psychopy.event |
||||||
import psychopy.visual |
import psychopy.visual |
||||||
|
|
||||||
|
|
||||||
class EyeLinker(object): |
class EyeLinker(object): |
||||||
def __init__(self, window, filename, eye): |
def __init__(self, window, filename, eye): |
||||||
if len(filename) > 12: |
if len(filename) > 12: |
||||||
raise ValueError( |
raise ValueError( |
||||||
'EDF filename must be at most 12 characters long including the extension.') |
'EDF filename must be at most 12 characters long including the extension.') |
||||||
|
|
||||||
if filename[-4:] != '.edf': |
if filename[-4:] != '.edf': |
||||||
raise ValueError( |
raise ValueError( |
||||||
'Please include the .edf extension in the filename.') |
'Please include the .edf extension in the filename.') |
||||||
|
|
||||||
if eye not in ('LEFT', 'RIGHT', 'BOTH'): |
if eye not in ('LEFT', 'RIGHT', 'BOTH'): |
||||||
raise ValueError('eye must be set to LEFT, RIGHT, or BOTH.') |
raise ValueError('eye must be set to LEFT, RIGHT, or BOTH.') |
||||||
|
|
||||||
self.window = window |
self.window = window |
||||||
self.edf_filename = filename |
self.edf_filename = filename |
||||||
self.edf_open = False |
self.edf_open = False |
||||||
self.eye = eye |
self.eye = eye |
||||||
self.resolution = tuple(window.size) |
self.resolution = tuple(window.size) |
||||||
self.tracker = pl.EyeLink() |
self.tracker = pl.EyeLink() |
||||||
#self.genv = PsychoPyCustomDisplay(self.window, self.tracker) |
#self.genv = PsychoPyCustomDisplay(self.window, self.tracker) |
||||||
self.genv = EyeLinkCoreGraphicsPsychoPy(self.tracker, self.window) |
self.genv = EyeLinkCoreGraphicsPsychoPy(self.tracker, self.window) |
||||||
|
|
||||||
if all(i >= 0.5 for i in self.window.color): |
if all(i >= 0.5 for i in self.window.color): |
||||||
self.text_color = (-1, -1, -1) |
self.text_color = (-1, -1, -1) |
||||||
else: |
else: |
||||||
self.text_color = (1, 1, 1) |
self.text_color = (1, 1, 1) |
||||||
|
|
||||||
def initialize_graphics(self): |
def initialize_graphics(self): |
||||||
self.set_offline_mode() |
self.set_offline_mode() |
||||||
pl.openGraphicsEx(self.genv) |
pl.openGraphicsEx(self.genv) |
||||||
|
|
||||||
def initialize_tracker(self): |
def initialize_tracker(self): |
||||||
if not self.edf_open: |
if not self.edf_open: |
||||||
raise RuntimeError('EDF file must be open before tracker can be initialized.') |
raise RuntimeError('EDF file must be open before tracker can be initialized.') |
||||||
|
|
||||||
pl.flushGetkeyQueue() |
pl.flushGetkeyQueue() |
||||||
self.set_offline_mode() |
self.set_offline_mode() |
||||||
|
|
||||||
self.send_command("screen_pixel_coords = 0 0 %d %d" % self.resolution) |
self.send_command("screen_pixel_coords = 0 0 %d %d" % self.resolution) |
||||||
self.send_message("DISPLAY_COORDS 0 0 %d %d" % self.resolution) |
self.send_message("DISPLAY_COORDS 0 0 %d %d" % self.resolution) |
||||||
|
|
||||||
#self.tracker.setFileEventFilter( |
#self.tracker.setFileEventFilter( |
||||||
# "LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT") |
# "LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT") |
||||||
#self.tracker.setFileSampleFilter( |
#self.tracker.setFileSampleFilter( |
||||||
# "LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS") |
# "LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS") |
||||||
#self.tracker.setLinkEventFilter( |
#self.tracker.setLinkEventFilter( |
||||||
# "LEFT,RIGHT,FIXATION,FIXUPDATE,SACCADE,BLINK,BUTTON,INPUT") |
# "LEFT,RIGHT,FIXATION,FIXUPDATE,SACCADE,BLINK,BUTTON,INPUT") |
||||||
#self.tracker.setLinkSampleFilter( |
#self.tracker.setLinkSampleFilter( |
||||||
# "LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS") |
# "LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS") |
||||||
self.tracker.sendCommand("file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT") |
self.tracker.sendCommand("file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT") |
||||||
self.tracker.sendCommand("link_event_filter = LEFT,RIGHT,FIXATION,FIXUPDATE,SACCADE,BLINK,BUTTON,INPUT") |
self.tracker.sendCommand("link_event_filter = LEFT,RIGHT,FIXATION,FIXUPDATE,SACCADE,BLINK,BUTTON,INPUT") |
||||||
self.tracker.sendCommand("file_sample_data = LEFT,RIGHT,GAZE,GAZERES,PUPIL,HREF,AREA,STATUS,HTARGET,INPUT") |
self.tracker.sendCommand("file_sample_data = LEFT,RIGHT,GAZE,GAZERES,PUPIL,HREF,AREA,STATUS,HTARGET,INPUT") |
||||||
self.tracker.sendCommand("link_sample_data = LEFT,RIGHT,GAZE,GAZERES,PUPIL,HREF,AREA,STATUS,HTARGET,INPUT") |
self.tracker.sendCommand("link_sample_data = LEFT,RIGHT,GAZE,GAZERES,PUPIL,HREF,AREA,STATUS,HTARGET,INPUT") |
||||||
|
|
||||||
def send_calibration_settings(self, settings=None): |
def send_calibration_settings(self, settings=None): |
||||||
defaults = { |
defaults = { |
||||||
'automatic_calibration_pacing': 1000, |
'automatic_calibration_pacing': 1000, |
||||||
'background_color': (0, 0, 0), |
'background_color': (0, 0, 0), |
||||||
'calibration_area_proportion': (0.5, 0.5), |
'calibration_area_proportion': (0.5, 0.5), |
||||||
'calibration_type': 'HV5', |
'calibration_type': 'HV5', |
||||||
'elcl_configuration': 'BTABLER', |
'elcl_configuration': 'BTABLER', |
||||||
'enable_automatic_calibration': 'YES', |
'enable_automatic_calibration': 'YES', |
||||||
'error_sound': '', |
'error_sound': '', |
||||||
'foreground_color': (255, 255, 255), |
'foreground_color': (255, 255, 255), |
||||||
'good_sound': '', |
'good_sound': '', |
||||||
'preamble_text': None, |
'preamble_text': None, |
||||||
'pupil_size_diameter': 'NO', |
'pupil_size_diameter': 'NO', |
||||||
'saccade_acceleration_threshold': 9500, |
'saccade_acceleration_threshold': 9500, |
||||||
'saccade_motion_threshold': 0.15, |
'saccade_motion_threshold': 0.15, |
||||||
'saccade_pursuit_fixup': 60, |
'saccade_pursuit_fixup': 60, |
||||||
'saccade_velocity_threshold': 30, |
'saccade_velocity_threshold': 30, |
||||||
'sample_rate': 1000, |
'sample_rate': 1000, |
||||||
'target_sound': '', |
'target_sound': '', |
||||||
'validation_area_proportion': (0.5, 0.5), |
'validation_area_proportion': (0.5, 0.5), |
||||||
} |
} |
||||||
|
|
||||||
if settings is None: |
if settings is None: |
||||||
settings = {} |
settings = {} |
||||||
|
|
||||||
settings.update(defaults) |
settings.update(defaults) |
||||||
|
|
||||||
self.send_command('elcl_select_configuration = %s' % settings['elcl_configuration']) |
self.send_command('elcl_select_configuration = %s' % settings['elcl_configuration']) |
||||||
|
|
||||||
pl.setCalibrationColors(settings['foreground_color'], settings['background_color']) |
pl.setCalibrationColors(settings['foreground_color'], settings['background_color']) |
||||||
pl.setCalibrationSounds( |
pl.setCalibrationSounds( |
||||||
settings['target_sound'], settings['good_sound'], settings['error_sound']) |
settings['target_sound'], settings['good_sound'], settings['error_sound']) |
||||||
|
|
||||||
if self.eye in ('LEFT', 'RIGHT'): |
if self.eye in ('LEFT', 'RIGHT'): |
||||||
self.send_command('active_eye = %s' % self.eye) |
self.send_command('active_eye = %s' % self.eye) |
||||||
|
|
||||||
self.send_command( |
self.send_command( |
||||||
'automatic_calibration_pacing = %i' % settings['automatic_calibration_pacing']) |
'automatic_calibration_pacing = %i' % settings['automatic_calibration_pacing']) |
||||||
|
|
||||||
if self.eye == 'BOTH': |
if self.eye == 'BOTH': |
||||||
self.send_command('binocular_enabled = YES') |
self.send_command('binocular_enabled = YES') |
||||||
else: |
else: |
||||||
self.send_command('binocular_enabled = NO') |
self.send_command('binocular_enabled = NO') |
||||||
|
|
||||||
#self.send_command( |
#self.send_command( |
||||||
# 'calibration_area_proportion %f %f' % settings['calibration_area_proportion']) |
# 'calibration_area_proportion %f %f' % settings['calibration_area_proportion']) |
||||||
|
|
||||||
|
|
||||||
self.send_command('calibration_type = %s' % settings['calibration_type']) |
self.send_command('calibration_type = %s' % settings['calibration_type']) |
||||||
self.send_command( |
self.send_command( |
||||||
'enable_automatic_calibration = %s' % settings['enable_automatic_calibration']) |
'enable_automatic_calibration = %s' % settings['enable_automatic_calibration']) |
||||||
if settings['preamble_text'] is not None: |
if settings['preamble_text'] is not None: |
||||||
self.send_command('add_file_preamble_text %s' % '"' + settings['preamble_text'] + '"') |
self.send_command('add_file_preamble_text %s' % '"' + settings['preamble_text'] + '"') |
||||||
self.send_command('pupil_size_diameter = %s' % settings['pupil_size_diameter']) |
self.send_command('pupil_size_diameter = %s' % settings['pupil_size_diameter']) |
||||||
#self.send_command( |
#self.send_command( |
||||||
# 'saccade_acceleration_threshold = %i' % settings['saccade_acceleration_threshold']) |
# 'saccade_acceleration_threshold = %i' % settings['saccade_acceleration_threshold']) |
||||||
#self.send_command('saccade_motion_threshold = %i' % settings['saccade_motion_threshold']) |
#self.send_command('saccade_motion_threshold = %i' % settings['saccade_motion_threshold']) |
||||||
#self.send_command('saccade_pursuit_fixup = %i' % settings['saccade_pursuit_fixup']) |
#self.send_command('saccade_pursuit_fixup = %i' % settings['saccade_pursuit_fixup']) |
||||||
#self.send_command( |
#self.send_command( |
||||||
# 'saccade_velocity_threshold = %i' % settings['saccade_velocity_threshold']) |
# 'saccade_velocity_threshold = %i' % settings['saccade_velocity_threshold']) |
||||||
self.send_command('sample_rate = %i' % settings['sample_rate']) |
self.send_command('sample_rate = %i' % settings['sample_rate']) |
||||||
#self.send_command( |
#self.send_command( |
||||||
# 'validation_area_proportion %f %f' % settings['validation_area_proportion']) |
# 'validation_area_proportion %f %f' % settings['validation_area_proportion']) |
||||||
|
|
||||||
def open_edf(self): |
def open_edf(self): |
||||||
self.tracker.openDataFile(self.edf_filename) |
self.tracker.openDataFile(self.edf_filename) |
||||||
self.edf_open = True |
self.edf_open = True |
||||||
|
|
||||||
def close_edf(self): |
def close_edf(self): |
||||||
self.tracker.closeDataFile() |
self.tracker.closeDataFile() |
||||||
self.edf_open = False |
self.edf_open = False |
||||||
|
|
||||||
def transfer_edf(self, newFilename=None): |
def transfer_edf(self, newFilename=None): |
||||||
if not newFilename: |
if not newFilename: |
||||||
newFilename = self.edf_filename |
newFilename = self.edf_filename |
||||||
|
|
||||||
# Prevents timeouts due to excessive printing |
# Prevents timeouts due to excessive printing |
||||||
sys.stdout = open(os.devnull, "w") |
sys.stdout = open(os.devnull, "w") |
||||||
self.tracker.receiveDataFile(self.edf_filename, newFilename) |
self.tracker.receiveDataFile(self.edf_filename, newFilename) |
||||||
sys.stdout = sys.__stdout__ |
sys.stdout = sys.__stdout__ |
||||||
print(newFilename + ' has been transferred successfully.') |
print(newFilename + ' has been transferred successfully.') |
||||||
|
|
||||||
def setup_tracker(self): |
def setup_tracker(self): |
||||||
self.window.flip() |
self.window.flip() |
||||||
self.tracker.doTrackerSetup() |
self.tracker.doTrackerSetup() |
||||||
|
|
||||||
def display_eyetracking_instructions(self): |
def display_eyetracking_instructions(self): |
||||||
self.window.flip() |
self.window.flip() |
||||||
|
|
||||||
psychopy.visual.Circle( |
psychopy.visual.Circle( |
||||||
self.window, units='pix', radius=18, lineColor='black', fillColor='white' |
self.window, units='pix', radius=18, lineColor='black', fillColor='white' |
||||||
).draw() |
).draw() |
||||||
psychopy.visual.Circle( |
psychopy.visual.Circle( |
||||||
self.window, units='pix', radius=6, lineColor='black', fillColor='black' |
self.window, units='pix', radius=6, lineColor='black', fillColor='black' |
||||||
).draw() |
).draw() |
||||||
|
|
||||||
psychopy.visual.TextStim( |
# psychopy.visual.TextStim( |
||||||
self.window, text='Sometimes a target that looks like this will appear.', |
# self.window, text='Sometimes a target that looks like this will appear.', |
||||||
color=self.text_color, units='norm', pos=(0, 0.22), height=0.05 |
# color=self.text_color, units='norm', pos=(0, 0.22), height=0.05 |
||||||
).draw() |
# ).draw() |
||||||
|
|
||||||
psychopy.visual.TextStim( |
# psychopy.visual.TextStim( |
||||||
self.window, color=self.text_color, units='norm', pos=(0, -0.18), height=0.05, |
# self.window, color=self.text_color, units='norm', pos=(0, -0.18), height=0.05, |
||||||
text='We use it to calibrate the eye tracker. Stare at it whenever you see it.' |
# text='We use it to calibrate the eye tracker. Stare at it whenever you see it.' |
||||||
).draw() |
# ).draw() |
||||||
|
|
||||||
psychopy.visual.TextStim( |
# psychopy.visual.TextStim( |
||||||
self.window, color=self.text_color, units='norm', pos=(0, -0.28), height=0.05, |
# self.window, color=self.text_color, units='norm', pos=(0, -0.28), height=0.05, |
||||||
text='Press any key to continue.' |
# text='Press any key to continue.' |
||||||
).draw() |
# ).draw() |
||||||
|
|
||||||
self.window.flip() |
self.window.flip() |
||||||
psychopy.event.waitKeys() |
psychopy.event.waitKeys() |
||||||
self.window.flip() |
self.window.flip() |
||||||
|
|
||||||
def calibrate(self, text=None): |
def calibrate(self, text=None): |
||||||
self.window.flip() |
self.window.flip() |
||||||
|
|
||||||
if text is None: |
if text is None: |
||||||
text = ( |
text = ( |
||||||
'Experimenter:\n' |
'Experimenter:\n' |
||||||
'If you would like to calibrate, press space.\n' |
'If you would like to calibrate, press space.\n' |
||||||
'To skip calibration, press the escape key.' |
'To skip calibration, press the escape key.' |
||||||
) |
) |
||||||
|
|
||||||
psychopy.visual.TextStim( |
psychopy.visual.TextStim( |
||||||
self.window, text=text, pos=(0, 0), height=0.05, units='norm', color=self.text_color |
self.window, text=text, pos=(0, 0), height=0.05, units='norm', color=self.text_color |
||||||
).draw() |
).draw() |
||||||
|
|
||||||
self.window.flip() |
self.window.flip() |
||||||
|
|
||||||
keys = psychopy.event.waitKeys(keyList=['escape', 'space']) |
keys = psychopy.event.waitKeys(keyList=['escape', 'space']) |
||||||
|
|
||||||
self.window.flip() |
self.window.flip() |
||||||
|
|
||||||
if 'space' in keys: |
if 'space' in keys: |
||||||
self.tracker.doTrackerSetup() |
self.tracker.doTrackerSetup() |
||||||
|
|
||||||
def drift_correct(self, position=None, setup=1): |
def drift_correct(self, position=None, setup=1): |
||||||
if position is None: |
if position is None: |
||||||
position = tuple([int(round(i/2)) for i in self.resolution]) |
position = tuple([int(round(i/2)) for i in self.resolution]) |
||||||
|
|
||||||
try: |
try: |
||||||
self.tracker.doDriftCorrect(position[0], position[1], 1, setup) |
self.tracker.doDriftCorrect(position[0], position[1], 1, setup) |
||||||
self.tracker.applyDriftCorrect() |
self.tracker.applyDriftCorrect() |
||||||
except RuntimeError as e: |
except RuntimeError as e: |
||||||
print(e.message) |
print(e.message) |
||||||
#self.tracker.doTrackerSetup() |
#self.tracker.doTrackerSetup() |
||||||
|
|
||||||
def record(self, trial_func): |
def record(self, trial_func): |
||||||
def wrapped_func(): |
def wrapped_func(): |
||||||
self.start_recording() |
self.start_recording() |
||||||
trial_func() |
trial_func() |
||||||
self.stop_recording() |
self.stop_recording() |
||||||
return wrapped_func |
return wrapped_func |
||||||
|
|
||||||
def start_recording(self): |
def start_recording(self): |
||||||
self.tracker.startRecording(1, 1, 1, 1) |
self.tracker.startRecording(1, 1, 1, 1) |
||||||
time.sleep(.1) # required |
time.sleep(.1) # required |
||||||
|
|
||||||
def stop_recording(self): |
def stop_recording(self): |
||||||
time.sleep(.1) # required |
time.sleep(.1) # required |
||||||
self.tracker.stopRecording() |
self.tracker.stopRecording() |
||||||
|
|
||||||
@property |
@property |
||||||
def gaze_data(self): |
def gaze_data(self): |
||||||
sample = self.tracker.getNewestSample() |
sample = self.tracker.getNewestSample() |
||||||
|
|
||||||
if self.eye == 'LEFT': |
if self.eye == 'LEFT': |
||||||
return sample.getLeftEye().getGaze() |
return sample.getLeftEye().getGaze() |
||||||
elif self.eye == 'RIGHT': |
elif self.eye == 'RIGHT': |
||||||
return sample.getRightEye().getGaze() |
return sample.getRightEye().getGaze() |
||||||
else: |
else: |
||||||
return (sample.getLeftEye().getGaze(), sample.getRightEye().getGaze()) |
return (sample.getLeftEye().getGaze(), sample.getRightEye().getGaze()) |
||||||
|
|
||||||
@property |
@property |
||||||
def pupil_size(self): |
def pupil_size(self): |
||||||
sample = self.tracker.getNewestSample() |
sample = self.tracker.getNewestSample() |
||||||
|
|
||||||
if self.eye == 'LEFT': |
if self.eye == 'LEFT': |
||||||
return sample.getLeftEye().getPupilSize() |
return sample.getLeftEye().getPupilSize() |
||||||
elif self.eye == 'RIGHT': |
elif self.eye == 'RIGHT': |
||||||
return sample.getRightEye().getPupilSize() |
return sample.getRightEye().getPupilSize() |
||||||
else: |
else: |
||||||
return (sample.getLeftEye().getPupilSize(), sample.getRightEye().getPupilSize()) |
return (sample.getLeftEye().getPupilSize(), sample.getRightEye().getPupilSize()) |
||||||
|
|
||||||
def set_offline_mode(self): |
def set_offline_mode(self): |
||||||
self.tracker.setOfflineMode() |
self.tracker.setOfflineMode() |
||||||
|
|
||||||
def send_command(self, cmd): |
def send_command(self, cmd): |
||||||
self.tracker.sendCommand(cmd) |
self.tracker.sendCommand(cmd) |
||||||
|
|
||||||
def send_message(self, msg): |
def send_message(self, msg): |
||||||
self.tracker.sendMessage(msg) |
self.tracker.sendMessage(msg) |
||||||
|
|
||||||
def send_status(self, status): |
def send_status(self, status): |
||||||
if len(status) >= 80: |
if len(status) >= 80: |
||||||
print('Warning: Status should be less than 80 characters.') |
print('Warning: Status should be less than 80 characters.') |
||||||
|
|
||||||
self.send_command("record_status_message '%s'" % status) |
self.send_command("record_status_message '%s'" % status) |
||||||
|
|
||||||
def close_connection(self): |
def close_connection(self): |
||||||
self.tracker.close() |
self.tracker.close() |
||||||
pl.closeGraphics() |
pl.closeGraphics() |
||||||
|
Loading…
Reference in new issue