読者です 読者をやめる 読者になる 読者になる

新しい関数の作成2

説明

 新しい関数の作成の工夫をもう少し紹介する。以前は,返り値を指定していない関数を作成したが,返り値を作成することでより便利に関数を利用できる。例えば,注視点を描く関数は以下である。

int cenX, cenY;
color textColor = color(0, 0, 0);

void setup()
{
  size(displayWidth,displayHeight); //windowの大きさ
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
 drawFixation();
}

void drawFixation() //注視点
{
  stroke(textColor); //線の色を指定
  strokeWeight(1); //線の太さを指定
  line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
  line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}

 この関数を実行すると,画面の中心に,色が黒の線の太さが1の40ピクセルの縦線と横線が引かれる(≒注視点が描画される)。大抵の実験では,注視点は常に同じ色で描画されるが,場合によっては色を実験内で変えたい場合がある。例えば,注意実験ではキャッチ試行と呼ばれる,刺激が提示されない試行が設定される場合がある。通常の試行は「黒い注視点→刺激」の順に出すが,キャッチ試行は「赤い注視点」だけが提示され,その場合は,すばやくスペースボタンを押すよう求めるという具合である。このような色を変化させた注視点を関数として描く場合には2つの方法がある。

1つ目

 単純に白い注視点の関数と,赤い注視点の関数を作成する方法である。以下の例では,それぞれ,drawBlackFxiationとdrwaRedFixationで違う色の注視点を描画している。わかりやすいと言えばわかりやすいが,コードが長くなる。

int cenX, cenY;
color blackFixationColor = color(0, 0, 0);
color redFixationColor = color(255, 0, 0);

void setup()
{
  size(displayWidth,displayHeight); //windowの大きさ
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
 drawRedFixation(); //赤い注視点を描画
}

void drawBlackFixation() //黒い注視点
{
  stroke(blackFixationColor); //線の色を指定
  strokeWeight(1); //線の太さを指定
  line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
  line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}

void drawRedFixation() //赤い注視点
{
  stroke(redFixationColor); //線の色を指定
  strokeWeight(1); //線の太さを指定
  line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
  line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}

2つ目

 注視点を描く関数を実行する際に色だけを実行ごとに指定するような関数を作成する。drawFixation(int r, int g, int b)がポイントとなっている。このr, g, bを実行時に任意の数値を入れることで線の色を操作する。例えば,赤い注視点であれば,実行時にdrawFixation(255, 0, 0)とすればよいし,黒であれば,drawFixation(0, 0, 0)とすればよい。

int cenX, cenY;

void setup()
{
  size(displayWidth,displayHeight); //windowの大きさ
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
 drawFixation(255,0,0); //赤い注視点を描画
}

void drawFixation(int r, int g, int b) //注視点の関数
{
  stroke(r,g,b); //線の色を指定
  strokeWeight(1); //線の太さを指定
  line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
  line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}

 同様に,色だけでなく,太さや描画位置なども実行時に指定するような関数として定義することもできる。ただし,あまり複雑にすると混乱するかもしれない。

void setup()
{
  size(displayWidth,displayHeight); //windowの大きさ
  pixelDensity(2); //retinaに対応
}

void draw()
{
 //drawFixation(R, G, B, 線の太さ, 線の長さ,X座標, Y座標)
 drawFixation(255, 0, 0, 3, 30, 100, 100); //赤い注視点を描画
}

void drawFixation(int r, int g, int b,
int fixationWeight, int fixationWidth, int x, int y) //注視点の関数
{
  stroke(r,g,b); //線の色を指定
  strokeWeight(fixationWeight); //線の太さを指定
  line(x,y+fixationWidth,x,y-fixationWidth); //線を引く(注視点の縦線)
  line(x+fixationWidth,y,x-fixationWidth,y); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}
}

ランダマイズ

 心理学実験では,刺激をランダムに呈示することが多い。そのためには,ランダムな順に配列を並び変える必要がある。例えば,刺激が刺激番号1〜5まである場合,参加者ごとに”5, 2, 3, 1, 4"などのランダム順で提示するという具合。プログラム言語によっては,ランダム配列を作成する関数が用意されている場合がある。例えば,Matlabではrandperm関数がある。Processingにはそのような関数はないので,一般的なプログラム言語でもよく使われる並び変える手法を使う。

//1〜5の配列を作成
int learningOrder [] = {1, 2, 3, 4, 5} ; 
//ランダム並び変える変数
int temp, rd, i; 

 for (int i = learningOrder.length-1; i >= 0; i--)
 {
   //random (x, y)でxからyの間で1つの数字をランダムに選択
   tmp = learningOrder[rd];
   rd = (int)random(0,i); 
   learningOrder[rd] = learningOrder[i];
   learningOrder[i] = tmp;
 }

 慣れないとわかりにくいが,learningOrderには1〜5の数字が1〜5番目にそれぞれ格納されている。そこで,一番大きい番目にある数字(5番目にある5)を他の場目にある数字(ランダムに決める番目)と入れ替えるというのをまず行う。Processingは0オリジンなので,5番目は[4]となる。ランダムな番目を選ぶため,まず,rdに1〜4番目のどれかをランダムに選ぶ。rd = (int)random(0, i)の部分がここにあたる。そして,ランダムな番目の数字を一時的にtempに格納する。そして,ランダムな番目に5番目のある数字を入れる。そして,最後に5番目にtempに格納されているランダムな番目の数字を入れる。を5番目,4番目,3番目,2番目,1番目とforループで実施していく。

 順番を並び変えるので,learningOrderのあたる配列の中身は自由である。注意実験では注視点の提示時間を固定すると,注視点提示直後に合わせて注意資源の投入が行われるため,練習効果(課題後半により成績が上がる)が顕著になる可能性がある。それを防ぐために,注視点をランダムに提示すると良い。以下のように,注視点の提示時間を5種類からランダムに並べ,毎試行でランダムに並び替えた配列の1番目を使うようにすればよい。

int fixationTime [] = {100, 200, 500, 1000, 1500} ; 
int temp, rd, i;

for (int i = fixationTime.length-1; i >= 0; i--)
{
  rd = (int)random(0,i);
  tmp = fixationTime[rd];
  fixationTime[rd] = fixationTime[i];
  fixationTime[i] = tmp;
}

 数字だけでなく,文字も並び変えることができる。文字の場合,tmepもString型にしないといけないが,並び変えたい変数によって並び変えのコードの型を毎回変更するのも面倒である。そこで,提示する文字配列(targetWords)の提示順をpresentOrderとして作成し,presentOrderをランダムな配列にすることで,targetWordsをランダムに提示する。

//文字刺激を配列に格納
String targetWords [] = {"one", "two", "three", "four", "five"} ; 

//提示順の変数
int presentOrder [] = new int [5]

int temp, rd, i; 

//提示順に0〜4をいれる
for (int i = 0; i < presentOrder.length; i++)
{
   presentOrder[i] = i;
}

 for (int i = presentOrder.length-1; i >= 0; i--)
 {
   tmp = presentOrder[rd];
   rd = (int)random(0,i); 
   presentOrder[rd] = presentOrder[i];
   presentOrder[i] = tmp;
 }

// targetWordsにあるpresentOrder[i]番目の単語を提示
text (targetWords[presentOrder[i]], width/2, height/2);

新しい関数の作成

説明

 Processingでは,自分で新しい関数も作成できるので,何回か繰り返すことの多い処理は新しい関数を作成し,それを毎回呼び出すほうがコードの可読性も上がり,コード短縮にも繋がる。あとで微細な変更を加えたい場合も楽である。例えば,全画面塗りつぶしは何回も使うコードであるため,fillAllなどの名前で関数として作成してそれを毎回呼び出す。

//色指定
color backgroundColor = color(255,255,255);

void draw()
{
 fillAll();
}

void fillAll() //全画面塗りつぶし
{
  fill(backgroundColor);
  rect(0,0,width,height);
}

注視点の場合

 新しい関数は「void 関数名 ()」で作成できる。なお,()に値を入れることもできる。同じように注視点も関数として作成すると楽である。drawFixation()を毎回呼び出すと注視点が表示される。

int cenX, cenY;
color textColor = color(0, 0, 0);

void setup()
{
  size(displayWidth,displayHeight); //windowの大きさ
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
 drawFixation();
}

void drawFixation() //注視点
{
  stroke(textColor); //線の色を指定
  strokeWeight(1); //線の太さを指定
  line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
  line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
  noStroke(); //枠線なし
}

 このように,毎回使うちょっとした関数を作成することもできるが,より複雑な構造のプログラムを書く場合には,実験の各段階を新しい関数として書いておき,drawループ内は実験の構造だけを記述するということも可能である。

画面サイズを変更

任意のウィンドウサイズ

 setup内のsize(x,y)でウィンドウサイズを指定できる。例えば,600×800のウィンドウを開きたい場合は以下のように書けば良い。

void setup()
{
 size(600, 800)
}

物理的なディスプレイサイズでウィンドウサイズ

 現在の物理的なディスプレイサイズでウィンドウを開きたい場合はsize(displayWidth,displayHeight)と書けば良い。

void setup()
{
 size(displayWidth, displayHeight)
}

 現在のディスプレイサイズの半分のウィンドウを開きたい場合は以下のように書く。

void setup()
{
 size(displayWidth/2, displayHeight/2)
}

心理学実験プログラムの構造

心理学実験用のプログラムをProcessing 3で作る際の構造について

 Processingは,draw()内の記述をずっとループするというのが特徴である。心理学実験では,「注視点→刺激」という風に刺激を1回出して,次の画面に切り替えて,という流れが多い。刺激提示時間の制御も必要となる。Processingではずっとループが続くため,プログラムの構造を工夫しないと,同じ刺激をずっと描画し続けてしまう。

 そこで,if関数によって実験を提示画面に細分化していく。つまり,注視点提示画面,刺激提示画面,などという風にいくつかの画面を作成し,それの順番に切り替えるように構造化する。以下のようなコードで実装する。このコードは,注視点(200ms)→文字(1000ms)を提示するというサンプルである。やや複雑な構造に見えるが,より本格的には注視点描画用の関数を新たに作成するなどで構造自体はよりすっきりさせられる。各ポイントについては以下で説明する。

注視点の描画

 注視点は,line関数で縦線と横線を1回ずつ描画して,十字を描いている。stroke(x,y,z)で線の色,strokeWeight(x)で線の太さを指定している。line(x1,y1,x2,y2)は,(x1, y1)から(x2, y2)に線を引く関数である。

画面の切替

 currentDisplayの値を切り替えることで現在の画面を指定している。各画面は1回刺激を描画したら,その画面がstimTimeの時間分,続くようになっている。特定の条件(今回は時間経過)が来たら,currentDisplayの値をnextDisplayで設定した値で代入し,次の画面に切り替えている。切替の際には,前の刺激を消したいので,rect関数によって,背景と同じ色で塗りつぶしている。

時間取得

 millis()によって,現在の時間を取得できる。startTime = millis()で開始時間を取得した後で刺激を描画し,その後は,drawループで経過時間をpassedTime = millis() - startTimeによって取得し続ける。passedTimeが指定した提示時間であるstimTimeを超えたら,currentDisplay = nextDisplayとし,画面を切り替えている。

コード

String testText;
PFont font;
boolean textDraw = true;
int cenX, cenY,startTime,passedTime,stimTime,
currentDisplay,nextDisplay; 

void setup ()
{
  size(500,500); //windowの大きさ
  background(255,255,255); //背景色を指定
  font = createFont("Yu Gothic",48,true); //フォントを指定
  textFont(font); 
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
  if (currentDisplay == 0)
  {
     if (textDraw) //テキスト描画スイッチ
     {
        startTime = millis(); //開始時間を計測
        
        stroke(0,0,0); //線の色を指定
        strokeWeight(1); //線の太さを指定
        line(cenX,cenY+20,cenX,cenY-20); //線を引く(注視点の縦線)
        line(cenX+20,cenY,cenX-20,cenY); //線を引く(注視点の縦線)
        noStroke(); //枠線なし
        textDraw = false; //描画スイッチOFF
        
        stimTime = 4000; //提示時間を指定
        nextDisplay = 1; //次の画面を設定
     } 
  }
  
  else if (currentDisplay == 1)
  {
     if (textDraw) //テキスト描画スイッチ
    {
        startTime = millis(); //開始時間を計測
        
        fill(0,0,0); //塗りの色を指定
        textSize(48); //テキストサイズを指定
        textAlign(CENTER,CENTER); //テキストを真ん中揃え
        testText = "にほんご"; //テキスト内容を指定
        text(testText, cenX,cenY); //テキストを描画
        textDraw = false; //描画スイッチOFF
        
        stimTime = 4000; //提示時間を指定
        nextDisplay = 2; //次の画面を設定
    }
  }
     
  else if (currentDisplay == 2)
  {
    exit(); //終了処理
  }
  
  passedTime = millis()-startTime; //途中経過を計測
   
  if (passedTime >= stimTime) //提示時間を超えたら
  {
    currentDisplay = nextDisplay ; //画面を切り替える
    fill(255,255,255); //全画面を塗りつぶし
    rect(0,0,width,height);
    textDraw = true; //テキストの描画スイッチON
  }
  
}

Processing 3.2.3(Mac)で作成

日本語の描画

説明

 Processing 3では初期状態では日本語を表示できないので,以下のコードを書くことで日本語を表示できる。今回はMacWindowsの両方に入っている游ゴシックを指定している。なお,retinaディスプレイ環境のため,pixelDensityを2にしているが,retinaディスプレイではない場合は,1にする方がいいかもしれない。

 別記事で書くかもしれないが,Processingはdraw内の記述をずーっとループし続けるので,文字描画のコードを普通に書いてしまうと,何回も文字を重ね書きし続けることになる。そのため,1回だけ描画したい刺激については,描画のON/OFFをif構文で切り替える必要がある。「if(textDraw)〜textDraw=false」部分がそこにあたる。大抵の心理学実験では刺激は1回の描画(提示)で良いので,基本的には描画スイッチのON/OFFでやっていくことになる。

 ちなみに,エディタでも日本語が初期状態では表示できないが,それはエディタの初期フォントが日本語フォントになっていないせい。環境設定の「エディタとコンソールのフォント」のフォントを日本語フォントに指定し直せばよい。MacならOsakaなどにする。

コード

String testText;
PFont font;
boolean textDraw = true;
int cenX, cenY;

void setup ()
{
  size(500,500); //windowの大きさ
  font = createFont("Yu Gothic",48,true); //フォントを指定
  textFont(font); 
  pixelDensity(2); //retinaに対応
  cenX = width/2; //windowの中心のX座標
  cenY = height/2; //windowの中心のY座標
}

void draw()
{
  if (textDraw) //テキスト描画スイッチ
  {
    fill(0,0,0); //塗りの色を指定
    textSize(48); //テキストサイズを指定
    textAlign(CENTER,CENTER); //テキストを真ん中揃え
    testText = "にほんご"; //テキスト内容を指定
    text(testText, cenX,cenY); //テキストを描画
    textDraw = false; //描画スイッチOFF
  }
}

Processing 3.2.3(Mac)で作成

Processing 3の学習リソース

インターネットサイト

公式ページのReference

https://processing.org/reference/

梶山先生(福岡大学)によるprocessing入門

http://monge.tec.fukuoka-u.ac.jp/cg_processing/0_processing.html

津田先生(京都大学)によるProcessingによる心理学実験入門

http://hiroyukitsuda.com/tutorial/processing

 基本的な使い方などは上記のサイトから十分に学習できるはず。Processing 2向けには津田先生による心理学実験の実装の入門ページが既に存在している。このブログでは,これらサイトを参考にしつつ,Processing 3を用いて心理学実験を作成することを書いていきたい。

Processingをはじめよう 第2版

Processing 3に対応した日本語書籍は現状はこの1冊くらい。

ブラウザでできる基礎・認知心理学実験演習―JavaScriptで書く実験プログラミング

Processing3ではなくJavaScriptでの心理実験の作成本。Processing 3からJavaScriptに書き出せたりと,両者は関連が強いので参考になる。

はじめよう実験心理学

Processing 3ではなく,Psychotoolbox+Matlabによる心理実験の作成に関する本。心理学実験をどう作るかという点は参考になる。