Skip to content
dotsExperiment_JQK_170109.m 11.6 KiB
Newer Older
Julian Kosciessa's avatar
Julian Kosciessa committed
%_______________________________________________________________________
%
% Run random dot motion experiment defined in createDotInfo
%_______________________________________________________________________
%
% Input
%
% dotInfo | experiment configuration (struct)
%    subj | identifier of subject (string)
% condSet | conditions (cell array of strings)
%_______________________________________________________________________
%
function dotsExperiment_JQK_170109(dotInfo, subj, condSet, DEBUG)

  %%
  %% sanitize function parameters
  %%
  if nargin == 0
    eval('help dotsExperiment')
    return
  end

  if not(exist('dotInfo','var'))
    error('Missing input: dotInfo')
  elseif isempty(dotInfo) || not(isstruct(dotInfo))
    error('Invalid input: dotInfo (requires non-empty struct)')
  end

  if not(exist('subj','var'))
    error('Missing input: subj')
  elseif not(isempty(subj)) && ischar(subj)
    dotInfo.subj = subj;
  else
    error('Invalid input: subj (requires non-empty string)')
  end

  if not(exist('condSet','var'))
    error('Missing input: condSet')
  elseif not(isempty(condSet)) && iscellstr(condSet) && min(cellfun(@length, condSet)) > 0
    dotInfo.condSet = condSet;
  else
    error('Invalid input: condSet (requires cell array of non-empty strings)')
  end

  if not(isfield(dotInfo,'deployed'))
    dotInfo.deployed = true;
  end

  %%
  %% for saving behavioural data
  %%
  sessionFile = ['dotsX_behav_' subj '_' datestr(now, 'dd-mm-yyyy_HH-MM')];
  sessionFile = fullfile(dotInfo.dataDir, sessionFile);
  
  %%
  %% prepare presentation
  %%
  displayList = Screen('screens');
  displayIdx = displayList(1);
  KbName('UnifyKeyNames'); % use portable key naming scheme
  oldVerbosityLevel = Screen('Preference', 'Verbosity', 2); % show errors and warnings
  arrowKeys = [dotInfo.keyLeft dotInfo.keyRight];
  baseDotInfo = dotInfo; % keep for saving later
  
    if DEBUG == 1
        Screen('Preference', 'SkipSyncTests', 1);
        PsychDebugWindowConfiguration(0, 0.2);
    else
        clear Screen; %PsychDebugWindowConfiguration([], 1);
    end;

  try

    %%
    %% prepare canvas
    %%
    screenInfo = openExperiment(50,50,displayIdx); clc; % open drawing canvas
    if numel(Screen('screens')) == 1 || dotInfo.deployed
      HideCursor(screenInfo.curWindow);
    end
    Screen('TextFont', screenInfo.curWindow, 'Helvetica');
    Screen('TextSize', screenInfo.curWindow, 20);
    Screen('TextStyle', screenInfo.curWindow, 0); % regular
    Screen('TextColor', screenInfo.curWindow, 255); % white

    % show intro only if not in debug mode
    if dotInfo.introNumTrial &&  DEBUG == 0
      exitSession = dotsIntroduction(dotInfo, screenInfo);
    else
      exitSession = false;
    end

    if exitSession
      closeExperiment;
      return
    end

    sessStartTime = GetSecs;

    %%
    %% condition start
    %%
    for s = 1:numel(condSet)

      count = 0; % trial number
      trialData = struct(); % result structure

      % at the beginning of each condition reset coherence variables
      dotInfo.lowStack = shiftStack(dotInfo.minCoh * ones(1,3));
      dotInfo.highStack = shiftStack(dotInfo.maxCoh * ones(1,3));
      dotInfo.coh = (dotInfo.lowStack.top + dotInfo.highStack.top)/2;
      dotInfo.resp = '';
      dotInfo.lastResp = '';
      dotInfo.lastLongResp = '';
      dotInfo.consecIdent = 0;
      dotInfo.regress = false;
      dotInfo.movAccuracy = NaN;
      dotInfo.longMovAccuracy = NaN;
      dotInfo.subjResponded = false;

      % have the subject get ready for the next condition
      DrawFormattedText(screenInfo.curWindow, 'ready?', 'center', 'center');
      Screen('Flip', screenInfo.curWindow);

      % wait for experimenter to resume session
      while true
        [exitSession, resumeSession] = checkKeys(dotInfo);
        if resumeSession
          break
        elseif exitSession
          closeExperiment;
          return
        end
      end

      %%
      %% block start
      %%
      for b = 1:dotInfo.numBlock % two blocks per condition

        dotInfo.stimMode = condSet{s};

        if strcmp(dotInfo.stimMode, 'sham')
          fprintf('> Noise off')
        else
          fprintf('> Noise %s', dotInfo.stimMode)
        end

        fprintf(' (%i/%i)\n\n', b, dotInfo.numBlock)

        nextstep = 'checkdur'; % start with fixation
        blockStart = GetSecs;
        blockMax = dotInfo.blockTime;
        done = 0;

        while true
          switch nextstep

            % check if the block time elapsed
            case 'checkdur'
              blockTime = GetSecs - blockStart;
              % exit block if the next trial could exceed maximum duration
              if blockTime + dotInfo.maxDotTime + dotInfo.fixMaxTime >= blockMax
                break
              else
                count = count + 1; % trial number
                fprintf('%3i', count)
              end
              nextstep = 'fixation';
        
            % show fixation dot
            case 'fixation'
              dotInfo.count = count;
              dotInfo = randomDotTrial(dotInfo, trialData);
              trialData(count).startTime = GetSecs - sessStartTime;
              trialData(count).fixTime = dotInfo.fixTime;
              trialData(count).corDir = find(dotInfo.dirSet == dotInfo.dir);
              trialData(count).coh = dotInfo.coh;
              trialData(count).dotDir = dotInfo.dir; % direction of dots
              trialData(count).rxtime = NaN; % reaction time
              trialData(count).lowStack = [dotInfo.lowStack.all];
              trialData(count).highStack = [dotInfo.highStack.all];
              trialData(count).regress = dotInfo.regress;
              trialData(count).consecIdent = dotInfo.consecIdent;
              trialData(count).lastResp = dotInfo.lastResp;
              trialData(count).lastLongResp = dotInfo.lastLongResp;
              targets = makeDotTargets(screenInfo, dotInfo); % initialize targets
              trialStartTime = GetSecs;
              showTargets(screenInfo, targets, 1);
              waitTime = dotInfo.fixTime;

              while true
                % exit wait loop when time delay is met
                if GetSecs - trialStartTime >= waitTime
                  nextstep = 'dots';
                  break
                end

                % stop experiment if exit key is pressed
                exitSession = checkKeys(dotInfo);
                if exitSession
                  fprintf('\n')
                  trialData(count) = [];
                  closeExperiment;
                  return
                end
              end
          
            % show moving dots
            case 'dots'
              screenInfo.rseed = []; % to make different random dots for each trial
              % response: 1 = left, 2 = right

              [~, rseed, start_time, end_time, response, response_time] = dotsX_JQK_170113(screenInfo, dotInfo, targets);
              trialData(count).rseed = rseed;
              keyPressed = ~isnan(response{3});
              keyDirection = response{3};

              if keyPressed && keyDirection > 0
                trialData(count).rxtime = response_time - start_time;
                if keyDirection == trialData(count).corDir;
                  trialData(count).correct = 1;
                  fprintf(' |  ok')
                else
                  trialData(count).correct = 0;
                  fprintf(' | err')
                end
              else % no key was pressed
                trialData(count).correct = 0;
                fprintf(' |   -')
              end

              correct = [trialData.correct];
              rxtime = [trialData.rxtime];
              correct = correct(not(isnan(rxtime))); % only trials with response
              dotInfo.subjResponded = not(isnan(rxtime(end))); % responded in last trial

              % short-term average accuracy
              if numel(correct) >= dotInfo.winSize
                dotInfo.movAccuracy = mean(correct(end-dotInfo.winSize+1:end));
              end
            
              % mid-term average accuracy
              if numel(correct) >= dotInfo.longWinSize
                dotInfo.longMovAccuracy = mean(correct(end-dotInfo.longWinSize+1:end));
              end

              trialData(count).movAccuracy = dotInfo.movAccuracy;
              trialData(count).longMovAccuracy = dotInfo.longMovAccuracy;

              if isnan(dotInfo.movAccuracy)
                fprintf(' | acc:  - ')
              else
                fprintf(' | acc: %3i',round(dotInfo.movAccuracy*100))
              end

              if isnan(dotInfo.longMovAccuracy)
                fprintf(' | long:  - ')
              else
                fprintf(' | long: %3i',round(dotInfo.longMovAccuracy*100))
              end

              fprintf(' | coh: %3i',round(dotInfo.coh))

              lowStack = round(dotInfo.lowStack.all);
              highStack = round(dotInfo.highStack.all);
              numBlank = numel(num2str(count));
              fprintf(' | %3i %3i %3i | %3i %3i %3i |', lowStack, highStack)

              if dotInfo.regress
                fprintf(' regress %s\n', dotInfo.regress)
                dotInfo.regress = false;
              else
                fprintf('\n')
              end

              % exit experiment if trial was aborted
              if keyPressed && keyDirection == -1
                closeExperiment;
                return
              end
              nextstep = 'checkdur';
          end
        end

        % take a break after every block except after the last block
        if not(s == numel(condSet) && b == dotInfo.numBlock)
          while true
            % exit wait loop when time delay is met
            blockTime = GetSecs - blockStart; 
            if blockTime >= blockMax + dotInfo.breakTime
              break
            end

            % stop experiment if exit key is pressed
            exitSession = checkKeys(dotInfo);
            if exitSession
              fprintf('\n')
              trialData(count) = [];
              closeExperiment;
              return
            end
          end
        end

        condData(s).stimMode = dotInfo.stimMode;
        condData(s).trialData = trialData;

        save(sessionFile, 'condData', 'baseDotInfo');

      end % block
    end % condition

    % wait for a short moment before showing task complete message
    outroStartTime = GetSecs;
    waitTime = 1;
    while true
      % exit wait loop when time delay is met
      if GetSecs - outroStartTime >= waitTime
        break
      end

      % stop experiment if exit key is pressed
      exitSession = checkKeys(dotInfo);
      if exitSession
        fprintf('\n')
        closeExperiment;
        return
      end
    end

    % inform the subject that the experiment is over
    DrawFormattedText(screenInfo.curWindow, 'task complete!', 'center', 'center');
    Screen('Flip', screenInfo.curWindow);

    sessTime = GetSecs - sessStartTime;
    meanFixTime = mean([trialData.fixTime]);
    numCorrect = numel(find([trialData.correct]));
    numTrial = numel([trialData.correct]);
    numResponded = numel(find(~isnan([trialData.rxtime])));
  
    fprintf('\nTotal Time: %.2f\n', sessTime) % incl. block startup waits
    fprintf('Average fixation: %.2fs\n', meanFixTime)
    fprintf('Responded: %i/%i\n', numResponded, numTrial)
    fprintf('Correct: %i/%i\n', numCorrect, numTrial)
    fprintf('Accuracy: %.1f\n', 100*numCorrect/numResponded)

    % wait for experimenter to close screen
    while true
      [exitKeyPressed, resumeKeyPressed] = checkKeys(dotInfo);
      if exitKeyPressed || resumeKeyPressed
        break
      end
    end

  catch exception
    getReport(exception) % show stack trace
  end

  closeExperiment; % close drawing canvas
  Screen('Preference', 'Verbosity', oldVerbosityLevel); % restore verbosity

end