-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathEyelinkStart.m
419 lines (366 loc) · 14.7 KB
/
EyelinkStart.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
function [P, window] = EyelinkStart(P, window, Name, inputDialog, FilePreamble, eyelinkconnected)
% EYELINKSTART performs startup routines for the EyeLink 1000 Plus
%
% [P, window] = EYELINKSTART(P, window, Name, inputDialog, FilePreamble)
%
% INPUT:
% 'P': struct with parameters. (all optional. struct can be empty)
% 'P.myWidth' & 'P.myHeight': Optional, (= Screen Resolution),
% 'P.BgColor': Background color, default: 0.5
% 'P.trackr.dummymode': 1=there's no real tracker connected (def.: 0)
% 'P.isET': 1(default) or 0. If 0, does nothing and returns.
% 'P.CalibLocations': Optional. Specify custom calibration grid.
% example:
% %x1,y1,[...],x9,y9 ;must be nine x-y pairs
% X = P.CenterX;
% A = P.PicWidth/2-20;
% Y = P.CenterY;
% B = P.PicHeight/2-20;
% P.CalibLocations = [X-A,Y-B, X,Y-B, X+A,Y-B,...
% X-A,Y, X,Y, X+A,Y,...
% X-A,Y+B, X,Y+B, X+A,Y+B];
% 'P.binocular': optional boolean. Prepare the tracker to do
% binocular measurements? Default is 0 (monocular).
%
% The following fields all have default values. They assure a
% consistent setup and should only be specified in case you have good
% reason to deviate from the default:
% 'P.binocular': boolean. 0 (default) = monocular
% 'P.heuristic_filter': char, default is '1 2' (as in DEFAULT.INI)
% 'P.active_eye': char, 'L' or 'R'
% 'P.pupil_size_diameter': char, 'AREA' (def.),'DIAMETER',..(see man)
% 'P.sampling_rate': int, 1000 (def.), 500, 250
% 'P.ellipse_mode': bool, 0 (def.) centroid, 1 is ellipse
% 'P.initial_thresholds': char, see SR programmers guide
% 'P.corneal_mode': bool, 1 (def.) use pupil-CR mode, 0 is pupil-only
% 'P.elcl_tt_power': float, Illuminator power. default is 2 (75%)
% 'P.eyelink_conf_table': use specific configuration table (def.
% 'MTABLER')
%
% 'window': PTB window pointer
% 'Name': char. Filename to which data are written. Check eyelink naming
% conventions. If inputDialog is 1, you're prompted for a
% filename, with 'Name' entered as default. Else, Name is simply
% taken as filename.
% 'inputDialog': bool. Ask the user whether the filename is good (pop-up)?
% Default is 0 (don't ask).
% 'FilePreamble': Optional, specify an experiment-specific preamble in
% the edf-files. Defaults to:'''Eyetracking Dataset AE
% Busch WWU Muenster <current working directory>'''
% Always needs to start & end with <'''> (3x)
% 'eyelinkconnected': Optional boolean. If present, it overrides P.isET.
%
%
% OUTPUT:
% 'P.el': Eyelink object. Necessary for interfacing the eyelink.
% 'P.eye_used': Which eye is being tracked?
%
% Wanja Moessing, June 2016
% WM: added FilePreamble on 26/09/2016
% WM: added custom calibrationpoints on 08/09/2017
% WM: add defaults and new documentation (22.08.2018)
% WM: add additional defaults to circumvent LASTRUN.INI 29/07/2019
% Copyright (C) 2016-2019 Wanja Mössing
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
%% Parse Input %%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if exist('eyelinkconnected', 'var')
if eyelinkconnected
P.isET = 1;
else
P.isET = 0;
end
end
if ~isfield(P, 'isET')
P.isET = 1;
elseif P.isET == 0
disp('No Eyelink connected - no connection started.');
return
end
if ~isfield(P, 'binocular')
P.binocular = 0;
end
if ~isfield(P, 'heuristic_filter')
P.heuristic_filter = '1 2';
end
if ~isfield(P, 'active_eye')
P.active_eye = 'R';
end
if ~isfield(P, 'pupil_size_diameter')
P.pupil_size_diameter = 0;
end
if ~isfield(P, 'sampling_rate')
P.sampling_rate = 1000; %(250, 500, 1000, 2000)
end
if ~isfield(P, 'ellipse_mode')
P.ellipse_mode = 0;
end
if ~isfield(P, 'initial_thresholds')
P.initial_thresholds = '66, 40, 66, 150, 150';
disp('Image processing thresholds set to default.');
end
if ~isfield(P, 'corneal_mode')
P.corneal_mode = 1;
end
if ~isfield(P, 'elcl_tt_power')
P.elcl_tt_power = 2;
end
if ~isfield(P, 'eyelink_conf_table')
P.eyelink_conf_table = 'MTABLER';
end
Screen(window, 'BlendFunction', GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if ~isfield(P, 'myWidth')
myres = Screen('Resolution', window);
P.myWidth = myres.width;
end
if ~isfield(P, 'myHeight')
myres = Screen('Resolution', window);
P.myHeight = myres.height;
end
if ~isfield(P, 'BgColor')
P.BgColor = 0.5;
end
if isfield(P, 'trackr')
if ~isfield(P.trackr, 'dummymode')
P.trackr.dummymode = 0;
end
else
P.trackr.dummymode = 0;
end
if isempty(regexp(Name,'.edf', 'once'))
if length(Name) > 8
error('EDF filename is too long.')
else
Name = strcat(Name,'.edf');
end
elseif length(Name)>12 %8+4 ('.edf')
Screen('CloseAll');
error('EDF filename is too long.')
end
if ~exist('inputDialog','var')
inputDialog = 0;
elseif isempty('inputDialog')
inputDialog = 0;
end
if ~isfield(P, 'CalibLocations')
P.CalibLocations = [];
end
if ~exist('FilePreamble','var')
[~, currentFolder, ~] = fileparts(pwd);
FilePreamble = ['''Eyetracking Dataset AE Busch WWU Muenster Experiment: ',currentFolder,''''];
elseif isempty('FilePreamble')
[~, currentFolder, ~] = fileparts(pwd);
FilePreamble = ['''Eyetracking Dataset AE Busch WWU Muenster Experiment: ',currentFolder,''''];
end
%make sure filepreamble is in the right format
quoteloc = regexp(FilePreamble,'''');
ErrText = ['The specified EDF Filename does not match the desired format. '...
'Make sure that it`s one string with single quotation marks at'...
' beginning and end. To include single quotation marks in a string'...
' add two additional single quotation marks (so 3 in total).'];
if ~isempty(quoteloc)
if ~quoteloc(1)==1 || ~quoteloc(2)==length(FilePreamble)
error(ErrText)
end
else
error(ErrText)
end
% get name for Eyetracker output
if inputDialog
prompt = {'Please enter a filename for the Eyetracker EDF output.'};
P.trackr.edfFile = inputdlg(prompt,'Create EDF file',1,{Name});
P.trackr.edfFile = P.trackr.edfFile{1};
else
P.trackr.edfFile = Name;
end
%name can only include alpha+digit+underscore------- -
if length(Name(1:strfind(Name,'.edf')-1))>8 || ~isempty(Name(regexp(Name(1:strfind(Name,'.edf')-1),'\W')))
fprintf(2,'Eyelink EDF File does not match the requested format.\n Only [0-9], [A-z] and [_] are allowed.\n');
CloseAndCleanup;
end
P.el = EyelinkInitDefaults(window);
% Open connection and throw an error if unsuccessful
if ~EyelinkInit(P.trackr.dummymode)
fprintf('Eyelink Init aborted.\n');
fprintf('Try to restart the Eyelink host PC.\n');
CloseAndCleanup; % cleanup function
return;
end
[~,P.trackr.ETversion] = Eyelink('GetTrackerVersion'); %Store EL-Software version
% set EDF file contents using the file_sample_data and
% file-event_filter commands
% set link data thtough link_sample_data and link_event_filter
Eyelink('command', 'file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,AREA,BLINK,MESSAGE,BUTTON,INPUT');
Eyelink('command', 'link_event_filter = LEFT,RIGHT,FIXATION,SACCADE,AREA,BLINK,MESSAGE,BUTTON,INPUT');
% check the software version
% add "HTARGET" to record possible target data for EyeLink Remote
if P.trackr.ETversion >=4
Eyelink('command', 'file_sample_data = LEFT,RIGHT,GAZE,HREF,AREA,HTARGET,GAZERES,STATUS,INPUT');
Eyelink('command', 'link_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA,HTARGET,STATUS,INPUT');
else
Eyelink('command', 'file_sample_data = LEFT,RIGHT,GAZE,HREF,AREA,GAZERES,STATUS,INPUT');
Eyelink('command', 'link_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS,INPUT');
end
% Open File on EyeLink Host
if ~P.trackr.dummymode
i = Eyelink('Openfile', P.trackr.edfFile);
if i~=0
fprintf('Cannot create EDF file ''%s'' ', P.trackr.edfFile);
Eyelink( 'Shutdown');
Screen('CloseAll');
return;
end
end
% write preamble to edf file
preamble = ['add_file_preamble_text ',FilePreamble];
Eyelink('command', preamble);
% tell tracker the screen resolution
Eyelink('command','screen_pixel_coords = %ld %ld %ld %ld', 0, 0, P.myWidth-1, P.myHeight-1);
Eyelink('message', 'DISPLAY_COORDS %ld %ld %ld %ld', 0, 0, P.myWidth-1, P.myHeight-1);
Eyelink('command', 'calibration_type = HV9'); %9-Pt Grid calibration
% modify calibration and validation target locations
if ~isempty(P.CalibLocations)
Eyelink('command', 'generate_default_targets = NO');
T = P.CalibLocations;
Eyelink('command','calibration_samples = 10');
Eyelink('command','calibration_sequence = 0,1,2,3,4,5,6,7,8,0');
Eyelink('command','calibration_targets = %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d',...
T(9),T(10), T(3),T(4), T(15),T(16), T(7),T(8), T(11),T(12),...
T(1),T(2), T(5),T(6), T(13),T(14), T(17),T(18));
Eyelink('command','validation_samples = 10');
Eyelink('command','validation_sequence = 0,1,2,3,4,5,6,7,8,0');
Eyelink('command','validation_targets = %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d %d,%d',...
T(9),T(10), T(3),T(4), T(15),T(16), T(7),T(8), T(11),T(12),...
T(1),T(2), T(5),T(6), T(13),T(14), T(17),T(18));
else
Eyelink('command', 'generate_default_targets = YES');
end
%% build setup defaults, to avoid having settings of a previous measurement
% always initiate the right Eye (just to have a default)
if strcmp(P.active_eye, 'R')
Eyelink('command', 'active_eye = RIGHT');
elseif strcmp(P.active_eye, 'L')
Eyelink('command', 'active_eye = LEFT');
end
% track monocular or binocular?
if P.binocular
Eyelink('command', 'binocular_enabled = YES');
else
Eyelink('command', 'binocular_enabled = NO');
end
% Use heuristic filtering?
Eyelink('command', 'heuristic_filter = %s', P.heuristic_filter);
% Convert pupil size to diameter?
if P.pupil_size_diameter
Eyelink('command', 'pupil_size_diameter = DIAMETER');
else
Eyelink('command', 'pupil_size_diameter = AREA');
end
% set default sampling rate
Eyelink('command', 'sample_rate = %d', P.sampling_rate);
% set centroid mode
if P.ellipse_mode
Eyelink('command', 'use_ellipse_fitter = YES');
else
Eyelink('command', 'use_ellipse_fitter = NO');
end
% set default graphical thresholds
Eyelink('command', 'initial_thresholds = %s', P.initial_thresholds);
% set to use pupil-corneal mode and not just pupil-only mode
if P.corneal_mode
Eyelink('command', 'corneal_mode = YES');
else
Eyelink('command', 'corneal_mode = NO');
end
% select MTABLER as default configuration table (monocular 35mm)
Eyelink('command', 'elcl_select_configuration %s', P.eyelink_conf_table);
% set Illuminator to 75%
Eyelink('command', 'elcl_tt_power = %d', P.elcl_tt_power);
%% start calibration
% make sure we're still connected.
if Eyelink('IsConnected')~=1 && P.trackr.dummymode == 0
fprintf('not connected, clean up\n');
Eyelink('Shutdown');
Screen('CloseAll');
return;
end
% Calibrate the eye tracker
% setup the proper calibration foreground and background colors
if length(P.BgColor)==3
P.el.backgroundcolor = P.BgColor;
elseif length (P.BgColor)==1
P.el.backgroundcolour = [P.BgColor P.BgColor P.BgColor];
end
P.el.calibrationtargetcolour = [0 0 0];
% parameters are in frequency, volume, and duration
% set the second value in each line to 0 to turn off the sound
P.el.cal_target_beep=[600 0.5 0.05];
P.el.drift_correction_target_beep=[600 0.5 0.05];
P.el.calibration_failed_beep=[400 0.5 0.25];
P.el.calibration_success_beep=[800 0.5 0.25];
P.el.drift_correction_failed_beep=[400 0.5 0.25];
P.el.drift_correction_success_beep=[800 0.5 0.25];
% you must call this function to apply the changes from above
EyelinkUpdateDefaults(P.el);
% Hide the mouse cursor;
Screen('HideCursorHelper', window);
EyelinkDoTrackerSetup(P.el);
% The SR-example code continues with restarting recording each
% trial. I don't really see a reason why we shouldn't have
% continuous data with event-markers. So I'll just start recording
% once and send messages.
% put tracker in idle mode and wait 50ms, then really start it.
Eyelink('Message', 'SETUP_FINISHED');
Eyelink('Command', 'set_idle_mode');
Eyelink('Command', 'clear_screen 0'); %clear ET-host screen
WaitSecs(0.05);
% this can optionally take four boolean input values, specifiying
% the datatypes recorded (file_samples, file_events, link_samples, link_events)
%Eyelink('StartRecording',P.trackr.capture(1),P.trackr.capture(2),P.trackr.capture(3),P.trackr.capture(4));
Eyelink('Command', 'set_idle_mode');
WaitSecs(0.05);
Eyelink('StartRecording');
% record a few samples before we actually start displaying
% otherwise you may lose a few msec of data
WaitSecs(0.1);
%set eye_used for gaze controla
P.eye_used = Eyelink('EyeAvailable'); % get eye that's tracked for gaze-control
if P.eye_used == P.el.BINOCULAR % if both eyes are tracked
P.eye_used = P.el.LEFT_EYE; % use left eye
end
% to activate parallel port readout without modifying the FINAL.INI on the
% eyelink host pc, uncomment these lines
%%tyical settings for straight-through TTL cable (data pins -> data pins)
Eyelink('Command','write_ioport 0xA 0x20');
Eyelink('Command','create_button 1 8 0x01 0');
Eyelink('Command','create_button 2 8 0x02 0');
Eyelink('Command','create_button 3 8 0x04 0');
Eyelink('Command','create_button 4 8 0x08 0');
Eyelink('Command','create_button 5 8 0x10 0');
Eyelink('Command','create_button 6 8 0x20 0');
Eyelink('Command','create_button 7 8 0x40 0');
Eyelink('Command','create_button 8 8 0x80 0');
Eyelink('Command','input_data_ports = 8');
Eyelink('Command','input_data_masks = 0xFF');
%%tyical settings for crossover TTL cable (data pins -> status pins)
%Eyelink('Command','write_ioport 0xA 0x0');
%Eyelink('Command','create_button 1 9 0x20 1');
%Eyelink('Command','create_button 2 9 0x40 1');
%Eyelink('Command','create_button 3 9 0x08 1');
%Eyelink('Command','create_button 4 9 0x10 1');
%Eyelink('Command','create_button 5 9 0x80 0');
%Eyelink('Command','input_data_ports = 9');
%Eyelink('Command','input_data_masks = 0xFF');
Eyelink('Message', '>EndOfEyeLinkStart');