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: */