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 "Component/ModelOptionDef.H" 00041 #include "Image/ColorOps.H" // for makeRGB() 00042 #include "Image/CutPaste.H" // for inplacePaste() 00043 #include "Image/DrawOps.H" // for drawLine() 00044 #include "Image/Image.H" 00045 #include "Image/MathOps.H" // for inplaceSpeckleNoise() 00046 #include "Image/LowPass.H" // for LowPass5x, LowPass5y 00047 #include "Image/ShapeOps.H" // for rescale() 00048 #include "Image/Transforms.H" 00049 #include "Image/Layout.H" 00050 #include "Psycho/PsychoDisplay.H" 00051 #include "Psycho/EyeTrackerConfigurator.H" 00052 #include "Psycho/EyeTracker.H" 00053 #include "Psycho/PsychoOpts.H" 00054 #include "Image/geom.h" 00055 #include "Psycho/ClassicSearchItem.H" 00056 #include "Psycho/SearchArray.H" 00057 #include "Component/EventLog.H" 00058 #include "Component/ComponentOpts.H" 00059 #include "Raster/Raster.H" 00060 #include "Util/MathFunctions.H" 00061 #include "Util/StringUtil.H" 00062 #include "Util/StringConversions.H" 00063 #include "GUI/GUIOpts.H" 00064 00065 #include <sstream> 00066 #include <ctime> 00067 #include <ctype.h> 00068 #include <vector> 00069 #include <string> 00070 #include <fstream> 00071 00072 using namespace std; 00073 00074 static const ModelOptionCateg MOC_RANDGENIMAGE = { 00075 MOC_SORTPRI_2, "Options for random image generation" }; 00076 00077 static const ModelOptionDef OPT_RandImageDims = 00078 { MODOPT_ARG(Dims), "GenImageDims", &MOC_RANDGENIMAGE, OPTEXP_CORE, 00079 "dimensions of the random image", 00080 "rand-image-dims", '\0', "<width>x<height>", "1280x720" }; 00081 /* 00082 static const ModelOptionDef OPT_TextureLibrary = 00083 { MODOPT_FLAG, "RemoveMagnitude", &MOC_RANDIMAGE, OPTEXP_CORE, 00084 "remove the phase component of an image", 00085 "remove-magnitude", '\0', "--[no]remove-phase", "false" }; 00086 */ 00087 static const ModelOptionDef OPT_NoiseMagnitude = 00088 { MODOPT_ARG(float), "NoiseMagnitude", &MOC_RANDGENIMAGE, OPTEXP_CORE, 00089 "adjust the magnitude of the overlaid noise, from 0.0 to 1.0", 00090 "noise-magnitude", '\0', "<float>", "0.0"}; 00091 00092 static const ModelOptionDef OPT_NoiseColor = 00093 { MODOPT_ARG(std::string), "NoiseColor", &MOC_RANDGENIMAGE, OPTEXP_CORE, 00094 "give the color of the overlaid noise, in white, pink, or brown", 00095 "noise-color", '\0', "[white,pink,brown]", "white"}; 00096 00097 //! number of frames in the mask 00098 //#define NMASK 10 00099 00100 // Trial design for contextual cueing experiments. May class this up soon. 00101 // Also may make Trial/Experiment classes as ModelComponents of this style. 00102 // Easier to encapsulate experimental designs as separate from trial content. 00103 00104 // But for now it's easier to keep everything public. 00105 enum NoiseColor { WHITE, PINK, BROWN }; 00106 struct trialAgenda 00107 { 00108 bool repeated; 00109 NoiseColor color; 00110 geom::vec2d targetloc; 00111 uint noiseSeed; 00112 trialAgenda(const bool r, const NoiseColor c, 00113 const geom::vec2d t, const uint n) 00114 { 00115 repeated = r; 00116 color = c; 00117 targetloc = t; 00118 noiseSeed = n; 00119 } 00120 00121 std::string colname() const 00122 { 00123 switch(color) { 00124 case WHITE: return "white"; 00125 case PINK: return "pink"; 00126 case BROWN: return "brown"; 00127 } 00128 return ""; 00129 } 00130 00131 void randomizeNoise(const uint Nbkgds) 00132 { 00133 noiseSeed = randomUpToNotIncluding(Nbkgds)+1; 00134 } 00135 00136 std::string backgroundFile() const 00137 { 00138 std::string stimdir = "/lab/jshen/projects/eye-cuing/stimuli/noiseseeds"; 00139 return sformat("%s/%s%03d.png",stimdir.c_str(),colname().c_str(),noiseSeed); 00140 } 00141 }; 00142 00143 std::string convertToString(const trialAgenda& val) 00144 { 00145 std::stringstream s; 00146 s << val.colname() << " noise, "; 00147 if (val.repeated) 00148 s << "repeated, seed " << val.noiseSeed; 00149 else 00150 s << "random"; 00151 00152 s << ", target @ (" << val.targetloc.x() << "," << val.targetloc.y() << ")"; 00153 return s.str(); 00154 } 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 Dims d); 00164 // Generate a random point uniformly in d 00165 Point2D<int> randomPointIn(const Dims d); 00166 00167 // Generate a random point uniformly in d 00168 geom::vec2d randomPtIn(const Rectangle d) 00169 { 00170 return geom::vec2d(randomInRange(d.left(),d.rightO()), 00171 randomInRange(d.top(),d.bottomO())); 00172 } 00173 00174 Image<byte> plainBkgd(const trialAgenda A) 00175 { 00176 //skips file validation step 00177 // return getPixelComponentImage(Raster::ReadRGB(A.backgroundFile()),0); 00178 return Raster::ReadGray(A.backgroundFile(),RASFMT_PNG); 00179 //the second arg can be 0,1,2 since the image is B&W 00180 } 00181 00182 Image<PixRGB<byte> > colorizeBkgd(const trialAgenda A,const uint Nbkgds) 00183 { 00184 trialAgenda B = A; 00185 00186 std::vector<Image<byte> > comp; 00187 for (uint i = 0; i < 3; i++) { 00188 if(!B.repeated) //randomize noise 00189 B.randomizeNoise(Nbkgds); 00190 else //systematically step 00191 B.noiseSeed = (B.noiseSeed)%Nbkgds+1; 00192 00193 //skips file validation step 00194 comp.push_back(plainBkgd(B)); 00195 } 00196 return makeRGB(comp[0],comp[1],comp[2]); 00197 } 00198 00199 void drawRandomLine(Image<byte> & im, const byte val, const int thickness); 00200 void extrapolateLine(Dims d, Point2D<int> & X, Point2D<int> & Y); 00201 00202 template <class T> 00203 Image<byte> makeNary(const Image<T>& src, const std::vector<T> thresholds, 00204 const std::vector<byte> levels); 00205 00206 00207 Image<byte> texturizeImage(const Image<byte> im, const uint Nlevels); 00208 Image<byte> discretizeImage(const Image<byte> im, const int Nlevels); 00209 Image<byte> getBrodatzTexture(uint seed, const Dims dims); 00210 Image<byte> getStretchedTexture(const std::string filename, const Dims dims); 00211 Image<byte> getTiledTexture(const std::string filename, const Dims dims); 00212 00213 // ###################################################################### 00214 static int submain(const int argc, char** argv) 00215 { 00216 MYLOGVERB = LOG_INFO; // suppress debug messages 00217 00218 // Instantiate a ModelManager: 00219 ModelManager manager("AppMedia: Flyover stimulus"); 00220 00221 // get dimensions of window 00222 if (manager.parseCommandLine(argc, argv,"<out_stem>", 1, 1) == false) 00223 return(1); 00224 00225 // Image<float> myFloatImg(dims.w(), dims.h(), ZEROS); 00226 //Image<double> myDoubleImg(dims.w(), dims.h(), ZEROS); 00227 char filename[255], texfile[255]; 00228 00229 OModelParam<Dims> dims(&OPT_RandImageDims, &manager); 00230 OModelParam<float> noiseMag(&OPT_NoiseMagnitude, &manager); 00231 OModelParam<std::string> noiseColor(&OPT_NoiseColor, &manager); 00232 00233 // get command line parameters for filename 00234 sprintf(filename, "%s.png",manager.getExtraArg(0).c_str()); 00235 sprintf(texfile, "%s-1.png",manager.getExtraArg(0).c_str()); 00236 00237 /* 00238 nub::soft_ref<OutputFrameSeries> ofs(new OutputFrameSeries(manager)); 00239 manager.addSubComponent(ofs); 00240 */ 00241 00242 // let's get all our ModelComponent instances started: 00243 manager.start(); 00244 00245 // **************** Experimental settings *************** // 00246 00247 // number of available noise frames in the stimuli folder 00248 const uint Nnoises = 100; 00249 00250 /* 00251 // size of screen - should be no bigger than 1920x1080 00252 const Dims screenDims = 00253 fromStr<Dims>(manager.getOptionValString(&OPT_SDLdisplayDims)); 00254 00255 // target/distractor type: we have choice of c o q - t l + 00256 const std::string Ttype = "T", Dtype = "L"; 00257 const double phiMax = M_PI*5/8, phiMin = M_PI*3/8; 00258 00259 ClassicSearchItemFactory 00260 targetsLeft(SearchItem::FOREGROUND, Ttype, 00261 itemsize, 00262 Range<double>(-phiMax,-phiMin)), 00263 targetsRight(SearchItem::FOREGROUND, Ttype, 00264 itemsize, 00265 Range<double>(phiMin,phiMax)), 00266 distractors(SearchItem::BACKGROUND, Dtype, 00267 itemsize, 00268 Range<double>(-M_PI/2,M_PI/2)); 00269 */ 00270 // ******************** Trial Design ************************* // 00271 00272 std::vector<rutz::shared_ptr<trialAgenda> > trials; 00273 const uint Ntrials = 1; 00274 const uint Nrepeats = 1; 00275 NoiseColor colors[Ntrials]; 00276 bool rep[Ntrials]; 00277 00278 // SearchArray sarray(dims, grid_spacing, min_spacing, itemsize); 00279 const PixRGB<byte> gray(128,128,128); 00280 00281 // Design and shuffle trials 00282 initRandomNumbers(); 00283 for (uint i = 0; i < Ntrials; i++) 00284 { 00285 colors[i] = BROWN; //NoiseColor(i%3); 00286 rep[i] = (i < Nrepeats); 00287 } 00288 00289 for (uint i = 0; i < Ntrials; i++) 00290 { 00291 // a random location for each target 00292 const geom::vec2d pos = randomPtIn(dims.getVal()); 00293 // a random seed for each trial 00294 const uint seed = randomInRange(1,Nnoises); 00295 trials.push_back 00296 (rutz::shared_ptr<trialAgenda> 00297 (new trialAgenda(rep[i],colors[i],pos,seed))); 00298 } 00299 00300 //tests 00301 Image<byte> myMap = discretizeImage(plainBkgd(*(trials[0])),4); 00302 Image<byte> myBkgd = texturizeImage(myMap,4); 00303 00304 LINFO("writing texture image to %s", filename); 00305 Raster::WriteGray(myBkgd,filename); 00306 00307 // test texture 00308 LINFO("writing pattern image to %s", texfile); 00309 Raster::WriteGray(myMap,texfile); 00310 00311 // stop all our ModelComponents 00312 manager.stop(); 00313 00314 // all done! 00315 return 0; 00316 } 00317 00318 // ###################################################################### 00319 00320 extern "C" int main(const int argc, char** argv) 00321 { 00322 // simple wrapper around submain() to catch exceptions (because we 00323 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 00324 // abort while SDL is in fullscreen mode, the X server won't return 00325 // to its original resolution) 00326 try 00327 { 00328 return submain(argc, argv); 00329 } 00330 catch (...) 00331 { 00332 REPORT_CURRENT_EXCEPTION; 00333 } 00334 00335 return 1; 00336 } 00337 00338 // ###################################################################### 00339 00340 // draw a random line 00341 void drawRandomLine(Image<byte> & im, const byte val, const int thickness) 00342 { 00343 Point2D<int> P = randomPointIn(im.getDims()); 00344 Point2D<int> Q = randomPointIn(im.getDims()); 00345 00346 extrapolateLine(im.getDims(),P,Q); 00347 drawLine(im, P, Q, val, thickness); 00348 } 00349 00350 // ###################################################################### 00351 00352 // extend a line segment to boundaries 00353 void extrapolateLine(Dims d, Point2D<int> & X, Point2D<int> & Y) 00354 { 00355 // check if X and Y are in d 00356 Image<byte> foo(d, NO_INIT); 00357 if(!(foo.coordsOk(X) && foo.coordsOk(Y)) || X == Y) return; 00358 00359 if (X.i == Y.i) {X.j = 0; Y.j = d.h(); return;} 00360 else if(X.j == Y.j) {X.i = 0; Y.j = d.w(); return;} 00361 else {float y_0 = (X.j*Y.i-X.i*Y.j)/(Y.i-X.i); 00362 float x_0 = (X.i*Y.j-X.j*Y.i)/(Y.j-X.j); 00363 float slope = (Y.j-X.j)/(Y.i-X.i); 00364 00365 std::vector<Point2D<int> > bounds; 00366 bounds.push_back(Point2D<int>(0,y_0)); 00367 bounds.push_back(Point2D<int>(x_0,0)); 00368 bounds.push_back(Point2D<int>(d.w()-1,y_0+(d.w()-1)*slope)); 00369 bounds.push_back(Point2D<int>(x_0+(d.h()-1)/slope,d.h()-1)); 00370 00371 bool Xdone = 0; 00372 for(int i = 0; i < 4; i++) 00373 if(foo.coordsOk(bounds[i])) { 00374 if(!Xdone) { 00375 X = bounds[i]; 00376 Xdone = true; 00377 } 00378 else { 00379 Y = bounds[i]; 00380 break; 00381 } 00382 } 00383 } 00384 } 00385 00386 // ###################################################################### 00387 00388 // Generate a random point uniformly in d 00389 geom::vec2d randomPtIn(const Dims d) 00390 { 00391 return geom::vec2d(randomInRange(0,d.w()), 00392 randomInRange(0,d.h())); 00393 } 00394 00395 // Generate a random point uniformly in d 00396 Point2D<int> randomPointIn(const Dims d) 00397 { 00398 return Point2D<int>(randomInRange(0,d.w()), 00399 randomInRange(0,d.h())); 00400 } 00401 00402 // ###################################################################### 00403 00404 // inspired from makeBinary in Transforms.C 00405 template <class T> 00406 Image<byte> makeNary(const Image<T>& src, const std::vector<T> thresholds, 00407 const std::vector<byte> levels) 00408 { 00409 ASSERT(thresholds.size() == levels.size() - 1); 00410 Image<byte> acc(src.getDims(),ZEROS); 00411 byte floor; 00412 for(uint i = 0; i < thresholds.size(); i++) 00413 { 00414 if(i == 0) 00415 { 00416 floor = levels[0]; 00417 } 00418 else 00419 { 00420 floor = 0; 00421 } 00422 acc += makeBinary(src, thresholds[i],floor,levels[1]); 00423 } 00424 00425 return acc; 00426 } 00427 00428 // ###################################################################### 00429 // maps discretized image to textured image 00430 Image<byte> texturizeImage(const Image<byte> im, const uint Nlevels) 00431 { 00432 uint i, seed; 00433 // Image<byte> levelImage = discretizeImage(im, Nlevels); 00434 std::vector<Image<byte> > texBkgds; 00435 for(i = 0; i < Nlevels; i++) 00436 { 00437 seed = randomInRange(0,112); // num brodatz images 00438 texBkgds.push_back(getBrodatzTexture(seed, im.getDims())); 00439 } 00440 00441 byte tiers[Nlevels]; 00442 for(uint i = 0; i < Nlevels; i++) 00443 tiers[i] = i*(255/(Nlevels-1)); 00444 00445 return mosaic(im, &texBkgds[0], tiers, Nlevels); 00446 //return mosaic(levelImage, &texBkgds[0], tiers, Nlevels); 00447 } 00448 00449 // ###################################################################### 00450 // discretizes image 00451 Image<byte> discretizeImage(const Image<byte> im, const int Nlevels) 00452 { 00453 byte imMin, imMax, i; 00454 getMinMax(im, imMin, imMax); 00455 00456 const byte Ncuts = Nlevels - 1; 00457 00458 // the ratios that partition the image 00459 float coeffs[Ncuts]; 00460 for(i = 0; i < Ncuts; i++) 00461 coeffs[i] = (i+1.0)/(Ncuts+1.0); 00462 00463 // the values in the noise image that partition the image 00464 std::vector<byte> cuts; 00465 for(i = 0; i < Ncuts; i++) 00466 cuts.push_back(imMax*coeffs[i]+imMin*(1-coeffs[i])); 00467 00468 // the mapped values of the outside image 00469 std::vector<byte> tiers; 00470 for(i = 0; i <= cuts.size(); i++) 00471 tiers.push_back(i*(255/cuts.size())); 00472 00473 // use makeNary to cut the image 00474 Image<byte> pattern = makeNary(im,cuts,tiers); 00475 00476 // draw a random line cutting across the image 00477 drawRandomLine(pattern, tiers[2], 50); 00478 00479 return pattern; 00480 00481 } 00482 00483 // ###################################################################### 00484 Image<byte> getBrodatzTexture(uint seed, const Dims dims) 00485 { 00486 char texPath[255]; 00487 00488 // there are only 111 images in the brodatz database 00489 const uint Nimages = 111; 00490 seed = seed % (Nimages - 1) + 1; 00491 00492 sprintf(texPath, "/lab/jshen/projects/eye-cuing/stimuli/textures/brodatz/D%u.png",seed); 00493 return getStretchedTexture(texPath, dims); 00494 } 00495 00496 // ###################################################################### 00497 Image<byte> getStretchedTexture(const std::string filename, const Dims dims) 00498 { 00499 Image<byte> pat = Raster::ReadGray(filename,RASFMT_PNG); 00500 return rescale(pat, dims); 00501 } 00502 00503 // ###################################################################### 00504 Image<byte> getTiledTexture(const std::string filename, const Dims dims) 00505 { 00506 //filename refers to a simple texture, black and white, PNG 00507 Image<byte> pat = Raster::ReadGray(filename,RASFMT_PNG); 00508 00509 const size_t nX = dims.w()/pat.getWidth()+1; 00510 const size_t nY = dims.h()/pat.getHeight()+1; 00511 00512 std::vector<Image<byte> > tiles(nX,pat); 00513 Layout<byte> horiztile(&tiles[0],nX,Layout<byte>::H); 00514 00515 std::vector<Layout<byte> > rows(nY,horiztile); 00516 Layout<byte> whole(&rows[0],nY,Layout<byte>::V); 00517 00518 return crop(whole.render(),Point2D<int>(0,0),dims); 00519 } 00520 00521 // ###################################################################### 00522 /* So things look consistent in everyone's emacs... */ 00523 /* Local Variables: */ 00524 /* indent-tabs-mode: nil */ 00525 /* End: */