00001 /*!@file AppPsycho/psycho-noisecuing.C Psychophysics display for a search for a 00002 target that is presented in various repeated noise backgrounds */ 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00006 // University of Southern California (USC) and the iLab at USC. // 00007 // See http://iLab.usc.edu for information about this project. // 00008 // //////////////////////////////////////////////////////////////////// // 00009 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00010 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00011 // in Visual Environments, and Applications'' by Christof Koch and // 00012 // Laurent Itti, California Institute of Technology, 2001 (patent // 00013 // pending; application number 09/912,225 filed July 23, 2001; see // 00014 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00015 // //////////////////////////////////////////////////////////////////// // 00016 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00017 // // 00018 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00019 // redistribute it and/or modify it under the terms of the GNU General // 00020 // Public License as published by the Free Software Foundation; either // 00021 // version 2 of the License, or (at your option) any later version. // 00022 // // 00023 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00024 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00025 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00026 // PURPOSE. See the GNU General Public License for more details. // 00027 // // 00028 // You should have received a copy of the GNU General Public License // 00029 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00030 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00031 // Boston, MA 02111-1307 USA. // 00032 // //////////////////////////////////////////////////////////////////// // 00033 // 00034 // Primary maintainer for this file: Laurent Itti <itti@usc.edu> 00035 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-searchGabor.C $ 00036 // $Id: psycho-searchGabor.C 10794 2009-02-08 06:21:09Z itti $ 00037 // 00038 00039 #include "Component/ModelManager.H" 00040 #include "Image/ColorOps.H" // for makeRGB() 00041 #include "Image/CutPaste.H" // for concatX() 00042 #include "Image/DrawOps.H" // for drawLine() 00043 #include "Image/Image.H" 00044 #include "Image/ShapeOps.H" // for rescale() 00045 #include "Psycho/PsychoDisplay.H" 00046 #include "Psycho/EyeTrackerConfigurator.H" 00047 #include "Psycho/EyeTracker.H" 00048 #include "Psycho/ClassicSearchItem.H" 00049 #include "Psycho/SearchArray.H" 00050 #include "Component/EventLog.H" 00051 #include "Raster/Raster.H" 00052 #include "Util/StringUtil.H" 00053 #include "GUI/GUIOpts.H" 00054 00055 #include <sstream> 00056 #include <ctime> 00057 #include <vector> 00058 #include <string> 00059 00060 using namespace std; 00061 00062 // Trial design for contextual cueing experiments. May class this up soon. 00063 // Also may make Trial/Experiment classes as ModelComponents of this style. 00064 // Easier to encapsulate experimental designs as separate from trial content. 00065 00066 // But for now it's easier to keep everything public. 00067 enum NoiseColor { WHITE, PINK, BROWN }; 00068 struct trialAgenda 00069 { 00070 bool repeated; //will the trial have a repeated background? 00071 NoiseColor color; 00072 geom::vec2d targetloc; 00073 uint noiseSeed[3]; 00074 Dims scrsize; 00075 trialAgenda(const bool r, const NoiseColor c, 00076 const geom::vec2d t, Dims d) 00077 { 00078 repeated = r; 00079 color = c; 00080 targetloc = t; 00081 scrsize = d; 00082 randomizeNoise(1); // initialize noise to the same pattern at first 00083 } 00084 trialAgenda() //NULL initializer 00085 { } 00086 00087 std::string colname() const 00088 { 00089 switch(color) { 00090 case WHITE: return "white"; 00091 case PINK: return "pink"; 00092 case BROWN: return "brown"; 00093 } 00094 return ""; 00095 } 00096 00097 void randomizeNoise(const uint Nbkgds) 00098 { 00099 for(uint i = 0; i < 3; i++) noiseSeed[i] = randomUpToNotIncluding(Nbkgds)+1; 00100 } 00101 00102 std::string backgroundFile(const uint comp) const //components are R=0,G=1,B=2 00103 { 00104 //unfortunate hard-coded path 00105 std::string stimdir = "/lab/jshen/projects/eye-cuing/stimuli/noiseseeds"; 00106 std::string backFile = sformat("%s/%s%03d.png",stimdir.c_str(),colname().c_str(),noiseSeed[comp]); 00107 00108 // check if file exists 00109 if(!Raster::fileExists(backFile)) 00110 { 00111 // stop all our ModelComponents 00112 // manager.stop(); 00113 LFATAL("Image file %s not found.", backFile.c_str()); 00114 } 00115 return backFile; 00116 } 00117 00118 Image<PixRGB<byte> > generateBkgd() { 00119 return colorizedBkgd(); 00120 } 00121 Image<PixRGB<byte> > colorizedBkgd() { 00122 std::vector<Image<byte> > comp; 00123 00124 for(uint i = 0; i < 3; i++) { 00125 //skips file validation step - dangerous 00126 Image<PixRGB<byte> > tmp = Raster::ReadRGB(backgroundFile(i)); 00127 comp.push_back(getPixelComponentImage(tmp,i)); 00128 } 00129 Image<PixRGB<byte> > RGBnoise = crop(makeRGB(comp[0],comp[1],comp[2]),Point2D<int>(0,0),scrsize); 00130 00131 //lower contrast by mixing with gray (64,64,64) or white (128,128,128) 00132 RGBnoise = RGBnoise/2 + PixRGB<byte>(64,64,64); 00133 return RGBnoise; 00134 } 00135 }; 00136 00137 std::string convertToString(const trialAgenda& val) 00138 { 00139 std::stringstream s; 00140 s << val.colname() << " noise, "; 00141 00142 if (val.repeated) 00143 s << "repeated, seed ("; 00144 else 00145 s << "random, seed ("; 00146 for(uint i = 0; i < 3; i++) 00147 s << val.noiseSeed[i] << (i<2 ? "," : ")"); 00148 00149 // target is printed with respect to origin at center - translating to origin at upper left 00150 s << ", target @ (" << val.targetloc.x() + val.scrsize.w()/2 << "," << val.targetloc.y() + val.scrsize.h()/2 << ")"; 00151 return s.str(); 00152 } 00153 00154 // Utility functions 00155 00156 // Generate a random integer uniformly in (x,y); 00157 int randomInRange(const int x, const int y) 00158 { 00159 return randomUpToNotIncluding(y-x-1)+(x+1); 00160 } 00161 00162 // Generate a random point uniformly in d 00163 geom::vec2d randomPtIn(const Rectangle d) 00164 { 00165 return geom::vec2d(randomInRange(d.left(),d.rightO()), 00166 randomInRange(d.top(),d.bottomO())); 00167 } 00168 00169 00170 00171 // ###################################################################### 00172 static int submain(const int argc, char** argv) 00173 { 00174 MYLOGVERB = LOG_INFO; // suppress debug messages 00175 00176 // Instantiate a ModelManager: 00177 ModelManager manager("Psycho Visual Search - cuing"); 00178 00179 nub::soft_ref<EventLog> el(new EventLog(manager)); 00180 manager.addSubComponent(el); 00181 00182 nub::soft_ref<EyeTrackerConfigurator> 00183 etc(new EyeTrackerConfigurator(manager)); 00184 manager.addSubComponent(etc); 00185 00186 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00187 manager.addSubComponent(d); 00188 00189 // get post-command-line configs: 00190 if (manager.parseCommandLine(argc, argv, "<Nblocks> <setSize>",2,2) == false) 00191 return(1); 00192 00193 // hook our various babies up: 00194 nub::soft_ref<EyeTracker> et = etc->getET(); 00195 d->setEyeTracker(et); 00196 d->setEventLog(el); 00197 et->setEventLog(el); 00198 00199 // EyeLink opens the screen for us, so make sure SDLdisplay is slave: 00200 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) 00201 d->setModelParamVal("SDLslaveMode", true); 00202 00203 // let's get all our ModelComponent instances started: 00204 manager.start(); 00205 00206 // pick the left and right response keys 00207 // TODO: allow for arrow keys 00208 char keyleft, keyright; 00209 do { 00210 d->pushEvent("===== key for T pointed left ====="); 00211 d->displayText("Press key for response for T pointed left:"); 00212 keyleft = d->waitForKey(); 00213 d->pushEvent("===== key for T pointed right ====="); 00214 d->displayText("Press key for response for T pointed right:"); 00215 keyright = d->waitForKey(); 00216 if (keyleft == keyright) { 00217 d->displayText(sformat("You must choose two different keys (%d/%d).",keyleft,keyright)); 00218 d->waitForKey(); 00219 } 00220 } 00221 while (keyleft == keyright); 00222 00223 // ********************* Initial calibration ********************// 00224 // let's display an ISCAN calibration grid: 00225 00226 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) { 00227 d->pushEventBegin("Calibration"); 00228 et->setBackgroundColor(d); 00229 et->calibrate(d); 00230 d->pushEventEnd("Calibration"); 00231 } 00232 else if(etc->getModelParamString("EyeTrackerType").compare("ISCAN") == 0) { 00233 d->clearScreen(); 00234 d->displayISCANcalib(); 00235 d->waitForKey(); 00236 00237 // let's do an eye tracker calibration: 00238 d->displayText("Press any key to calibrate; other key to skip"); 00239 int c = d->waitForKey(); 00240 if (c == ' ') d->displayEyeTrackerCalibration(3, 3); 00241 } 00242 00243 // we are ready to start: 00244 d->clearScreen(); 00245 d->displayText("Press any key to start the experiment"); 00246 d->waitForKey(); 00247 00248 // **************** Experimental settings *************** // 00249 00250 // number of images in each block, etc. 00251 const uint Nrepeats = 6; //repeats per block 00252 const uint Nblocks = fromStr<int>(argv[1]);; //blocks per experiment 00253 const uint Ntrials = 12; //blocks per trial 00254 const uint Nbreak = 5; //blocks per break 00255 00256 // time out for each trial in seconds 00257 const double Ntimeout = 5.0; 00258 00259 // number of available noise frames in the stimuli folder 00260 const uint Nnoises = 256; 00261 00262 // physical layout of search arrays 00263 const int itemsize = 90; 00264 const int setSize = fromStr<int>(argv[2]); // from cmd line 00265 const double grid_spacing = 120.0, min_spacing = grid_spacing; 00266 00267 // size of screen - should be no bigger than 1920x1080 00268 const Dims screenDims = 00269 fromStr<Dims>(manager.getOptionValString(&OPT_SDLdisplayDims)); 00270 00271 // target/distractor type: we have choice of c o q - t l + 00272 const std::string Ttype = "T", Dtype = "L"; 00273 const double phiMax = M_PI*5/8, phiMin = M_PI*3/8; //make the rotation easy to see 00274 00275 ClassicSearchItemFactory 00276 targetsLeft(SearchItem::FOREGROUND, Ttype, 00277 itemsize, 00278 Range<double>(-phiMax,-phiMin)), 00279 targetsRight(SearchItem::FOREGROUND, Ttype, 00280 itemsize, 00281 Range<double>(phiMin,phiMax)), 00282 distractors(SearchItem::BACKGROUND, Dtype, 00283 itemsize, 00284 Range<double>(-M_PI/2,M_PI/2)); 00285 00286 // ******************** Trial Design ************************* // 00287 std::vector<rutz::shared_ptr<trialAgenda> > trials; 00288 int tIndex[Ntrials]; 00289 NoiseColor colors[Ntrials]; 00290 bool rep[Ntrials]; 00291 00292 SearchArray sarray(screenDims, grid_spacing, min_spacing, itemsize); 00293 const PixRGB<byte> gray(128,128,128); 00294 00295 // Design and shuffle trials 00296 initRandomNumbers(); 00297 for (uint i = 0; i < Ntrials; i++) 00298 { 00299 tIndex[i] = i; // for index shuffling 00300 colors[i] = NoiseColor(i%3); 00301 rep[i] = (i < Nrepeats); 00302 } 00303 00304 for (uint i = 0; i < Ntrials; i++) 00305 { 00306 // a random location for each target 00307 const geom::vec2d pos = randomPtIn(sarray.itemBounds()); 00308 00309 rutz::shared_ptr<trialAgenda> myTrial(new trialAgenda(rep[i],colors[i],pos,screenDims)); 00310 myTrial->randomizeNoise(Nnoises); //initialize noise seed 00311 trials.push_back(myTrial); 00312 } 00313 00314 char buf[256]; 00315 //******************* block loop:***********************// 00316 for (uint iblock = 1; iblock <= Nblocks; iblock++) { 00317 00318 // block design - shuffle blocks 00319 randShuffle(tIndex,Ntrials); 00320 00321 d->displayText(sformat("Beginning block %d",iblock)); 00322 d->waitForKey(); 00323 00324 sprintf(buf,"===== Beginning block %u/%u =====", iblock,Nblocks); 00325 d->pushEvent(buf); 00326 LINFO("%s",buf); 00327 00328 rutz::shared_ptr<trialAgenda> thisTrial; 00329 //******************* trial loop:***********************// 00330 for (uint itrial = 1; itrial <= Ntrials; itrial++) { 00331 00332 //send trial information to log 00333 sprintf(buf, "===== Block %u/%u, Trial %u/%u =====", 00334 iblock, Nblocks, itrial, Ntrials); 00335 d->pushEvent(buf); 00336 LINFO("%s",buf); 00337 00338 // pick up the trial depending on this block's sequence 00339 thisTrial = trials[tIndex[itrial-1]]; 00340 00341 // clear display 00342 d->clearScreen(); 00343 00344 // If the background needs randomization, do it here 00345 if(!(thisTrial->repeated)) 00346 thisTrial->randomizeNoise(Nnoises); 00347 00348 // get the background image 00349 Image< PixRGB<byte> > bkgdimg = thisTrial->generateBkgd(); 00350 00351 // clear the search array of objects 00352 sarray.clear(); 00353 00354 // ******************** generate search array *********************// 00355 // pick target location and orientation, and place 00356 // NB: P is in the center-as-origin coordinate frame 00357 geom::vec2d P = thisTrial->targetloc; 00358 const bool isTargetLeft = (randomDouble() < 0.5); 00359 const std::string tarDir = isTargetLeft ? "left" : "right"; 00360 if (isTargetLeft) 00361 sarray.addElement(targetsLeft.make(P)); 00362 else 00363 sarray.addElement(targetsRight.make(P)); 00364 00365 // generate the distractors 00366 sarray.generateBackground(distractors, 2, false, 5000, false); 00367 // reduce # of distractors to correct amount 00368 sarray.pareBackground(setSize); 00369 00370 // generate SDL display 00371 Image< PixRGB<byte> > img = bkgdimg + sarray.getImage() - gray; 00372 SDL_Surface *surf1 = d->makeBlittableSurface(img, true); 00373 00374 // give a chance to other processes if single-CPU: 00375 usleep(200000); 00376 00377 // drift calibration if we're on EyeLink 00378 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) { 00379 et->recalibrate(d,-1); 00380 // start the eyetracker 00381 d->displayFixation(); 00382 usleep(300000); //longer SOA to hopefully avoid crash 00383 et->track(true); 00384 d->displayFixationBlink(-1,-1,2,2); //short blink, 2 v-refreshes 00385 } 00386 else if(etc->getModelParamString("EyeTrackerType").compare("ISCAN") == 0) { 00387 // display fixation, ready to go whenever the user is ready: 00388 d->displayFixation(); 00389 d->waitForKey(); 00390 // start the eyetracker 00391 et->track(true); 00392 d->displayFixationBlink(-1,-1,2,2); //short blink, 2 v-refreshes 00393 } 00394 else { //no input 00395 d->displayFixation(); 00396 usleep(100000); 00397 d->displayFixationBlink(-1,-1,2,2); //short blink, 2 v-refreshes 00398 } 00399 00400 //************************ Display array************************// 00401 sprintf(buf, "===== Showing array: %s, turned %s =====", 00402 toStr(*thisTrial).c_str(), tarDir.c_str()); 00403 LINFO("%s",buf); 00404 00405 // actually push the array onto the screen 00406 d->waitNextRequestedVsync(false, true); 00407 d->pushEvent(buf); 00408 d->displaySurface(surf1, 0, true); 00409 00410 // get response back for key (NB: doesn't always take key input for ISCAN) 00411 const int resp = d->waitForKeyTimeout(1000*Ntimeout); 00412 00413 // response processing 00414 if(resp == -1) 00415 sprintf(buf, "trial timed out"); 00416 else if(resp != keyleft && resp != keyright) 00417 sprintf(buf,"trial mistaken - neither key pressed (%c, %c/%c)",resp, keyleft, keyright); 00418 else if((resp==keyleft) == isTargetLeft) 00419 sprintf(buf,"trial correct, correct = %s", tarDir.c_str()); 00420 else 00421 sprintf(buf,"trial incorrect, correct = %s", tarDir.c_str()); 00422 00423 // push to both the queue and psych data file 00424 d->pushEvent(buf); 00425 LINFO("%s",buf); 00426 00427 usleep(50000); 00428 et->track(false); 00429 00430 // clear screen and free the image from memory 00431 d->clearScreen(); 00432 SDL_FreeSurface(surf1); 00433 } // end trial loop 00434 00435 //allow for a break after every 5 blocks, then recalibrate 00436 if (iblock%Nbreak==0 && iblock!=Nblocks) 00437 { 00438 d->displayText("Please take a break: press any key to continue when ready."); 00439 d->waitForKey(); 00440 00441 if (etc->getModelParamString("EyeTrackerType").compare("EL") == 0) { 00442 d->pushEventBegin("Calibration"); 00443 et->setBackgroundColor(d); 00444 et->calibrate(d); 00445 d->pushEventEnd("Calibration"); 00446 } 00447 else if(etc->getModelParamString("EyeTrackerType").compare("ISCAN") == 0) { 00448 d->clearScreen(); 00449 d->displayISCANcalib(); 00450 d->waitForKey(); 00451 00452 // let's do an eye tracker calibration: 00453 d->displayText("<SPACE> to calibrate; other key to skip"); 00454 int c = d->waitForKey(); 00455 if (c == ' ') d->displayEyeTrackerCalibration(3, 3); 00456 } 00457 } 00458 } 00459 00460 d->clearScreen(); 00461 d->displayText("Search trials complete."); 00462 d->waitForKey(); 00463 00464 d->displayText("Which background is more familiar?"); 00465 d->waitForKey(); 00466 00467 //************************ Background recall trials************************// 00468 int iRepeat = 0; 00469 for(uint i = 0; i < Ntrials; i++) { 00470 rutz::shared_ptr<trialAgenda> thisTrial = trials[i]; 00471 if(!thisTrial->repeated) continue; 00472 iRepeat++; 00473 00474 // NB: if this doesn't work, construct a new one 00475 trialAgenda randTrial = (*thisTrial); // deep copy? 00476 Dims halfDims = screenDims/2; 00477 00478 00479 // generate two different noise patterns, downscaled by 1/2 00480 Image<PixRGB<byte> > rpt_bkgd = rescale(thisTrial->generateBkgd(),halfDims); 00481 randTrial.randomizeNoise(Nnoises); 00482 Image<PixRGB<byte> > rand_bkgd = rescale(randTrial.generateBkgd(),halfDims); 00483 00484 // flip a coin and decide left or right 00485 const bool isRepeatLeft = (randomDouble() < 0.5); 00486 const std::string repeatDir = isRepeatLeft ? "left" : "right"; 00487 00488 Image<PixRGB<byte> > two_bkgd; 00489 if (isRepeatLeft) 00490 two_bkgd = concatX(rpt_bkgd, rand_bkgd); 00491 else 00492 two_bkgd = concatX(rand_bkgd,rpt_bkgd); 00493 00494 // draw two pixel thick vertical line 00495 drawLine(two_bkgd, Point2D<int>(halfDims.w()-1, 0), Point2D<int>(halfDims.w()-1, halfDims.h()-1), 00496 PixRGB<byte>(255, 255, 0), 1); 00497 drawLine(two_bkgd, Point2D<int>(halfDims.w(), 0), Point2D<int>(halfDims.w(), halfDims.h()-1), 00498 PixRGB<byte>(255, 255, 0), 1); 00499 00500 sprintf(buf, "===== Recognition for array: %s, on %s side =====", 00501 toStr(*thisTrial).c_str(), repeatDir.c_str()); 00502 LINFO("%s",buf); 00503 00504 d->displayText(sformat("Recognition trial %d",iRepeat)); 00505 d->waitForKey(); 00506 SDL_Surface *surf1 = d->makeBlittableSurface(two_bkgd, true); 00507 00508 // push the two images onto the screen 00509 d->waitNextRequestedVsync(false, true); 00510 d->pushEvent(buf); 00511 d->displaySurface(surf1, 0, true); 00512 00513 // get response back for key (NB: doesn't always take key input for ISCAN) 00514 const int resp = d->waitForKey(); 00515 00516 // response processing 00517 if(resp != keyleft && resp != keyright) 00518 sprintf(buf,"recognition trial mistaken - neither key pressed (%c, %c/%c)",resp, keyleft, keyright); 00519 else if((resp==keyleft) == isRepeatLeft) 00520 sprintf(buf,"recognition trial correct, correct = %s", repeatDir.c_str()); 00521 else 00522 sprintf(buf,"recognition trial incorrect, correct = %s", repeatDir.c_str()); 00523 00524 // push to both the queue and psych data file 00525 d->pushEvent(buf); 00526 LINFO("%s",buf); 00527 } 00528 00529 //************************ Target recall trials************************// 00530 d->displayText("Where would the target be? Click with the mouse."); 00531 d->waitForMouseClick(); 00532 00533 iRepeat = 0; 00534 for(uint i = 0; i < Ntrials; i++) { 00535 rutz::shared_ptr<trialAgenda> thisTrial = trials[i]; 00536 if(!thisTrial->repeated) continue; 00537 iRepeat++; 00538 00539 Image<PixRGB<byte> > rpt_bkgd = thisTrial->generateBkgd(); 00540 00541 00542 sprintf(buf, "===== Target retrieval for array: %s =====", 00543 toStr(*thisTrial).c_str()); 00544 LINFO("%s",buf); 00545 00546 d->displayText(sformat("Target retrieval trial %d",iRepeat)); 00547 d->waitForMouseClick(); 00548 SDL_Surface *surf1 = d->makeBlittableSurface(rpt_bkgd, true); 00549 00550 // push the two images onto the screen 00551 d->waitNextRequestedVsync(false, true); 00552 d->pushEvent(buf); 00553 d->displaySurface(surf1, 0, true); 00554 00555 // get response back from mouse 00556 d->showCursor(true); 00557 SDL_Event event; 00558 while( SDL_WaitEvent( &event )) { 00559 if(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT ) { 00560 break; 00561 } 00562 } 00563 /* 00564 switch (event.type) { 00565 case SDL_MOUSEMOTION: 00566 printf("Mouse moved by %d,%d to (%d,%d)\n", 00567 event.motion.xrel, event.motion.yrel, 00568 event.motion.x, event.motion.y); 00569 //break; 00570 case SDL_MOUSEBUTTONDOWN: 00571 printf("Mouse button %d pressed at (%d,%d)\n", 00572 event.button.button, event.button.x, event.button.y); 00573 break; 00574 case SDL_MOUSEBUTTONUP: 00575 printf("Mouse button %d raised at (%d,%d)\n", 00576 event.button.button, event.button.x, event.button.y); 00577 //break; 00578 00579 } 00580 }*/ 00581 d->showCursor(false); 00582 00583 Point2D<int> resp(event.button.x,event.button.y); 00584 // response processing 00585 sprintf(buf,"target placed at %s",toStr(resp).c_str()); 00586 00587 // push to both the queue and psych data file 00588 d->pushEvent(buf); 00589 LINFO("%s",buf); 00590 } 00591 00592 d->displayText("Experiment complete. Press any key to exit. Thank you!"); 00593 d->waitForKey(); 00594 00595 // stop all our ModelComponents 00596 manager.stop(); 00597 00598 // all done! 00599 return 0; 00600 } 00601 00602 // ###################################################################### 00603 00604 extern "C" int main(const int argc, char** argv) 00605 { 00606 // simple wrapper around submain() to catch exceptions (because we 00607 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 00608 // abort while SDL is in fullscreen mode, the X server won't return 00609 // to its original resolution) 00610 try 00611 { 00612 return submain(argc, argv); 00613 } 00614 catch (...) 00615 { 00616 REPORT_CURRENT_EXCEPTION; 00617 } 00618 00619 return 1; 00620 } 00621 // ###################################################################### 00622 /* So things look consistent in everyone's emacs... */ 00623 /* Local Variables: */ 00624 /* indent-tabs-mode: nil */ 00625 /* End: */