psycho-motion.C

Go to the documentation of this file.
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: */
Generated on Sun May 8 08:40:08 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3