00001 /*!@file AppPsycho/psycho-movie.C Psychophysics display of movies */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Po-He Tseng <ptseng@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-movie-fixicon.C $ 00035 // $Id: psycho-movie-fixicon.C 13712 2010-07-28 21:00:40Z itti $ 00036 00037 #include "Component/ModelManager.H" 00038 #include "Image/Image.H" 00039 #include "Raster/Raster.H" 00040 #include "Media/MPEGStream.H" 00041 #include "Media/MediaOpts.H" 00042 #include "Psycho/PsychoDisplay.H" 00043 #include "Psycho/EyeTrackerConfigurator.H" 00044 #include "Psycho/EyeTracker.H" 00045 #include "Psycho/PsychoOpts.H" 00046 #include "Component/EventLog.H" 00047 #include "Component/ComponentOpts.H" 00048 #include "Util/MathFunctions.H" 00049 #include "Util/Types.H" 00050 #include "Video/VideoFrame.H" 00051 #include "rutz/time.h" 00052 00053 #include "SDL/SDL_rotozoom.h" 00054 00055 #include <deque> 00056 #include <sstream> 00057 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 // ###################################################################### 00126 static int submain(const int argc, char** argv) 00127 { 00128 MYLOGVERB = LOG_INFO; // suppress debug messages 00129 00130 // Instantiate a ModelManager: 00131 ModelManager manager("Psycho Oculomotor Capture"); 00132 00133 // Instantiate our various ModelComponents: 00134 nub::soft_ref<InputMPEGStream> mp 00135 (new InputMPEGStream(manager, "Input MPEG Stream", "InputMPEGStream")); 00136 manager.addSubComponent(mp); 00137 00138 nub::soft_ref<EventLog> el(new EventLog(manager)); 00139 manager.addSubComponent(el); 00140 00141 nub::soft_ref<EyeTrackerConfigurator> 00142 etc(new EyeTrackerConfigurator(manager)); 00143 manager.addSubComponent(etc); 00144 00145 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00146 manager.addSubComponent(d); 00147 00148 manager.setOptionValString(&OPT_InputMPEGStreamPreload, "true"); 00149 manager.setOptionValString(&OPT_EventLogFileName, "psychodata.psy"); 00150 manager.setOptionValString(&OPT_EyeTrackerType, "EL"); 00151 00152 // Parse command-line: 00153 if (manager.parseCommandLine(argc, argv, "<# of trials>", 1, 1) == false) 00154 return(1); 00155 00156 // construct an array to indicate target location 00157 int ntrial = fromStr<int>(manager.getExtraArg(0).c_str()); 00158 LINFO("Total Trials: %i", ntrial); 00159 int trials[ntrial]; 00160 for (int i=0; i< ntrial; i++) 00161 trials[i] = i % 6; 00162 00163 // randomize stimulus presentation order: 00164 int trialindex[ntrial]; 00165 for (int i = 0; i < ntrial; i ++) 00166 trialindex[i] = i; 00167 randShuffle(trialindex, ntrial); 00168 00169 // hook our various babies up and do post-command-line configs: 00170 nub::soft_ref<EyeTracker> et = etc->getET(); 00171 d->setEyeTracker(et); 00172 d->setEventLog(el); 00173 et->setEventLog(el); 00174 00175 // EyeLink opens the screen for us, so make sure SDLdisplay is slave: 00176 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) 00177 d->setModelParamVal("SDLslaveMode", true); 00178 00179 // let's get all our ModelComponent instances started: 00180 manager.start(); 00181 00182 // vision test (generate random letters again if pressing 'r') 00183 LINFO("*****************************************************************************"); 00184 LINFO("visual acuity test: [r] to regenerate another string; other keys to continue."); 00185 int key; 00186 do { 00187 d->displayRandomText(6, 8); 00188 key = d->waitForKey(true); 00189 } while(key == 114 || key == 101 || key == 116 || key == 102); 00190 00191 // let's do an eye tracker calibration: 00192 d->pushEventBegin("Calibration"); 00193 et->setBackgroundColor(d); 00194 et->calibrate(d); 00195 d->pushEventEnd("Calibration"); 00196 00197 LINFO("Press any key to start......"); 00198 d->displayText("Press any key to start......", true, 0, 10); 00199 d->waitForKey(true); 00200 00201 // preparation for the main loop 00202 const int x = d->getWidth()/2; 00203 const int y = d->getHeight()/2; 00204 const int bradius = x/2.5; // radius for displaying 6 possible target locations 00205 const int sradius = 66; // radius for target/distractor circle 00206 const int isradius = sradius - 8; 00207 00208 //const PixRGB<byte> tcolor = PixRGB<byte>(255,0,0); // target color (red) 00209 const PixRGB<byte> tcolor = PixRGB<byte>(0,255,0); // target color (green) 00210 const PixRGB<byte> dcolor = PixRGB<byte>(255,0,0); // distractor color (red) 00211 const PixRGB<byte> black = PixRGB<byte>(255,255,255); // black color (red) 00212 const PixRGB<byte> bcolor = d->getGrey(); // background color 00213 00214 // calulate circle locations 00215 // clockwise: 0, right; 1, lower-right; 2, lower-left; 3, left; 4, upper-left; 5, upper-right 00216 int tcirpos[6][2]; 00217 int dcirpos[6][2]; 00218 const double PI = 3.141592; 00219 for (int i=0; i<6; i++) { 00220 // potential target locations 00221 tcirpos[i][0] = x + bradius * cos(i*PI/3); 00222 tcirpos[i][1] = y + bradius * sin(i*PI/3); 00223 00224 // potential distractor locations 00225 dcirpos[i][0] = x + bradius * cos(i*PI/3 + PI/6); 00226 dcirpos[i][1] = y + bradius * sin(i*PI/3 + PI/6); 00227 } 00228 00229 // create arrow for pointing direction 00230 const int arrowsize = 16; 00231 SDL_Surface *arrowsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, arrowsize, arrowsize, 24, 0, 0, 0, 0); 00232 SDL_Rect arect; 00233 arect.x = 0; arect.y = 0; arect.w = arrowsize; arect.h = arrowsize; 00234 SDL_FillRect(arrowsurf, &arect, d->getUint32color(bcolor)); 00235 arect.x = 1; arect.y = 1; arect.w = arrowsize-1; arect.h = (int)arrowsize/5; 00236 SDL_FillRect(arrowsurf, &arect, d->getUint32color(tcolor)); 00237 arect.w = (int)arrowsize/5; arect.h = arrowsize-1; 00238 SDL_FillRect(arrowsurf, &arect, d->getUint32color(tcolor)); 00239 00240 // create some T and non-T for discrimination 00241 const int sizeT = 10; 00242 SDL_Surface *isT = SDL_CreateRGBSurface(SDL_SWSURFACE, sizeT, sizeT, 24, 0, 0, 0, 0); 00243 SDL_Surface *noT = SDL_CreateRGBSurface(SDL_SWSURFACE, sizeT, sizeT, 24, 0, 0, 0, 0); 00244 arect.x = 0; arect.y = 0; arect.w = sizeT; arect.h = sizeT; // background 00245 SDL_FillRect(isT, &arect, d->getUint32color(bcolor)); 00246 SDL_FillRect(noT, &arect, d->getUint32color(bcolor)); 00247 arect.x = sizeT/2; arect.y = 0; arect.w = 1; arect.h = sizeT; //vertical bar 00248 SDL_FillRect(isT, &arect, d->getUint32color(tcolor)); 00249 SDL_FillRect(noT, &arect, d->getUint32color(tcolor)); 00250 arect.x = 0; arect.y = 0; arect.w = sizeT; arect.h = 1; //horizontal bar, T 00251 SDL_FillRect(isT, &arect, d->getUint32color(tcolor)); 00252 arect.x = 0; arect.y = 2; arect.w = sizeT; arect.h = 1; //horizontal bar, non-T 00253 SDL_FillRect(noT, &arect, d->getUint32color(tcolor)); 00254 00255 SDL_Surface *isTd = SDL_CreateRGBSurface(SDL_SWSURFACE, sizeT, sizeT, 24, 0, 0, 0, 0); 00256 SDL_Surface *noTd = SDL_CreateRGBSurface(SDL_SWSURFACE, sizeT, sizeT, 24, 0, 0, 0, 0); 00257 arect.x = 0; arect.y = 0; arect.w = sizeT; arect.h = sizeT; // background 00258 SDL_FillRect(isTd, &arect, d->getUint32color(bcolor)); 00259 SDL_FillRect(noTd, &arect, d->getUint32color(bcolor)); 00260 arect.x = sizeT/2; arect.y = 0; arect.w = 1; arect.h = sizeT; //vertical bar 00261 SDL_FillRect(isTd, &arect, d->getUint32color(dcolor)); 00262 SDL_FillRect(noTd, &arect, d->getUint32color(dcolor)); 00263 arect.x = 0; arect.y = 0; arect.w = sizeT; arect.h = 1; //horizontal bar, T 00264 SDL_FillRect(isTd, &arect, d->getUint32color(dcolor)); 00265 arect.x = 0; arect.y = 2; arect.w = sizeT; arect.h = 1; //horizontal bar, non-T 00266 SDL_FillRect(noTd, &arect, d->getUint32color(dcolor)); 00267 00268 // initialze T and Not-T index that will be randomized later 00269 int tntidx[6]; 00270 for(int i=0; i< 6; i++) 00271 tntidx[i] = i; 00272 00273 // main loop 00274 double delay; 00275 int j, iscontrol, thisTarget, thisDistractor, thisSOA; 00276 int tnt[6], correctArray[ntrial]; 00277 SDL_Surface *surf = SDL_CreateRGBSurface(SDL_SWSURFACE, d->getWidth(), d->getHeight(), 24, 0, 0, 0, 0); 00278 SDL_Rect rect, rr; rect.x = 0; rect.y = 0; rect.w = d->getWidth(); rect.h = d->getHeight(); 00279 Timer timer; 00280 double rt, avg; 00281 avg = 0; 00282 00283 for (int i=0; i < ntrial; i++) { 00284 thisTarget = trials[trialindex[i]]; 00285 thisSOA = rand()%150; 00286 thisDistractor = rand()%6; 00287 if (rand()%10 < 1){ 00288 iscontrol = 1; 00289 } else { 00290 iscontrol = 0; 00291 } 00292 00293 delay = 1000000; // + randomUpToIncluding(1000000); 00294 SDL_FillRect(surf, &rect, d->getUint32color(d->getGrey())); 00295 00296 // clean screen and do drift correction 00297 et->recalibrate(d,13); 00298 d->clearScreen(); 00299 00300 // randomize T and Not-T 00301 randShuffle(tntidx, 6); 00302 for (j=0; j<6; j++) 00303 tnt[tntidx[j]] = j % 2; //0, non-T; 1, T 00304 00305 // track eye 00306 d->displayFilledCircle(x, y, 3, black, true); 00307 usleep(900000); 00308 et->track(true); 00309 usleep(100000); 00310 00311 // display all possible target locations 00312 for (j=0; j<6; j++){ 00313 filledCircleRGBA(surf, tcirpos[j][0], tcirpos[j][1], sradius, tcolor.red(), tcolor.green(), tcolor.blue(), 0XFF); 00314 filledCircleRGBA(surf, tcirpos[j][0], tcirpos[j][1], isradius, bcolor.red(), bcolor.green(), bcolor.blue(), 0XFF); 00315 00316 rr.x = tcirpos[j][0]-isT->w/2; rr.y = tcirpos[j][1]-isT->h/2; 00317 if (tnt[j] == 0) { 00318 SDL_BlitSurface(noT, NULL, surf, &rr); 00319 } else { 00320 SDL_BlitSurface(isT, NULL, surf, &rr); 00321 } 00322 } 00323 00324 // is it an control trial? 00325 if (iscontrol == 1) { 00326 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], sradius, tcolor.red(), tcolor.green(), tcolor.blue(), 0XFF); 00327 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], isradius, bcolor.red(), bcolor.green(), bcolor.blue(), 0XFF); 00328 } 00329 00330 // display array 00331 filledCircleRGBA(surf, x, y, 3, black.red(), black.green(), black.blue(), 0XFF); 00332 d->displaySurface(surf, -2, true); 00333 d->pushEvent("Array UP"); 00334 00335 // waiting before target comes up 00336 usleep(delay); 00337 00338 // show direction 00339 LINFO("thisTarget: %i, isT: %i", thisTarget, tnt[thisTarget]); 00340 00341 // show 00342 for (j=0; j<6; j++) 00343 if (j != thisTarget){ 00344 filledCircleRGBA(surf, tcirpos[j][0], tcirpos[j][1], sradius, dcolor.red(), dcolor.green(), dcolor.blue(), 0XFF); 00345 filledCircleRGBA(surf, tcirpos[j][0], tcirpos[j][1], isradius, bcolor.red(), bcolor.green(), bcolor.blue(), 0XFF); 00346 00347 rr.x = tcirpos[j][0]-isT->w/2; rr.y = tcirpos[j][1]-isT->h/2; 00348 if (tnt[j] == 0) { 00349 SDL_BlitSurface(noTd, NULL, surf, &rr); 00350 } else { 00351 SDL_BlitSurface(isTd, NULL, surf, &rr); 00352 } 00353 } 00354 00355 timer.reset(); 00356 if (thisSOA < 34){ 00357 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], sradius, dcolor.red(), dcolor.green(), dcolor.blue(), 0XFF); 00358 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], isradius, bcolor.red(), bcolor.green(), bcolor.blue(), 0XFF); 00359 d->displaySurface(surf, -2, true); 00360 d->pushEvent(sformat("Target direction shown: %d", thisTarget)); 00361 if (iscontrol == 1) 00362 d->pushEvent("Distractor shown: control condition"); 00363 else 00364 d->pushEvent(sformat("Distractor shown: %f (SOA = %d ms)", thisDistractor+0.5, thisSOA)); 00365 00366 } else { 00367 00368 // display instruction direction first 00369 d->displaySurface(surf, -2, true); 00370 d->pushEvent(sformat("Target direction shown: %d", thisTarget)); 00371 00372 // wait 00373 usleep(thisSOA*1000); 00374 00375 // display distractor 00376 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], sradius, dcolor.red(), dcolor.green(), dcolor.blue(), 0XFF); 00377 filledCircleRGBA(surf, dcirpos[thisDistractor][0], dcirpos[thisDistractor][1], isradius, bcolor.red(), bcolor.green(), bcolor.blue(), 0XFF); 00378 d->displaySurface(surf, -2, true); 00379 d->pushEvent(sformat("Distractor shown: %f (SOA = %d ms)", thisDistractor+0.5, thisSOA)); 00380 00381 } 00382 00383 // wait for response 00384 //LINFO("Press 4 for T, 6 for Not-T, or Space Bar to skip."); 00385 do { 00386 key = d->waitForKey(); 00387 } while (key != 52 && key != 54 && key != 32); 00388 rt = timer.getMilliSecs(); 00389 int kk = 0; 00390 if (key==52) 00391 kk = 1; 00392 else if (key==32) 00393 kk = -1; // space bar 00394 00395 d->pushEvent(sformat("Responded %d (%f ms), correct %d", kk, rt, kk==tnt[thisTarget])); 00396 LINFO("Responded %d (%f ms), correct %d", kk, rt, kk==tnt[thisTarget]); 00397 avg = onlineMean(avg, rt, i+1); 00398 00399 d->clearScreen(); 00400 00401 usleep(50000); 00402 et->track(false); 00403 00404 correctArray[i] = (kk==tnt[thisTarget]); 00405 00406 // display percentage completed 00407 double completion = 100*((double)i+1)/double(ntrial); 00408 if ((int)completion % 5 == 0 && completion - (int)completion == 0){ 00409 00410 d->clearScreen(); 00411 d->displayText(sformat("%i %% completed", (int)completion), true, 0, 10); 00412 usleep(1000000); 00413 } 00414 00415 // take a break for a quarter of trials 00416 if ((int)completion%50 == 0 && i<ntrial-1 && completion-(int)completion==0) { 00417 d->clearScreen(); 00418 d->displayText("Please Take a Break", true, 0, 10); 00419 00420 // performance information 00421 int cc = 0; 00422 for (j=0; j<=i; j++) 00423 if (correctArray[j]==1) 00424 cc++; 00425 LINFO("Average RT: %f ms, Correctness: %f%%", avg, 100*(double)cc/((double)i+1)); 00426 00427 LINFO("Break time. Press [Space] to continue, or [ESC] to terminate the experiment."); 00428 d->waitForKey(true); 00429 d->displayText("Calibration", true, 0, 10); 00430 d->pushEventBegin("Calibration"); 00431 et->calibrate(d); 00432 d->pushEventEnd("Calibration"); 00433 } 00434 } 00435 00436 SDL_FreeSurface(arrowsurf); 00437 SDL_FreeSurface(surf); 00438 00439 // performance information 00440 int cc = 0; 00441 for (j=0; j<ntrial; j++) 00442 if (correctArray[j]==1) 00443 cc++; 00444 LINFO("Average RT: %f ms, Correctness: %f%%", avg, 100*(double)cc/((double)ntrial)); 00445 00446 d->clearScreen(); 00447 d->displayText("Experiment complete. Thank you!", true, 0, 10); 00448 d->waitForKey(true); 00449 00450 // stop all our ModelComponents 00451 manager.stop(); 00452 00453 // all done! 00454 return 0; 00455 } 00456 00457 // ###################################################################### 00458 extern "C" int main(const int argc, char** argv) 00459 { 00460 // simple wrapper around submain() to catch exceptions (because we 00461 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 00462 // abort while SDL is in fullscreen mode, the X server won't return 00463 // to its original resolution) 00464 try 00465 { 00466 return submain(argc, argv); 00467 } 00468 catch (...) 00469 { 00470 REPORT_CURRENT_EXCEPTION; 00471 } 00472 00473 return 1; 00474 } 00475 00476 // ###################################################################### 00477 /* So things look consistent in everyone's emacs... */ 00478 /* Local Variables: */ 00479 /* indent-tabs-mode: nil */ 00480 /* End: */