Skip to content

Commit

Permalink
fix updateVariables and improve updateFixationValues; [wip] doc updates
Browse files Browse the repository at this point in the history
  • Loading branch information
iandol committed Feb 9, 2022
1 parent 1d6ae26 commit 4b42836
Show file tree
Hide file tree
Showing 9 changed files with 547 additions and 382 deletions.
Binary file modified CoreProtocols/Figure Ground.mat
Binary file not shown.
348 changes: 233 additions & 115 deletions CoreProtocols/FigureGroundStateInfo.m

Large diffs are not rendered by default.

39 changes: 24 additions & 15 deletions DefaultStateInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,21 @@

%--------------------fixate entry
fixEntryFcn = {
% update the fixation window to initial values
@()updateFixationValues(eT,tS.fixX,tS.fixY,[],tS.firstFixTime); %reset fixation window
@()startRecording(eT); % start eyelink recording for this trial
@()startRecording(eT); % start eyelink recording for this trial (tobii ignores this)
% tracker messages that define a trial start
@()trackerMessage(eT,'V_RT MESSAGE END_FIX END_RT'); % Eyelink commands
@()trackerMessage(eT,sprintf('TRIALID %i',getTaskIndex(me))); %Eyelink start trial marker
@()trackerMessage(eT,['UUID ' UUID(sM)]); %add in the uuid of the current state for good measure
% draw to the etetracker display
@()trackerClearScreen(eT); % blank the eyelink screen
@()trackerDrawFixation(eT); %draw fixation window on eyelink computer
@()trackerDrawStimuli(eT,stims.stimulusPositions); %draw location of stimulus on eyelink
@()statusMessage(eT,'Initiate Fixation...'); %status text on the eyelink
@()show(stims{tS.nStims}); % show only last stim in list (fix cross)
@()logRun(me,'INITFIX'); %fprintf current trial info to command window
% show the last stimulus (fixation cross)
@()show(stims{tS.nStims});
@()logRun(me,'INITFIX');
};

%--------------------fix within
Expand All @@ -307,18 +311,20 @@
fixExitFcn = {
@()statusMessage(eT,'Show Stimulus...');
@()updateFixationValues(eT,[],[],[],tS.stimulusFixTime); %reset a maintained fixation of 1 second
@()show(stims); % show all stims)
@()trackerMessage(eT,'END_FIX');
@()show(stims); % show all stims
@()trackerMessage(eT,'END_FIX');
};

%--------------------what to run when we enter the stim presentation state
stimEntryFcn = {
@()syncTime(eT); %EDF sync message
@()doStrobe(me,true)
stimEntryFcn = {
% send an eyeTracker sync message (reset to 0)
@()syncTime(eT);
% send stimulus value strobe
@()doStrobe(me,true);
};

%--------------------what to run when we are showing stimuli
stimFcn = {
stimFcn = {
@()draw(stims);
@()drawPhotoDiode(s,[1 1 1]);
@()animate(stims); % animate stimuli for subsequent draw
Expand All @@ -337,7 +343,7 @@
};

%as we exit stim presentation state
stimExitFcn = {
stimExitFcn = {
@()sendStrobe(io,255);
};

Expand All @@ -348,11 +354,11 @@
@()trackerMessage(eT,'END_RT'); %send END_RT message to tracker
@()trackerMessage(eT,['TRIAL_RESULT ' str2double(tS.CORRECT)]); %send TRIAL_RESULT message to tracker
@()trackerDrawText(eT,'Correct! :-)');
@()stopRecording(eT); %eyelink starts/stops on every trial (for tobii this is does nothing)
@()setOffline(eT); %for eyelink set offline (tobii this does nothing)
@()stopRecording(eT); % eyelink starts/stops on every trial (for tobii this is does nothing)
@()setOffline(eT); % for eyelink set offline (tobii this does nothing)
@()needEyeSample(me,false); % no need to collect eye data until we start the next trial
@()hide(stims); %hide all stims
@()logRun(me,'CORRECT'); %fprintf current trial info
@()hide(stims); % hide all stims
@()logRun(me,'CORRECT'); % print current trial info
};

%correct stimulus
Expand Down Expand Up @@ -424,7 +430,10 @@
%--------------------change functions based on tS settings
% this shows an example of how to use tS options to change the function
% lists run by the state machine. We can prepend or append new functions to
% the cell arrays...
% the cell arrays.
% updateTask = updates task object
% resetRun = randomise current trial within the block
% checkTaskEnded = see if taskSequence has finished
if tS.includeErrors % we want to update our task even if there were errors
incExitFcn = [ {@()updateTask(me,tS.INCORRECT)}; incExitFcn ]; %update our taskSequence
breakExitFcn = [ {@()updateTask(me,tS.BREAKFIX)}; breakExitFcn ]; %update our taskSequence
Expand Down
112 changes: 70 additions & 42 deletions communication/eyelinkManager.m
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
% ========================================================================
%> @class eyelinkManager
%> @brief eyelinkManager wraps around the eyelink toolbox functions offering a
%> consistent interface and methods for fixation window control
%>
%> Class methods enable the user to test for common behavioural eye tracking
%> tasks with single commands. For example, to initiate a task we normally place
%> a fixation cross on the screen and ask the subject to saccade to the cross
%> and maintain fixation for a particular duration. This is achieved using
%> testSearchHoldFixation('yes','no'), using the properties: fixation.initTime
%> to time how long the subject has to saccade into the window, fixation.time
%> for how long they must maintain fixation, fixation.radius for the radius
%> around fixation.X and fixation.Y position. The method returns the 'yes'
%> string if the rules are matched, and 'no' if they are not, thus enabling
%> experiment code to simply call this method until it returns 'yes''. Other
%> methods include isFixated(), testFixationTime(), testHoldFixation().
%> tasks with single commands. For example, to initiate a task we normally
%> place a fixation cross on the screen and ask the subject to saccade to
%> the cross and maintain fixation for a particular duration. This is
%> achieved using testSearchHoldFixation('yes','no'), using the properties:
%> fixation.initTime to time how long the subject has to saccade into the
%> window, fixation.time for how long they must maintain fixation,
%> fixation.radius for the radius around fixation.X and fixation.Y position.
%> The method returns the 'yes' string if the rules are matched, and 'no' if
%> they are not, thus enabling experiment code to simply call this method
%> until it returns 'yes''. Other methods include isFixated(),
%> testFixationTime(), testHoldFixation().
%>
%> This class enables several types of behavioural control:
%>
%> 1. Fixation window: one or more areas where the subject must enter with their eye position within a certain time and must maintain fixation for a certain time. Windows can be circular or rectangular.
%> 2. Exclusion zones: one or more rectangular areas that cause failure if entered.
%> 3. Fix initiation zone: an area the eye must stay with for a certain time before a saccade. For example if a subect fixates, then must saccade a time X, do not allow the eye to leave this zone before X + t (t by default is 100ms). This stops potential cheating by the subject.
%> 1. Fixation window: one or more areas where the subject must enter with
%> their eye position within a certain time and must maintain fixation
%> for a certain time. Windows can be circular or rectangular.
%> 2. Exclusion zones: one or more rectangular areas that cause failure if
%> entered.
%> 3. Fix initiation zone: an area the eye must stay with for a certain time
%> before a saccade. For example if a subect fixates, then must saccade a
%> time X, do not allow the eye to leave this zone before X + t (t by
%> default is 100ms). This stops potential cheating by the subject.
%>
%> Try using the demo mode to see it in action (read the runDemo() code to
%> understand how to use the class):
Expand All @@ -28,15 +36,16 @@
%> >> eT.runDemo();
%>```
%>
%> Multiple fixation windows can be assigned (either circular or rectangular),
%> and in addition multiple exclusion windows (exclusionZone) can ensure a
%> subject doesn't saccade to particular parts of the screen. fixInit allows you
%> to define a minimum time with which the subject can initiate a saccade away
%> from a position (which stops a subject cheating by moving the eyes too soon).
%> Multiple fixation windows can be assigned (either circular or
%> rectangular), and in addition multiple exclusion windows (exclusionZone)
%> can ensure a subject doesn't saccade to particular parts of the screen.
%> fixInit allows you to define a minimum time with which the subject can
%> initiate a saccade away from a position (which stops a subject cheating
%> by moving the eyes too soon).
%>
%> For the eyelink we also allow the use of remote calibration and can call a
%> reward systems during calibration / validation to improve subject performance
%> compared to the eyelink toolbox alone.
%> For the eyelink we also allow the use of remote calibration and can call
%> a reward systems during calibration / validation to improve subject
%> performance compared to the eyelink toolbox alone.
%>
%> Copyright ©2014-2022 Ian Max Andolina — released: LGPL3, see LICENCE.md
% ========================================================================
Expand All @@ -48,12 +57,14 @@
%> if X and Y have multiple rows, assume each row is a different
%> fixation window. so that multiple fixtation windows can be used.
%>
%> if radius has a single value, assume circular window if radius has 2
%> values assume width × height rectangle (not strictly a radius!)
%> if radius has a single value, assume circular window if radius
%> has 2 values assume width × height rectangle (not strictly a
%> radius!)
%>
%> initTime is the time the subject has to initiate fixation
%>
%> time is the time the subject must maintain fixation within the window
%> time is the time the subject must maintain fixation within the
%> window
%>
%> strict = false allows subject to exit and enter window without
%> failure, useful during training
Expand All @@ -62,10 +73,11 @@
%> Use exclusion zones where no eye movement allowed: [-degX +degX -degY
%> +degY] Add rows to generate multiple exclusion zones.
exclusionZone double = []
%> we can define an optional window that the subject must stay inside
%> before they saccade to other targets. This restricts guessing and
%> "cheating", by forcing a minimum delay (default = 100ms / 0.1s)
%> before initiating a saccade. Only used if X position is not empty.
%> we can define an optional window that the subject must stay
%> inside before they saccade to other targets. This restricts
%> guessing and "cheating", by forcing a minimum delay (default =
%> 100ms / 0.1s) before initiating a saccade. Only used if X
%> position is not empty.
fixInit struct = struct('X',[],'Y',[],'time',0.1,'radius',2)
%> add a manual offset to the eye position, similar to a drift correction
%> but handled by the eyelinkManager.
Expand Down Expand Up @@ -553,10 +565,11 @@ function setOffline(me)
end

% ===================================================================
function success = driftOffset(me)
%> @fn driftOffset
%> @brief wrapper for EyelinkDoDriftCorrection
%>
% ===================================================================
function success = driftOffset(me)
success = false;
escapeKey = KbName('ESCAPE');
stopkey = KbName('Q');
Expand Down Expand Up @@ -617,10 +630,11 @@ function setOffline(me)
end

% ===================================================================
%> @brief wrpper for CheckRecording
function error = checkRecording(me)
%> @fn checkRecording
%> Wrapper for CheckRecording
%>
% ===================================================================
function error = checkRecording(me)
if me.isConnected
error=Eyelink('CheckRecording');
else
Expand All @@ -629,11 +643,12 @@ function setOffline(me)
end

% ===================================================================
%> @brief get a sample from the tracker, if dummymode=true then use
function sample = getSample(me)
%> @fn getSample
%> Get a sample from the tracker, if dummymode=true then use
%> the mouse as an eye signal
%>
% ===================================================================
function sample = getSample(me)
if me.isConnected && Eyelink('NewFloatSampleAvailable') > 0
me.currentSample = Eyelink('NewestFloatSample');% get the sample in the form of an event structure
if ~isempty(me.currentSample) && isstruct(me.currentSample)
Expand Down Expand Up @@ -679,10 +694,20 @@ function setOffline(me)


% ===================================================================
%> @brief Sinlge method to update the fixation parameters
function updateFixationValues(me,x,y,inittime,fixtime,radius,strict)
%> @fn updateFixationValues(me,x,y,inittime,fixtime,radius,strict)
%>
%> Sinlge method to update the fixation parameters. See property
%> descriptions for full details. You can pass empty values if you only
%> need to update one parameter, e.g. me.updateFixationValues([],[],1);
%>
%> @param x X position
%> @param y Y position
%> @param inittime time to initiate fixation
%> @param fixtime time to maintain fixation
%> @paran radius radius of fixation window
%> @param strict allow or disallow re-entering the fixation window
% ===================================================================
function updateFixationValues(me,x,y,inittime,fixtime,radius,strict)
resetFixation(me);
if nargin > 1 && ~isempty(x)
if isinf(x)
Expand All @@ -699,14 +724,17 @@ function updateFixationValues(me,x,y,inittime,fixtime,radius,strict)
end
end
if nargin > 3 && ~isempty(inittime)
if iscell(inittime) && length(inittime)==4
me.fixation.initTime = inittime{1};
me.fixation.time = inittime{2};
me.fixation.radius = inittime{3};
me.fixation.strict = inittime{4};
if iscell(inittime)
lst = {'initTime','time','radius','strict'};
for i = 1:length(inittime)
if contains(lst{i},'time','ignorecase',true) && length(inittime{i}) == 2
inittime{i} = randi(inittime{i}.*1000)/1000;
end
me.fixation.(lst{i}) = inittime{i}(1);
end
elseif length(inittime) == 2
me.fixation.initTime = randi(inittime.*1000)/1000;
elseif length(inittime)==1
elseif length(inittime) == 1
me.fixation.initTime = inittime;
end
end
Expand All @@ -721,7 +749,7 @@ function updateFixationValues(me,x,y,inittime,fixtime,radius,strict)
if nargin > 6 && ~isempty(strict); me.fixation.strict = strict; end
if me.verbose
fprintf('-+-+-> eyelinkManager:updateFixationValues: X=%g | Y=%g | IT=%s | FT=%s | R=%g | Strict=%i\n', ...
me.fixation.X, me.fixation.Y, num2str(me.fixation.initTime), num2str(me.fixation.time), ...
me.fixation.X, me.fixation.Y, num2str(me.fixation.initTime,'%.2f '), num2str(me.fixation.time,'%.2f '), ...
me.fixation.radius,me.fixation.strict);
end
end
Expand Down
2 changes: 1 addition & 1 deletion help/METHODS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ We highlight the main classes and methods that are most useful when building a p
Note that this command implicitly calls `resetFixation(eT)` as any previous fixation becomes invalid.

- **`resetFixation(eT, removeHistory)`** / **`resetFixationTime(eT)`** / **`resetFixationHistory(eT)`** / **`resetExclusionZone(eT)`** / **`resetFixInit(eT)`**
`resetFixation`: resets all fixation counters that track how long a fixation was held for. Pass `removeHistory` == true also calls `resetFixationHistory(eT)` to remove the local log of previous eye positions (used for plotting to a MATLAB figure on every trial). `resetFixationTime`: only reset the fixation window timers. `resetExclusionZone`: resets (removes) the exclusion zones. `resetFixInit`: fix init is a timer that stops a saccade away from a position to occur too quickly. This reset removes this check.
`resetFixation`: resets all fixation counters that track how long a fixation was held for. Pass `removeHistory` == true also calls `resetFixationHistory(eT)` to remove the temporary log of previous eye positions (usually call it only after each trial). `resetFixationTime`: only reset the fixation window timers. `resetExclusionZone`: resets (removes) the exclusion zones. `resetFixInit`: fix init is a timer that stops a saccade away from a position to occur too quickly. This reset removes this check.

- **`resetOffset(eT)`**
Reset the drift offset back to `X = 0; Y = 0` — see `driftOffset(eT)` for the method that sets this value.
Expand Down
Loading

0 comments on commit 4b42836

Please sign in to comment.