00001 /*!@file Neuro/SimulationViewerEyeMvt.C comparison between saliency and 00002 human eye movements */ 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2003 // 00006 // by the 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/Neuro/SimulationViewerEyeMvt.C $ 00036 // $Id: SimulationViewerEyeMvt.C 14535 2011-02-18 22:40:51Z siagian $ 00037 // 00038 00039 #include "Neuro/SimulationViewerEyeMvt.H" 00040 00041 #include "Channels/ChannelBase.H" 00042 #include "Channels/ChannelOpts.H" // for LevelSpec option 00043 #include "Component/OptionManager.H" 00044 #include "Component/ModelOptionDef.H" 00045 #include "Image/ColorOps.H" // for contrastModulate() 00046 #include "Image/CutPaste.H" // for concatX() 00047 #include "Image/DrawOps.H" // for colGreyCombo() 00048 #include "Image/FilterOps.H" // for lowPass3() 00049 #include "Image/MathOps.H" // for scramble() 00050 #include "Image/Transforms.H" // for segmentObjectClean(), contour2D() 00051 #include "Image/ShapeOps.H" // for rescale() 00052 #include "Neuro/NeuroOpts.H" 00053 #include "Neuro/NeuroSimEvents.H" 00054 #include "Neuro/SpatialMetrics.H" 00055 #include "Psycho/EyeData.H" 00056 #include "Simulation/SimEventQueue.H" 00057 #include "Transport/FrameInfo.H" 00058 #include "Transport/FrameOstream.H" 00059 #include "Util/sformat.H" 00060 #include "rutz/trace.h" 00061 00062 #include <fstream> 00063 #include <iostream> 00064 00065 // for some reason this is not recognized when it appears in NeuroOpts.C 00066 const ModelOptionDef OPT_SMhistoryQlen = 00067 { MODOPT_ARG(uint), "SMhistoryQ", &MOC_DISPLAY, OPTEXP_CORE, 00068 "Keep a historical queue of salieny maps (one per new input frame), " 00069 "and report the history of saliency values over the entire queue " 00070 "and at a saccade target location, for each saccade", 00071 "sm-history-qlen", '\0', "<uint>", "0" }; 00072 00073 // ###################################################################### 00074 SimulationViewerEyeMvt:: 00075 SimulationViewerEyeMvt(OptionManager& mgr, const std::string& descrName, 00076 const std::string& tagName) : 00077 SimulationViewer(mgr, descrName, tagName), 00078 SIMCALLBACK_INIT(SimEventClockTick), 00079 SIMCALLBACK_INIT(SimEventSaveOutput), 00080 itsMetrics(new SpatialMetrics(mgr)), 00081 itsSaveTraj(&OPT_SVsaveTraj, this), 00082 itsSaveMegaCombo(&OPT_SVEMsaveMegaCombo, this), 00083 itsDelayCacheSize(&OPT_SVEMdelayCacheSize, this), 00084 itsMaxCacheSize(&OPT_SVEMmaxCacheSize, this), 00085 itsSampleAtStart(&OPT_SVEMsampleAtStart, this), 00086 itsDisplaySacNum(&OPT_SVEMdisplaySacNum, this), 00087 itsDisplayPatch(&OPT_SVdisplayPatch, this), 00088 itsPatchSize(&OPT_SVpatchSize, this), 00089 itsEraseMarker(&OPT_SVeraseMarker, this), 00090 itsDisplayFOA(&OPT_SVdisplayFOA, this), 00091 itsLevelSpec(&OPT_LevelSpec, this), 00092 itsOutFname(&OPT_SVEMoutFname, this), 00093 itsPriorRandomDistro(&OPT_SVEMpriorRandomDistro, this), 00094 itsUseSaccadeInBlink(&OPT_SVEMuseSaccadeInBlink, this), 00095 itsUseDiagColor(&OPT_SVEMuseDiagColors, this), 00096 itsLabelEyePatch(&OPT_SVEMlabelEyePatch, this), 00097 itsWriteFrameNum(&OPT_SVEMwriteFrameNumber, this), 00098 itsNumRandomSamples(&OPT_SVEMnumRandomSamples, this), 00099 itsMaxComboWidth(&OPT_SVEMmaxComboWidth, this), 00100 itsSMhistoryQlen(&OPT_SMhistoryQlen, this), 00101 itsDelayCache(), itsMaxCache(), itsHeadSM(), 00102 itsDrawings(), itsCurrTime(), itsFrameNumber(-1), 00103 itsHeaderCrafted(false), itsOutFields(), itsEyeStyles(), 00104 itsTargets(), itsSMhistory(), itsOutFile(0), 00105 itsRandFile(0),itsRawToRetOffset() 00106 { 00107 GVX_TRACE(__PRETTY_FUNCTION__); 00108 00109 this->addSubComponent(itsMetrics); 00110 00111 // disable IOR: 00112 LINFO("NOTE: disabling IOR and SE, selecting FixedSaccadeController"); 00113 getManager().setOptionValString(&OPT_IORtype, "None"); 00114 getManager().setOptionValString(&OPT_ShapeEstimatorMode, "None"); 00115 00116 // select an eyetrack EyeHeadController: 00117 getManager().setOptionValString(&OPT_EyeHeadControllerType, "EyeTrack"); 00118 00119 // change default to --display-additive=false; user can still 00120 // override this on the command-line 00121 getManager().setOptionValString(&OPT_SVdisplayAdditive, "false"); 00122 } 00123 00124 // ###################################################################### 00125 SimulationViewerEyeMvt::~SimulationViewerEyeMvt() 00126 { 00127 GVX_TRACE(__PRETTY_FUNCTION__); 00128 } 00129 00130 // ###################################################################### 00131 void SimulationViewerEyeMvt::start1() 00132 { 00133 GVX_TRACE(__PRETTY_FUNCTION__); 00134 00135 if (itsDelayCacheSize.getVal()) 00136 itsDelayCache.setMaxSize(itsDelayCacheSize.getVal()); 00137 00138 if (itsMaxCacheSize.getVal()) 00139 itsMaxCache.setMaxSize(itsMaxCacheSize.getVal()); 00140 00141 // open output file: 00142 if (itsOutFname.getVal().empty() == false) 00143 { 00144 if (itsOutFile) delete itsOutFile; 00145 itsOutFile = new std::ofstream(itsOutFname.getVal().c_str()); 00146 if (itsOutFile->is_open() == false) 00147 LFATAL("Cannot open '%s' for writing", itsOutFname.getVal().c_str()); 00148 } 00149 00150 // open random file if one exists 00151 if (itsPriorRandomDistro.getVal().empty() == false) 00152 { 00153 if (itsRandFile) delete itsRandFile; 00154 itsRandFile = new std::ifstream(itsPriorRandomDistro.getVal().c_str()); 00155 if (itsRandFile->is_open() == false) 00156 LFATAL("Cannot open '%s' for reading", 00157 itsPriorRandomDistro.getVal().c_str()); 00158 else 00159 { 00160 while (! itsRandFile->eof()) 00161 // FIXME: robust input for incorrect files (i.e with e-1 or decimals) 00162 { 00163 Point2D<int> pr; 00164 float ii, jj; 00165 (*itsRandFile) >> ii >> jj; 00166 pr = Point2D<int>(ii,jj); 00167 randPoints.push_back(pr); 00168 } 00169 itsRandFile->close(); 00170 } 00171 } 00172 00173 // set our SM history queue length: 00174 if (itsSMhistoryQlen.getVal()) 00175 itsSMhistory.setMaxSize(itsSMhistoryQlen.getVal()); 00176 00177 SimulationViewer::start1(); 00178 } 00179 00180 // ###################################################################### 00181 void SimulationViewerEyeMvt::stop1() 00182 { 00183 GVX_TRACE(__PRETTY_FUNCTION__); 00184 00185 if (itsOutFile) { delete itsOutFile; itsOutFile = 0; } 00186 if (itsRandFile) {delete itsRandFile; itsRandFile = 0;} 00187 } 00188 00189 // ###################################################################### 00190 void SimulationViewerEyeMvt:: 00191 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect) 00192 { 00193 // get the current time (mostly for SVEyeRegion) 00194 itsCurrTime = q.now(); 00195 // any new input frame from the retina? If so, let's initialize or 00196 // clear all our drawings: 00197 bool gotnewinput = false; 00198 if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this)) { 00199 itsDrawings.resize(e->frame().getDims(), true); 00200 gotnewinput = true; 00201 itsFrameNumber++; // used for derived class SVEyeRegion 00202 // this member is never used in SVEyeMvt 00203 } 00204 00205 // Let's update our sliding caches with the current SM/AGM/etc: 00206 const Image<float> currsm = getMap(q, false); 00207 00208 // if we have a delay cache, we push the SM into it and push 00209 // what comes out of it into our max-computation cache; 00210 // otherwise, we directly push the SM into the max-computation 00211 // cache: 00212 if (itsDelayCacheSize.getVal()) 00213 { 00214 // let's push the SM into our delay sliding cache: 00215 itsDelayCache.push_back(currsm); 00216 00217 // transfer oldest delay image to our max computation cache: 00218 if (int(itsDelayCache.size()) >= itsDelayCacheSize.getVal()) 00219 itsHeadSM = itsDelayCache.front(); 00220 else 00221 itsHeadSM.resize(currsm.getDims(), true); // blank map 00222 } 00223 else 00224 itsHeadSM = currsm; // no delay cache 00225 00226 // if we have a saliency history queue, and a new input, push the 00227 // current sm into it: 00228 if (itsSMhistoryQlen.getVal() && gotnewinput) 00229 itsSMhistory.push_back(itsHeadSM); 00230 00231 // if we have a max cache, let's push the (possibly delayed) sm into it: 00232 if (itsMaxCacheSize.getVal()) itsMaxCache.push_back(itsHeadSM); 00233 00234 // all right, get the latest retina image (whether new or not) as we 00235 // will need it to do coordinate transforms, coordinate clippings, etc: 00236 SeC<SimEventRetinaImage> retev = q.check<SimEventRetinaImage>(this, SEQ_ANY); 00237 if (retev.is_valid() == false) 00238 LFATAL("I need some SimEventRetinaImage in the queue to function."); 00239 // set the offset 00240 itsRawToRetOffset = retev->offset(); 00241 00242 // any eye-tracker action? 00243 SeC<SimEventEyeTrackerData> e = q.check<SimEventEyeTrackerData>(this); 00244 00245 while(e.is_valid()) { 00246 // parse that event: 00247 const rutz::shared_ptr<EyeData> trackCurrPos = e->data(); 00248 const uint tnum = e->trackerNum(); 00249 const std::string tfn = e->trackerFilename(); 00250 // add entry to style vector when we get a new trackernum 00251 if (itsEyeStyles.size() <= tnum) { 00252 itsEyeStyles.resize(tnum+1); 00253 00254 itsEyeStyles[tnum].pSize = itsPatchSize.getVal(); 00255 itsEyeStyles[tnum].col = e->trackerColor(); 00256 00257 // give distinct label, TODO: maybe write a switch for this 00258 // itsEyeStyles[tnum].label = convertToString(tnum); 00259 // huge hack right now, relies on directory tree data/<subject>/ pieces 00260 itsEyeStyles[tnum].label = tfn.substr(tfn.find("data")+5,2); 00261 } 00262 00263 // draw small patch at current eye position: 00264 if (itsDisplayPatch.getVal()) 00265 { 00266 // erase previous eye markers first 00267 if (itsEraseMarker.getVal()) itsDrawings.resize(retev->frame().getDims(), true); 00268 00269 // draw marker at eye position 00270 drawEye(trackCurrPos, tnum); 00271 } 00272 00273 // do we want to do any other sample processing? 00274 // (for SVEyeRegion or other derived classes) 00275 extraSampleProcessing(trackCurrPos); 00276 00277 bool isUsableTrace = true; 00278 // if we can't use saccades in blink and it's in blink, don't process the event 00279 if (itsUseSaccadeInBlink.getVal() == false && trackCurrPos->isInBlink() == true) isUsableTrace = false; 00280 // NB: what about combined saccades? 00281 00282 // are we starting a saccade/special region with extra data? 00283 if (trackCurrPos->hasSaccadeTargetData() && isUsableTrace) 00284 { 00285 // extract the saccade destination for display: 00286 Point2D<int> dp = rawToRet(trackCurrPos->saccadeTarget()); 00287 00288 CLINFO("**** Tracker %03d: Saccade to (%d, %d) at %.1fms ****", 00289 tnum, dp.i, dp.j, q.now().msecs()); 00290 // check that saccade target is okay: 00291 if (itsDrawings.coordsOk(dp) == false) 00292 CLINFO("Tracker %03d: Saccade target (%d, %d) out of bounds " 00293 "-- IGNORING", tnum, dp.i, dp.j); 00294 else 00295 // do some saliency sampling at saccade target, but may 00296 // have to be delayed to end of a saccade... So for now 00297 // let's just push the eye data into a temporary storage: 00298 itsTargets[tnum] = trackCurrPos; 00299 } 00300 00301 // is it time to take a bunch of samples at the saccade target of 00302 // this tracker? Either we want to sample at the start of a 00303 // saccade and just pushed some saccade data, or we want to sample 00304 // at end and we are ending the saccade during which we pushed 00305 // some data: 00306 std::map<int, rutz::shared_ptr<EyeData> >::iterator 00307 itr = itsTargets.find(tnum); 00308 00309 if (itr != itsTargets.end() && isUsableTrace && 00310 (itsSampleAtStart.getVal() || trackCurrPos->isInSaccade() == false)) 00311 { 00312 // extract the saccade target data: 00313 const rutz::shared_ptr<EyeData> trackEventSample = itr->second; 00314 itsTargets.erase(itr); // we are now taking care of this one 00315 00316 // changed from trackCurrPos->...(); 00317 if(!(trackEventSample->hasSaccadeTargetData()) ) 00318 LFATAL("Tracker %03d: Marked event has incomplete targeting data.", tnum); 00319 00320 Point2D<int> trackerTarget = rawToRet(trackEventSample->saccadeTarget()); 00321 00322 LINFO("Tracker %03d [%s]: Taking saccade samples at (%d, %d) at time %.1fms", 00323 tnum, tfn.c_str(), trackerTarget.i, trackerTarget.j, q.now().msecs()); 00324 00325 // draw circle around target: 00326 if (itsDisplayFOA.getVal()) drawFOA(trackerTarget, tnum); 00327 00328 // indicate saccade number? 00329 if (itsDisplaySacNum.getVal()) 00330 { 00331 if (tnum == 0) { 00332 std::string fld("typenum"); //subject to change 00333 if (trackCurrPos->hasMetaData(fld.c_str())) 00334 writeText(itsDrawings, Point2D<int>(0, 0), 00335 sformat(" %1g ", 00336 trackEventSample->getMetaDataField("typenum")).c_str(), 00337 PixRGB<byte>(1, 1, 1)); 00338 else 00339 LFATAL("--svem-display-sacnum does not have numbered saccades available"); 00340 } 00341 else 00342 LFATAL("--svem-display-sacnum only supported with 1 tracker"); 00343 } 00344 00345 if (itsOutFile) { 00346 LINFO("writing to file %s........", itsOutFname.getVal().c_str()); 00347 00348 // also creates the header the first time 00349 std::string statline = craftSVEMOutput(tfn, trackEventSample); 00350 00351 // write the header line now - only once for each output 00352 if(!itsHeaderCrafted) 00353 { 00354 writeHeader(); 00355 itsHeaderCrafted = true; 00356 } 00357 (*itsOutFile) << statline << std::endl; 00358 } 00359 }//end sampling at saccade target 00360 00361 // any more events like that in the queue? 00362 e = q.check<SimEventEyeTrackerData>(this); 00363 } 00364 } 00365 00366 // ###################################################################### 00367 void SimulationViewerEyeMvt::drawEye(const rutz::shared_ptr<EyeData> rawPos, const uint tN) 00368 { 00369 // convert to retinal coordinates (accounting for any shifting, 00370 // embedding, etc): 00371 Point2D<int> currPos = rawToRet(rawPos->position()); 00372 00373 // note: these functions are tolerant to 00374 // coordinates outside the actual image area 00375 PixRGB<byte> col; 00376 // select a drawing color: 00377 // note: we use 'almost black' (1,1,1) instead of black so that we can later 00378 // composite our drawings with various images, with pure black 00379 // then being treated as transparent): 00380 if (!itsUseDiagColor.getVal()) 00381 { 00382 col = itsEyeStyles[tN].col; 00383 // dec/inc will clamp/saturate properly 00384 if (rawPos->isInSaccade()) col += byte(64); 00385 if (rawPos->isInBlink()) col -= byte(128); 00386 } 00387 else //diagnostic colors 00388 { 00389 byte r = 1, g = 1, b = 1; 00390 if (rawPos->isInSaccade()) g = 255; //green for saccade 00391 else if (rawPos->isInSmoothPursuit()) 00392 {r = 255;b = 255;} // magenta 00393 else if (rawPos->isInFixation()) b = 255; // blue 00394 if (rawPos->isInCombinedSaccade()) {b = 255;g = 255;} //cyan 00395 00396 col = PixRGB<byte>(r,g,b); //default black 00397 if (rawPos->isInBlink()) col-=byte(128); // let's just make the cursor darker 00398 } 00399 drawPatchBB(itsDrawings, currPos, itsEyeStyles[tN].pSize, 00400 col, PixRGB<byte>(1)); 00401 00402 //output the label we got next to the patch 00403 if(itsLabelEyePatch.getVal()) { 00404 writeText(itsDrawings, currPos+itsEyeStyles[tN].pSize/2, 00405 itsEyeStyles[tN].label.c_str(), 00406 PixRGB<byte>(255,255,255), PixRGB<byte>(0,0,0), 00407 SimpleFont::FIXED(10), true); 00408 } 00409 } 00410 00411 // ###################################################################### 00412 void SimulationViewerEyeMvt::drawFOA(const Point2D<int> target, const uint tN) 00413 { 00414 PixRGB<byte> col = itsEyeStyles[tN].col; col += byte(64); 00415 // since drawFOA is only called when a saccade is triggered, 00416 // the color adjustment for a saccade is used. 00417 00418 const int radius = itsMetrics->getFoveaRadius(); 00419 drawCircle(itsDrawings, target, radius, col, 2); 00420 } 00421 00422 // ###################################################################### 00423 std::string SimulationViewerEyeMvt::craftSVEMOutput(const std::string tfn, 00424 const rutz::shared_ptr<EyeData> data) 00425 { 00426 // Write some measurements to a string. Here, we just put all the stats 00427 // into a text string, and we will send that off to be 00428 // written to disk. 00429 // The first time we run this, we build a header for the file 00430 // to know what stats to read. This is done in the craft submodules. 00431 00432 std::string output = 00433 sformat("%s ", tfn.c_str()); 00434 if(!itsHeaderCrafted) itsOutFields.push_back("filename"); 00435 00436 if(itsWriteFrameNum.getVal()) { 00437 output += sformat("%u ",itsFrameNumber); 00438 if(!itsHeaderCrafted) itsOutFields.push_back("framenum"); 00439 } 00440 00441 // add model free output from the original eyetrack-data file 00442 output += craftModelFreeOutput(data); 00443 00444 // construct the saliency map, 00445 // if requested from the max of several previous maps 00446 Image<float> smap; 00447 if (itsMaxCacheSize.getVal()) 00448 smap = itsMaxCache.getMax(); 00449 else smap = itsHeadSM; 00450 00451 // add samples of saliency map at the target / in the frame 00452 output += craftSMSamples(data, smap); 00453 00454 // add history of saliency map across past frames for key locations 00455 if (itsSMhistoryQlen.getVal()) output += craftSMHistory(data, smap); 00456 00457 return output; 00458 } 00459 00460 // ###################################################################### 00461 std::string SimulationViewerEyeMvt:: 00462 craftModelFreeOutput(const rutz::shared_ptr<EyeData> data) 00463 { 00464 // legacy behavior - this was the old eyesal output for the model-free numbers 00465 // retained for reference 00466 /* 00467 std::string output = 00468 sformat("%s %d %d %d %d %g %g %g %g", tfn.c_str(), rawdp.i, rawdp.j, 00469 itsCurrTarget.i, itsCurrTarget.j, d->pupilDiameter(), d->saccadeAmplitude(), 00470 d->saccadeDuration(), d->saccadeTime()); 00471 */ 00472 00473 Point2D<int> target = rawToRet(data->saccadeTarget()); 00474 std::string output = sformat("%d %d ", target.i, target.j); 00475 00476 // start writing fields for the header 00477 if(!itsHeaderCrafted) 00478 { 00479 itsOutFields.push_back("ret_targetx"); 00480 itsOutFields.push_back("ret_targety"); 00481 } 00482 00483 // output all the extra (now precomputed) parameters, 00484 // default from Eye-markup is saccade target first, 00485 // then amplitude and peak velocity, then time onset and offset of saccade. 00486 // then the interval between this saccade and the next, 00487 // and lastly the number of the saccade (discriminates between saccades 00488 // in blink, etc.) 00489 std::string foo; 00490 for (ParamMap::key_iterator iter = data->getMetaData()->keys_begin(); 00491 iter != data->getMetaData()->keys_end(); 00492 ++iter) 00493 { 00494 // get rid of any leading asterisk 00495 foo = *iter; 00496 00497 // add this to the queue of arguments 00498 if(!itsHeaderCrafted) itsOutFields.push_back(foo); 00499 output += sformat (" %g", data->getMetaDataField(foo)); 00500 } 00501 00502 return output; 00503 } 00504 00505 // ###################################################################### 00506 std::string SimulationViewerEyeMvt::craftSMSamples(const rutz::shared_ptr<EyeData> data, 00507 const Image<float> smap) 00508 { 00509 std::string output; 00510 // get the map level to scale things down: 00511 int sml = itsLevelSpec.getVal().mapLevel(); 00512 00513 // get target 00514 Point2D<int> target = rawToRet(data->saccadeTarget()); 00515 00516 // get ready to take samples in the saliency map and in all 00517 // conspicuity maps, at the scale of the saliency map: 00518 const int radius = itsMetrics->getFoveaRadius(); 00519 const int rad = radius >> sml; 00520 Point2D<int> p(target.i >> sml, target.j >> sml); 00521 00522 // just in case the image has weird dims, make sure the sampling 00523 // location is in it: 00524 00525 p.clampToDims(smap.getDims()); 00526 00527 // get the min/max/avg and saliency samples: 00528 float mi, ma, av; getMinMaxAvg(smap, mi, ma, av); 00529 00530 // get a sample around the saccade target location: 00531 00532 float val = getLocalMax(smap, p, rad); 00533 00534 // add necessary headers 00535 if(!itsHeaderCrafted) 00536 { 00537 itsOutFields.push_back("sal_val"); 00538 itsOutFields.push_back("sal_min"); 00539 itsOutFields.push_back("sal_max"); 00540 itsOutFields.push_back("sal_av"); 00541 } 00542 00543 output = sformat(" %g %g %g %g ", val, mi, ma, av); 00544 00545 // now get a bunch of random samples from the saliency map: 00546 if (itsNumRandomSamples.getVal() < smap.getSize()) 00547 { 00548 // ok, we want fewer samples than are actually in the 00549 // saliency map, so choose them randomly: 00550 for (int k = 0; k < itsNumRandomSamples.getVal(); ++k) 00551 { 00552 if (itsPriorRandomDistro.getVal().empty()) // uniform random 00553 { 00554 Point2D<int> randp(randomUpToNotIncluding(smap.getWidth()), 00555 randomUpToNotIncluding(smap.getHeight())); 00556 output += sformat(" %g %d %d", getLocalMax(smap, randp, rad), 00557 randp.i, randp.j); 00558 } 00559 else // take random points from file 00560 { 00561 const int randn = randomUpToNotIncluding(randPoints.size()); 00562 Point2D<int> randp(randPoints[randn].i >> sml, 00563 randPoints[randn].j >> sml); 00564 00565 LINFO("%d %d", randPoints[randn].i, randPoints[randn].j); 00566 // just in case the image has weird dims, make sure the sampling 00567 // location is in it: 00568 randp.clampToDims(smap.getDims()); 00569 00570 output += sformat(" %g %d %d", getLocalMax(smap, randp, rad), 00571 randPoints[randn].i, randPoints[randn].j); 00572 } 00573 if(!itsHeaderCrafted) 00574 { 00575 itsOutFields.push_back("rand_sal"); 00576 itsOutFields.push_back("rand_x"); 00577 itsOutFields.push_back("rand_y"); 00578 } 00579 } 00580 } 00581 else 00582 { 00583 // ok, we want more or as many samples as are in the 00584 // saliency map, so just dump each map value once: 00585 Point2D<int> rp; 00586 for (rp.j = 0; rp.j < smap.getHeight(); ++rp.j) 00587 for (rp.i = 0; rp.i < smap.getWidth(); ++rp.i) 00588 { 00589 output += sformat(" %g %d %d", getLocalMax(smap, rp, rad), 00590 rp.i, rp.j); 00591 if(!itsHeaderCrafted) 00592 { 00593 itsOutFields.push_back("rand_sal"); 00594 itsOutFields.push_back("rand_x"); 00595 itsOutFields.push_back("rand_y"); 00596 } 00597 } 00598 } 00599 return output; 00600 } 00601 00602 // ###################################################################### 00603 std::string SimulationViewerEyeMvt::craftSMHistory(const rutz::shared_ptr<EyeData> data, 00604 Image<float> smap) 00605 { 00606 std::string output; 00607 // get the map level to scale things down: 00608 int sml = itsLevelSpec.getVal().mapLevel(); 00609 00610 // get target 00611 Point2D<int> target = rawToRet(data->saccadeTarget()); 00612 00613 // get ready to take samples in the saliency map and in all 00614 // conspicuity maps, at the scale of the saliency map: 00615 const int radius = itsMetrics->getFoveaRadius(); 00616 const int rad = radius >> sml; 00617 Point2D<int> p(target.i >> sml, target.j >> sml); 00618 p.clampToDims(smap.getDims()); 00619 00620 // save history of saliency values at saccade target? 00621 output += std::string(" "); 00622 00623 // FIXME: Error thrown on one of these clampToDims for out of range? 00624 // also get saccade start location, scaled down to SM size: 00625 Point2D<int> pp(data->position()); 00626 pp.i >>= sml; pp.j >>= sml; pp.clampToDims(smap.getDims()); 00627 00628 // note: even though the queue may not be full yet, we 00629 // will always write itsSMhistoryQlen samples here, to 00630 // facilitate reading the data into matlab using 00631 // dlmread(). Saliency values for non-existent maps will 00632 // be set to 0: 00633 size_t ql = itsSMhistory.size(); 00634 for (uint i = 0; i < itsSMhistoryQlen.getVal(); ++ i) 00635 { 00636 if (i < ql) 00637 { 00638 // get min, max and avg saliency values over history: 00639 float mmi, mma, mmav; 00640 getMinMaxAvg(itsSMhistory[ql - 1 - i], mmi, mma, mmav); 00641 00642 // and also value at one random location: 00643 Point2D<int> randp; 00644 if (itsPriorRandomDistro.getVal().empty()) // uniform random 00645 { 00646 randp.i = randomUpToNotIncluding(smap.getWidth()); 00647 randp.j = randomUpToNotIncluding(smap.getHeight()); 00648 } 00649 else // prior from file 00650 { 00651 const int randn = 00652 randomUpToNotIncluding(randPoints.size()); 00653 randp.i = randPoints[randn].i >> sml; 00654 randp.j = randPoints[randn].j >> sml; 00655 00656 // just in case the image has weird dims, make sure the sampling 00657 // location is in it: 00658 randp.clampToDims(smap.getDims()); 00659 } 00660 00661 output += 00662 sformat(" %g %g %g %g %g", 00663 getLocalMax(itsSMhistory[ql - 1 - i], p, rad), 00664 getLocalMax(itsSMhistory[ql - 1 - i], pp, rad), 00665 mma, mmav, 00666 getLocalMax(itsSMhistory[ql - 1 - i], randp, rad)); 00667 } 00668 else output += std::string(" 0.0 0.0 0.0 0.0 0.0"); 00669 if(!itsHeaderCrafted) 00670 { 00671 itsOutFields.push_back("hist_sacendsal"); 00672 itsOutFields.push_back("hist_sacstartsal"); 00673 itsOutFields.push_back("hist_maxsal"); 00674 itsOutFields.push_back("hist_avgsal"); 00675 itsOutFields.push_back("hist_randsal"); 00676 } 00677 } 00678 return output; 00679 } 00680 00681 // ###################################################################### 00682 void SimulationViewerEyeMvt::writeHeader() 00683 { 00684 LINFO("writing header for file %s (%zu fields)", itsOutFname.getVal().c_str(), 00685 itsOutFields.size()); 00686 (*itsOutFile) << "#" << itsOutFields[0]; 00687 for(uint i = 1; i < itsOutFields.size(); i++) 00688 (*itsOutFile) << " " << itsOutFields[i]; 00689 (*itsOutFile) << std::endl; 00690 } 00691 00692 // ###################################################################### 00693 void SimulationViewerEyeMvt::extraSampleProcessing(const rutz::shared_ptr<EyeData>) {} 00694 // empty here, written so that EyeRegion can do extra output on each eye position 00695 00696 // ###################################################################### 00697 Image< PixRGB<byte> > SimulationViewerEyeMvt::getTraj(SimEventQueue& q) 00698 { 00699 GVX_TRACE(__PRETTY_FUNCTION__); 00700 00701 // get the latest retina image: 00702 Image< PixRGB<byte> > input; 00703 if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this, SEQ_ANY)) 00704 input = e->frame().colorByte(); 00705 else 00706 LFATAL("Could not find required SimEventRetinaImage"); 00707 00708 // make a composite of the input + the drawings: 00709 Image< PixRGB<byte> > comp = composite(itsDrawings, input, PixRGB<byte>(0,0,0)); 00710 00711 // return a plain traj? 00712 if (itsSaveTraj.getVal()) return comp; 00713 00714 // let's get the current saliency map (normalized): 00715 const Dims dims = input.getDims(); 00716 Image<float> sm = getMap(q, false); 00717 if (sm.initialized()) sm = rescaleOpt(sm, dims, itsDisplayInterp.getVal()); 00718 else sm.resize(dims, true); // blank 00719 Image< PixRGB<byte> > smc = toRGB(Image<byte>(sm)); 00720 00721 // make a composite of the instantaneous SM + the drawings: 00722 Image< PixRGB<byte> > smcomp = composite(itsDrawings, smc); 00723 00724 00725 // otherwise, return mega combo; we have two formats: if we have a 00726 // max-cache, it will be a 4-image format, otherwise a 2-image 00727 // format: 00728 Image< PixRGB<byte> > ret; 00729 if (itsMaxCacheSize.getVal()) 00730 { 00731 // 4-image format 00732 Image<float> maxsm = 00733 rescaleOpt(itsMaxCache.getMax(), dims, itsDisplayInterp.getVal()); 00734 Image< PixRGB<byte> > maxsmc = toRGB(Image<byte>(maxsm)); 00735 00736 ret = concatX(concatY(input, smcomp), 00737 concatY(comp, composite(itsDrawings, maxsmc))); 00738 00739 drawGrid(ret, dims.w()-1, dims.h()-1, 3, 3, PixRGB<byte>(128, 128, 128)); 00740 } 00741 else 00742 { 00743 // 2-image format: 00744 ret = concatX(comp, smcomp); 00745 drawLine(ret, Point2D<int>(dims.w()-1, 0), Point2D<int>(dims.w()-1, dims.h()-1), 00746 PixRGB<byte>(255, 255, 0), 1); 00747 drawLine(ret, Point2D<int>(dims.w(), 0), Point2D<int>(dims.w(), dims.h()-1), 00748 PixRGB<byte>(255, 255, 0), 1); 00749 } 00750 00751 // make sure image is not unreasonably large: 00752 while (ret.getWidth() > itsMaxComboWidth.getVal()) 00753 ret = decY(lowPass3y(decX(lowPass3x(ret)))); 00754 00755 return ret; 00756 } 00757 00758 // ###################################################################### 00759 void SimulationViewerEyeMvt:: 00760 onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e) 00761 { 00762 this->save1(e->sinfo()); 00763 } 00764 00765 // ###################################################################### 00766 void SimulationViewerEyeMvt::save1(const ModelComponentSaveInfo& sinfo) 00767 { 00768 GVX_TRACE(__PRETTY_FUNCTION__); 00769 SimEventQueue *q = dynamic_cast<const SimModuleSaveInfo&>(sinfo).q; 00770 00771 // update the trajectory: 00772 Image< PixRGB<byte> > res = getTraj(*q); 00773 00774 // save results? 00775 if (itsSaveMegaCombo.getVal() || itsSaveTraj.getVal()) 00776 { 00777 // get the OFS to save to, assuming sinfo is of type 00778 // SimModuleSaveInfo (will throw a fatal exception otherwise): 00779 nub::ref<FrameOstream> ofs = 00780 dynamic_cast<const SimModuleSaveInfo&>(sinfo).ofs; 00781 00782 ofs->writeRGB(res, "T", 00783 FrameInfo("SimulationViewerEyeMvt trajectory", SRC_POS)); 00784 00785 } 00786 } 00787 00788 // ###################################################################### 00789 /* So things look consistent in everyone's emacs... */ 00790 /* Local Variables: */ 00791 /* indent-tabs-mode: nil */ 00792 /* End: */