diff --git a/CoreProtocols/Saccade_AntiSaccadeStateInfo.m b/CoreProtocols/Saccade_AntiSaccadeStateInfo.m index cbd015a6..de4f0d1e 100644 --- a/CoreProtocols/Saccade_AntiSaccadeStateInfo.m +++ b/CoreProtocols/Saccade_AntiSaccadeStateInfo.m @@ -325,8 +325,8 @@ %--------------------fix within fixWithin = { @()draw(stims); %draw stimulus - @()trackerDrawEyePosition(eT); - @()trackerFlip(eT, 1); + @()trackerDrawEyePosition(eT); % for tobii + @()trackerFlip(eT, 1); % for tobii }; %--------------------test we are fixated for a certain length of time @@ -383,7 +383,7 @@ @()updateExclusionZones(me, tS.useTask, tS.exclusionRadius); @()trackerMessage(eT,'END_FIX'); @()hide(stims, 3); -}; +}; if strcmpi(tS.type,'saccade') fsExit = [ fsExit; { @()edit(stims,1,'alphaOut',tS.targetAlpha2) } ]; else diff --git a/runExperiment.m b/runExperiment.m index 55f6e5ae..29022639 100644 --- a/runExperiment.m +++ b/runExperiment.m @@ -671,7 +671,7 @@ function runTask(me) else run(me.userFunctionsFile) end - me.userFunctions = ans; + me.userFunctions = ans; %#ok uF = me.userFunctions; uF.rE = me; uF.s = s; uF.task = task; uF.eT = eT; uF.stims = stims; uF.io = io; uF.rM = rM; @@ -795,6 +795,9 @@ function runTask(me) warning('We had to reopen the labJackT to ensure a stable connection...') end + %---------set up our behavioural plot + createPlot(bR, eT); drawnow; + %-----take over the keyboard + bump priority KbReleaseWait; %make sure keyboard keys are all released if ~isdeployed; commandwindow; end @@ -802,11 +805,7 @@ function runTask(me) %warning('off'); %#ok<*WNOFF> ListenChar(-1); %2=capture all keystrokes end - drawnow; Priority(MaxPriority(s.win)); %bump our priority to maximum allowed - - %---------set up our behavioural plot - createPlot(bR, eT); %-----profiling starts here if uncommented %profile clear; profile on; @@ -837,18 +836,18 @@ function runTask(me) if me.doFlip; s.finishDrawing(); end % potential optimisation, but note stateMachine may run non-drawing tasks in update() %------check eye position manually. Also potentially log - % this retrieved value, but REMEMBER eyelink will save - % the properly sampled eye data in the EDF; this is just - % a backup sampled at the GPU FPS wrapped in the PTB - % loop. + % this retrieved value, but REMEMBER eyetracker will save + % the properly sampled eye data in the EDF; this is + % just a backup sampled at the GPU FPS wrapped in the + % PTB loop. if me.needSample; getSample(eT); end if tS.recordEyePosition && me.useEyeLink saveEyeInfo(me, sM, eT, tS); end %------Check keyboard for commands (remember we can turn - % this off using either tS.keyExclusionPattern [per-state - % toggle] or tS.checkKeysDuringStimulus). + % this off using either tS.keyExclusionPattern + % [per-state toggle] or tS.checkKeysDuringStimulus). if tS.checkKeysDuringStimulus || ~contains(sM.currentName,tS.keyExclusionPattern) tS = checkKeys(me,tS); end @@ -905,17 +904,22 @@ function runTask(me) tL.screenLog.trackerEndTime = getTrackerTime(eT); tL.screenLog.trackerEndOffset = getTimeOffset(eT); - updatePlot(bR, me); %update our behavioural plot for final state - show(stims); %make all stimuli visible again, useful for editing - drawBackground(s); - trackerClearScreen(eT); - trackerDrawText(eT,['FINISHED TASK:' me.name]); - Screen('Flip', s.win); - Priority(0); - ListenChar(0); - RestrictKeysForKbCheck([]); - ShowCursor; - warning('on'); + try %#ok<*TRYNC> + drawBackground(s); + trackerClearScreen(eT); + trackerDrawText(eT,['FINISHED TASK:' me.name]); + Screen('Flip', s.win); + Priority(0); + ListenChar(0); + RestrictKeysForKbCheck([]); + ShowCursor; + warning('on'); + end + + try + updatePlot(bR, me); %update our behavioural plot for final state + show(stims); %make all stimuli visible again, useful for editing + end %notify(me,'endAllRuns'); me.isRunning = false; @@ -931,13 +935,13 @@ function runTask(me) close(io); end - close(s); %screen - close(io); % I/O system - close(eT); % eyetracker, should save the data for us we've already given it our name and folder - WaitSecs(0.25); - close(aM); % audio manager - close(rM); % reward manager + try close(s); end %screen + try close(io); end % I/O system + try close(eT); end % eyetracker, should save the data for us we've already given it our name and folder + try close(aM); end % audio manager + try close(rM); end % reward manager + WaitSecs(0.25); fprintf('\n\n======>>> Total ticks: %g | stateMachine ticks: %g\n', tS.totalTicks, sM.totalTicks); fprintf('======>>> Tracker Time: %g | PTB time: %g | Drift Offset: %g\n', ... tL.screenLog.trackerEndTime-tL.screenLog.trackerStartTime, ... @@ -978,9 +982,11 @@ function runTask(me) if me.diaryMode; diary off; end me.stateInfo = []; - if isa(me.stateMachine,'stateMachine'); me.stateMachine.reset; end - if isa(me.stimuli,'metaStimulus'); me.stimuli.reset; end - + try + if isa(me.stateMachine,'stateMachine'); me.stateMachine.reset; end + if isa(me.stimuli,'metaStimulus'); me.stimuli.reset; end + end + clear rE tL s tS bR rM eT io sM task catch ME diff --git a/screenManager.m b/screenManager.m index cb8a03c3..24eacd79 100644 --- a/screenManager.m +++ b/screenManager.m @@ -818,7 +818,7 @@ function close(me) end me.isInAsync = false; if me.isPlusPlus - BitsPlusPlus('Close'); + try BitsPlusPlus('Close'); end end me.finaliseMovie(); me.moviePtr = []; kind = Screen(me.win, 'WindowKind'); diff --git a/stimuli/discStimulus.m b/stimuli/discStimulus.m index 6b5115ec..78a42d10 100644 --- a/stimuli/discStimulus.m +++ b/stimuli/discStimulus.m @@ -10,19 +10,19 @@ %> type can be "simple" or "flash" type = 'simple' %> colour for flash, empty to inherit from screen background with 0 alpha - flashColour = [] + flashColour double = [] %> time to flash on and off in seconds - flashTime = [0.25 0.25] + flashTime double {mustBeVector(flashTime)} = [0.25 0.25] %> is the ON flash the first flash we see? - flashOn = true + flashOn logical = true %> contrast scales from foreground to screen background colour - contrast = 1 + contrast double {mustBeInRange(contrast,0,1)} = 1 %> cosine smoothing sigma in pixels for mask - sigma = 31.0 + sigma double = 31.0 %> use colour or alpha [default] channel for smoothing? - useAlpha = true + useAlpha logical = true %> use cosine (0), hermite (1, default), or inverse hermite (2) - smoothMethod = 1 + smoothMethod double = 1 end properties (SetAccess = protected, GetAccess = public) diff --git a/stimuli/spotStimulus.m b/stimuli/spotStimulus.m index fe668336..6d00c58d 100644 --- a/stimuli/spotStimulus.m +++ b/stimuli/spotStimulus.m @@ -89,13 +89,14 @@ function setup(me,sM) if isempty(me.isVisible); me.show; end me.sM = sM; + if ~sM.isOpen; warning('Screen needs to be Open!'); end me.ppd=sM.ppd; fn = fieldnames(spotStimulus); for j=1:length(fn) if isempty(me.findprop([fn{j} 'Out'])) && isempty(regexp(fn{j},me.ignoreProperties, 'once'))%create a temporary dynamic property p=me.addprop([fn{j} 'Out']); - p.Transient = true;%p.Hidden = true; + p.Transient = true; if strcmp(fn{j},'size');p.SetMethod = @set_sizeOut;end if strcmp(fn{j},'xPosition');p.SetMethod = @set_xPositionOut;end if strcmp(fn{j},'yPosition');p.SetMethod = @set_yPositionOut;end @@ -125,6 +126,102 @@ function setup(me,sM) computeColour(me); computePosition(me); setAnimationDelta(me); + if me.doAnimator;setup(me.animator, me);end + + function set_xPositionOut(me, value) + me.xPositionOut = value * me.ppd; + end + function set_yPositionOut(me,value) + me.yPositionOut = value*me.ppd; + end + function set_sizeOut(me,value) + me.sizeOut = value * me.ppd; %divide by 2 to get diameter + end + function set_colourOut(me, value) + me.isInSetColour = true; + if length(value)==4 + alpha = value(4); + elseif isempty(me.findprop('alphaOut')) + alpha = me.alpha; + else + alpha = me.alphaOut; + end + switch length(value) + case 4 + if isempty(me.findprop('alphaOut')) + me.alpha = alpha; + else + me.alphaOut = alpha; + end + case 3 + value = [value(1:3) alpha]; + case 1 + value = [value value value alpha]; + end + if isempty(me.colourOutTemp);me.colourOutTemp = value;end + me.colourOut = value; + me.isInSetColour = false; + if isempty(me.findprop('contrastOut')) + contrast = me.contrast; %#ok<*PROPLC> + else + contrast = me.contrastOut; + end + if ~me.inSetup && ~me.stopLoop && contrast < 1 + computeColour(me); + end + end + function set_flashColourOut(me, value) + me.isInSetColour = true; + if length(value)==4 + alpha = value(4); + elseif isempty(me.findprop('alphaOut')) + alpha = me.alpha; + else + alpha = me.alphaOut; + end + switch length(value) + case 3 + value = [value(1:3) alpha]; + case 1 + value = [value value value alpha]; + end + if isempty(me.flashColourOutTemp);me.flashColourOutTemp = value;end + me.flashColourOut = value; + me.isInSetColour = false; + if isempty(me.findprop('contrastOut')) + contrast = me.contrast; %#ok<*PROPLC> + else + contrast = me.contrastOut; + end + if ~me.inSetup && ~me.stopLoop && contrast < 1 + computeColour(me); + end + end + function set_alphaOut(me, value) + if me.isInSetColour; return; end + me.alphaOut = value; + if isempty(me.findprop('colourOut')) + me.colour = [me.colour(1:3) me.alphaOut]; + else + me.colourOut = [me.colourOut(1:3) me.alphaOut]; + end + if isempty(me.findprop('flashColourOut')) + if ~isempty(me.flashColour) + me.flashColour = [me.flashColour(1:3) me.alphaOut]; + end + else + if ~isempty(me.flashColourOut) + me.flashColourOut = [me.flashColourOut(1:3) me.alphaOut]; + end + end + end + function set_contrastOut(me, value) + if iscell(value); value = value{1}; end + me.contrastOut = value; + if ~me.inSetup && ~me.stopLoop && value < 1 + computeColour(me); + end + end end % =================================================================== @@ -232,118 +329,6 @@ function reset(me) %======================================================================= methods ( Access = protected ) %-------PROTECTED METHODS-----% %======================================================================= - % =================================================================== - %> @brief sizeOut Set method - %> - % =================================================================== - function set_sizeOut(me,value) - me.sizeOut = value * me.ppd; %divide by 2 to get diameter - end - - % =================================================================== - %> @brief colourOut SET method - %> - % =================================================================== - function set_colourOut(me, value) - me.isInSetColour = true; - if length(value)==4 - alpha = value(4); - elseif isempty(me.findprop('alphaOut')) - alpha = me.alpha; - else - alpha = me.alphaOut; - end - switch length(value) - case 4 - if isempty(me.findprop('alphaOut')) - me.alpha = alpha; - else - me.alphaOut = alpha; - end - case 3 - value = [value(1:3) alpha]; - case 1 - value = [value value value alpha]; - end - if isempty(me.colourOutTemp);me.colourOutTemp = value;end - me.colourOut = value; - me.isInSetColour = false; - if isempty(me.findprop('contrastOut')) - contrast = me.contrast; %#ok<*PROPLC> - else - contrast = me.contrastOut; - end - if ~me.inSetup && ~me.stopLoop && contrast < 1 - computeColour(me); - end - end - - % =================================================================== - %> @brief colourOut SET method - %> - % =================================================================== - function set_flashColourOut(me, value) - me.isInSetColour = true; - if length(value)==4 - alpha = value(4); - elseif isempty(me.findprop('alphaOut')) - alpha = me.alpha; - else - alpha = me.alphaOut; - end - switch length(value) - case 3 - value = [value(1:3) alpha]; - case 1 - value = [value value value alpha]; - end - if isempty(me.flashColourOutTemp);me.flashColourOutTemp = value;end - me.flashColourOut = value; - me.isInSetColour = false; - if isempty(me.findprop('contrastOut')) - contrast = me.contrast; %#ok<*PROPLC> - else - contrast = me.contrastOut; - end - if ~me.inSetup && ~me.stopLoop && contrast < 1 - computeColour(me); - end - end - - % =================================================================== - %> @brief alphaOut SET method - %> - % =================================================================== - function set_alphaOut(me, value) - if me.isInSetColour; return; end - me.alphaOut = value; - if isempty(me.findprop('colourOut')) - me.colour = [me.colour(1:3) me.alphaOut]; - else - me.colourOut = [me.colourOut(1:3) me.alphaOut]; - end - if isempty(me.findprop('flashColourOut')) - if ~isempty(me.flashColour) - me.flashColour = [me.flashColour(1:3) me.alphaOut]; - end - else - if ~isempty(me.flashColourOut) - me.flashColourOut = [me.flashColourOut(1:3) me.alphaOut]; - end - end - end - - % =================================================================== - %> @brief contrastOut SET method - %> - % =================================================================== - function set_contrastOut(me, value) - if iscell(value); value = value{1}; end - me.contrastOut = value; - if ~me.inSetup && ~me.stopLoop && value < 1 - computeColour(me); - end - end % =================================================================== %> @brief computeColour triggered event diff --git a/tools/behaviouralRecord.m b/tools/behaviouralRecord.m index 382b276e..6d4297a6 100644 --- a/tools/behaviouralRecord.m +++ b/tools/behaviouralRecord.m @@ -1,7 +1,7 @@ % ======================================================================== classdef behaviouralRecord < optickaCore %> @class behaviouralRecord -%> @brief Create a GUI and update behavioural record for a behavioural +%> @brief Create a GUI and update performance plots for a behavioural %> task %> %> @@ -87,9 +87,10 @@ function plot(me) %> % =================================================================== function createPlot(me, eL) + tt=tic; if ~me.plotOnly reset(me); - me.date = datestr(now); + me.date = datetime('now'); end if isfield(me.h,'root') && ~isempty(findobj(me.h.root)) close(me.h.root); @@ -100,7 +101,7 @@ function createPlot(me, eL) eL.fixation.time = 1; eL.fixation.initTime = 1; end - tx = {['INFORMATION @ ' me.date]}; + tx = {['START @ ' char(me.date)]}; tx{end+1} = ['RUN = ' me.comment]; tx{end+1} = ['RADIUS = ' num2str(eL.fixation.radius)]; tx{end+1} = ' '; @@ -127,7 +128,8 @@ function createPlot(me, eL) me.h.grid.RowSpacing = 2; me.h.grid.Padding = [3 3 3 3]; me.h.panel = uipanel(me.h.grid); - me.h.info = uitextarea(me.h.grid, 'HorizontalAlignment', 'center','FontName',mfont); + me.h.info = uitextarea(me.h.grid, 'HorizontalAlignment', 'center',... + 'FontName', mfont, 'Editable', 'off', 'WordWrap', 'off'); me.h.box = tiledlayout(me.h.panel,3,3); me.h.box.Padding='compact'; me.h.axis1 = nexttile(me.h.box, [2 2]); @@ -141,23 +143,21 @@ function createPlot(me, eL) set([me.h.axis1 me.h.axis2 me.h.axis3 me.h.axis4 me.h.axis5], ... {'Box','XGrid','YGrid'},{'on','on','on'}); - xlabel(me.h.axis1, 'Run Number') - xlabel(me.h.axis2, 'Time') - xlabel(me.h.axis3, 'Group') - xlabel(me.h.axis4, '#') - xlabel(me.h.axis5, 'x') - ylabel(me.h.axis1, 'Yes / No') - ylabel(me.h.axis2, 'Number #') - ylabel(me.h.axis3, '% success') - ylabel(me.h.axis4, '% success') - ylabel(me.h.axis5, 'y') - title(me.h.axis1,'Success () / Fail ()') - title(me.h.axis2,'Response Times') - title(me.h.axis3,'Hit (blue) / Miss (red)') - title(me.h.axis4,'Average (n=10) Hit / Miss %') - title(me.h.axis5,'Last Eye Position') - %hn = findobj(me.h.axis2,'Type','patch'); - %set(hn,'FaceColor','k','EdgeColor','k'); + xlabel(me.h.axis1, 'Run Number'); + xlabel(me.h.axis2, 'Time'); + xlabel(me.h.axis3, 'Group'); + xlabel(me.h.axis4, '#'); + xlabel(me.h.axis5, 'x'); + ylabel(me.h.axis1, 'Yes / No'); + ylabel(me.h.axis2, 'Number #'); + ylabel(me.h.axis3, '% success'); + ylabel(me.h.axis4, '% success'); + ylabel(me.h.axis5, 'y'); + title(me.h.axis1,'Success () / Fail ()'); + title(me.h.axis2,'Response Times'); + title(me.h.axis3,'Hit (blue) / Miss (red)'); + title(me.h.axis4,'Average (n=10) Hit / Miss %'); + title(me.h.axis5,'Last Eye Position'); end % =================================================================== @@ -171,10 +171,14 @@ function updatePlot(me, rE) sM = rE.stateMachine; eT = rE.eyeTracker; end + + %-----profiling starts here if uncommented + %tt = tic; profile clear; profile on; + if ~me.plotOnly if me.tick == 1 reset(me); - me.startTime = clock; + me.startTime = datetime('now'); end if exist('sM','var') if ~isempty(regexpi(sM.currentName,me.correctStateName,'once')) @@ -303,7 +307,7 @@ function updatePlot(me, rE) if ~me.plotOnly && ~isempty(me.response) n = length(me.response); - me.trials(n).now = clock; + me.trials(n).now = datetime('now'); me.trials(n).info = me.info; me.trials(n).tick = me.tick; me.trials(n).comment = me.comment; @@ -312,8 +316,9 @@ function updatePlot(me, rE) me.trials(n).yAll = me.yAll; end - t = {['INFORMATION @ ' me.date]}; - t{end+1} = ['RUN time = ' num2str(etime(me.trials(end).now,me.startTime)/60) 'mins']; + t = {['START @ ' char(me.date)]}; + d = me.trials(end).now - me.startTime; + t{end+1} = ['RUN time = ' char(d)]; t{end+1} = ['RUN:' me.comment]; t{end+1} = ['INFO:' me.info]; t{end+1} = ['RADIUS (red) b|n = ' num2str(me.radius(end)) 'deg']; @@ -328,11 +333,20 @@ function updatePlot(me, rE) t{end+1} = ' '; t{end+1} = '============Logged trial info============'; - for i = 1:length(me.trials) + if me.plotOnly + startt = 1; endt = length(me.trials); + elseif length(me.trials) <= 10 + startt = 1; endt = length(me.trials); + else + startt = length(me.trials)-10; endt = length(me.trials); + end + for i = startt:endt t{end+1} = ['#' num2str(i) '<' num2str(me.trials(i).response) '>: ' me.trials(i).info ' <> ' me.trials(i).comment]; end + me.h.info.Value = t'; - set(me.h.info,'Value', t'); + %-----get our profiling report for our task loop + % toc(tt); profile off; profile viewer; if ~me.plotOnly me.tick = me.tick + 1;