SimulationViewerEyeMvt.C

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