00001 /*!@file AppPsycho/psycho-classic-antisaccade.C Psychophysics display of pro-/anti-saccade task. 00002 The paradigm is similar to Munoz et al., 1998 except that targets are 10 degree eccentric and 00003 only overlap condition is used. 00004 ./bin/psycho-classic-antisaccade <number of trials> <block(0) or interleave (1)>*/ 00005 00006 // //////////////////////////////////////////////////////////////////// // 00007 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00008 // University of Southern California (USC) and the iLab at USC. // 00009 // See http://iLab.usc.edu for information about this project. // 00010 // //////////////////////////////////////////////////////////////////// // 00011 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00012 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00013 // in Visual Environments, and Applications'' by Christof Koch and // 00014 // Laurent Itti, California Institute of Technology, 2001 (patent // 00015 // pending; application number 09/912,225 filed July 23, 2001; see // 00016 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00017 // //////////////////////////////////////////////////////////////////// // 00018 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00019 // // 00020 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00021 // redistribute it and/or modify it under the terms of the GNU General // 00022 // Public License as published by the Free Software Foundation; either // 00023 // version 2 of the License, or (at your option) any later version. // 00024 // // 00025 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00026 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00027 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00028 // PURPOSE. See the GNU General Public License for more details. // 00029 // // 00030 // You should have received a copy of the GNU General Public License // 00031 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00032 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00033 // Boston, MA 02111-1307 USA. // 00034 // //////////////////////////////////////////////////////////////////// // 00035 // 00036 // Primary maintainer for this file: Po-He Tseng <ptseng@usc.edu> 00037 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-movie-fixicon.C $ 00038 // $Id: psycho-movie-fixicon.C 13712 2010-07-28 21:00:40Z itti $ 00039 00040 #include "Component/ModelManager.H" 00041 #include "Image/Image.H" 00042 #include "Raster/Raster.H" 00043 #include "Media/MPEGStream.H" 00044 #include "Media/MediaOpts.H" 00045 #include "Psycho/PsychoDisplay.H" 00046 #include "Psycho/EyeTrackerConfigurator.H" 00047 #include "Psycho/EyeTracker.H" 00048 #include "Psycho/PsychoOpts.H" 00049 #include "Component/EventLog.H" 00050 #include "Component/ComponentOpts.H" 00051 #include "Util/MathFunctions.H" 00052 #include "Util/Types.H" 00053 #include "Video/VideoFrame.H" 00054 #include "rutz/time.h" 00055 00056 #include <deque> 00057 #include <sstream> 00058 00059 // ###################################################################### 00060 static int waitForFixation(const Point2D<int> fparr [], 00061 const int arraylen, 00062 const int radius, //in pixel 00063 double fixlen, // in msec 00064 nub::soft_ref<EyeTracker> et, 00065 nub::soft_ref<PsychoDisplay> d, 00066 const bool do_drift_correction = false) 00067 { 00068 Point2D<int> ip; 00069 double dist [arraylen], sdist; 00070 double tt=0, adur=0, ax=0, ay=0; 00071 Timer timer, timer2; 00072 fixlen = fixlen/1000; 00073 int status = 1; 00074 00075 // clean up queue for key response 00076 while(d->checkForKey() != -1); 00077 00078 // wait for fixation 00079 timer2.reset(); 00080 while(timer.getSecs() < fixlen) { 00081 ip = et->getFixationPos(); 00082 00083 // check distance between fixation point to all possible targets 00084 for (int i=0; i<arraylen; i++) 00085 dist[i] = sqrt(pow(ip.i-fparr[i].i, 2) + pow(ip.j-fparr[i].j, 2)); 00086 00087 // get the closest target 00088 if (arraylen == 1) 00089 sdist = dist[0]; 00090 else 00091 sdist = std::min(dist[0], dist[1]); 00092 00093 // inside of tolerance? 00094 if (sdist > radius && (ip.i!=-1 && ip.j!=-1)){ 00095 timer.reset(); 00096 }else{ 00097 tt = timer.getSecs()-tt; 00098 adur += tt; 00099 ax += tt * ip.i; 00100 ay += tt * ip.j; 00101 } 00102 00103 //LINFO("(%i, %i) - (%i, %i) dist: %f, %f", fparr[0].i, fparr[0].j, ip.i, ip.j, sdist, timer.getSecs()); 00104 00105 // time out 00106 if (timer2.getSecs() > 5 || d->checkForKey() > 0){ 00107 d->pushEvent("bad trial - time out / no response."); 00108 status = -1; 00109 break; 00110 } 00111 } 00112 00113 // do drift correction 00114 if (status == 1 && do_drift_correction == true) { 00115 // when there's only 1 target on screen 00116 et->manualDriftCorrection(Point2D<double>(ax/adur, ay/adur), 00117 Point2D<double>(fparr[0].i, fparr[0].j)); 00118 LINFO("drift correction: (%i, %i) (%i, %i)", (int)(ax/adur), (int)(ay/adur), fparr[0].i, fparr[0].j); 00119 } 00120 00121 return status; 00122 } 00123 00124 // ###################################################################### 00125 static int submain(const int argc, char** argv) 00126 { 00127 MYLOGVERB = LOG_INFO; // suppress debug messages 00128 00129 // Instantiate a ModelManager: 00130 ModelManager manager("Psycho Pro-Saccade"); 00131 00132 // Instantiate our various ModelComponents: 00133 nub::soft_ref<InputMPEGStream> mp 00134 (new InputMPEGStream(manager, "Input MPEG Stream", "InputMPEGStream")); 00135 manager.addSubComponent(mp); 00136 00137 nub::soft_ref<EventLog> el(new EventLog(manager)); 00138 manager.addSubComponent(el); 00139 00140 nub::soft_ref<EyeTrackerConfigurator> 00141 etc(new EyeTrackerConfigurator(manager)); 00142 manager.addSubComponent(etc); 00143 00144 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00145 manager.addSubComponent(d); 00146 00147 manager.setOptionValString(&OPT_InputMPEGStreamPreload, "true"); 00148 manager.setOptionValString(&OPT_EventLogFileName, "psychodata.psy"); 00149 manager.setOptionValString(&OPT_EyeTrackerType, "EL"); 00150 00151 // Parse command-line: 00152 if (manager.parseCommandLine(argc, argv, "<# of trials> <block(0) or interleave(1)>", 2, 2) == false) 00153 return(1); 00154 00155 // construct an array to indicate which trial is this: 00156 // 1: prosaccade, left 00157 // 2: prosaccade, right 00158 // 3: antisaccade, left 00159 // 4: antisaccade, right 00160 int ntrial = fromStr<int>(manager.getExtraArg(0).c_str()); 00161 int interleave = fromStr<int>(manager.getExtraArg(1).c_str()); 00162 00163 LINFO("Total Trials: %i", ntrial); 00164 int trials[ntrial]; 00165 int trialindex[ntrial]; 00166 if(interleave==1){ 00167 // interleave design 00168 for (int i=0; i< ntrial; i++) 00169 trials[i] = 1 + i % 4; 00170 // randomize stimulus presentation order: 00171 for (int i = 0; i < ntrial; i ++) 00172 trialindex[i] = i; 00173 randShuffle(trialindex, ntrial); 00174 }else{ 00175 // block design 00176 int ntrial4 = ntrial/4; 00177 int tmptrials[4][ntrial4]; 00178 int tmpidx[ntrial4]; 00179 int blockidx[4] = {0, 1, 0, 1}; 00180 for (int i=0; i<4; i++){ 00181 for (int j=0; j<ntrial4; j++){ 00182 if (i%2==0) 00183 tmptrials[i][j] = 1 + j%2; //prosaccades 00184 else 00185 tmptrials[i][j] = 3 + j%2; //antisaccades 00186 } 00187 } 00188 for (int i=0; i<ntrial4; i++) 00189 tmpidx[i] = i; 00190 00191 // block order (as long as the first 2 blocks are not the same) 00192 do { 00193 randShuffle(blockidx, 4); 00194 } while(blockidx[0] == blockidx[1]); 00195 00196 // assign trial 00197 int c = 0; 00198 for (int i=0; i<4; i++){ 00199 randShuffle(tmpidx, ntrial4); 00200 for (int j=0; j<ntrial4; j++){ 00201 trials[c] = tmptrials[blockidx[i]][tmpidx[j]]; 00202 trialindex[c] = c; 00203 c++; 00204 } 00205 } 00206 } 00207 00208 // hook our various babies up and do post-command-line configs: 00209 nub::soft_ref<EyeTracker> et = etc->getET(); 00210 d->setEyeTracker(et); 00211 d->setEventLog(el); 00212 et->setEventLog(el); 00213 00214 // EyeLink opens the screen for us, so make sure SDLdisplay is slave: 00215 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) 00216 d->setModelParamVal("SDLslaveMode", true); 00217 00218 // let's get all our ModelComponent instances started: 00219 manager.start(); 00220 00221 // vision test (generate random letters again if pressing 'r') 00222 LINFO("*****************************************************************************"); 00223 LINFO("visual acuity test: [r] to regenerate another string; other keys to continue."); 00224 int key; 00225 do { 00226 d->displayRandomText(6, 8); 00227 key = d->waitForKey(true); 00228 } while(key == 114 || key == 101 || key == 116 || key == 102); 00229 00230 // let's do an eye tracker calibration: 00231 d->pushEventBegin("Calibration"); 00232 et->setBackgroundColor(d); 00233 et->calibrate(d); 00234 d->pushEventEnd("Calibration"); 00235 00236 LINFO("Press any key to start......"); 00237 d->displayText("Press any key to start......", true, 0, 10); 00238 d->waitForKey(true); 00239 00240 // main loop 00241 const int x = d->getWidth()/2; 00242 const int y = d->getHeight()/2; 00243 const int dist = x; 00244 int trialtype = 0; 00245 int tx = x, txt = x; 00246 const PixRGB<byte> green = PixRGB<byte>(0,255,0); 00247 const PixRGB<byte> yellow = PixRGB<byte>(255,255,0); 00248 ///Point2D<int> ip = Point2D<int>(0,y);; 00249 Point2D<int> targetpoint = Point2D<int>(0,y); 00250 ///int status; 00251 Timer timer; 00252 const int fixation_tolerance = 135; 00253 int displayTask = 1; 00254 00255 for (int i=0; i < ntrial; i++) { 00256 trialtype = trials[trialindex[i]]; 00257 00258 // display task instruction at the center of the screen 00259 d->clearScreen(); 00260 00261 if (i%10 == 0){ 00262 et->recalibrate(d,13); 00263 d->clearScreen(); 00264 } 00265 00266 double delay = 1000000; 00267 if (trialtype == 1 || trialtype == 2){ 00268 00269 if (displayTask == 1){ 00270 d->displayText("Same", true, 0, 10); 00271 if (interleave == 1) 00272 usleep(1000000); 00273 else 00274 d->waitForKey(); 00275 } 00276 00277 // start eye tracker 00278 et->track(true); 00279 usleep(500000); 00280 00281 // involuntary, green fixation 00282 d->clearScreen(); 00283 if (trialtype == 1) 00284 d->pushEvent("====== Trial Type: 1 (leftward, pro-saccade) ======"); 00285 else 00286 d->pushEvent("====== Trial Type: 2 (rightward, pro-saccade) ======"); 00287 00288 // display cue, wait 0.5-1.5 seconds..... 00289 d->displayFixation(x+dist/2, y, false); 00290 d->displayFixation(x-dist/2, y, false); 00291 d->displayFilledCircle(x, y, 5, green); 00292 d->pushEvent("Display cue (green)"); 00293 usleep(delay); 00294 00295 // display target 00296 tx = x + dist * (trialtype-1.5); 00297 timer.reset(); 00298 //d->displayFilledCircleBlink(tx, y, 3, green, 3, 1); 00299 //d->displayFilledCircle(x, y, 5, d->getGrey(), false); 00300 d->displayFilledCircle(tx, y, 5, green); 00301 d->pushEvent("Display target (green)"); 00302 00303 // get response 00304 targetpoint.i = tx; 00305 /*status = */waitForFixation(&targetpoint, 1, fixation_tolerance, 100, et, d); 00306 double tt = timer.getSecs(); 00307 00308 LINFO("(pro-saccade) response time: %f", tt); 00309 d->pushEvent(sformat("pro-saccade, response time: %f", tt)); 00310 00311 d->displayCircle(targetpoint.i, targetpoint.j, 60, yellow); 00312 usleep(1000000); 00313 00314 // display information 00315 d->clearScreen(); 00316 00317 } else if (trialtype == 3 || trialtype == 4){ 00318 00319 if (displayTask == 1){ 00320 d->displayText("Opposite", true, 0, 10); 00321 if (interleave == 1) 00322 usleep(1000000); 00323 else 00324 d->waitForKey(); 00325 } 00326 00327 // start eye tracker 00328 et->track(true); 00329 usleep(500000); 00330 00331 // involuntary, green fixation 00332 d->clearScreen(); 00333 if (trialtype == 3) 00334 d->pushEvent("====== Trial Type: 3 (leftward, anti-saccade) ======"); 00335 else 00336 d->pushEvent("====== Trial Type: 4 (rightward, anti-saccade) ======"); 00337 00338 // display cue, wait 0.5-1.5 seconds..... 00339 d->displayFixation(x+dist/2, y, false); 00340 d->displayFixation(x-dist/2, y, false); 00341 d->displayFilledCircle(x, y, 5, green); 00342 d->pushEvent("Display cue (green)"); 00343 usleep(delay); 00344 00345 // display target 00346 tx = x + dist * (-1*(trialtype-3.5)); 00347 txt = x + dist * (trialtype-3.5); 00348 timer.reset(); 00349 //d->displayFilledCircleBlink(tx, y, 3, green, 3, 1); 00350 //d->displayFilledCircle(x, y, 5, d->getGrey(), false); 00351 d->displayFilledCircle(tx, y, 5, green); 00352 d->pushEvent("Display target (green)"); 00353 00354 // get response 00355 targetpoint.i = txt; 00356 /*status = */waitForFixation(&targetpoint, 1, fixation_tolerance, 100, et, d); 00357 double tt = timer.getSecs(); 00358 00359 LINFO("(anti-saccade) response time: %f", tt); 00360 d->pushEvent(sformat("anti-saccade, response time: %f", tt)); 00361 00362 d->displayCircle(targetpoint.i, targetpoint.j, 60, yellow); 00363 usleep(1000000); 00364 00365 // display information 00366 d->clearScreen(); 00367 }else{ 00368 //bug, die!! 00369 d->displayColorDotFixation(x, y, PixRGB<byte>(0,0,255)); 00370 return(1); 00371 } 00372 00373 // stop the eye tracker: 00374 usleep(50000); 00375 et->track(false); 00376 00377 // display percentage completed 00378 double completion = 100*((double)i+1)/double(ntrial); 00379 //LINFO("completion %f, %f, %f", completion, (double)i+1, double(ntrial)); 00380 if ((int)completion % 5 == 0 && completion-(int)completion==0){ 00381 d->clearScreen(); 00382 d->displayText(sformat("%i %% completed", (int)completion), true, 0, 10); 00383 usleep(1000000); 00384 } 00385 00386 if (interleave == 0) 00387 displayTask = 0; 00388 00389 // take a break for a quarter of trials 00390 if ((int)completion%25 == 0 && i<ntrial-1 && completion-(int)completion==0) { 00391 d->clearScreen(); 00392 d->displayText("Please Take a Break", true, 0, 10); 00393 LINFO("Break time. Press [Space] to continue, or [ESC] to terminate the experiment."); 00394 d->waitForKey(true); 00395 d->displayText("Calibration", true, 0, 10); 00396 d->pushEventBegin("Calibration"); 00397 et->calibrate(d); 00398 d->pushEventEnd("Calibration"); 00399 displayTask = 1; 00400 } 00401 } 00402 00403 d->clearScreen(); 00404 d->displayText("Experiment complete. Thank you!", true, 0, 10); 00405 d->waitForKey(true); 00406 00407 // stop all our ModelComponents 00408 manager.stop(); 00409 00410 // all done! 00411 return 0; 00412 } 00413 00414 // ###################################################################### 00415 extern "C" int main(const int argc, char** argv) 00416 { 00417 // simple wrapper around submain() to catch exceptions (because we 00418 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 00419 // abort while SDL is in fullscreen mode, the X server won't return 00420 // to its original resolution) 00421 try 00422 { 00423 return submain(argc, argv); 00424 } 00425 catch (...) 00426 { 00427 REPORT_CURRENT_EXCEPTION; 00428 } 00429 00430 return 1; 00431 } 00432 00433 // ###################################################################### 00434 /* So things look consistent in everyone's emacs... */ 00435 /* Local Variables: */ 00436 /* indent-tabs-mode: nil */ 00437 /* End: */