Browse Source

Lots of updates: Changin Necker instructions, making Necker default, removing binocular rivalry from default. And many more.

master
Mike 4 years ago
parent
commit
6450c16bed
Signed by: mike
GPG Key ID: EFBDC90BE2BF394F
  1. BIN
      .DS_Store
  2. BIN
      Images/neckerInstruction.tif
  3. 9
      SaccadePursuit.py
  4. 109
      SaccadePursuitEyeTracking.py
  5. 849
      Screening.py
  6. 63
      calibrate.py
  7. 546
      eyelinker.py
  8. 19
      template.py

BIN
.DS_Store vendored

Binary file not shown.

BIN
Images/neckerInstruction.tif

Binary file not shown.

9
SaccadePursuit.py

@ -222,7 +222,7 @@ class SPtask(template.BaseExperiment):
self.experiment_window, text='+', color=self.stim_color, height=self.fixation_size, units='deg') self.experiment_window, text='+', color=self.stim_color, height=self.fixation_size, units='deg')
if tracker: if tracker:
tracker.send_message('Start Fixation') tracker.send_message('Start Stim')
for frameN in range(int(round(wait_time*60))): for frameN in range(int(round(wait_time*60))):
stim.draw() stim.draw()
@ -355,9 +355,11 @@ class SPtask(template.BaseExperiment):
self.experiment_window.color = self.necker_bg_color self.experiment_window.color = self.necker_bg_color
stim = psychopy.visual.ImageStim( stim = psychopy.visual.ImageStim(
self.experiment_window, self.experiment_window,
image=self.necker_file) image=self.necker_file, pos=(0.15,0.15))
stim.size *= self.necker_scale stim.size *= self.necker_scale
stim.setColor(self.necker_color, 'rgb255') stim.setColor(self.necker_color, 'rgb255')
stim2 = psychopy.visual.TextStim(
self.experiment_window, text='+', color=self.stim_color, height=self.fixation_size, units='deg')
responses = [] responses = []
if tracker: if tracker:
tracker.send_message('Start Stim') tracker.send_message('Start Stim')
@ -372,9 +374,10 @@ class SPtask(template.BaseExperiment):
direction = 'Center' direction = 'Center'
responses.append((response[0],direction,psychopy.core.getAbsTime())) responses.append((response[0],direction,psychopy.core.getAbsTime()))
if tracker: if tracker:
#print(response[0]) print('Direction: %s, Key: %s' % (direction, response[0]))
tracker.send_message('Direction: %s, Key: %s' % (direction, response[0])) tracker.send_message('Direction: %s, Key: %s' % (direction, response[0]))
stim.draw() stim.draw()
#stim2.draw()
self.experiment_window.flip() self.experiment_window.flip()
#print(responses) #print(responses)
return responses return responses

109
SaccadePursuitEyeTracking.py

@ -77,14 +77,14 @@ pursuit_time = [40, 20, 13.33]
# Necker Cube Parameters # Necker Cube Parameters
number_of_necker_trials = 1 number_of_necker_trials = 1
number_of_necker_blocks = 3 number_of_necker_blocks = 1
necker_time = 60 necker_time = 60
necker_color = [190, 190, 190] necker_color = [190, 190, 190]
necker_bg_color = [30, 30, 30] necker_bg_color = [30, 30, 30]
necker_scale = 0.5 necker_scale = 0.25
necker_file = os.path.join(image_directory, 'Necker1.tif') necker_file = os.path.join(image_directory, 'Necker1.tif')
necker_response_box_file = os.path.join(image_directory, 'ResponseBox5.tif') necker_response_box_file = os.path.join(image_directory, 'neckerInstruction.tif')
necker_response_box_scale = 0.22 necker_response_box_scale = 0.307
# Fixation Parameters # Fixation Parameters
number_of_fixation_trials = 1 number_of_fixation_trials = 1
@ -121,11 +121,12 @@ data_fields = [
# Add additional questions here # Add additional questions here
questionaire_dict = { questionaire_dict = {
'Run Target Fixation': True,
'Run Smooth Pursuit': True, 'Run Smooth Pursuit': True,
'Run Saccade': True, 'Run Saccade': True,
'Run Anti-Saccade': True, 'Run Anti-Saccade': True,
# 'Run Necker Cube': False, 'Run Necker Cube': True,
'Run Binocular Rivalry': True, 'Run Binocular Rivalry': False,
} }
questionaire_order = [ questionaire_order = [
@ -134,10 +135,11 @@ questionaire_order = [
'Timepoint', 'Timepoint',
'Experimenter Initials', 'Experimenter Initials',
'Gender', 'Gender',
'Run Target Fixation',
'Run Smooth Pursuit', 'Run Smooth Pursuit',
'Run Saccade', 'Run Saccade',
'Run Anti-Saccade', 'Run Anti-Saccade',
# 'Run Necker Cube', 'Run Necker Cube',
'Run Binocular Rivalry', 'Run Binocular Rivalry',
] ]
@ -153,8 +155,10 @@ instruct_text = [
# 'phase, you will see a cube, and are to respond as ' # 'phase, you will see a cube, and are to respond as '
# 'indicated.' # 'indicated.'
# '\n\n' # '\n\n'
'Do not move your head during the trials of this ' #'Do not move your head during the trials of this '
'experiment.\n\nMove only your eyes.\n'), #'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' # 'You will be offered rests between sections.\n\n'
# 'Press any key to continue.'), # 'Press any key to continue.'),
] ]
@ -305,14 +309,16 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
sys.exit(1) sys.exit(1)
conditions = [] conditions = []
if self.experiment_info['Run Target Fixation']:
conditions.append('Fixation')
if self.experiment_info['Run Smooth Pursuit']: if self.experiment_info['Run Smooth Pursuit']:
conditions.append('Pursuit') conditions.append('Pursuit')
if self.experiment_info['Run Saccade']: if self.experiment_info['Run Saccade']:
conditions.append('Saccade') conditions.append('Saccade')
if self.experiment_info['Run Anti-Saccade']: if self.experiment_info['Run Anti-Saccade']:
conditions.append('AntiSaccade') conditions.append('AntiSaccade')
# if self.experiment_info['Run Necker Cube']: if self.experiment_info['Run Necker Cube']:
# conditions.append('Necker') conditions.append('Necker')
if self.experiment_info['Run Binocular Rivalry']: if self.experiment_info['Run Binocular Rivalry']:
conditions.append('Rivalry') conditions.append('Rivalry')
@ -360,6 +366,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
numBlocks = self.number_of_saccade_blocks numBlocks = self.number_of_saccade_blocks
numTrials = self.number_of_saccade_trials numTrials = self.number_of_saccade_trials
elif condition == 'Fixation': elif condition == 'Fixation':
self.display_fixation_instructions()
numBlocks = self.number_of_fixation_blocks numBlocks = self.number_of_fixation_blocks
numTrials = self.number_of_fixation_trials numTrials = self.number_of_fixation_trials
elif condition == 'Pursuit': elif condition == 'Pursuit':
@ -367,7 +374,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
numBlocks = self.number_of_pursuit_blocks numBlocks = self.number_of_pursuit_blocks
numTrials = self.number_of_pursuit_trials numTrials = self.number_of_pursuit_trials
elif condition == 'Necker': elif condition == 'Necker':
self.display_text_screen(text=necker_instruct_text, self.display_text_screen(#text=necker_instruct_text,
image_file=self.necker_response_box_file, image_file=self.necker_response_box_file,
image_scale=self.necker_response_box_scale) image_scale=self.necker_response_box_scale)
numBlocks = self.number_of_necker_blocks numBlocks = self.number_of_necker_blocks
@ -412,17 +419,17 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
self.tracker.send_status(status) self.tracker.send_status(status)
# psychopy.sound.Sound('C').play() # psychopy.sound.Sound('C').play()
self.tracker.start_recording() self.tracker.start_recording()
if condition == 'Pursuit' and trial_num == 0: # if condition == 'Pursuit' and trial_num == 0:
self.display_fixation( # self.display_fixation(
self.fixation_trial_time, self.tracker) # self.fixation_trial_time, self.tracker)
data = self.run_trial( data = self.run_trial(
trial, block_num, trial_num, self.tracker) trial, block_num, trial_num, self.tracker)
self.tracker.stop_recording() self.tracker.stop_recording()
# psychopy.sound.Sound('C').play() # psychopy.sound.Sound('C').play()
else: else:
if condition == 'Pursuit' and trial_num == 0: # if condition == 'Pursuit' and trial_num == 0:
self.display_fixation( # self.display_fixation(
self.fixation_trial_time, self.tracker) # self.fixation_trial_time, self.tracker)
data = self.run_trial(trial, block_num, trial_num) data = self.run_trial(trial, block_num, trial_num)
data.update({'Condition': condition}) data.update({'Condition': condition})
self.send_data(data) self.send_data(data)
@ -437,6 +444,7 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
self.display_fixation(0.5, []) self.display_fixation(0.5, [])
self.save_data_to_csv() self.save_data_to_csv()
# Display text to prepare for next block
if block_num + 1 != numBlocks: if block_num + 1 != numBlocks:
self.display_break() self.display_break()
@ -636,6 +644,42 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
return keys 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( def display_rivalry_instructions(
self, anti=False, bg_color=[0, 0, 0], wait_for_input=True, self, anti=False, bg_color=[0, 0, 0], wait_for_input=True,
image_file=[], image_scale=0.25, **kwargs): image_file=[], image_scale=0.25, **kwargs):
@ -704,8 +748,8 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
lineColor=color, lineWidth=lineSize) lineColor=color, lineWidth=lineSize)
textObject.draw() textObject.draw()
stim1.draw() #stim1.draw()
stim2.draw() #stim2.draw()
stimBorder1.draw() stimBorder1.draw()
stimBorder2.draw() stimBorder2.draw()
stimFix1_top.draw() stimFix1_top.draw()
@ -727,10 +771,35 @@ class EyeTrackingSaccadePursuit(SaccadePursuit.SPtask):
self.experiment_window.flip() self.experiment_window.flip()
keys = None keys = None
showImages = False
if wait_for_input: if wait_for_input:
psychopy.core.wait(.2) # Prevents accidental key presses psychopy.core.wait(.2) # Prevents accidental key presses
keys = psychopy.event.waitKeys() keys = psychopy.event.waitKeys()
while keys != ['space']: 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() keys = psychopy.event.waitKeys()
self.experiment_window.flip() self.experiment_window.flip()

849
Screening.py

@ -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()

63
calibrate.py

@ -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()

546
eyelinker.py

@ -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()

19
template.py

@ -121,9 +121,9 @@ class BaseExperiment(object):
""" """
overwrite_dlg = psychopy.gui.Dlg( overwrite_dlg = psychopy.gui.Dlg(
'New File?', labelButtonOK='New File', 'Overwrite?', labelButtonOK='Overwrite',
labelButtonCancel='Cancel', screen=0) labelButtonCancel='New File', screen=0)
overwrite_dlg.addText('File already exists.') overwrite_dlg.addText('File already exists. Overwrite?')
overwrite_dlg.show() overwrite_dlg.show()
return overwrite_dlg.OK return overwrite_dlg.OK
@ -181,7 +181,7 @@ class BaseExperiment(object):
if os.path.isfile(filename + '.txt'): if os.path.isfile(filename + '.txt'):
if self.overwrite_ok is None: if self.overwrite_ok is None:
self.overwrite_ok = self._confirm_overwrite() self.overwrite_ok = self._confirm_overwrite()
if self.overwrite_ok: if not self.overwrite_ok:
# If the file exists make a new filename # If the file exists make a new filename
i = 1 i = 1
new_filename = filename + '(' + str(i) + ')' new_filename = filename + '(' + str(i) + ')'
@ -189,8 +189,6 @@ class BaseExperiment(object):
i += 1 i += 1
new_filename = filename + '(' + str(i) + ')' new_filename = filename + '(' + str(i) + ')'
filename = new_filename filename = new_filename
else:
raise Exception('Filename Error')
filename = filename + '.txt' filename = filename + '.txt'
@ -217,7 +215,7 @@ class BaseExperiment(object):
if os.path.isfile(data_filename + '.csv'): if os.path.isfile(data_filename + '.csv'):
if self.overwrite_ok is None: if self.overwrite_ok is None:
self.overwrite_ok = self._confirm_overwrite() self.overwrite_ok = self._confirm_overwrite()
if self.overwrite_ok: if not self.overwrite_ok:
# If the file exists and we can't overwrite make a new filename # If the file exists and we can't overwrite make a new filename
i = 1 i = 1
new_filename = data_filename + '(' + str(i) + ')' new_filename = data_filename + '(' + str(i) + ')'
@ -225,8 +223,6 @@ class BaseExperiment(object):
i += 1 i += 1
new_filename = data_filename + '(' + str(i) + ')' new_filename = data_filename + '(' + str(i) + ')'
data_filename = new_filename data_filename = new_filename
else:
raise Exception('Filename Error')
self.experiment_data_filename = data_filename + '.csv' self.experiment_data_filename = data_filename + '.csv'
@ -378,7 +374,10 @@ class BaseExperiment(object):
bottomOfScreen = int(math.floor(-self.experiment_window.size[1]/2+sizey/2))+5 bottomOfScreen = int(math.floor(-self.experiment_window.size[1]/2+sizey/2))+5
imageObject.size = [sizex,sizey] imageObject.size = [sizex,sizey]
#imageObject.pos = (0,belowText) #imageObject.pos = (0,belowText)
imageObject.pos = (0,bottomOfScreen) if text:
imageObject.pos = (0,bottomOfScreen)
else:
imageObject.pos = (0,0)
imageObject.draw() imageObject.draw()
else: else:
textObject.draw() textObject.draw()

Loading…
Cancel
Save