Skip to content
dotsX_JQK_MAT_170704.m 27.4 KiB
Newer Older
Julian Kosciessa's avatar
Julian Kosciessa committed
%_______________________________________________________________________
%
% core function for drawing random dots motion on presentation screen
%_______________________________________________________________________
%
function [frames,dotInfo, ExperimentProtocol,ResultMat,DisplayInfo] = dotsX_JQK_MAT_170704(screenInfo,dotInfo,targets,indTrial,ExperimentProtocol,ResultMat,DisplayInfo)

debug.Timing = 1;

% DOTSX display dots or targets on screen
%
% [frames,rseed,start_time,end_time,response,response_time] = dotsX(screenInfo,dotInfo,targets)
%
% For information on minimum fields of screenInfo and dotInfo arguments, see
% also openExperiment and createDotInfo. The input argument - "targets" is not
% necessary unless showing targets with the dots. Since rex only likes integers, 
% almost everything is in visual degrees * 10.
%
%   dotInfo.numDotField     number of dot patches that will be shown on screen
%   dotInfo.coh             vertical vectors, dots coherence (0...999) for each 
%                           dot patch
%   dotInfo.speed           vertical vectors, dots speed (10th deg/sec) for each 
%                           dot patch
%   dotInfo.dir             vertical vectors, dots direction (degrees) for each 
%                           dot patch
%   dotInfo.dotSize         size of dots in pixels, same for all patches
%   dotInfo.movingDotColor  color of dots in RGB for moving dots, same for all patches
%   dotInfo.randomDotColor  color of dots in RGB for random dots, same for all patches
%   dotInfo.maxDotsPerFrame determined by testing video card
%   dotInfo.apXYD           x, y coordinates, and diameter of aperture(s) in 
%                           visual degrees          
%   dotInfo.maxDotTime      optional to set maximum duration (sec). If not provided, 
%                           dot presentation is terminated only by user response
%   dotInfo.trialtype       1 fixed duration, 2 reaction time
%   dotInfo.keys            a set of keyboard buttons that can terminate the 
%                           presentation of dots (optional)
%   dotInfo.mouse           a set of mouse buttons that can terminate the 
%                           presentation of dots (optional)
%
%   screenInfo.curWindow    window pointer on which to plot dots
%   screenInfo.center       center of the screen in pixels
%   screenInfo.ppd          pixels per visual degree
%   screenInfo.monRefresh   monitor refresh value
%   screenInfo.dontclear    If set to 1, flip will not clear the framebuffer 
%                           after Flip - this allows incremental drawing of 
%                           stimuli. Needs to be zero for dots to be erased.
%   screenInfo.rseed        random # seed, can be empty set[] 
%
%   targets.rects           dimensions for drawOval
%   targets.colors          color of targets
%   targets.show            optional, if only showing certain targets but don't
%                           want to change targets structure (index number of 
%                           targets) to be shown during dots

% DisplayInfo       contains info on which conjunction of dots was
%                   presented where

%
% Algorithm:
%   All calculations take place within a square aperture in which the dots are 
% shown. The dots are constructed in 3 sets that are plotted in sequence.  For 
% each set, the probability that a dot is replotted in motion -- as opposed to 
% randomly replaced -- is given by the dotInfo.coh value. This routine generates 
% a set of dots as an (ndots,2) matrix of locations, and then plots them.  In 
% plotting the next set of dots (e.g., set 2), it prepends the preceding set 
% (e.g., set 1).
%

% created by MKMK July 2006, based on ShadlenDots by MNS, JIG and others
% adapted by JQK 2017

% Structures are not altered in this function, so should not have memory
% problems from matlab creating new structures.

% CURRENTLY THERE IS AN ALMOST ONE SECOND DELAY FROM THE TIME DOTSX IS
% CALLED UNTIL THE DOTS START ON THE SCREEN! THIS IS BECAUSE OF PRIORITY.
% NEED TO EVALUATE WHETHER PRIORITY IS REALLY NECESSARY.

% JQK: Note that you need to be CD'd to the superdirectory, so that the
% image referencing works.

% 170704 JQK:   - added confidence ratings; cntrl+alt as answer
%                   alternatives; cleanup
% 170707 JQK:   - added confidence onset timing
%               - take into consideration ifi in presentation loop
Julian Kosciessa's avatar
Julian Kosciessa committed

if nargin < 3
    targets = [];
    showtar = [];
else
    if isfield(targets,'show')
        showtar = targets.show;
    else
        showtar = 1:size(targets.rects,1);
    end
end

curWindow = screenInfo.curWindow;

%% restrict keys to be used during experiment

if strcmp(dotInfo.confOptions, '2')
    RestrictKeysForKbCheck([dotInfo.keyLeft(3), dotInfo.keyRight(3), dotInfo.keyModifier, ...
        dotInfo.keyEscape, dotInfo.keyReturn, dotInfo.keyPause]);
elseif strcmp(dotInfo.confOptions, '4')
    RestrictKeysForKbCheck([dotInfo.keyLeft, dotInfo.keyRight, dotInfo.keyConf1, ...
        dotInfo.keyConf2, dotInfo.keyConf3, dotInfo.keyConf4, dotInfo.keyModifier, ...
        dotInfo.keyEscape, dotInfo.keyReturn, dotInfo.keyPause]);
end

%% other initializiation

% set new random seed at each iteration and retain seed
rng(sum(100*clock), 'twister');
[dotInfo.curSeed{indTrial}] = rng;

ExperimentProtocol = [ExperimentProtocol; {'MATOnset'}, {datestr(now, 'yymmdd_HHMMSS')}, {GetSecs()}, {[]}, {[]}, {[]}, {[]}];

% Query the frame duration
ifi = Screen('GetFlipInterval', curWindow);

% Create the aperture square
%apRect = floor(createTRect(dotInfo.apXYD, screenInfo));

apD = dotInfo.apXYD(:,3); % diameter of aperture
center = repmat(screenInfo.center,size(dotInfo.apXYD(:,1)));

% Change x,y coordinates to pixels (y is inverted - pos on bottom, neg. on top)
center = [center(:,1) + dotInfo.apXYD(:,1)/10*screenInfo.ppd center(:,2) - ...
    dotInfo.apXYD(:,2)/10*screenInfo.ppd]; % where you want the center of the aperture
center(:,3) = dotInfo.apXYD(:,3)/2/10*screenInfo.ppd; % add diameter
d_ppd = floor(apD/10 * screenInfo.ppd);	% size of aperture in pixels
dotSize = dotInfo.dotSize; % probably better to leave this in pixels, but not sure

% ndots is the number of dots shown per video frame. Dots will be placed in a 
% square of the size of aperture.
% - Size of aperture = Apd*Apd/100  sq deg
% - Number of dots per video frame = 16.7 dots per sq deg/sec,
% When rounding up, do not exceed the number of dots that can be plotted in a 
% video frame (dotInfo.maxDotsPerFrame). maxDotsPerFrame was originally in 
% setupScreen as a field in screenInfo, but makes more sense in createDotInfo as 
% a field in dotInfo.
ndots = min(dotInfo.maxDotsPerFrame, ...
    ceil(16.7 * apD .* apD * 0.01 / screenInfo.monRefresh));

%% MAT definition

% 170627: For each feature, the dissociation may differ based on difficulty
% of the stimulus.

for indFeature = 1:4
    dotInfo.MAT.ndotsH(1,indFeature) = dotInfo.MAT.(['percAtt', num2str(indFeature), 'H']) * ndots;
    dotInfo.MAT.ndotsL(1,indFeature) = dotInfo.MAT.(['percAtt', num2str(indFeature), 'L']) * ndots;
    % The following is just a sanity check and should not cause problems in
    % the current setup.
    if dotInfo.MAT.ndotsL(1,indFeature) > dotInfo.MAT.ndotsH(1,indFeature)
        disp('Warning: Check percentages for the likelihood. The second percentage appears to be larger than the first.');
    end
end

% get the a priori defined higher probability option for each feature
curChoices = dotInfo.HighProbChoice(:,indTrial);
curChoices(isnan(curChoices)) = 1; % only necessary if we test a single condition

% encode info on currently active parameters for each feature
for indAtt = 1:4
    atts.(dotInfo.MAT.attNames{indAtt}) = [];
    atts.(dotInfo.MAT.attNames{indAtt})(1,1) = dotInfo.MAT.(dotInfo.MAT.attNames{indAtt})(curChoices(indAtt));
    atts.(dotInfo.MAT.attNames{indAtt})(1,2) = dotInfo.MAT.(dotInfo.MAT.attNames{indAtt})(3-curChoices(indAtt));
    atts.(dotInfo.MAT.attNames{indAtt})(2,1) = curChoices(indAtt);
    atts.(dotInfo.MAT.attNames{indAtt})(2,2) = 3-curChoices(indAtt);
end


frameCounter = 0;

RDMFlipTimeSecs = 1/dotInfo.Hz_RDM;
RDMFlipTimeFrames = round(RDMFlipTimeSecs / ifi);

%% get display size (for BG dots)

dontclear = screenInfo.dontclear;

% The main loop
frames = 0;
priorityLevel = MaxPriority(curWindow,'KbCheck');
Priority(priorityLevel);

% Make sure the fixation still on
for i = showtar
    Screen('FillOval',screenInfo.curWindow,targets.colors(i,:),targets.rects(i,:));
end

Screen('DrawingFinished',curWindow,dontclear);

%% get starting parameters

% How dots are presented: 1st group of dots are shown in the first frame, a 2nd 
% group are shown in the second frame, a 3rd group shown in the third frame.
% Then in the next (4th) frame, some percentage of the dots from the 1st frame 
% are replotted according to the speed/direction and coherence. Similarly, the 
% same is done for the 2nd group, etc.

% initialize dot fields
for df = 1 : dotInfo.numDotField
    ss{df} = rand(ndots(df)*3, 2); % array of dot positions raw [x,y]
    % Divide dots into three sets
    Ls{df} = cumsum(ones(ndots(df),3)) + repmat([0 ndots(df) ndots(df)*2], ... 
        ndots(df), 1);
    loopi(df) = 1; % loops through the three sets of dots
end

%% PRESENT CUE

if dotInfo.durCue > 0

    if dotInfo.DirAttn(indTrial) == 1
        %Cue = dotInfo.targetAtt(indTrial); % current attribute
        Cue = dotInfo.MAT.attNamesDE{dotInfo.targetAtt(indTrial)};
        %DrawFormattedText(screenInfo.curWindow, Cue, 'center', 'center');
        % instead of text, show exemplars
        CueImg = ['img/', dotInfo.MAT.attNames{dotInfo.targetAtt(indTrial)},'?.png'];
        [cueLoad,~,~] = imread(CueImg);
        cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
        smallIm = CenterRect([0 0 floor(size(cueLoad,2)/1.5) floor(size(cueLoad,1)/1.5)], screenInfo.screenRect);
        Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object 

    elseif dotInfo.DirAttn(indTrial) == 2
    %     Cue = '?';
    %     DrawFormattedText(screenInfo.curWindow, Cue, 'center', 'center');

        CueImg = ['img/??.png'];
        [cueLoad,~,~] = imread(CueImg);
        cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
        smallIm = CenterRect([0 0 floor(size(cueLoad,2)/1.5) floor(size(cueLoad,1)/1.5)], screenInfo.screenRect);
        Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object  

    end
    CueOnset = Screen('Flip', curWindow,0,dontclear);
    ExperimentProtocol = [ExperimentProtocol; {'CueOnset'}, {CueOnset}, {[]}, {[]}, {[]}, {indTrial}, {[]}];

    while GetSecs()-CueOnset < dotInfo.durCue % wait until cuetime is over
    end
    
end

%% PRESENT MAT STIMULUS

TimeStamping = [];
RDMUpdate = 0;
numOfFlips = 0;
StartTime = GetSecs();

ExperimentProtocol = [ExperimentProtocol; {'TrialOnset'}, {StartTime}, {[]}, {[]}, {[]}, {indTrial}, {[]}];

% initiate response cue
KbQueueCreate; KbQueueStart;
while GetSecs()-StartTime < dotInfo.durPres-ifi % loop while presentation time has not been reached
Julian Kosciessa's avatar
Julian Kosciessa committed
    

    % On each update, the assignments of the attributes to pixels changes.
    
    % get point cloud according to direction distribution (no incoherence here)
    
    if dotInfo.MAT.coherence ~= 0
        [maxVal,~] = max([dotInfo.MAT.percAtt2H, dotInfo.MAT.percAtt2L]);
    else maxVal = 0;
    end;
        
    for df = 1:dotInfo.numDotField

        % update movement field with current direction
        % dxdy is an N x 2 matrix that gives jumpsize in units on 0..1
        % deg/sec * ap-unit/deg * sec/jump = ap-unit/jump
        dxdy_1{df} = repmat((dotInfo.speed(df)/10) * (10/apD(df)) * ...
        (3/screenInfo.monRefresh) * [cos(pi*atts.direction(1,1)/180.0), ...
        -sin(pi*atts.direction(1,1)/180.0)], ndots(df),1);
    
        dxdy_2{df} = repmat((dotInfo.speed(df)/10) * (10/apD(df)) * ...
        (3/screenInfo.monRefresh) * [cos(pi*atts.direction(1,2)/180.0), ...
        -sin(pi*atts.direction(1,2)/180.0)], ndots(df),1);
        
        % ss is the matrix with 3 sets of dot positions, dots from the last 2 
        %   positions and current dot positions
        % Ls picks out the set (e.g., with 5 dots on the screen at a time, 1:5, 
        %   6:10, or 11:15)
        
        % Lthis has the dot positions from 3 frames ago, which is what is then
        Lthis{df}  = Ls{df}(:,loopi(df));
        
        % Moved in the current loop. This is a matrix of random numbers - starting 
        % positions of dots not moving coherently.
        this_s{df} = ss{df}(Lthis{df},:);
        
        % Update the loop pointer
        loopi(df) = loopi(df)+1;
        
        if loopi(df) == 4
            loopi(df) = 1;
        end
        
        % Compute new locations, how many dots move coherently
        L = rand(ndots(df),1) < maxVal;
        % Offset the selected dots
        this_s{df}(L,:) = bsxfun(@plus,this_s{df}(L,:),dxdy_1{df}(L,:));
        
        if sum(~L) > 0
            if maxVal ~= 0
                this_s{df}(~L,:) = bsxfun(@plus,this_s{df}(~L,:),dxdy_2{df}(~L,:)); % get the opposite direction for the rest
            else 
                this_s{df}(~L,:) = rand(sum(~L),2);	% get new random locations for the rest
            end;
        end
        
        % Check to see if any positions are greater than 1 or less than 0 which 
        % is out of the square aperture, and replace with a dot along one of the
        % edges opposite from the direction of motion.
        N = sum((this_s{df} > 1 | this_s{df} < 0)')' ~= 0;
        
        if sum(N) > 0
            xdir = sin(pi*atts.direction(1,1)/180.0); % Simplify things and just use the primary direction here.
            ydir = cos(pi*atts.direction(1,1)/180.0);
            % Flip a weighted coin to see which edge to put the replaced dots
            if rand < abs(xdir)/(abs(xdir) + abs(ydir))
                this_s{df}(find(N==1),:) = [rand(sum(N),1),(xdir > 0)*ones(sum(N),1)];
            else
                this_s{df}(find(N==1),:) = [(ydir < 0)*ones(sum(N),1),rand(sum(N),1)];
            end
        end
        
        % Convert for plot
        this_x{df} = floor(d_ppd(df) * this_s{df});	% pix/ApUnit
        
        % It assumes that 0 is at the top left, but we want it to be in the 
        % center, so shift the dots up and left, which means adding half of the 
        % aperture size to both the x and y directions.
        dot_show{df} = (this_x{df} - d_ppd(df)/2)';
        
    end
    
    % For each feature, randomly select higher amount of dots in the first
    % cell and lower amount of dots in the second cell. The randomization
    % is conducted before each flip.
    
    % Ordering of features: color, direction, size, luminance
    
    for indFeature = 1:4
        tmp_randChoice = randperm(ndots); % Note that the seed has been saved, so the process should be replicable.
        if indFeature == 2 && maxVal ~= 0 % direction is already set
            randMat{indFeature,indTrial,1} = find(L); % L is always the higher probability
            randMat{indFeature,indTrial,2} = find(~L);
        else 
            randMat{indFeature,indTrial,1} = tmp_randChoice(1:ceil(dotInfo.MAT.ndotsH(1,indFeature))); % round up; might cause slightly more than the requested lower probability
            randMat{indFeature,indTrial,2} = tmp_randChoice(ceil(dotInfo.MAT.ndotsH(1,indFeature))+1:end);
        end
    end
    
    % For displaying, we need to know the dots of conjunction attributes
    % that have to be drawn. These will be encoded in the randMat structure.
    
    combs = allcomb([1,2],[1,2],[1,2],[1,2]); % Note that 1 & 2 refer to the higher/lower prob option here.
    
    for indComb = 1:size(combs,1)
        dotsIdx{indComb} = mintersect(randMat{1,indTrial,combs(indComb,1)},...
            randMat{2,indTrial,combs(indComb,2)},...
            randMat{3,indTrial,combs(indComb,3)},...
            randMat{4,indTrial,combs(indComb,4)});
    end
    
    %% After all computations, flip to draw dots from the previous loop. 
    % For the first call, this doesn't draw anything.
    
    vbl = Screen('Flip', curWindow,0,dontclear);
    
    if RDMUpdate == 1
        ExperimentProtocol = [ExperimentProtocol; {'RDMUpdate'}, {vbl}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
    end
    
%     if debug.Timing == 1
%         TimeStamping = [TimeStamping; vbl];
%     end

    %% Draw random dots if it's time to update, although nothing is flipped yet    
    
    RDMUpdate = 0;
    if ~mod(frameCounter, RDMFlipTimeFrames)
        
        RDMUpdate = 1;
        
        % Note that dots that fall outside the circle are just dropped.
        % This may screw up the properties of the display.

        % NaN out-of-circle dots                
        xyDis = dot_show{1};
        outCircle = sqrt(xyDis(1,:).^2 + xyDis(2,:).^2) + dotInfo.dotSize/2 > center(df,3);        
        dots2Display = dot_show{1};
        dots2Display(:,outCircle) = NaN;

        Disp.color = cell(size(combs,1),1);
        Disp.dirDots = cell(size(combs,1),1);
        Disp.dotSize = cell(size(combs,1),1);
        Disp.lum = cell(size(combs,1),1);
        for indComb = 1:size(combs,1)
            if numel(dotsIdx{indComb}) > 0
                % referencing: attribute vector(index of higher/lower choice(prob of current comb))
                % combs(indComb, X): for this combination, is feature X higher high or low prob
                % atts.x(2,1): high prob option for feature; atts.X(2,2): low prob option
                Disp.color{indComb,1}   = dotInfo.MAT.color(atts.color(2,combs(indComb, 1)),:);
                Disp.dirDots{indComb,1} = dots2Display(:, dotsIdx{indComb}); % Note that 1 is always the index for the dots of the highest probability direction.
                Disp.dirDots{indComb,1} = Disp.dirDots{indComb,1}(:,~any(isnan(Disp.dirDots{indComb,1}),1)); % remove NaN columns
                Disp.dotSize{indComb,1} = dotInfo.MAT.size(atts.size(2,combs(indComb, 3)));
                Disp.lum{indComb,1}     = dotInfo.MAT.luminance(atts.luminance(2,combs(indComb, 4)));
            end
        end
        
        % Note that the intersection allocation should be saved for subsequent
        % processing as it is unlikely that the dimensions are orthogonal.
        % Therefore, they may constitute relevant intertrial variance in the
        % presentation (e.g. encoding model). Furthermore, the location
        % position can inform the visual sampling process.
        
        numOfFlips = numOfFlips+1;
        DisplayInfo.CombPositionByTrial{1,indTrial}(:,numOfFlips) = Disp.dirDots;
        DisplayInfo.CombSamples(:,indTrial,numOfFlips) = cellfun(@numel, Disp.dirDots);
        
    end
        
    if exist('Disp')
        for indComb = 1:size(combs,1)
            if numel(Disp.dirDots{indComb}) > 0
                Screen('DrawDots',curWindow,Disp.dirDots{indComb},Disp.dotSize{indComb},Disp.lum{indComb}.*Disp.color{indComb},center(df,1:2));
            end
        end
    end

    % Draw targets
    for i = showtar
        Screen('FillOval',screenInfo.curWindow,targets.colors(i,:),targets.rects(i,:));
    end
    
    %% Prepare next dots presentation
    
    % Tell PTB to get ready while doing computations for next dots presentation
    Screen('DrawingFinished',curWindow,dontclear);
    %Screen('BlendFunction', curWindow, GL_ONE, GL_ZERO);
       
    for df = 1 : dotInfo.numDotField
        % Update the dot position array for the next loop
        ss{df}(Lthis{df}, :) = this_s{df};
    end

end

% Present the last frame of dots
vbl = Screen('Flip',curWindow,0,dontclear);

if RDMUpdate == 1
    ExperimentProtocol = [ExperimentProtocol; {'RDMUpdate'}, {vbl}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
end

% if debug.Timing == 1
%     TimeStamping = [TimeStamping; vbl];
% end

% Erase the last frame of dots, but leave up fixation and targets (if targets 
% are up). Make sure the fixation still on.
showTargets(screenInfo,targets,showtar);

%% QUERY RESPONSE

if dotInfo.durResp ~= 0

    CueImg = ['img/', dotInfo.MAT.attNames{dotInfo.targetAtt(indTrial)},'.png'];
    [cueLoad,~,~] = imread(CueImg);
    cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
    smallIm = CenterRect([0 0 floor(size(cueLoad,2)/1.5) floor(size(cueLoad,1)/1.5)], screenInfo.screenRect);
    Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object 

    % Cue = [dotInfo.MAT.attNamesDE{dotInfo.targetAtt(indTrial)}, '?'];
    % DrawFormattedText(screenInfo.curWindow, Cue, 'center', 'center', [255 255 255]);

    RespOnset = Screen('Flip', curWindow,0,dontclear);
    ExperimentProtocol = [ExperimentProtocol; {'RespOnset'}, {RespOnset}, {[]}, {[]}, {[]}, {indTrial}, {[]}];

    if dotInfo.HighProbAtt(indTrial) == 1
        correctResp = dotInfo.keyLeft;
        wrongResp = dotInfo.keyRight;
    elseif dotInfo.HighProbAtt(indTrial) == 2
        correctResp = dotInfo.keyRight;
        wrongResp = dotInfo.keyLeft;
    end 
    
    accuracy = NaN; kp = NaN; rt_acc = NaN; curSelAcc = NaN;
    while GetSecs()-RespOnset < dotInfo.durResp % wait until cuetime is over
        %% check for responses
        [keyIsDown, firstPress, ~, ~, ~] = KbQueueCheck;
        if keyIsDown == 1
            kp = find(firstPress);
            rt_acc = firstPress(kp)-RespOnset; % reference to onset of last interval
            % encode accuracy
            if ismember(kp, correctResp)
                accuracy = 1;
                disp('Correct');
            elseif ismember(kp, wrongResp)
                accuracy = 0;
                disp('Wrong');
            end
            % update picture to be presented
            if dotInfo.highlightChoice == 1 && max(ismember(kp, dotInfo.keyLeft))
                curSelAcc = 1;
                CueImg = ['img/', dotInfo.MAT.attNames{dotInfo.targetAtt(indTrial)},'_c',num2str(curSelAcc),'.png'];
                [cueLoad,~,~] = imread(CueImg);
                cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
                smallIm = CenterRect([0 0 floor(size(cueLoad,2)/1.5) floor(size(cueLoad,1)/1.5)], screenInfo.screenRect);
                Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object
                Screen('Flip', curWindow,0,dontclear);
            elseif dotInfo.highlightChoice == 1 && max(ismember(kp, dotInfo.keyRight))
                curSelAcc = 2;
                CueImg = ['img/', dotInfo.MAT.attNames{dotInfo.targetAtt(indTrial)},'_c',num2str(curSelAcc),'.png'];
                [cueLoad,~,~] = imread(CueImg);
                cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
                smallIm = CenterRect([0 0 floor(size(cueLoad,2)/1.5) floor(size(cueLoad,1)/1.5)], screenInfo.screenRect);
                Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object
                Screen('Flip', curWindow,0,dontclear);
            end
            if ismember(kp, dotInfo.keyPause)
                ExperimentProtocol = [ExperimentProtocol; {'Pause'}, {GetSecs}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
                Screen('TextColor', screenInfo.curWindow, [255 255 255]);
                DrawFormattedText(screenInfo.curWindow, 'User Pause','center', 'center');
                Screen('Flip', screenInfo.curWindow, 0);
                pause(.2)
                KbWait
                ExperimentProtocol = [ExperimentProtocol; {'Continue'}, {GetSecs}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
            end;
            
            ExperimentProtocol = [ExperimentProtocol; {'Resp'}, {firstPress(kp)}, {rt_acc}, {kp}, {accuracy}, {indTrial}, {[]}];

            if dotInfo.feedback == 1 % show accuracy by textcolor
                if accuracy == 1
                    DrawFormattedText(screenInfo.curWindow, 'korrekt', 'center', 'center', [0 255 0]);
                elseif accuracy == 0
                    DrawFormattedText(screenInfo.curWindow, 'falsch', 'center', 'center', [255 0 0]);
                end
                Screen('Flip', curWindow,0,dontclear); clear TextColor;
            end

        end % end of response encode loop
    end
    
    ResultMat(indTrial,1:4) = [dotInfo.DirAttn(indTrial), dotInfo.targetAtt(indTrial), rt_acc, accuracy]; % only encodes last response; go to ExperimentProtocol to check previous responses
    
end;

%% QUERY CONFIDENCE

% confidence scale of four options: two left, two right / one left, one right

if dotInfo.durConf ~= 0
    
    % display visual confidence probe

    CueImg = ['img/confidence_',dotInfo.confOptions,'.png'];
    [cueLoad,~,~] = imread(CueImg);
    cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
    smallIm = CenterRect([0 0 floor(size(cueLoad,2)/2) floor(size(cueLoad,1)/2)], screenInfo.screenRect);
    Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object 
    
    ConfOnset = Screen('Flip', curWindow,0,dontclear);
    ExperimentProtocol = [ExperimentProtocol; {'ConfOnset'}, {ConfOnset}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
Julian Kosciessa's avatar
Julian Kosciessa committed
    
    kp = NaN; rt_conf = NaN; conf = NaN;curSelConf = NaN;
    while GetSecs()-ConfOnset < dotInfo.durConf % wait until cuetime is over
        %% check for responses
        [keyIsDown, firstPress, ~, ~, ~] = KbQueueCheck;
        if keyIsDown == 1
            kp = find(firstPress);
            rt_conf = firstPress(kp)-ConfOnset; % reference to onset of last interval
            % encode confidence
            if ismember(kp, dotInfo.keyConf1)
                conf = 1; disp('1');
            elseif ismember(kp, dotInfo.keyConf2)
                conf = 2; disp('2');
            elseif ismember(kp, dotInfo.keyConf3)
                conf = 3; disp('3');
            elseif ismember(kp, dotInfo.keyConf4)
                conf = 4; disp('4');
            end
            if ~isnan(conf) && dotInfo.highlightChoice == 1
                curSelConf = conf;
                CueImg = ['img/confidence_',dotInfo.confOptions,'_c',num2str(curSelConf),'.png'];
                [cueLoad,~,~] = imread(CueImg);
                cue2Disp = Screen('MakeTexture',screenInfo.curWindow,cueLoad);
                smallIm = CenterRect([0 0 floor(size(cueLoad,2)/2) floor(size(cueLoad,1)/2)], screenInfo.screenRect);
                Screen('DrawTexture', screenInfo.curWindow, cue2Disp, [], smallIm); % draw the object 
                Screen('Flip', curWindow,0,dontclear);
            end
            ExperimentProtocol = [ExperimentProtocol; {'Conf'}, {firstPress(kp)}, {rt_acc}, {kp}, {accuracy}, {indTrial}, {conf}];
        end % end of confidence encoding
    end
    ResultMat(indTrial,5) = conf; % only encodes last response; go to ExperimentProtocol to check previous responses
    ResultMat(indTrial,6) = rt_conf;
end

%% output descriptive summary statistics

if ~isempty(ResultMat)
    SummaryOutput{2,1} = '#misses';
    SummaryOutput{3,1} = 'accuracy';
    SummaryOutput{4,1} = 'rt';
    for indAtt = 1:4
        SummaryOutput{1,indAtt+1} = dotInfo.MAT.attNames{indAtt};
        % amount of misses
        SummaryOutput{2,indAtt+1} = sum(isnan(ResultMat(ResultMat(:,2)==indAtt,4)));
        % accuracy of the different categories
        SummaryOutput{3,indAtt+1} = nanmean(ResultMat(ResultMat(:,2)==indAtt,4));
        % rts of the different categories
        SummaryOutput{4,indAtt+1} = nanmean(ResultMat(ResultMat(:,2)==indAtt,3));
        if dotInfo.durConf ~= 0
            SummaryOutput{5,1} = 'confidence';
            % confidence of the different categories
            SummaryOutput{5,indAtt+1} = nanmean(ResultMat(ResultMat(:,2)==indAtt,5));
        end;
    end;
    disp(SummaryOutput)
end

%% close

OffsetTime = GetSecs;
ExperimentProtocol = [ExperimentProtocol; {'TrialOffset'}, {OffsetTime}, {[]}, {[]}, {[]}, {indTrial}, {[]}];
ExperimentProtocol = [ExperimentProtocol; {'MATOffset'}, {datestr(now, 'yymmdd_HHMMSS')}, {GetSecs()}, {[]}, {[]}, {[]}, {[]}];

Priority(0);