00001 /*!@file AppPsycho/psycho-motion.C Psychophysics display of randomly moving 00002 clouds of dots for checking motion coherence */ 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: Vidhya Navalpakkam <navalpak@usc.edu> 00035 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-motion.C $ 00036 // $Id: psycho-motion.C 9412 2008-03-10 23:10:15Z farhan $ 00037 // 00038 00039 #include "Component/ModelManager.H" 00040 #include "Image/DrawOps.H" 00041 #include "Image/Image.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 "GUI/GUIOpts.H" 00049 #include "Raster/Raster.H" 00050 #include "Util/MathFunctions.H" 00051 #include "Video/RgbConversion.H" // for toVideoYUV422() 00052 00053 00054 // create a circular cloud of random dots at center (x,y) and radius r 00055 void createCloud (DOT* dots, int numDots, int x, int y, int r, int move); 00056 // set coherence of the cloud 00057 void setCoherence (DOT* dots, int numDots, int numCoherent, int move); 00058 00059 // ###################################################################### 00060 extern "C" int main(const int argc, char** argv) 00061 { 00062 MYLOGVERB = LOG_INFO; // suppress debug messages 00063 00064 // Instantiate a ModelManager: 00065 ModelManager manager("Psycho motion"); 00066 00067 // Instantiate our various ModelComponents: 00068 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00069 manager.addSubComponent(d); 00070 00071 nub::soft_ref<EyeTrackerConfigurator> 00072 etc(new EyeTrackerConfigurator(manager)); 00073 manager.addSubComponent(etc); 00074 00075 nub::soft_ref<EventLog> el(new EventLog(manager)); 00076 manager.addSubComponent(el); 00077 00078 // set some display params 00079 manager.setOptionValString(&OPT_SDLdisplayDims, "640x480"); 00080 d->setModelParamVal("PsychoDisplayBackgroundColor", PixRGB<byte>(0)); 00081 d->setModelParamVal("PsychoDisplayTextColor", PixRGB<byte>(255)); 00082 d->setModelParamVal("PsychoDisplayBlack", PixRGB<byte>(255)); 00083 d->setModelParamVal("PsychoDisplayWhite", PixRGB<byte>(128)); 00084 d->setModelParamVal("PsychoDisplayFixSiz", 5); 00085 d->setModelParamVal("PsychoDisplayFixThick", 5); 00086 manager.setOptionValString(&OPT_EventLogFileName, "psychodata.psy"); 00087 manager.setOptionValString(&OPT_EyeTrackerType, "ISCAN"); 00088 00089 00090 // Parse command-line: 00091 if (manager.parseCommandLine(argc, argv, "<radius> <midCoherence> " 00092 "<targetCoherence> <numDots> <life> " 00093 "<waitFrames> <move> <startTrial#>", 00094 1, -1)==false) 00095 return(1); 00096 00097 // hook our various babies up and do post-command-line configs: 00098 nub::soft_ref<EyeTracker> et = etc->getET(); 00099 d->setEyeTracker(et); 00100 d->setEventLog(el); 00101 et->setEventLog(el); 00102 00103 // let's get all our ModelComponent instances started: 00104 manager.start(); 00105 00106 // let's display an ISCAN calibration grid: 00107 d->clearScreen(); 00108 d->displayISCANcalib(); 00109 d->waitForKey(); 00110 00111 // let's do an eye tracker calibration: 00112 d->displayText("<SPACE> to calibrate; other key to skip"); 00113 int c = d->waitForKey(); 00114 if (c == ' ') d->displayEyeTrackerCalibration(3, 3); 00115 00116 d->clearScreen(); 00117 00118 // read the radius of the cloud and the coherence 00119 int radius = manager.getExtraArgAs<int>(0); 00120 int midCoherence = manager.getExtraArgAs<int>(1); 00121 int targetCoherence = manager.getExtraArgAs<int>(2); 00122 int numDots = manager.getExtraArgAs<int>(3); 00123 int life = manager.getExtraArgAs<int>(4); 00124 int waitNum = manager.getExtraArgAs<int>(5); 00125 int move = manager.getExtraArgAs<int>(6); 00126 int startTrial = manager.getExtraArgAs<int>(7); 00127 00128 // show a fixation cross on a blank screen: 00129 d->clearScreen(); 00130 00131 initRandomNumbers(); 00132 int numTrial = 0; 00133 DOT clouds[25][numDots]; // allocate space for dots 00134 Image< PixRGB<byte> > bufima(d->getDims(), NO_INIT); 00135 00136 // create background stimuli 00137 int leg = 15; int bgDots = 2 * leg - 1; 00138 DOT targetShape[bgDots], distractorShape[bgDots]; 00139 for (int i = 0; i < bgDots; i++) { 00140 if (i < leg) { 00141 (targetShape[i]).x = 0; (targetShape[i]).y = -i; 00142 (distractorShape[i]).x = 0; (distractorShape[i]).y = -i; 00143 } 00144 else { 00145 (targetShape[i]).x = leg-i; (targetShape[i]).y = -leg; 00146 (distractorShape[i]).x = i-leg; (distractorShape[i]).y = 0; 00147 } 00148 } 00149 DOT bg[25][bgDots]; // allot space for background 00150 00151 while (numTrial < 30) 00152 { 00153 d->createYUVoverlay(SDL_YV12_OVERLAY); 00154 char reco[10]; sprintf (reco, "reco-%d", startTrial + numTrial); 00155 numTrial ++; 00156 FILE* f = fopen (reco, "w"); 00157 // ready to go whenever the user is ready: 00158 d->displayText("hit any key when ready"); 00159 d->waitForKey(); 00160 d->waitNextRequestedVsync(false, true); 00161 // create stimulus on the fly 00162 int targetCloud = -1; 00163 // decide position of LESS, MID, HIGH and target 00164 int index[25]; 00165 for (int i = 0; i < 25; i++) 00166 index[i] = i; 00167 randShuffle (index, 25); 00168 // draw target and distractor clouds 00169 int cenx[25], ceny[25]; 00170 for (int i = 0; i < 25; i++){ 00171 int idx = index[i]; 00172 int col = idx % 5, row = idx / 5; 00173 cenx[idx] = col * 128 + 64 + (int) (randomDouble()*31) - 15; 00174 ceny[idx] = row * 96 + 48 + (int) (randomDouble()*25) - 12 ; 00175 DOT* dots = clouds[idx]; // dots of this cloud 00176 createCloud (dots, numDots, cenx[idx], ceny[idx], radius, move); 00177 if (i == 24) { // TARGET type 00178 setCoherence (dots, numDots, 00179 (int) (targetCoherence*numDots/100), move); 00180 targetCloud = idx; 00181 LINFO ("target location: (%d,%d)", row, col); 00182 fprintf (f, "%d %d %d %d target\n", row, col, cenx[idx],ceny[idx]); 00183 // generate background stimulus 00184 for (int j = 0; j < bgDots; j++){ 00185 DOT* dot_bg = bg[idx] + j; 00186 dot_bg->x = (targetShape[j]).x + cenx[idx] + leg/2; 00187 dot_bg->y = (targetShape[j]).y + ceny[idx] + leg/2; 00188 } 00189 } 00190 else { // DISTRACTOR type 00191 for (int j = 0; j < bgDots; j++){ 00192 DOT* dot_bg = bg[idx] + j; 00193 dot_bg->x = (distractorShape[j]).x + cenx[idx] - leg/2; 00194 dot_bg->y = (distractorShape[j]).y + ceny[idx] + leg/2; 00195 } 00196 if (i < 8){ // LESS type 00197 setCoherence (dots, numDots, 0, move); 00198 fprintf (f, "%d %d %d %d less\n", 00199 row, col, cenx[idx], ceny[idx]); 00200 } 00201 else if (i < 16){ // MID type 00202 setCoherence (dots, numDots, 00203 (int) (midCoherence*numDots/100), move); 00204 fprintf (f, "%d %d %d %d mid\n", row, col, cenx[idx], ceny[idx]); 00205 } 00206 else if (i < 24){ // HIGH type 00207 setCoherence (dots, numDots, numDots, move); 00208 fprintf (f, "%d %d %d %d high\n", 00209 row, col, cenx[idx], ceny[idx]); 00210 } 00211 } 00212 } 00213 fclose (f); // close the reco file 00214 00215 // start the eye tracker: 00216 et->track(true); 00217 00218 // blink the fixation: 00219 d->displayFixationBlink(); 00220 // generate the stimulus on the fly and display it until key press 00221 int time = 0; 00222 while (d->checkForKey() == -1){ 00223 time ++; 00224 // at each time instant, update position of the dots in each cloud 00225 for (int i = 0; i < 25; i++){ 00226 int idx = index[i]; 00227 /* 00228 The following probabilistic renewal is according to Britten 00229 et. al(1992). But it causes more flicker in the random case 00230 and none in the coherent case. 00231 00232 if (i < 8) renewal = 0.0f; 00233 else if (i < 16) renewal = midCoherence/100.0f; 00234 else if (i < 24) renewal = 1.0f; 00235 else renewal = targetCoherence/100.0f; 00236 */ 00237 for (int j = 0; j < numDots; j++){ // update each cloud 00238 DOT *dot = &(clouds[idx][j]); 00239 int x1 = dot->x - cenx[idx]; 00240 int y1 = dot->y - ceny[idx]; // dist wrt center 00241 /* similar to Britten et. al (1992) 00242 if (randomDouble() > renewal) { 00243 int size = 2 * radius + 1; 00244 x1 = (int) (randomDouble()*size) - radius; 00245 y1 = (int) (randomDouble()*size) - radius; 00246 } 00247 */ 00248 if (dot->coherent == 1) y1 += dot->dy; 00249 else { 00250 // move the random dot in a random direction 00251 if (dot->age > life){ 00252 // choose a new random direction 00253 dot->age = 0; 00254 double rand = randomDouble(); 00255 if (rand <= 0.33) dot->dx = move; 00256 else if (rand <= 0.66) dot->dx = -move; 00257 else dot->dx = 0; 00258 rand = randomDouble(); 00259 if (rand <= 0.33) dot->dy = move; 00260 else if (rand <= 0.66) dot->dy = -move; 00261 else dot->dy = 0; 00262 if (dot->dx == 0 && dot->dy == 0){ 00263 if (randomDouble() <= 0.5) dot->dy = move; 00264 else dot->dy = -move; 00265 } 00266 } 00267 x1 += dot->dx; 00268 y1 += dot->dy; 00269 dot->age += 1; 00270 } 00271 // reappearance of a dot that crosses the boundary 00272 if (x1 < -radius) x1 = radius; 00273 else if (x1 > radius) x1 = -radius; 00274 if (y1 < -radius) y1 = radius; 00275 else if (y1 > radius) y1 = -radius; 00276 // absolute position of the dot 00277 dot->x = x1 + cenx[idx]; 00278 dot->y = y1 + ceny[idx]; 00279 } 00280 } 00281 00282 // draw the updated clouds 00283 bufima.clear(); 00284 00285 for (int i = 0; i < 25; i++) 00286 for (int j = 0; j < numDots; j++){ 00287 int x = (clouds[i][j]).x; 00288 int y = (clouds[i][j]).y; 00289 drawDisk(bufima, Point2D<int>(x, y), 2, PixRGB<byte>(120)); 00290 } 00291 for (int i = 0; i < 25; i++) 00292 for (int j = 0; j < bgDots; j++){ 00293 int x = (bg[i][j]).x; 00294 int y = (bg[i][j]).y; 00295 bufima.setVal(x, y, PixRGB<byte>(75)); 00296 } 00297 00298 00299 SDL_Overlay *ovl = d->lockYUVoverlay(); 00300 toVideoYUV422(bufima, 00301 ovl->pixels[0], ovl->pixels[1], ovl->pixels[2]); 00302 d->unlockYUVoverlay(); 00303 d->displayYUVoverlay(time, SDLdisplay::NEXT_VSYNC); 00304 00305 d->waitFrames(waitNum); 00306 } 00307 // stop the eye tracker: 00308 usleep(50000); 00309 et->track(false); 00310 00311 // no cheating: flash a grid of random numbers and check response 00312 int correctResponse = d->displayNumbers (targetCloud/5, 00313 targetCloud%5, true); 00314 d->pushEvent(std::string("===== Showing noCheat =====")); 00315 00316 // flash the image for 99ms: 00317 for (int j = 0; j < 10; j ++) d->waitNextRequestedVsync(); 00318 00319 // wait for response: 00320 d->displayText("Enter the number at the target location"); 00321 c = d->waitForKey(); 00322 int c2 = d->waitForKey(); 00323 // check if the response is correct 00324 int observedResponse = 10*(c-48) + c2-48; 00325 LINFO (" subject entered %d and correct response is %d", 00326 observedResponse, correctResponse); 00327 if (observedResponse == correctResponse) 00328 { 00329 d->displayText("Correct!"); 00330 d->pushEvent(std::string("===== Correct =====")); 00331 } 00332 else 00333 { 00334 d->displayText("Wrong! "); 00335 d->pushEvent(std::string("===== Wrong =====")); 00336 } 00337 // maintain display 00338 for (int j = 0; j < 30; j ++) d->waitNextRequestedVsync(); 00339 d->destroyYUVoverlay(); 00340 } 00341 00342 d->clearScreen(); 00343 d->displayText("Experiment complete. Thank you!"); 00344 d->waitForKey(); 00345 00346 00347 // stop all our ModelComponents 00348 manager.stop(); 00349 00350 // all done! 00351 return 0; 00352 } 00353 00354 // ###################################################################### 00355 // create a circular cloud of random dots at center (x,y) and radius r 00356 void createCloud (DOT* dots, int numDots, int cx, int cy, int r, int move) 00357 { 00358 int i = 0; 00359 int size = 2 * r + 1; 00360 while (i < numDots){ 00361 // generate a dot 00362 int x = (int) (randomDouble()*size) - r; 00363 int y = (int) (randomDouble()*size) - r; 00364 dots[i].x = cx + x; // set x coordinate 00365 dots[i].y = cy + y; // set y coordinate 00366 dots[i].coherent = 0; // set coherent 00367 dots[i].age = (int) (randomDouble() * 5); 00368 double rand = randomDouble(); 00369 if (rand <= 0.33) dots[i].dx = move; 00370 else if (rand <= 0.66) dots[i].dx = -move; 00371 else dots[i].dx = 0; 00372 rand = randomDouble(); 00373 if (rand <= 0.33) dots[i].dy = move; 00374 else if (rand <= 0.66) dots[i].dy = -move; 00375 else dots[i].dy = 0; 00376 if (dots[i].dx == 0 && dots[i].dy == 0){ 00377 if (randomDouble() <= 0.5) dots[i].dy = move; 00378 else dots[i].dy = -move; 00379 } 00380 i++; 00381 } 00382 } 00383 // ###################################################################### 00384 // set coherence of the cloud 00385 void setCoherence (DOT* dots, int numDots, int numCoherent, int move) 00386 { 00387 // initialize an array of dot indices 00388 int index[numDots]; 00389 for (int i = 0; i < numDots; i++) 00390 index[i] = i; 00391 00392 // randomize index 00393 randShuffle (index, numDots); 00394 00395 // set coherence of the dots 00396 for (int i = 0; i < numCoherent; i++){ 00397 dots[index[i]].coherent = 1; 00398 dots[index[i]].dx = 0; 00399 dots[index[i]].dy = move; 00400 } 00401 } 00402 00403 00404 00405 // ###################################################################### 00406 /* So things look consistent in everyone's emacs... */ 00407 /* Local Variables: */ 00408 /* indent-tabs-mode: nil */ 00409 /* End: */