知覚実験テンプレート

知覚実験のテンプレートとして利用できるシンプルなプログラムです。

実験の課題は、傾いた正方形の中にある線分を矢印キーを押すことによってスクリーンに対して垂直にすることです。

Oltman, P. K. (1968). A portable rod-and-frame apparatus. Perceptual and Motor Skills, 26, 503-506 で紹介されているような実験です。

1試行の流れは次の通り

  1. 凝視点呈示
  2. スペースキーを押す
  3. 傾いた正方形と線分の呈示
  4. 矢印キーで線分の傾きを調整
  5. エンターキーで回答を決定

実験の結果は、実験プログラム(Mファイル)と同じ場所に、「被験者名+日時」のファイル名でCSV形式で保存されます。

Matlabのコマンドライン上で、

LineAngleExp1('testuser')

のような形(testuserが被験者名)で実行することにより、被験者名を指定できます。

角度についてですが、水平状態を0度として、プラスに増加すると時計回りに回転します。

つまり90度は時計での6時の位置です。

左矢印は、時計回りで、0.5度間隔です。

上矢印は、時計回りで、5度間隔です。

右矢印は、反時計回りで、0.5度間隔です。

下矢印は、反時計回りで、5度間隔です。

線分は0度のときに水平になります。

正方形は0度のときにひし形になることに注意してください。

setApexで、正方形と線分の頂点座標を決定しています。

setApexの部分を、自分の好みの刺激に変更するとよいでしょう。

GetSecs、WaitSecsは本プログラムでは使用していませんが、これらの関数は高速なプログラム動作のためにあらかじめ読み込んでおいたほうがよいファイル(コマンド)になります。(help GetSecsなどを参照してください)

同様にあらかじめ読み込んでおいたほうがよいファイル(コマンド)があればここに追加してください。

実験についてですが、アンチエイリアシングのムラが見えてしまうと、アーティファクトな影響がありそうなので、十分な視距離をとるなどの工夫が必要でしょう。

%  実験の課題は、傾いた正方形の中にある線分を矢印キーを押すことによって
%  スクリーンに対して垂直にすることです。
%
%  凝視点の画面でスペースキーを押すと刺激を呈示。 
%  左と上キーが同方向で、左が細かい調整、上が粗い調整。
%  右と下キーが同方向で、右が細かい調整、下が粗い調整。
%  矢印キーで調整したあと、エンターキーを押すと回答の決定です。
%

% 正方形の傾きは条件毎に変更可能です。(以下ではすべての条件で30度にしています)

%  ただし矢印キーで調整するのは線分の傾きであり、正方形の傾きは1試行中は固定です。
%
function LineAngleExp1(subName)
ListenChar(2);
AssertOpenGL;
%OS で共通のキー配置にする
KbName('UnifyKeyNames');
% 手作りfunction。必要に応じてコメントアウトしてください。
%myKeyCheck;
%定数
lineLength = 50;   %線分の長さ(ピクセル 整数)
squareSize = 100;  %正方形の一辺の長さ(ピクセル 整数)
%一度読み込んでおく必要がある関数たち
GetSecs;
WaitSecs(1);
if ~exist('subName', 'var')
    subName = 'taro';
end;
% 条件の設定
%  各行が各条件に対応
%  1列目は線分の傾き、2列目は正方形の傾き)
condTable = [
    30 30;
    45 30;
    60 30];
%1ブロックの試行数
nTrials = size(condTable, 1);
%繰り返し回数
repeatNum = 2;
%answerに回答が代入される。初期化。
answer = zeros(nTrials * repeatNum, 6);
%カーソルを消す
HideCursor;

%ランダム化のために必須

rand('state',sum(100*clock));
try
 
  screenNumber = max(Screen('Screens'));
 
  bgColor = [0 0 0];
  %ウィンドウでの呈示
  %[windowPtr, windowRect] = Screen('OpenWindow', screenNumber, bgColor, [10 50 750 550]);
  %フルスクリーンでの呈示
  [windowPtr, windowRect]=Screen('OpenWindow', screenNumber, bgColor);
 
  %中心座標
  [center(1), center(2)] = RectCenter(windowRect);
 
 
  fixLength = 30; %凝視点線分の長さ
  %凝視点の座標
  fixApex = [
      fixLength/2, -fixLength/2, 0, 0;
      0, 0, fixLength/2, -fixLength/2];
 
  %アンチエイリアシング
  Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
  for s=1:repeatNum
      %order = [1:nTrials]; % 刺激を番号順で呈示するとき
      order = randperm(nTrials); % 刺激をランダムに呈示するとき
     
      for t=1:length(order)
          %凝視点の呈示
          Screen('DrawLines', windowPtr, fixApex, 2, [255 255 255], center, 0);
          Screen('Flip', windowPtr);
         
          spaceFlag = 0;
          lineAngle = condTable(order(t), 1);
          squareAngle = condTable(order(t), 2);
         
          while 1
            % キー入力を待つ
            [baseTime, keyCode] = KbWait([], 3);
           
            if keyCode(KbName('space'))
                spaceFlag = 1;
            end;
           
            if keyCode(KbName('ESCAPE'))
                error('ESCキーによって強制終了しました。'); % catch文に飛ぶ
            end;
           
            if spaceFlag && keyCode(KbName('LeftArrow'))
                lineAngle = lineAngle + 0.5;               
            end;
           
            if spaceFlag && keyCode(KbName('UpArrow'))
                lineAngle = lineAngle + 5;               
            end;
           
            if spaceFlag && keyCode(KbName('RightArrow'))
                lineAngle = lineAngle - 0.5;               
            end;
           
            if spaceFlag && keyCode(KbName('DownArrow'))
                lineAngle = lineAngle - 5;               
            end;
           
            if spaceFlag && keyCode(KbName('Return'))
                answer(t + nTrials*(s-1), 1) = s; %繰り返し
                answer(t + nTrials*(s-1), 2) = t; %試行
                answer(t + nTrials*(s-1), 3) = order(t); %条件
                answer(t + nTrials*(s-1), 4) = condTable(order(t), 1); %線分の初期角度
                answer(t + nTrials*(s-1), 5) = condTable(order(t), 2); %正方形の角度
                answer(t + nTrials*(s-1), 6) = lineAngle; %回答
                break;
            end;
           
            if spaceFlag
                apex = setApex(lineAngle, lineLength, squareAngle, squareSize);
                % 画面中央を原点として、水平方向「右」と、垂直方向「下」が正の値
                Screen('DrawLines', windowPtr, apex, 2, [255, 255, 255], center, 1);
                Screen('Flip', windowPtr);               
            end;
          end;       
      end;
  end;
 
  %データの記録
  saveFileName = [subName datestr(now,30) '.csv']; % ファイル名が重複しないように日時のデータを入れている
  Fid=fopen(saveFileName, 'wt');
  fprintf(Fid, '正方形の一辺の長さ,%d (pix) \n', squareSize);
  fprintf(Fid, '線分の長さ,%d (pix) \n', lineLength);
  fprintf(Fid, '\n');
  fprintf(Fid, '%s \n', ['繰り返し,試行,条件,線分の初期角度,正方形の角度,回答']);
  fprintf(Fid, '%d,%d,%d,%d,%d,%f \n', answer');
  fclose(Fid);
 
  DrawFormattedText(windowPtr, '実験は終わりです。スペースキーを押してください。', 'center', 'center', [255 255 255]);
  Screen('Flip', windowPtr);
  KbWait;
 
  sca; % Screen('CloseAll');
  ShowCursor;
  ListenChar(0);
catch
  sca;
  ShowCursor;
  ListenChar(0);
  psychrethrow(psychlasterror);
end
% lineAngleとsquareAnlgeの単位は度。
% lineLengthとsquareSizeの単位はピクセル。
% squareSizeは正方形の一辺の長さ。
% 画面中央を原点として、水平方向「右」と、垂直方向「下」が正の値
function [newApex] = setApex(lineAngle, lineLength, squareAngle, squareSize)
    dist = squareSize / sqrt(2); %正方形の中央から各頂点までの距離
    sqTheta = squareAngle / 180 * pi; % square theta
   
    newApex = ones(2, 10); %初期化
   
    %正方形の座標
    newApex(1, 1) = dist * cos(sqTheta);
    newApex(2, 1) = dist * sin(sqTheta);
    newApex(1, 2) = dist * cos(sqTheta + pi/2);
    newApex(2, 2) = dist * sin(sqTheta + pi/2);
    newApex(1, 3) = dist * cos(sqTheta + pi/2);
    newApex(2, 3) = dist * sin(sqTheta + pi/2);
    newApex(1, 4) = dist * cos(sqTheta + pi);
    newApex(2, 4) = dist * sin(sqTheta + pi);
    newApex(1, 5) = dist * cos(sqTheta + pi);
    newApex(2, 5) = dist * sin(sqTheta + pi);
    newApex(1, 6) = dist * cos(sqTheta + 3*pi/2);
    newApex(2, 6) = dist * sin(sqTheta + 3*pi/2);
    newApex(1, 7) = dist * cos(sqTheta + 3*pi/2);
    newApex(2, 7) = dist * sin(sqTheta + 3*pi/2);
    newApex(1, 8) = dist * cos(sqTheta);
    newApex(2, 8) = dist * sin(sqTheta);
    %線分の座標
    lineTheta = lineAngle / 180 * pi;
    newApex(1, 9) = lineLength/2 * cos(lineTheta);
    newApex(2, 9) = lineLength/2 * sin(lineTheta);
    newApex(1, 10) = lineLength/2 * cos(lineTheta + pi);
    newApex(2, 10) = lineLength/2 * sin(lineTheta + pi);