psycho-obfba2.C

Go to the documentation of this file.
00001 /*!@file AppPsycho/psycho-obfba2.C Psychophysics display */
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: Laurent Itti <itti@usc.edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-obfba2.C $
00035 // $Id: psycho-obfba2.C 13712 2010-07-28 21:00:40Z itti $
00036 //
00037 
00038 #include "Component/ModelManager.H"
00039 #include "Image/CutPaste.H"  // for inplacePaste()
00040 #include "Image/DrawOps.H"
00041 #include "Image/FilterOps.H"
00042 #include "Image/Image.H"
00043 #include "Image/Kernels.H"   // for gaborFilter()
00044 #include "Psycho/PsychoDisplay.H"
00045 #include "GUI/GUIOpts.H"
00046 #include "Psycho/Staircase.H"
00047 #include "Raster/Raster.H"
00048 #include "Util/MathFunctions.H"
00049 #include "Util/Timer.H"
00050 
00051 #include <deque>
00052 #include <fstream>
00053 
00054 #define GABORSD   15.0F
00055 #define GABORPER  15.0F
00056 
00057 // number of frames for scanner setup:
00058 #define NFSETUP 12*60
00059 
00060 // number of wait frames during which instruction message is displayed
00061 #define NFINIT 36
00062 
00063 // number of frames per stimulus interval:
00064 #define NFSTIM 37
00065 
00066 // number of frames per inter-period:
00067 #define NFIPI 15
00068 
00069  // number of blank frames between two trials
00070 #define NFISI 55
00071 
00072 // number of trials
00073 #define NTRIAL 12
00074 
00075 /* for jianwei first run (sunday):
00076    init=29 stim=17 ipi=7 isi=26 trial=25 (total 1675)*/
00077 
00078 /* Experiment file format is:
00079    # message                      message
00080    ## message                     message with a pause before
00081    ABC CDE
00082 */
00083 
00084 //! Set this to true if you want to block until responses are given
00085 const bool blocking = false;
00086 
00087 //! Set this to true if you want to add drop shadows
00088 bool shadow = true;
00089 
00090 //! Types of tasks
00091 enum TaskType { None = 0, Orientation = 1, Drift = 2, Blank = 3 };
00092 
00093 //! A summary struct to handle task settings
00094 struct PsychoTask {
00095   nub::soft_ref<Staircase> s;
00096   TaskType tt;
00097   float ampl;
00098   double theta;
00099   double drift;
00100 };
00101 
00102 //! Parse task definitions given at command line
00103 void parseTaskDefinition(const std::string& arg, PsychoTask& p,
00104                          const std::string name)
00105 {
00106   if (arg.length() != 3) LFATAL("Task definition should have 3 chars");
00107   p.s->stop();
00108   p.s->setModelParamVal("FileName", name);
00109   switch(arg[0])
00110     {
00111     case 'N':
00112       p.tt = None;
00113       p.ampl = 127.0f;
00114       p.s->setModelParamVal("InitialValue", 0.0);
00115       p.s->setModelParamVal("DeltaValue", 0.0);
00116       p.s->setModelParamVal("MinValue", 0.0);
00117       p.s->setModelParamVal("MaxValue", 0.0);
00118       break;
00119     case 'O':
00120       p.tt = Orientation;
00121       p.ampl = 127.0f;
00122       p.s->setModelParamVal("InitialValue", 4.0);
00123       p.s->setModelParamVal("DeltaValue", 0.5);
00124       p.s->setModelParamVal("MinValue", 4.0);
00125       p.s->setModelParamVal("MaxValue", 4.0);
00126       break;
00127     case 'D':
00128       p.tt = Drift;
00129       p.ampl = 127.0f;
00130       p.s->setModelParamVal("InitialValue", 8.0);
00131       p.s->setModelParamVal("DeltaValue", 2.0);
00132       p.s->setModelParamVal("MinValue", 8.0);
00133       p.s->setModelParamVal("MaxValue", 8.0);
00134       break;
00135     case 'B':
00136       p.tt = Blank;
00137       p.ampl = 0.0f;
00138       p.s->setModelParamVal("InitialValue", 0.0);
00139       p.s->setModelParamVal("DeltaValue", 0.0);
00140       p.s->setModelParamVal("MinValue", 0.0);
00141       p.s->setModelParamVal("MaxValue", 0.0);
00142       break;
00143     default:
00144       LFATAL("Incorrect task '%c'", arg[0]);
00145     }
00146 
00147   switch(arg[1])
00148     {
00149     case 'V': p.theta = 0.0; break;
00150     case 'H': p.theta = 90.0; break;
00151     default: LFATAL("Incorrect orientation definition '%c'", arg[1]);
00152     }
00153 
00154   switch(arg[2])
00155     {
00156     case 'F': p.drift = 28.0; break;
00157     case 'S': p.drift = 16.0; break;
00158     default: LFATAL("Incorrect drift definition '%c'", arg[1]);
00159     }
00160   p.s->start();
00161 }
00162 
00163 
00164 // ######################################################################
00165 //! Psychophysics display
00166 extern "C" int main(const int argc, char** argv)
00167 {
00168   MYLOGVERB = LOG_INFO;  // suppress debug messages
00169 
00170   // Instantiate a ModelManager:
00171   ModelManager manager("Psycho OBFBA");
00172 
00173   // Instantiate our various ModelComponents:
00174   nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager));
00175   manager.addSubComponent(d);
00176 
00177   manager.setOptionValString(&OPT_SDLdisplayDims, "640x480");
00178   manager.setOptionValString(&OPT_SDLdisplayRefreshUsec, "16666.66666"); //60Hz
00179 
00180   PsychoTask p[2];
00181   p[0].s.reset(new Staircase(manager, "obfbaL", "obfbaL"));
00182   manager.addSubComponent(p[0].s);
00183   p[1].s.reset(new Staircase(manager, "obfbaR", "obfbaR"));
00184   manager.addSubComponent(p[1].s);
00185 
00186   Image< PixRGB<byte> > background = Raster::ReadRGB("b640.ppm");
00187 
00188   // Parse command-line:
00189   if (manager.parseCommandLine(argc, argv, "<taskfile.txt>", 1, 1) == false)
00190     return(1);
00191 
00192   // let's get all our ModelComponent instances started:
00193   manager.start();
00194 
00195   // make sure we catch exceptions and revert to normal display in case of bug:
00196   try {
00197     // open the taskfile:
00198     std::string fname = manager.getExtraArg(0);
00199     std::ifstream ifs(fname.c_str());
00200     if (ifs.is_open() == false)
00201       LFATAL("Cannot read %s", manager.getExtraArg(0).c_str());
00202     std::deque<std::string> line;
00203     while(1) {
00204       char str[1000];
00205       ifs.getline(str, 1000);
00206       if (strlen(str) == 0) break;
00207       line.push_back(std::string(str));
00208     }
00209     ifs.close();
00210 
00211     // let's build the background image:
00212     int w = d->getDims().w(), h = d->getDims().h();
00213     PixRGB<byte> black(0), grey(d->getGrey()), white(255);
00214 
00215     Image< PixRGB<byte> > background2 = background;
00216     Image< PixRGB<byte> > blk(w-80, h-120, NO_INIT);
00217     blk.clear(PixRGB<byte>(grey));
00218     inplacePaste(background, blk, Point2D<int>(40, 60));
00219 
00220     // draw a fixation cross in our image:
00221     drawCross(background, Point2D<int>(w/2, h/2), black, 10, 2);
00222     drawCross(background, Point2D<int>(w/2, h/2), white, 10, 1);
00223     drawCross(background2, Point2D<int>(w/2, h/2), black, 10, 2);
00224     drawCross(background2, Point2D<int>(w/2, h/2), white, 10, 1);
00225 
00226     // find out the size and position of the Gabor patches:
00227     Image<float> tmp = gaborFilter<float>(GABORSD, GABORPER, 0.0F, 0.0F);
00228     int pw = tmp.getWidth(), ph = tmp.getHeight();
00229     LINFO("Gabor patch size: %dx%d", pw, ph);
00230     Point2D<int> lpos((w/2 - pw) / 2, (h - ph) / 2);
00231     Point2D<int> rpos(w/2 + (w/2 - pw) / 2, (h - ph) / 2);
00232     Image< PixRGB<byte> > blank(tmp.getDims(), NO_INIT);
00233     blank.clear(PixRGB<byte>(grey));
00234 
00235     // draw grey boxes in our background image around the gabors:
00236     int border = 40;  // border size all round each gabor
00237     Image<float> mult(background2.getDims(), NO_INIT);
00238     mult.clear(1.0F);
00239     Image<float> mask(blank.getWidth() + border * 2,
00240                       blank.getHeight() + border * 2, NO_INIT);
00241     mask.clear(0.5F);
00242     int off = 10;
00243     inplacePaste(mult, mask, Point2D<int>(lpos.i-border+off, lpos.j-border+off));
00244     inplacePaste(mult, mask, Point2D<int>(rpos.i-border+off, rpos.j-border+off));
00245     background2 *= mult;
00246     Image< PixRGB<byte> > tmp2(blank.getWidth() + border*2,
00247                                blank.getHeight() + border * 2, NO_INIT);
00248     tmp2.clear(PixRGB<byte>(grey));
00249     inplacePaste(background2, tmp2, Point2D<int>(lpos.i - border, lpos.j - border));
00250     inplacePaste(background2, tmp2, Point2D<int>(rpos.i - border, rpos.j - border));
00251 
00252     // prepare a blittable image surface from the background image:
00253     SDL_Surface *bgimg = d->makeBlittableSurface(background);
00254     SDL_Surface *bg2img = d->makeBlittableSurface(background2);
00255 
00256     // ready to go:
00257     Timer tim, tim2, tim3;
00258     if (system("/bin/sync")) LERROR("error in sync");
00259     usleep(500000);
00260 
00261     // let's do the scanner setup:
00262     d->displayText("Ready");
00263     while(d->waitForKey() != ' ') ;  // wait for SPACE keypress
00264     tim.reset();
00265     d->clearScreen(true);
00266     d->waitFrames(NFSETUP);
00267     LINFO("Scanner warmup took %llxms", tim.get());
00268     int count = 0;
00269 
00270     std::string msg; bool do_wait = false;
00271     while(line.size() > 0)
00272       {
00273         std::string str = line.front(); line.pop_front();
00274 
00275         // is it a message?
00276         if (str[0] == '#')
00277           {
00278             msg = str; msg.erase(0, 1); do_wait = false;
00279             if (msg[0] == '#') { msg.erase(0, 1); do_wait = true; }
00280           }
00281         else
00282           // it's a double task definition
00283           {
00284             tim3.reset();
00285             // initialize our staircases:
00286             for (int i = 0; i < 2; i ++) {
00287               std::string xx(str.c_str() + i*4, 3);
00288               std::string pname("STAIR-");
00289               if (i == 0) pname += "L"; else pname += "R";
00290               char gogo[10]; sprintf(gogo, "%03d", count);
00291               pname += gogo; pname += fname;
00292               parseTaskDefinition(xx, p[i], pname);
00293             }
00294             count ++;
00295 
00296             // prepare the Gabors:
00297             float sd = GABORSD, per = GABORPER;
00298             Image< PixRGB<byte> > left, right;
00299 
00300             // wait for key if requested
00301             if (do_wait) d->waitForKey();
00302 
00303             // ready to go:
00304             LINFO("%s", msg.c_str());
00305             d->displayText(msg.c_str());
00306             d->waitFrames(NFINIT);
00307             d->waitNextRequestedVsync(false, true);
00308 
00309             // show background image and fixation cross:
00310             if (str.length() > 8)
00311               d->displaySurface(bg2img, -2);
00312             else
00313               d->displaySurface(bgimg, -2);
00314 
00315             // go over the trials:
00316             for (int trial = 0; trial < NTRIAL; trial ++)
00317               {
00318                 tim.reset();
00319                 tim2.reset();
00320 
00321                 int nf = NFSTIM;  // number of frames per stimulus interval
00322                 int nb1 = NFIPI;  // number of frames per inter-period
00323                 int nb2 = NFISI; // number of blank frames between two trials
00324                 float base = d->getGrey().luminance(); // base Gabor grey
00325                 int rphil = randomUpToIncluding(90);
00326                 int rphir = randomUpToIncluding(90);
00327 
00328                 int idxL = 0, idxR = 1;
00329                 double dl1, dl2, dr1, dr2;
00330                 p[idxL].s->getValues(dl1, dl2);
00331                 p[idxR].s->getValues(dr1, dr2);
00332 
00333                 double dt1L = 0.0, dt2L = 0.0, dd1L = 0.0, dd2L = 0.0,
00334                   dt1R = 0.0, dt2R = 0.0, dd1R = 0.0, dd2R = 0.0;
00335                 switch(p[idxL].tt)
00336                   {
00337                   case None:
00338                   case Blank:
00339                     dt1L = 0.0; dt2L = 0.0; dd1L = 0.0; dd2L = 0.0;
00340                     break;
00341                   case Orientation:
00342                     dt1L = dl1; dt2L = dl2; dd1L = 0.0; dd2L = 0.0;
00343                     break;
00344                   case Drift:
00345                     dt1L = 0.0; dt2L = 0.0; dd1L = dl1; dd2L = dl2;
00346                     break;
00347                   }
00348                 switch(p[idxR].tt)
00349                   {
00350                   case None:
00351                   case Blank:
00352                     dt1R = 0.0; dt2R = 0.0; dd1R = 0.0; dd2R = 0.0;
00353                     break;
00354                   case Orientation:
00355                     dt1R = dr1; dt2R = dr2; dd1R = 0.0; dd2R = 0.0;
00356                     break;
00357                   case Drift:
00358                     dt1R = 0.0; dt2R = 0.0; dd1R = dr1; dd2R = dr2;
00359                     break;
00360                   }
00361 
00362                 // stimulus presentation period 1:
00363                 for (int i = 0; i < nf; i ++)
00364                   {
00365                     left =
00366                       gaborFilter<byte>(sd, per,
00367                                         i*(p[idxL].drift + dd1L) + rphil,
00368                                         p[idxL].theta + dt1L, base,
00369                                         p[idxL].ampl);
00370                     right =
00371                       gaborFilter<byte>(sd, per,
00372                                         i*(p[idxR].drift + dd1R) + rphir,
00373                                         p[idxR].theta + dt1R, base,
00374                                         p[idxR].ampl);
00375                     d->displayImagePatch(left, lpos, i, false, false);
00376                     d->displayImagePatch(right, rpos, i, true, true);
00377                   }
00378                 long int t0 = tim.getReset();
00379 
00380                 // inter-stimulus blank:
00381                 d->displayImagePatch(blank, lpos, -2, false, false);
00382                 d->displayImagePatch(blank, rpos, -2);
00383                 d->waitFrames(nb1);
00384                 long int t1 = tim.getReset();
00385 
00386                 // stimulus presentation period 2:
00387                 rphil = randomUpToIncluding(90);
00388                 rphir = randomUpToIncluding(90);
00389                 for (int i = 0; i < nf; i ++)
00390                   {
00391                     left =
00392                       gaborFilter<byte>(sd, per,
00393                                         i*(p[idxL].drift + dd2L) + rphil,
00394                                         p[idxL].theta + dt2L, base,
00395                                         p[idxL].ampl);
00396                     right =
00397                       gaborFilter<byte>(sd, per,
00398                                         i*(p[idxR].drift + dd2R) + rphir,
00399                                         p[idxR].theta + dt2R, base,
00400                                         p[idxR].ampl);
00401                     d->displayImagePatch(left, lpos, i, false, false);
00402                     d->displayImagePatch(right, rpos, i, true, true);
00403                   }
00404                 long int t2 = tim.getReset();
00405 
00406                 // inter-trial blank:
00407                 d->displayImagePatch(blank, lpos, -2, false, false);
00408                 d->displayImagePatch(blank, rpos, -2);
00409                 if (blocking == false) d->waitFrames(nb2);
00410 
00411                 // collect the response(s):
00412                 if (p[idxL].tt != None && p[idxL].tt != Blank)
00413                   {
00414                     int c;
00415                     if (blocking) c = d->waitForKey();
00416                     else c = d->checkForKey();
00417                     if (c != -1) p[idxL].s->setResponse( (c == '\r') );
00418                     else p[idxL].s->setResponse( (randomDouble() < 0.5) ); // rnd
00419                   }
00420                 else p[idxL].s->setResponse(false);
00421 
00422                 if (p[idxR].tt != None && p[idxR].tt != Blank)
00423                   {
00424                     int c;
00425                     if (blocking) c = d->waitForKey();
00426                     else c = d->checkForKey();
00427                     if (c != -1) p[idxR].s->setResponse( (c == '\r') );
00428                     else p[idxR].s->setResponse( (randomDouble() < 0.5) ); // rnd
00429                   }
00430                 else p[idxR].s->setResponse(false);
00431 
00432                 long int t3 = tim.getReset();
00433                 long int tt = tim2.get();
00434                 float pe = 16.666666f;
00435                 LINFO("Trial %d: p1=%ldms (%df) ipi=%ldms (%df) p2=%ldms (%df) "
00436                       "isi=%ldms (%df) tot=%ldms (%df)",
00437                       trial,
00438                       t0, int(t0/pe + 0.4999f),
00439                       t1, int(t1/pe + 0.4999f),
00440                       t2, int(t2/pe + 0.4999f),
00441                       t3, int(t3/pe + 0.4999f),
00442                       tt, int(tt/pe + 0.4999f));
00443               }
00444 
00445             // reset the staircases before the next epoch:
00446             for (int i = 0; i < 2; ++i)
00447               p[i].s->reset(MC_RECURSE);
00448 
00449             float pe = 16.666666f;
00450             long int tt = tim3.get();
00451             LINFO("Task took %ldms (%df)", tt, int(tt/pe + 0.4999f));
00452           }
00453       }
00454 
00455     d->clearScreen();
00456     d->displayText("Experiment complete. Thank you!");
00457     d->waitFrames(100);
00458   }
00459   catch (...)
00460     {
00461       REPORT_CURRENT_EXCEPTION;
00462     }
00463 
00464   // stop all our ModelComponents
00465   manager.stop();
00466 
00467   // all done!
00468   return 0;
00469 }
00470 
00471 // ######################################################################
00472 /* So things look consistent in everyone's emacs... */
00473 /* Local Variables: */
00474 /* indent-tabs-mode: nil */
00475 /* End: */
Generated on Sun May 8 08:04:12 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3