forked from jooh/matlab-studytools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStudy.m
More file actions
381 lines (369 loc) · 15.9 KB
/
Copy pathStudy.m
File metadata and controls
381 lines (369 loc) · 15.9 KB
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
classdef Study < hgsetget & dynamicprops
% Master class for running cognitive experiments
properties
quietppt = 1;
debug = 0; % shorthand for windowed=1, verbose=1
verbose = 0;
windowed = 0;
screen = [];
window = [];
rect = [];
totdist = 500;
screenwidth = 380;
keyboardkeys = {'v','b','n','m'};
buttonboxkeys = [28 26 22 14];
validkeys = [];
location = 'pc';
scanobj = ScanObjNull;
psychaudio = [];
samplerate = [];
naudiochannels= [];
resolution = [];
oldresolution = struct;
px2deg = [];
deg2px = [];
bgcolor = [128 128 128];
colors = struct('white',[],'black',[],'grey',[]);
textpar = struct('font','tahoma','size',20,'style',0,...
'vspacing',1.4,'color',[1 1 1],'txtwrap',50);
xcenter = [];
ycenter = [];
conditions = Condition([]); % main events during runtrials
precondition = Condition([]); % run before main trial loop
postcondition = Condition([]); % run after main trial loop
trials = []; % constructed by initialisetrials (can be subbed)
printfun =[];
logfile = '';
timestart = []; % First scan/GetSecs time stamp in run
timecontrol = []; % SecondTiming or ScanTiming instance
ET_serial = ''; % handle to eyetracking serial port object
eyetrack = 0;
rundur = []; % estimated run duration from initialisetrials
feedback = 0; % flag for displaying performance feedback
score; % summary descriptives in subclass
forcesync = 1;
end
methods
function s = Study(varargin)
if nargin==0
% initialisation of inherited objects etc
return
end
s = varargs2structfields(varargin,s);
end
function openwindow(self)
if self.debug
self.verbose = 1;
self.windowed = 1;
end
if self.verbose
self.printfun = @display; %(x) fprintf([x '\n']);
else
self.printfun = @(x)x;
end
self.printfun('openwindow')
if isempty(self.logfile)
self.logfile = fullfile(tempdir,sprintf('study_%s.txt',...
datestr(now,'yyyy_mm_dd_HHMM')));
end
KbName('UnifyKeyNames');
% always convert key presses to something recognisable
self.keyboardkeys = KbName(self.keyboardkeys);
switch self.location
case {'pc','mba'}
screens = Screen('Screens');
self.screen = screens(ceil(length(screens)/2));
self.totdist = '500';
self.screenwidth = '380';
% assume you've entered a cell array of keys
self.validkeys = self.keyboardkeys;
self.printfun('running in PC mode');
% default to native for our Dells
if isempty(self.resolution)
if strcmp(self.location,'pc')
self.resolution = [1280 1024];
else
self.resolution = [1440 900];
end
end
case 'mri'
self.screen = 0;
self.totdist = 913;
self.screenwidth = 268;
self.validkeys = self.buttonboxkeys;
self.scanobj = actxserver('MRISync.ScannerSync');
self.printfun('running in scanner mode');
% default to projector native
if isempty(self.resolution)
self.resolution = [1024 768];
end
case 'mrilcd'
self.screen = 0;
self.totdist = 1565;
self.screenwidth = 698.4;
self.validkeys = self.buttonboxkeys;
self.scanobj = actxserver('MRISync.ScannerSync');
self.printfun('running in scanner mode (new LCD)');
% default to lcd native
if isempty(self.resolution)
self.resolution = [1920 1080];
end
case 'mrilcd43'
self.screen = 0;
self.totdist = 1565;
self.screenwidth = 522;
self.validkeys = self.buttonboxkeys;
self.scanobj = actxserver('MRISync.ScannerSync');
self.printfun('running in scanner mode (new LCD, 4:3 aspect)');
% I think we usually go with this res here
if isempty(self.resolution)
self.resolution = [1024 768];
end
otherwise
error('unrecognised location: %s',self.location)
end
% make sure no one has hacked their way around sync problems
Screen('Preference','SkipSyncTests',double(~self.forcesync));
self.px2deg = (2 * atan(self.screenwidth/2/self.totdist) * ...
(180/pi)) / self.resolution(1);
% And the reciprocal
self.deg2px = self.px2deg^-1;
% you are probably entering greyscale values in [0 1] range
if self.bgcolor <= 1
self.bgcolor = uint8(self.bgcolor * 255);
end
% Figure out a text color
if mean(self.bgcolor) > 200
% black on light backgrounds
self.textpar.color = [0 0 0];
else
% white on dark backgrounds
self.textpar.color = [255 255 255];
end
if self.eyetrack
self.ET_serial = serial('COM1','BaudRate',115200,...
'Databits',8);
fopen(self.ET_serial);
set(self.ET_serial,'timeout',.1);
wstate=warning('off',...
'MATLAB:serial:fgetl:unsuccessfulRead');
fprintf(self.ET_serial,'ET_STP');
fprintf(self.ET_serial,'ET_CLR');
fprintf(self.ET_serial,'ET_REC');
end
% open window
if self.quietppt
Screen('Preference','Verbosity',0);
else
self.printfun('---------- ---------- ----------')
self.printfun('---------- PPT GOOBLEDEGOOK ----------')
self.printfun('---------- ---------- ----------')
end
if self.windowed
self.oldresolution = Screen('Resolution',self.screen);
res = [0 0 self.resolution];
[self.window self.rect] = Screen('OpenWindow',...
self.screen,self.bgcolor,res);
else
self.oldresolution = Screen('Resolution',self.screen,...
self.resolution(1),self.resolution(2));
[self.window self.rect] = Screen('OpenWindow',...
self.screen,self.bgcolor);
end
if ~self.quietppt
self.printfun('---------- ---------- ----------')
self.printfun('---------- / PPT GOOBLEDEGOOK ----------')
self.printfun('---------- ---------- ----------')
end
% set default bgcolor
%Screen(self.window,'FillRect',self.bgcolor);
% screen center
self.xcenter = self.rect(3)/2;
self.ycenter = self.rect(4)/2;
% enable alpha blending
Screen('BlendFunction',self.window,GL_SRC_ALPHA,...
GL_ONE_MINUS_SRC_ALPHA);
% priority (0 for normal, 2 locks all non-Matlab)
Priority(1);
% basic color
self.colors.white = WhiteIndex(self.window);
self.colors.black = BlackIndex(self.window);
self.colors.grey = ceil((...
self.colors.white+self.colors.black)/2);
% and fonts
Screen('TextFont',self.window,self.textpar.font);
Screen('TextSize',self.window,self.textpar.size);
Screen('TextStyle',self.window,self.textpar.style);
Screen('TextColor',self.window,self.textpar.color);
HideCursor;
Screen(self.window,'Flip');
% audio configuration
InitializePsychSound;
if PsychPortAudio('GetOpenDeviceCount') == 1
PsychPortAudio('Close',0);
end
if ~isempty(self.samplerate)
if ispc
audiodevices = PsychPortAudio('GetDevices',2);
outdevice = strcmp('Microsoft Sound Mapper - Output',{audiodevices.DeviceName});
hd.outdevice = 3;
self.psychaudio = PsychPortAudio('Open',audiodevices(outdevice).DeviceIndex,[],[],self.samplerate,self.naudiochannels);
else
self.psychaudio = PsychPortAudio('Open',[],[],[],self.samplerate,self.naudiochannels);
end
end
end
function closewindow(self)
% for some reason Screen('Close',self.window) doesn't work
self.printfun('closewindow')
Screen('CloseAll');
if self.eyetrack
fprintf(self.ET_serial,'ET_STP');
outfile = sprintf('D:\\StudyData_%s.idf',...
datestr(now,'yyyymmdd_HHMM_SS'));
fprintf(self.ET_serial,['ET_SAV "' outfile '"']);
if ~isempty(self.ET_serial)
fclose(self.ET_serial);
end
end
end
function runtrials(self,trialorder)
self.printfun('runtrials')
% need to reinit since this gets destroyed easily
self.initialisescanobj;
if ~isempty(self.trials)
self.printfun('existing trials will be discarded')
end
ntrials = length(trialorder);
self.printfun(sprintf('running %d trials',ntrials));
self.initialisetrials(trialorder);
self.printfun(sprintf('logfile: %s',self.logfile));
diary(self.logfile);
% run precon - instructions, calibration, wait trigger etc
if ~isempty(self.precondition)
self.printfun('running precondition')
self.precondition.call;
end
self.printfun(sprintf(...
'TRIAL\t\tTIME\t\tCYCLE\t\tCONDITION\t\tRESPONSE\t\t'));
% start second / scan timer (maybe count dummies)
self.timestart = self.timecontrol.begin;
for t = 1:ntrials
fprintf(self.ET_serial,sprintf('ET_REM %s.png',...
self.trials(t).condition.name));
self.trials(t).starttime = self.timecontrol.check;
self.trials(t).condition.call;
% update the central trial log with the new result from
% the condition instance
self.trials(t) = catstruct(self.trials(t),...
self.trials(t).condition.result(...
self.trials(t).condition.ncalls));
self.scoretrial(t);
self.printfun(sprintf(...
'%04d\t %8.3f\t %8.3f\t %10s\t %10s',t,...
self.trials(t).time(1)-self.trials(1).time(1),...
self.trials(t).time(1)-...
self.trials(max([t-1 1])).time(1),...
self.trials(t).condition.name,...
mat2str(cell2mat(self.trials(t).response))));
% if you have set the soa field to a value greater than the
% sum total durations this will control lag
self.timecontrol.waituntil(self.timestart + ...
self.trials(t).timing);
end
if ~isempty(self.postcondition)
self.printfun('running postcondition')
self.postcondition.call;
end
diary('off');
self.printfun('finished log');
self.printfun('DONE');
end
function initialisetrials(self,trialorder)
self.printfun('initialisetrials')
% initialise log files in each condition
[coninds,counts] = count_unique(trialorder);
% prepare log struct arrays inside each condition
for c = 1:length(coninds)
self.conditions(coninds(c)).preparelog(counts(c));
end
% and a global log file
ntrials = length(trialorder);
% NB must be in alphabetical order to work with catstruct
self.trials = struct('condition',...
num2cell(self.conditions(trialorder)),'endtime',[],...
'response',[],'responsetime',[],'score',[],'starttime',[],...
'time',[],'timing',...
num2cell(cumsum([self.conditions(trialorder).soa])));
% This loop sets up the cell arrays etc with appropriate
% nevents for each trial
for t = 1:length(self.trials)
self.trials(t).response = self.trials(t).condition.response;
self.trials(t).responsetime = self.trials(t).response;
self.trials(t).time = self.trials(t).condition.time;
end
% And finally, a global global result file for broad
% descriptives across trials (computed by scoretrial)
self.initialisescore(trialorder);
self.rundur = self.trials(end).timing;
t_end = self.trials(end).timing + ...
self.trials(end).condition.soa;
if isinf(t_end) || isnan(t_end)
self.printfun('run duration estimate not possible');
else
self.printfun(sprintf('run duration: %.2f %s',...
t_end,self.timecontrol.units));
end
end
function initialisescanobj(self)
err = invoke(self.scanobj,'Initialize','');
assert(~err,'Keithley error');
invoke(self.scanobj,'SetTimeout',60e3);
invoke(self.scanobj,'SetMSPerSample',2);
end
function res = exportstatic(self)
% export data in static struct form
res = get(self);
res.conditions = get(res.conditions);
for t = 1:length(res.trials)
res.trials(t).condition = get(res.trials(t).condition);
res.trials(t).condition.studyfield = [];
res.trials(t).condition.result = [];
res.trials(t).condition.timecontrol = [];
end
if ~isempty(res.precondition)
res.precondition = get(res.precondition);
end
if ~isempty(res.postcondition)
res.postcondition = get(res.postcondition);
end
if isa(self.timecontrol,'ScanTiming')
% update TR estimate
self.timecontrol.estimatetr;
end
res.timecontrol = get(res.timecontrol);
% strip function handles since these can cause crashes
handles = structfun(@(x)isa(x,'function_handle'),res);
if any(handles)
fns = fieldnames(res)';
for f = fns(handles)
res.(f{1}) = [];
end
end
% serial port object also doesn't save well
res.ET_serial = [];
% scanobj as NAME of class rather than class instance itself
res.scanobj = class(res.scanobj);
end
function scoretrial(self,t)
% do nothing
end
function initialisescore(self,trials)
% no score
end
end
%methods (Abstract)
%scoretrial(self,t)
%initialisescore(self)
%end
end