Skip to content
createDotInfo_JQK_170814.m 19.2 KiB
Newer Older
Julian Kosciessa's avatar
Julian Kosciessa committed
%_______________________________________________________________________
%
% Configuration for running dotsExperiment
%_______________________________________________________________________
%
% Output
%
% dotInfo | configuration for state switch MAT (struct)


% 170704    - incorporated thresholds
% 170809    - incorporated state dimensionality & accompanying
%               randomization changes
% 170814    - introduce run format; adapted timing

function dotInfo = createDotInfo_JQK_170814(varargin)

    dotInfo.trialsPerAtt = 16;  % number of trials within state order & attribute (half of this for each choice) 
    dotInfo.numOfAtt = 4;       % number of attributes to be included
    dotInfo.numOrders = 4;
    dotInfo.totalTrials = dotInfo.trialsPerAtt*dotInfo.numOfAtt*dotInfo.numOrders;
    
    %dotInfo.runAmount = 4;      % number of runs; each run should have each category x times
        
    dotInfo.durBlockOnset = 5;  % duration of block onset
    dotInfo.durCue = 2;         % duration of cue
    %dotInfo.durFixCue = 3;      % duration of fixcross with cues
    dotInfo.durPres = 3;        % duration of presentation
    dotInfo.durResp = 2;        % duration of question
    dotInfo.durConf = 2;        % duration of confidence
    dotInfo.durReward = 0;      % duration of reward
    dotInfo.durITI = 2;         % duration of ITI
    
    dotInfo.timing = 'absolute'; % use absolute timing with reference to run onset? alternative: 'relative'

    dotInfo.trialDuration.all = dotInfo.durCue+dotInfo.durPres+dotInfo.durResp+dotInfo.durConf+dotInfo.durReward+dotInfo.durITI;
    dotInfo.trialDuration.Cue = dotInfo.durCue;
    dotInfo.trialDuration.Pres = dotInfo.durCue+dotInfo.durPres;
    dotInfo.trialDuration.Resp = dotInfo.durCue+dotInfo.durPres+dotInfo.durResp;
    dotInfo.trialDuration.Conf = dotInfo.durCue+dotInfo.durPres+dotInfo.durResp+dotInfo.durConf;
    dotInfo.trialDuration.Reward = dotInfo.durCue+dotInfo.durPres+dotInfo.durResp+dotInfo.durConf+dotInfo.durReward;
    
    dotInfo.confOptions = '2';      % 2 or 4 confidence options
    dotInfo.highlightChoice = 1;    % highlight choice by retaining only chosen option?
    
    dotInfo.feedback = 0;   % no feedback
    
    dotInfo.dirSet          = [180, 0];     % dots in left or right direction
    dotInfo.numDotField     = 1;            % show a single dot patches on screen
    dotInfo.apXYD           = [0 0 130];    % coordinates and diameter of aperture
    dotInfo.speed           = [50];         % speed of dot motion
    dotInfo.trialtype       = [2 1 1];      % reaction time, not relevant, keyboard
    dotInfo.dotSize         = 3;            % dot size in pixels
    dotInfo.maxDotTime      = 2;            % maximum duration of moving dots
    dotInfo.fixXY           = [0 0];        % fixation coordinates
    dotInfo.fixDiam         = 2;            % fixation diameter
    dotInfo.fixColor        = [0 150 200];  % blue fixation dot
    dotInfo.fixMinTime      = 0.75;         % minimum fixation
    dotInfo.fixMaxTime      = 1.25;         % maximum fixation
    %dotInfo.maxDotsPerFrame = 300;          % depends on graphics card
    dotInfo.fixTime         = 2;            % JQK: fixed onset fixation time
    dotInfo.DotsPerFrame    = 48*3;

    dotInfo.breakTime = 60;                   % pause for 60 seconds between runs
    
    % update frequency of on-screen content
    
    dotInfo.Hz_RDM  = 48; % kinematogram
    
    % multi-attribute task

    if ~isempty(varargin)
        thresholds = varargin{1,1};
        dotInfo.MAT.percAtt1H = thresholds.color;
        dotInfo.MAT.percAtt2H = thresholds.direction;
        dotInfo.MAT.percAtt3H = thresholds.size;
        dotInfo.MAT.percAtt4H = thresholds.luminance;
    else 
        dotInfo.MAT.percAtt1H = .65;
        dotInfo.MAT.percAtt2H = .65;
        dotInfo.MAT.percAtt3H = .65;
        dotInfo.MAT.percAtt4H = .65;
    end
    dotInfo.MAT.percAtt1L = 1-dotInfo.MAT.percAtt1H;
    dotInfo.MAT.percAtt2L = 1-dotInfo.MAT.percAtt2H;
    dotInfo.MAT.percAtt3L = 1-dotInfo.MAT.percAtt3H;
    dotInfo.MAT.percAtt4L = 1-dotInfo.MAT.percAtt4H; 
    
    %dotInfo.MAT.color = [255 255 255; 255 0 0];                             % define dot color
    dotInfo.MAT.color = [177,213,57; 234,35,93];                             % define dot color
    %dotInfo.MAT.coherence = 1;                                              % define dot movement (coherence)
    dotInfo.MAT.coherence = .65;                                              % define dot movement (coherence)
    dotInfo.MAT.direction = [180 0];                                        % left and right
    dotInfo.MAT.size = [5 8];                                               % define dot size
    dotInfo.MAT.luminance = [.6 1];                                        % define dot luminance
    dotInfo.MAT.attNames = {'color'; 'direction'; 'size'; 'luminance'};
    dotInfo.MAT.attNamesDE = {'Farbe'; 'Richtung'; 'Gre'; 'Helligkeit'};

    %% specify keys
    % Use OS X keyboard naming scheme across all plattforms
    % Otherwise LeftControl/RightControl are not recognized
    KbName('UnifyKeyNames');

    if ismac % Mac keyboard
        dotInfo.keyLeft = [KbName('LeftControl'), KbName('LeftAlt'), KbName('LeftArrow')];      % left response
        dotInfo.keyRight = [KbName('RightControl'), KbName('RightAlt'), KbName('RightArrow')];  % right response
        dotInfo.keyConf1 = [KbName('LeftControl'), KbName('LeftArrow')];       % lowest confidence
        dotInfo.keyConf2 = KbName('LeftAlt');                                  % intermediate confidence low
        dotInfo.keyConf3 = KbName('RightAlt');                                 % intermediate confidence high
        dotInfo.keyConf4 = [KbName('RightControl'), KbName('RightArrow')];     % highest confidence
    else % PC keyboard
%         dotInfo.keyLeft = KbName('LeftControl');
%         dotInfo.keyRight = KbName('RightControl');
        dotInfo.keyLeft = [KbName('LeftControl'), KbName('LeftAlt'), KbName('LeftArrow')];
        dotInfo.keyRight = [KbName('RightControl'), KbName('RightAlt'), KbName('RightArrow')];
        dotInfo.keyConf1 = [KbName('LeftControl'), KbName('LeftArrow')];
        dotInfo.keyConf2 = KbName('LeftAlt');
        dotInfo.keyConf3 = KbName('RightAlt');
        dotInfo.keyConf4 = [KbName('RightControl'), KbName('RightArrow')];
    end

    dotInfo.keyModifier = KbName('LeftAlt'); % to prevent accidental input
    dotInfo.keyEscape = KbName('Escape'); %
    dotInfo.keyReturn = KbName('Return'); % continue experiment
    dotInfo.keyPause = KbName('p');

    %% randomize stimuli and non-target duration
    
    % set random seed

    rseed = sum(100*clock);
    rng(rseed,'twister');
    [dotInfo.rngSetting] = rng;
    
    %% Here we need to decide the following settings:
    
    %  1. How many attribute dimensions (i.e. state manipulation)? [block-wise to avoid 'meta-four state']
    %  2. Which attributes to be chosen? Same in each block?
    %  3. Which target in each trial?
    %  4. Which option of the attribute is winning?
    
    %% decide cueing state order (1/2/3/4) & higher probability choice (within attribute)
    
    dotInfo.blockLengthDim = 8; % amount of consecutive trials belonging to the same state dimension block; needs to be a multiple of 4
    
    dotInfo.blockAmount     = (dotInfo.trialsPerAtt*dotInfo.numOfAtt*dotInfo.numOrders)/dotInfo.blockLengthDim; % amount of blocks (i.e. determined by total trial count and block length) 
    dotInfo.blocksPerOrd    = dotInfo.blockAmount/dotInfo.numOrders;                        % amount of blocks per state dimension
    dotInfo.StateOrder      = NaN(dotInfo.blockAmount, dotInfo.blockLengthDim);             % prelim. matrix for state dimension order
    dotInfo.AttCues         = cell(dotInfo.blockAmount, dotInfo.blockLengthDim);            % prelim. cell struc for attribute cues
    dotInfo.targetAtt       = NaN(dotInfo.blockAmount, dotInfo.blockLengthDim);             % prelim. matrix for target attribute
    dotInfo.targetOption    = NaN(dotInfo.blockAmount, dotInfo.blockLengthDim);             % prelim. matrix for target option
    dotInfo.HighProbChoice  = cell(dotInfo.blockAmount, dotInfo.blockLengthDim);            % prelim. cell struc for winning options
    
    dotInfo.blockTime = dotInfo.durBlockOnset+dotInfo.blockLengthDim*...
        (dotInfo.durCue+dotInfo.durPres+dotInfo.durResp+dotInfo.durConf+dotInfo.durReward+dotInfo.durITI);
    
    % get amounts of runs to achieve approx. 10 min per run
    
    %dotInfo.runAmount = ceil(dotInfo.blockAmount*dotInfo.blockTime/60/10);
    dotInfo.runAmount = dotInfo.blocksPerOrd/2; % now 4 runs! rather go for many runs vs. not including many breaks
    dotInfo.blocksPerRun = 8;
    
    % Pseudo-randomize the block order, i.e. allowing for repeting dimension blocks.
    % This is performed run-wise, such that each run contains a single
    % block of each state order. We also make sure that there are no
    % adjacent repeats of dimension blocks.
    
    criterion = 0;
    while criterion == 0
        allCombsBlocks = perms([1,2,3,4]);
        chosenBlockOrders = randperm(size(allCombsBlocks,1),dotInfo.blocksPerRun); % have two dimension blocks each iteration
        blockDimOrder = reshape(allCombsBlocks(chosenBlockOrders,:)',[],1);
        if min(abs(diff(blockDimOrder))) ~= 0 % check that there are no repeats
            criterion = 1;
        end
    end
    dotInfo.StateOrder = repmat(blockDimOrder,1,dotInfo.blockLengthDim); % final state order output for presentation
    
%     blockDimOrder = repmat(1:4,1,16); tmp_rand = randperm(dotInfo.blockAmount);   
%     blockDimOrder = blockDimOrder(tmp_rand);
%     dotInfo.StateOrder = repmat(blockDimOrder', 1, dotInfo.blockLengthDim);            % final state order output for presentation
    
    % get combinatorials for attribute cues for each dimension order
    
    for indOrd = 1:dotInfo.numOrders
        combsDim{indOrd} = nchoosek([1:dotInfo.numOfAtt], indOrd); % 4, 6, 4, 1
    end
    
    for indOrd = 1:dotInfo.numOrders
        tmp_blocks = find(blockDimOrder == indOrd);
        tmp_blockCues = repmat(combsDim{indOrd},floor(numel(tmp_blocks)/size(combsDim{indOrd},1)),1);
        if indOrd == 2 % there are 6 options, hence add two more; important: add all attributes 1-4!
            tmp_blockCues = [tmp_blockCues; combsDim{indOrd}(1,:); combsDim{indOrd}(end,:)]; 
        end
        rand.blockCues(indOrd,:) = randperm(size(tmp_blockCues,1));
        for indBlock = 1:numel(tmp_blocks)
            dotInfo.AttCues(tmp_blocks(indBlock),:) = {tmp_blockCues(rand.blockCues(indOrd,indBlock),:)}; % encode attribute cues
        end
    end
    
    %% determine target attribute
    
    % The logic here is the following: We now have blocks, in which the
    % same state order and the same attribute targets are presented. Now,
    % wihtin those attribute options, but across blocks, we choose trials
    % such that each target attribute will occur the same number of times
    % within-order (but not necessarily within-cue combination or block).
    % Then we also choose half of the target attribute trials randomly and
    % allocate them to the target option (i.e. red/white), such that these
    % are also matched in amount within-attribute. The target attribute
    % matching is done based on the groups of cue conjunctions.
    
    indCatch = [];
    for indOrd = 1:4
        
        % for all attributes, get location in the attribute cues
        for indAtt = 1:4
            index = cellfun(@(x) ismember(indAtt,x), dotInfo.AttCues(blockDimOrder==indOrd,:), 'UniformOutput', 0);
            indCatch{indOrd, indAtt} = find(cell2mat(index));
        end
        
        TargetPos = []; % intiate target structure
        
        if indOrd == 1
            % target attributes are already fixed in this condition
            for indAtt = 1:4
                TargetPos{indAtt} = indCatch{indOrd, indAtt};
            end
        end
        
        if indOrd == 2
            % split each doublet in half
            TargetPos = cell(1,4); % cell structure (1*attribute) containing the trial indices for state order = 3
            for indDoublet = 1:size(combsDim{indOrd},1)
                Doublets{indDoublet} = intersect(indCatch{indOrd, combsDim{indOrd}(indDoublet,1)}, indCatch{indOrd, combsDim{indOrd}(indDoublet,2)});
                % randomize and reshape into two splits
                DoubletsExtracted{indDoublet} = reshape(Doublets{indDoublet}(randperm(numel(Doublets{indDoublet}))), 2,[]);
                for indAttribute = 1:4
                    if combsDim{indOrd}(indDoublet,1) == indAttribute
                        TargetPos{indAttribute} = [TargetPos{indAttribute}, DoubletsExtracted{indDoublet}(1,:)];
                    elseif combsDim{indOrd}(indDoublet,2) == indAttribute
                        TargetPos{indAttribute} = [TargetPos{indAttribute}, DoubletsExtracted{indDoublet}(2,:)];
                    end
                end
            end
            clear indDoublet Doublets DoubletsExtracted indAttribute;
        end
            
        if indOrd == 3

            % get intersection indices

            Triplets{1} = intersect(intersect(indCatch{indOrd, 1}, indCatch{indOrd, 2}), indCatch{indOrd, 3}); % 1 2 1 0
            Triplets{2} = intersect(intersect(indCatch{indOrd, 2}, indCatch{indOrd, 3}), indCatch{indOrd, 4}); % 0 1 2 1
            Triplets{3} = intersect(intersect(indCatch{indOrd, 1}, indCatch{indOrd, 3}), indCatch{indOrd, 4}); % 1 0 1 2
            Triplets{4} = intersect(intersect(indCatch{indOrd, 1}, indCatch{indOrd, 2}), indCatch{indOrd, 4}); % 2 1 0 1

            % randomize order and split into four equal sized groups
            % multiple of these splits will be given to one of the intersected
            % attributes according to the sheme above on the right. Such split
            % is necessary as we cannot divide by three here.

            for indIntersect = 1:4
                TripletsExtracted{indIntersect} = reshape(Triplets{indIntersect}(randperm(numel(Triplets{indIntersect}))), 4,[]);
            end

            % extract these randomized indices into the four target attribute categories

            TargetPos = cell(1,4); % cell structure (1*attribute) containing the trial indices for state order = 3
            extractMat = [1,2,1,0; 0,1,2,1; 1,0,1,2; 2,1,0,1];
            for indAttribute = 1:4
                for indIntersect = 1: extractMat(indAttribute,1)
                    TargetPos{indAttribute} = [TargetPos{indAttribute}, TripletsExtracted{1}(1,:)];
                    TripletsExtracted{1}(1,:) = [];
                end
                for indIntersect = 1: extractMat(indAttribute,2)
                    TargetPos{indAttribute} = [TargetPos{indAttribute}, TripletsExtracted{2}(1,:)];
                    TripletsExtracted{2}(1,:) = [];
                end
                for indIntersect = 1: extractMat(indAttribute,3)
                    TargetPos{indAttribute} = [TargetPos{indAttribute}, TripletsExtracted{3}(1,:)];
                    TripletsExtracted{3}(1,:) = [];
                end
                for indIntersect = 1: extractMat(indAttribute,4)
                    TargetPos{indAttribute} = [TargetPos{indAttribute}, TripletsExtracted{4}(1,:)];
                    TripletsExtracted{4}(1,:) = [];
                end
            end
            clear Triplets TripletsExtracted indAttribute indIntersect;
        end % indOrd == 3
        
        if indOrd == 4
            % split each doublet in four
            TargetPos = cell(1,4); % cell structure (1*attribute) containing the trial indices for state order = 3
            Quadruples{1} = indCatch{indOrd,1};
            % randomize and reshape into two splits
            QuadruplesExtracted{1} = reshape(Quadruples{1}(randperm(numel(Quadruples{1}))), 4,[]);
            for indAttribute = 1:4
                TargetPos{indAttribute} = [TargetPos{indAttribute}, QuadruplesExtracted{1}(indAttribute,:)];
            end
            clear Quadruples QuadruplesExtracted indAttribute;
        end
        
        % add target attributes to matrix
        idxCurrentOrder = find(dotInfo.StateOrder == indOrd); % indexes trials of current state order
        for indAttribute = 1:4
            dotInfo.targetAtt(idxCurrentOrder(TargetPos{indAttribute})) = indAttribute;
        end
        
        %% determine target choice
        
        % for each attribute, randomize within-order, which option will be the winner
        for indAttribute = 1:4
            tmp_curTrials = idxCurrentOrder(TargetPos{indAttribute});
            tmp_curTrialsPerm = reshape(tmp_curTrials(randperm(numel(tmp_curTrials))),2,[]);
            dotInfo.targetOption(tmp_curTrialsPerm(1,:)) = 1;
            dotInfo.targetOption(tmp_curTrialsPerm(2,:)) = 2;
        end; clear tmp*
        
    end % state order loop

    %% Here, we could go back and try to fix a transition probability. This step will be skipped for now.
    
    %% select parameters of remaining attributes one each trial
    
    % these are chosen such that within each dimension and attribute, all
    % other parameter constellations are presented (i.e. the 16 combinations 
    % should be presented in each category of the design)
    
    combs = allcomb([1,2],[1,2],[1,2],[1,2]); % Note that 1 & 2 refer to the higher/lower prob option here.
    
    % The target option is always already fixed as done above. This leaves
    % eight remaining combinations for each multi-attribute display that
    % should be allocated within-order, within-target-attribute.
    
    % 1. get current state dim order
    % 2. get current attribute
    % 3. get high/low option
    % 4. distribute the remaining categories
    
    for indOrd = 1:4
        for indAtt = 1:4
            for indChoice = 1:2
                idxCurrentOrder_l1 = find(dotInfo.StateOrder == indOrd); % indexes trials of current state order
                idxCurrentAtt_l2 =  find(dotInfo.targetAtt(idxCurrentOrder_l1) == indAtt);
                indCurrentChoice_l3 = find(dotInfo.targetOption(idxCurrentOrder_l1(idxCurrentAtt_l2)) == indChoice);
                indCombined = idxCurrentOrder_l1(idxCurrentAtt_l2(indCurrentChoice_l3));
                % get combinations with current parameters (regarding target attribute & choice)
                curCombs = combs(combs(:,indAtt)==indChoice,:);
                % randomize trials
                indRecomb = indCombined(randperm(numel(indCombined)));
                % repeat to match amount of trials
                curCombs = repmat(curCombs, numel(indRecomb)/size(curCombs,1),1);
                for indTrial = 1:numel(indRecomb)
                    indTargetTrial = indRecomb(indTrial);
                    dotInfo.HighProbChoice{indTargetTrial} = curCombs(indTrial,:);
                end
            end % choice
        end % attribute
    end % order
    
    %% put everything into block wrappers
    
    edges = 1:dotInfo.blocksPerRun:32+1;
    for indRun = 1:dotInfo.runAmount
        dotInfo.StateOrderRun{indRun} = dotInfo.StateOrder(edges(indRun):edges(indRun+1)-1,:);
        dotInfo.AttCuesRun{indRun} = dotInfo.AttCues(edges(indRun):edges(indRun+1)-1,:);
        dotInfo.targetAttRun{indRun} = dotInfo.targetAtt(edges(indRun):edges(indRun+1)-1,:);
        dotInfo.targetOptionRun{indRun} = dotInfo.targetOption(edges(indRun):edges(indRun+1)-1,:);
        dotInfo.HighProbChoiceRun{indRun} = dotInfo.HighProbChoice(edges(indRun):edges(indRun+1)-1,:);
    end
    
    
end