医療従事者・研究者用ノート

視覚刺激のプログラミング

本日は論文紹介ではなく、視覚刺激のプログラミングについてご紹介したいと思います。

Tech Boost

プログラミンは様々なソフトがありますが、私が研究で使用しているソフトは主に2つです。

1つ目が無料で使用できる「Processing」というソフトを使用しています。

これは、視覚刺激や聴覚刺激を用いる場合に使用しています。

2つ目が有料ですが、「MATLAB」を使用しています。

これは、データ解析に使用しています。

正直、MATLABがないと、いくら時間があっても足りません。

また、エクセルなどで解析を手作業でしようとするとミスが生じる可能性が高くなります。

そのため、コードを書けば莫大なデータをボタン1つで解析してくれるので大変便利なソフトです。

本日は、無料で使用できる「Processing」の視覚刺激のコードを1つご紹介したいと思います。

もし、興味がある方は下記のURLからソフトをダウンロードしてみて下さい。

https://processing.org

本日は、最も簡単な「Target cancellation task」のコードをご紹介したいと思います。

この課題は図1の6箇所の位置からランダムで1箇所に星が出現し、マウスでクリックまたはタッチパネルであればポインティングする課題になっています。

図1

例えば、半側空間無視患者であれば、左側に出現したターゲットに対する反応時間が長くなったり、見落として正答率が低下したりしてしまいます。

下記が「Target cancellation task」のコードになります。

// Experimental settings
boolean isDebug;
int numTrial;
int fixationDuration;
int itiDuration;
int itiRandomDuration;
int itiTotalDuration;
int judgementDuration;
float beepDuration;
int rewardDuration;
color backgroundColor;
int target_id;
float random_num;
int target_type;
int i;

// Variables
int currentState;
int currentTrial;
int baseTime;
// int response;
int RT;
PFont font;

// added by yoshida
int circle_diam;
PVector center;

int number_of_targets;
PVector[] center_Target;

int number_of_distractors;
PVector[] center_Distractor;

PVector target_deviation;
PVector[] stimulus_deviation;

int touch_margin;
int touch_window;
boolean currentCorrect;
boolean fixCorrect;

// data storage
PrintWriter output;

//task the order
int[] get_no_dup_order_numbers(int start_rr, int end_rr) {
  int num_size = (end_rr+1) - start_rr; 
  IntList nums = new IntList(num_size);
  for (int i = end_rr; i >= 0; i--) {
    nums.append(i);};
  nums.shuffle();
  int[] result = nums.array();
  return result;}
  int[] rr = get_no_dup_order_numbers(0, 59);{
  println(rr);}
  
  int rrr = 0;
  int n = 0;

void setup(){
  size( 1280, 1024 );
  frameRate( 60 );
   
  isDebug = false;
  numTrial = 61;
  fixationDuration = 0;

  itiDuration = 500;
  itiRandomDuration = 0;
  
  judgementDuration = 30000;
  backgroundColor = 255;
  target_type = 1; // 1 for circle, 2 for arc
  
  baseTime = 0;
  font = createFont( "Georgia", 24 );
  textFont( font );

  // 
  circle_diam = 55;
  touch_margin = 0; // permit mistouch
  touch_window = circle_diam/2 + touch_margin;
  
  center = new PVector(width/2, height/2);
  
  // initialize targets
  number_of_targets = 1;
  center_Target = new PVector[number_of_targets];
  for (int i = 0; i < center_Target.length; i++){
    center_Target[i] = center;
  }

  // initialize distractors
  number_of_distractors = 6;
  center_Distractor = new PVector[number_of_distractors];
  for (int i = 0; i < center_Distractor.length; i++){
    center_Distractor[i] = center;
  }
  
  // initialize stimulus_deviation
  target_deviation = new PVector(300, 150);
  stimulus_deviation = new PVector[number_of_distractors];
  stimulus_deviation[0] = new PVector( 1.5 * target_deviation.x, -1 * target_deviation.y) ;
  stimulus_deviation[1] = new PVector(-1.5 * target_deviation.x, -1 * target_deviation.y) ;
  stimulus_deviation[2] = new PVector(0 * target_deviation.x, -1 * target_deviation.y) ;
  stimulus_deviation[3] = new PVector( 1.5 * target_deviation.x, 1 * target_deviation.y) ;
  stimulus_deviation[4] = new PVector(-1.5 * target_deviation.x, 1 * target_deviation.y) ;
  stimulus_deviation[5] = new PVector(0 * target_deviation.x, 1 * target_deviation.y) ;
  
  beepDuration = 0.5;  // sec
  rewardDuration = 500; // msec
  
   // data storage
  int y = year();   // 2003, 2004, 2005, etc.
  int m = month();  // Values from 1 - 12
  int d = day();    // Values from 1 - 31
  int h = hour();
  int mi = minute();
  int s = second();
  int mil = millis();
  output = createWriter("data/"+nf(y,4)+nf(m,2)+nf(d,2)+nf(h,2)+nf(mi,2)+nf(s,2)+nf(mil,3)+ ".txt");
  
  // save general parameters
    output.println( circle_diam  + "\t" + touch_window  + "\t"
        + beepDuration  + "\t" + rewardDuration  + "\t" + fixationDuration);

   //noCursor();
  
  currentTrial = 0;
  set_NextTrial();
  currentState = 0; // go back to 0 (to wait for return key)
}

void star(float x, float y, float r) { 
   pushMatrix(); 
   translate(x, y); 
   scale(1, -1); 
   rotate(HALF_PI); 
   beginShape(); 
   for (int i = 0; i < 5; i++) { 
     vertex(r * cos(4 * PI * i / 5), r * sin(4 * PI * i / 5)); 
   } 
   endShape(CLOSE); 
   popMatrix(); 
 } 

void draw() {
  background( backgroundColor );
  noStroke();
   
if( currentState == 1 ){
    fixationPhase();
  } else if( currentState == 2 ){
    responsePhase();
  } else if( currentState == 3 ){
    rewardPhase();
  } else if( currentState == 4 ){
    itiPhase();
  } else if( currentState == 5 ){
    endPhase();
  }
 
  if( isDebug ){ drawDebugInfo(); }
}



void fixationPhase(){

  // check elapsed time to transit state
  int elapsedTime = millis() - baseTime;
  if( elapsedTime > fixationDuration ){
    transitState();
  }
}

void responsePhase(){
  drawStimuli();

  // check elapsed time to transit state
  int elapsedTime = millis() - baseTime;
  if( elapsedTime > judgementDuration ){
    transitState();
  }
}

void rewardPhase(){
  // check elapsed time to transit state
  int elapsedTime = millis() - baseTime;
  if( elapsedTime > rewardDuration ){
    //arduino.digitalWrite(ledPin, Arduino.LOW);
      currentState = 4;
      n++;
  }
}

void itiPhase(){
  // check elapsed time to transit state
  int elapsedTime = millis() - baseTime;
  if( elapsedTime > itiTotalDuration & mousePressed == false ){
    transitState();
  }
}

void endPhase(){
  output.flush(); // Writes the remaining data to the file
  output.close(); // Finishes the file
  exit(); // Stops the program
}

void transitState(){
  if( currentState == 1 ){
    if ( random_num > 1 ){
                       
                   }   
      
    else  {
             
  }
    
    if ( fixCorrect ){ currentState = 2;}
    else             { currentState = 2; }// jump into ITI, no targets
    baseTime = millis();
  }
  else if( currentState == 2 ){
    currentState = 3;
    baseTime = millis();
    if (currentCorrect){
     //arduino.digitalWrite(ledPin, Arduino.HIGH);
     // out.playNote( 0, beepDuration, "A5" );
    }
  }
  else if( currentState == 3 ){
     currentState = 4;
  }
  else if( currentState == 4 ){
    if( currentTrial == numTrial - 1 ){
      currentState = 5;
    }
    else {
      baseTime = millis();
      // move on to next trial
      set_NextTrial();
    }
  }
}


void saveTrialData(){
  // record performance
  if (number_of_targets == 1 ){
    output.println( currentTrial + "\t" + rrr + "\t"
        + mouseX + "\t" + mouseY + "\t" + RT  + "\t");
  }
  else if (number_of_targets == 2 ){
    output.println( currentTrial + "\t" + rrr + "\t"
        + mouseX + "\t" + mouseY + "\t" + RT  + "\t");
  }
  println( currentTrial + "\t" + rrr + "\t"
        + mouseX + "\t" + mouseY + "\t" + RT);
}

void keyPressed() {
  if ( key == ENTER || key == RETURN ){
    if( currentState == 0 ){
      currentState = 1;
      baseTime = millis();
    }
  }
  // push ESC key to quit, endPhase() will close the file. 
  else if (key == ESC){
    key=0;
    endPhase();
  }
}

void mousePressed() {
  if( currentState == 2 ){   // target phase 
    RT = millis() - baseTime;
    if (mouseY >= center_Target[0].y- touch_window && mouseY <= center_Target[0].y + touch_window
     && mouseX >= center_Target[0].x- touch_window&& mouseX <= center_Target[0].x+ touch_window  ){
      currentCorrect = true; // correct
      transitState();
    }
    else{
      currentCorrect = false; // incorrect
      transitState();
    }
    saveTrialData();
  
  }
  else if( currentState == 1 ){    // fixation phase
    if (mouseY >= height/2 - touch_window && mouseY <= height/2 + touch_window
     && mouseX >= width/2  - touch_window && mouseX <= width/2  + touch_window){
      fixCorrect = true; // correct
      transitState();
    }
    else{
      fixCorrect = false; // incorrect
    }

  }
}

void drawDebugInfo(){
  fill( 255 ); // set font color
  text("currentState: " + currentState, 20, 30 );
  text("currentTrial: " + currentTrial, 20, 60 );
  text("mouseXY: " + mouseX + " " + mouseY , 20, 90 );
  text("mousePressed: " + mousePressed , 20, 120 );
  text("currentCorrect: " + currentCorrect, 20, 150 );
  float elapsedTime = (millis() - baseTime) / 1000.0;
  text("elapsedTime: " + nf( elapsedTime, 1, 3 ), 20, 180 );
}

void set_NextTrial(){
      currentTrial++;
      currentState = 1;
      currentCorrect = false;
      fixCorrect = false;
      itiTotalDuration = itiDuration + (int)random(itiRandomDuration);
      
      
}

void drawStimuli(){
  rrr = rr[n];{
     if(rrr>=0 && rrr<=9){
      fill( 0,0,0 );
      star( center_Target[i].x-300, center_Target[i].y + 200, circle_diam);;
    }
    else if(rrr>=10 && rrr<=19){
      fill( 0,0,0 );
      star( center_Target[i].x-300, center_Target[i].y - 200, circle_diam);;
    }
    else if(rrr>=20 && rrr<=29){
      fill( 0,0,0 );
      star( center_Target[i].x+300, center_Target[i].y + 200, circle_diam);;
    }
    else if(rrr>=30 && rrr<=39){
      fill( 0,0,0 );
      star( center_Target[i].x+300, center_Target[i].y - 200, circle_diam);;
    }
    else if(rrr>=40 && rrr<=49){
      fill( 0,0,0 );
      star( center_Target[i].x, center_Target[i].y + 200, circle_diam);;
    }
    else if(rrr>=50 && rrr<=59){
      fill( 0,0,0 );
      star( center_Target[i].x, center_Target[i].y - 200, circle_diam);;
    }
}}
  

この他にも、「Local/Global attention task」、「Line bisection discrimination task」、「Selective attention task」、「Target selection task」、「Auditory attention task」のコードがあるので必要な方は有料となってしまいますが、下記にご連絡下さい。

また、この他にも要望があれば刺激を作成することも可能です。

プログラミングを学びたい方は今であれば無料説明会もあるので参加してモチベーションを高めるもの1つです。

Twitter:@kengo_brain

Instagram:@graine_kr

関連書籍

Processingをはじめよう 第2版 (Make: PROJECTS)新品価格
¥2,200から
(2020/9/30 22:12時点)

投稿者

kengo.brain.science@gmail.com

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です