diff --git a/AwaitGoodFixation.m b/AwaitGoodFixation.m index bfdee41..ba50d00 100644 --- a/AwaitGoodFixation.m +++ b/AwaitGoodFixation.m @@ -1,10 +1,13 @@ function [exp_params, hadToRecalibrate] = AwaitGoodFixation(exp_params, ... - window, windowRect, fixationScreen, getKeyInput) + window, windowRect, ... + fixationScreen, fixationSourceRect, fixationDestRect,... + getKeyInput) %AWAITGOODFIXATION wait for good fixation and recalibrate if necessary goodFixation = false; +hadToRecalibrate = false; while(~goodFixation) goodFixation = fixationPoint(window, fixationScreen, ... - windowRect(3) / 2, windowRect(4) / 2, ... + fixationSourceRect, fixationDestRect, ... exp_params.fixation_threshold, exp_params.fixation_time, ... exp_params.fixation_timeout); @@ -48,18 +51,19 @@ end function resultcode = fixationPoint(window, fixationScreen, ... - fixX, fixY, fixAccuracy, fixTime, timeout) -%should probably add in some keyboard awareness here, in case we need to -%cancel or pause or something during fixation. -Screen('DrawTexture',window,fixationScreen); + fixationSourceRect, fixationDestRect, ... + fixAccuracy, fixTime, timeout) +Screen('DrawTexture',window,fixationScreen, fixationSourceRect, fixationDestRect); Screen('flip',window); fixOnTime = GetSecs; +fixX = (fixationDestRect(1) + fixationDestRect(3)) / 2; +fixY = (fixationDestRect(2) + fixationDestRect(4)) / 2; resultcode = 0; firstGoodTime = -1; lastGoodTime = -1; while (GetSecs - fixOnTime < timeout) && (resultcode < 1) - fsample = eyelink('NewestFloatSample'); + fsample = Eyelink('NewestFloatSample'); if isstruct(fsample) gx = max(fsample.gx); %gaze position in pixels - I think whichever eye is not tracked is -32768, so this should pull out the useful position gy = max(fsample.gy); diff --git a/lib/functional/curry.m b/lib/functional/curry.m new file mode 100644 index 0000000..49bd507 --- /dev/null +++ b/lib/functional/curry.m @@ -0,0 +1,92 @@ + +function func = curry(varargin) +% FUNC = CURRY(N, F, ARGS) +% FUNC = CURRY(F, ARGS) +% Return a curried function of F. If N is given, then N arguments are +% expected for F (otherwise, N is taken to be NARGIN(F)). If N is +% negative, then |N| or more arguments are accepted (but F is invoked as +% soon as there are at least N arguments). ARGS are counted in N. If +% less than N arguments are supplied in total, then a *new* curried +% function involving all arguments so far is returned. +% +% FUNC = CURRY() +% This actually just returns CURRY and is a sort of `base case' for +% currying functions. +% +% FUNC = CURRY(N) +% This returns a function that waits for F to invoke CURRY(N, F, ...). +% + + if nargin == 0 + func = @curry; + return; + end + + arg = varargin{1}; + varargin(1) = []; + + if isnumeric(arg) + many = arg < 0; + params = abs(arg); + if isempty(varargin) + callback = []; + else + callback = varargin{1}; + varargin(1) = []; + end + elseif islambda(arg) + many = true; + params = nargin(arg); + callback = arg; + else + error('Curry:Arguments', 'First argument must be either number of arguments or a function.'); + end + args = varargin; + % If not many, then we also know params but not necessarily callback. + if ~many + func = genExactFunc(callback, params, args); + else + func = genManyFunc(callback, params, args); + end +end + +% We always return a function, as we don't know what the actual outputs +% call for until an invocation. +% We also don't know if callback is known yet. +function func = genExactFunc(callback, params, args) + function varargout = invokeCallback(varargin) + n = numel(varargin) + numel(args); + if n < params + varargout{1} = genExactFunc(callback, params, [args, varargin]); + elseif n > params + error('Curry:Invocation', 'Function was supposed to be called with exactly %d parameters, but has %d instead.', params, n); + else + varargout{1:nargout} = callback(args{:}, varargin{:}); + end + end + + function target = invokeCurry(varargin) + target = curry(params, varargin{:}); + end + + % If we don't know what the callback is yet, just wait and have curry + % figure it out and reinvoke this function. + if isempty(callback) + func = @invokeCurry; + else + func = @invokeCallback; + end +end + +function func = genManyFunc(callback, params, args) + function varargout = invokeCallback(varargin) + n = numel(varargin) + numel(args); + if n < params + varargout{1} = genManyFunc(callback, params, [args, varargin]); + else + varargout{1:nargout} = callback(args{:}, varargin{:}); + end + end + + func = @invokeCallback; +end diff --git a/lib/functional/islambda.m b/lib/functional/islambda.m new file mode 100644 index 0000000..b278dfb --- /dev/null +++ b/lib/functional/islambda.m @@ -0,0 +1,9 @@ + +function b = islambda(a) +% BOOL = ISLAMBDA(VALUE) +% Returns true when VALUE is a function handle, either an explicit lambda +% expression or a named function. +% + + b = strcmp(class(a), 'function_handle'); +end diff --git a/runExperiment.m b/runExperiment.m index dbe6929..648551d 100644 --- a/runExperiment.m +++ b/runExperiment.m @@ -93,9 +93,6 @@ data.recalibrated = false(0, 1); -% grating -gratingImage = exp_params.grating_image; - % save presentation order to session file save(sessionFile, 'data', 'exp_params'); @@ -112,10 +109,20 @@ % the PsychToolbox screen and go back to Matlab command window % in case of an error try - [window, windowRect] = Screen('OpenWindow', ... - exp_params.monitor_id, exp_params.background_color); + [window, ScrVars] = GenScreenSetup(exp_params.background_color); + windowRect = ScrVars.winRect; Priority(MaxPriority(window, 'WaitBlanking')); windowSize = windowRect(3:4); + exp_params.fixation_position_x = windowSize(1) / 2; + exp_params.fixation_position_y = windowSize(2) / 2; + + % grating + ScrWdCm = 70; % + DistToScrCm = 85; % + gratingImageDegrees = 3; + Geo = GKlab_ScreenGeometry(ScrVars.winWidth, ScrWdCm, DistToScrCm); + gratingImage = createGratingImage(gratingImageDegrees, ... + Geo.FovDegPerPix, gray, black); % background texture backgroundMatrix = exp_params.background_color * ones(windowSize); @@ -130,6 +137,10 @@ exp_params.fixation_size, exp_params.fixation_width, ... exp_params.background_color, white); oddFixationScreen = Screen('maketexture', window, oddFixationMatrix); + fixationSourceRect = [0, 0, size(normalFixationMatrix)]; + fixationDestRect = CenterRectOnPointd(fixationSourceRect, ... + exp_params.fixation_position_x, ... + exp_params.fixation_position_y); % grating texture gratingTexture = Screen('MakeTexture', window, gratingImage); @@ -164,7 +175,7 @@ % start experiment if exp_params.eyelink - eyelink('message', 'EXP_START'); + Eyelink('message', 'EXP_START'); end Screen('DrawTexture', window, backgroundScreen); @@ -172,7 +183,8 @@ % setup blocks blockIndex = 1; - score = 0; % counter + imagesPerBlock = ... + exp_params.images_per_sequence * exp_params.sequences_per_block; % show block 1 Screen('flip', window); @@ -192,24 +204,28 @@ trialIter = trialIter + 1; data.trial(trialIter, 1) = trialIter; fprintf('Trial %d\n', trialIter); - data.grating_location(trialIter, 1) = prod(windowSize) * rand(1); - [data.grating_location_y(trialIter, 1), ... - data.grating_location_x(trialIter, 1)] = ... + data.fixation_position_x(trialIter, 1) = exp_params.fixation_position_x; + data.fixation_position_y(trialIter, 1) = exp_params.fixation_position_y; + data.grating_position(trialIter, 1) = prod(windowSize) * rand(1); + [data.grating_position_x(trialIter, 1), ... + data.grating_position_y(trialIter, 1)] = ... ind2sub(windowSize, ... - data.grating_location(trialIter)); + data.grating_position(trialIter)); data.odd(trialIter, 1:3) = rand(1, 3) < 0.01; %% fixation fixationScreen = getFixationScreen(data.odd(trialIter, 1), ... normalFixationScreen, oddFixationScreen); - Screen('DrawTexture', window, fixationScreen); + Screen('DrawTexture', window, fixationScreen, ... + fixationSourceRect, fixationDestRect); Screen('flip', window); data.recalibrated(trialIter, 1) = false; % check fixation only every images_per_sequence trials - if mod(trialIter, exp_params.images_per_sequence) == 0 + if mod(trialIter - 1, exp_params.images_per_sequence) == 0 if exp_params.eyelink [exp_params, hadToRecalibrate] = AwaitGoodFixation(exp_params, ... window, windowRect, fixationScreen, ... + fixationSourceRect, fixationDestRect, ... curry(@WaitForKeyKeyboard, keyboards,0, exp_params.exit_keys)); data.recalibrated(trialIter, 1) = hadToRecalibrate; else @@ -220,16 +236,16 @@ %% present grating sourceRect = [0, 0, size(gratingImage)]; destRect = CenterRectOnPointd(sourceRect, ... - data.grating_location_x(trialIter), ... - data.grating_location_y(trialIter)); + data.grating_position_x(trialIter), ... + data.grating_position_y(trialIter)); fixationScreen = getFixationScreen(data.odd(trialIter, 2), ... normalFixationScreen, oddFixationScreen); - Screen('DrawTexture', window, fixationScreen); + Screen('DrawTexture', window, fixationScreen, ... + fixationSourceRect, fixationDestRect); Screen('DrawTexture', window, gratingTexture, sourceRect, destRect); vblGratingStart = Screen('flip', window); if exp_params.eyelink - eyelink('message', sprintf('GRATING_%d ON (/%d)', ... - trialIter, nTrials)); + Eyelink('message', sprintf('GRATING_%d ON', trialIter)); end sendTriggers(triggerDevice, exp_params.num_triggers_image_onoff, ... exp_params.trigger_duration, exp_params.trigger_interval); @@ -238,12 +254,13 @@ fixationScreen = getFixationScreen(data.odd(trialIter, 3), ... normalFixationScreen, oddFixationScreen); Screen('DrawTexture', window, backgroundScreen); - Screen('DrawTexture', window, fixationScreen); + Screen('DrawTexture', window, fixationScreen, ... + fixationSourceRect, fixationDestRect); vblBackgroundStart = Screen('flip', window, ... vblGratingStart + exp_params.grating_duration - 1 / 60 / 10); if exp_params.eyelink - eyelink('message', sprintf('GRATING_%d OFF', trialIter)); - eyelink('message', sprintf('BACKGROUND_%d ON', trialIter)); + Eyelink('message', sprintf('GRATING_%d OFF', trialIter)); + Eyelink('message', sprintf('BACKGROUND_%d ON', trialIter)); end sendTriggers(triggerDevice, exp_params.num_triggers_image_onoff, ... exp_params.trigger_duration, exp_params.trigger_interval); @@ -257,10 +274,8 @@ % off vblBackgroundEnd = WaitSecs(exp_params.background_duration); if exp_params.eyelink - eyelink('message', sprintf('BACKGROUND_%d OFF', trialIter)); + Eyelink('message', sprintf('BACKGROUND_%d OFF', trialIter)); end - sendTriggers(triggerDevice, exp_params.num_triggers_image_onoff, ... - exp_params.trigger_duration, exp_params.trigger_interval); data.backgroundPresentationDuration(trialIter, 1) = ... vblBackgroundEnd - vblBackgroundStart; @@ -283,14 +298,11 @@ exp_params.r_keys == data.responseKey(trialIter), ... 1, 'first')); assert(~isnan(data.response(trialIter))); - + data.truth(trialIter, 1) = any(any(data.odd(trialIter - ... exp_params.images_per_score_sequence + 1:trialIter, :))); data.correct(trialIter, 1) = ... data.response(trialIter) == data.truth(trialIter); - if data.correct(trialIter) - score = score + 1; - end else data.choicePresentationStart(trialIter, 1) = NaN(1); data.reactionTime(trialIter, 1) = NaN(1); @@ -301,22 +313,20 @@ end % block done? - if(mod(trialIter, exp_params.images_per_sequence * ... - exp_params.sequences_per_block) == 0) + if(mod(trialIter, imagesPerBlock) == 0) Screen('DrawTexture', window, backgroundScreen); Screen('flip', window); sendTriggers(triggerDevice, exp_params.num_triggers_interrupt, ... exp_params.trigger_duration, exp_params.trigger_interval); if exp_params.eyelink - eyelink('message', sprintf('BACKGROUND_%d OFF', trialIter)); + Eyelink('message', sprintf('BACKGROUND_%d OFF', trialIter)); end fprintf('Saving data to %s...\n', sessionFile); save(sessionFile, 'data', '-append'); - performance = score / (exp_params.images_per_sequence * ... - exp_params.sequences_per_block); - score = 0; + performance = data.correct(trialIter - imagesPerBlock + 1:... + trialIter) / imagesPerBlock; drawScoreScreen(window, blockIndex, performance, ... exp_params.performanceMessages, black); WaitForKey(keyboards, 0, exp_params.exit_keys); @@ -330,7 +340,7 @@ 'center', 'center', black); Screen('flip', window); WaitSecs(1); - + WaitForKey(keyboards, 0, exp_params.exit_keys); end end @@ -341,7 +351,7 @@ % save last eyelink file name (if recalibration occured) eyelinkFile = exp_params.eyelink_file; if exp_params.eyelink - eyelink('message', 'EXPERIMENT_END'); + Eyelink('message', 'EXPERIMENT_END'); end % show goodbye screen @@ -438,18 +448,18 @@ end function deviceIndex = initializeTrigger() - % initialize PMD1208FS - device=initPMD1208FS; - if isnumeric(device) && device<0 - error('Trigger device not found'); - else - device=device.index; - err=DaqDConfigPort(device,0,0); % port = 0 direction = 0 - FastDaqDout=inline('PsychHID(''SetReport'', device, 2, hex2dec(''04''), uint8([0 port data]))','device', 'port', 'data'); - end - deviceIndex = DaqDeviceIndex; - DaqDConfigPort(deviceIndex,0,0); - DaqDOut(deviceIndex,0,0); +% initialize PMD1208FS +device=initPMD1208FS; +if isnumeric(device) && device<0 + error('Trigger device not found'); +else + device=device.index; + err=DaqDConfigPort(device,0,0); % port = 0 direction = 0 + FastDaqDout=inline('PsychHID(''SetReport'', device, 2, hex2dec(''04''), uint8([0 port data]))','device', 'port', 'data'); +end +deviceIndex = DaqDeviceIndex; +DaqDConfigPort(deviceIndex,0,0); +DaqDOut(deviceIndex,0,0); end function sendTriggers(deviceIndex, numTriggers, ... diff --git a/setupAndRun.m b/setupAndRun.m index 2774fa1..6a922e4 100644 --- a/setupAndRun.m +++ b/setupAndRun.m @@ -1,3 +1,5 @@ +addpath(genpath(pwd)); + black = 0; gray = 128; white = 255; @@ -10,27 +12,21 @@ end %% advanced options -ScrWdCm = 36; % -DistToScrCm = 53.34; % -[monitorID, ScrVars] = GenScreenSetup; -Geo = GKlab_ScreenGeometry(ScrVars.winWidth, ScrWdCm, DistToScrCm); +monitorID = 1; + images_per_sequence = 5; images_per_score_sequence = 15; sequences_per_block = 150; -gratingImageDegrees = 3; - -% create grating -gratingImage = createGratingImage(gratingImageDegrees, Geo.FovDegPerPix, ... - gray, black); % confirm experimental details fprintf('*************************************************\n'); fprintf('PHYSIOLOGY_EXPERIMENT Identification-Occlusion\n'); fprintf('Experiment Start: %s\n', datestr(now)); +fprintf('Monitor ID: %d\n', monitorID); fprintf('*************************************************\n'); %% setup eyetracker -eyelink = ~debug; +eyelink = 0; if(eyelink) [eyelinkFile, success] = setupEyetracker(monitorID, subjectName); if ~success @@ -42,13 +38,12 @@ %% run actual experiment [eyelinkFile, eyelink] = runExperiment(... - 'grating_image', gratingImage, ... 'images_per_sequence', images_per_sequence, ... 'images_per_score_sequence', images_per_score_sequence, ... 'sequences_per_block', sequences_per_block, ... 'subject_name', subjectName, ... 'eyelink', eyelink, 'eyelink_file', eyelinkFile, ... - 'monitor_ID', monitorID, 'debug_mode', debug); + 'debug_mode', debug); % grab eyelink data if eyelink diff --git a/setupEyetracker.m b/setupEyetracker.m index 2174b92..4b9398a 100644 --- a/setupEyetracker.m +++ b/setupEyetracker.m @@ -31,6 +31,7 @@ ShowCursor; Screen('CloseAll'); + WaitSecs(1); catch err disp(getReport(err, 'extended')); Screen('CloseAll');