SimulationViewerEyeHand.C

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: Dicky Nauli Sihite <sihite@usc.edu>
00035 // $HeadURL:
00036 // $Id:
00037 //
00038 
00039 #include "Neuro/SimulationViewerEyeHand.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 "Psycho/HandData.H"
00057 #include "Simulation/SimEventQueue.H"
00058 #include "Transport/FrameInfo.H"
00059 #include "Transport/FrameOstream.H"
00060 #include "Util/sformat.H"
00061 #include "rutz/trace.h"
00062 
00063 #include <fstream>
00064 #include <iostream>
00065 
00066 // for some reason this is not recognized when it appears in NeuroOpts.C
00067 const ModelOptionDef OPT_SMhistoryQlen =
00068   { MODOPT_ARG(uint), "SMhistoryQ", &MOC_DISPLAY, OPTEXP_CORE,
00069     "Keep a historical queue of salieny maps (one per new input frame), "
00070     "and report the history of saliency values over the entire queue "
00071     "and at a saccade target location, for each saccade",
00072     "sm-history-qlen", '\0', "<uint>", "0" };
00073 
00074 
00075 // ######################################################################
00076 SimulationViewerEyeHand::
00077 SimulationViewerEyeHand(OptionManager& mgr, const std::string& descrName,
00078                        const std::string& tagName) :
00079   SimulationViewer(mgr, descrName, tagName),
00080   SIMCALLBACK_INIT(SimEventClockTick),
00081   SIMCALLBACK_INIT(SimEventSaveOutput),
00082   itsMetrics(new SpatialMetrics(mgr)),
00083   itsSaveTraj(&OPT_SVsaveTraj, this),
00084   itsDelayCacheSize(&OPT_SVEMdelayCacheSize, this),
00085   itsMaxCacheSize(&OPT_SVEMmaxCacheSize, this),
00086   itsSampleAtStart(&OPT_SVEMsampleAtStart, this),
00087   itsDisplaySacNum(&OPT_SVEMdisplaySacNum, this),
00088   itsDisplayPatch(&OPT_SVdisplayPatch, this),
00089   itsPatchSize(&OPT_SVpatchSize, this),
00090   itsEraseMarker(&OPT_SVeraseMarker, this),
00091   itsDisplayFOA(&OPT_SVdisplayFOA, this),
00092   itsLevelSpec(&OPT_LevelSpec, this),
00093   itsOutFname(&OPT_SVEMoutFname, this),
00094   itsPriorRandomDistro(&OPT_SVEMpriorRandomDistro, this),
00095   itsUseSaccadeInBlink(&OPT_SVEMuseSaccadeInBlink, this),
00096   itsUseDiagColor(&OPT_SVEMuseDiagColors, this),
00097   itsLabelEyePatch(&OPT_SVEMlabelEyePatch, this),
00098   itsNumRandomSamples(&OPT_SVEMnumRandomSamples, this),
00099   itsMaxComboWidth(&OPT_SVEMmaxComboWidth, this),
00100   itsSMhistoryQlen(&OPT_SMhistoryQlen, this),
00101   itsDisplayHand(&OPT_SVHandDisplay, this),
00102   itsSaveCombo(&OPT_SVEMsaveMegaCombo, this),
00103   itsDelayCache(), itsMaxCache(), itsHeadSM(), 
00104   itsDrawings(), itsCurrTime(),  itsFrameNumber(-1), 
00105   itsHeaderCrafted(false), itsOutFields(), itsEyeStyles(), 
00106   itsEyeTargets(), itsHandTargets(), itsSMhistory(), itsOutFile(0), 
00107   itsRandFile(0),itsRawToRetOffset()
00108 {
00109 GVX_TRACE(__PRETTY_FUNCTION__);
00110 
00111   this->addSubComponent(itsMetrics);
00112 
00113   // disable IOR:
00114   LINFO("NOTE: disabling IOR and SE, selecting FixedSaccadeController");
00115   getManager().setOptionValString(&OPT_IORtype, "None");
00116   getManager().setOptionValString(&OPT_ShapeEstimatorMode, "None");
00117 
00118   // select an eyetrack EyeHeadController:
00119   getManager().setOptionValString(&OPT_EyeHeadControllerType, "EyeTrack");
00120 
00121   // Select HandTrack for the HandControllerType
00122   getManager().setOptionValString(&OPT_HandControllerType, "HandTrack");
00123 
00124   // change default to --display-additive=false; user can still
00125   // override this on the command-line
00126   getManager().setOptionValString(&OPT_SVdisplayAdditive, "false");
00127 }
00128 
00129 // ######################################################################
00130 SimulationViewerEyeHand::~SimulationViewerEyeHand()
00131 {
00132 GVX_TRACE(__PRETTY_FUNCTION__);
00133 }
00134 
00135 // ######################################################################
00136 void SimulationViewerEyeHand::start1()
00137 {
00138 GVX_TRACE(__PRETTY_FUNCTION__);
00139 
00140   if (itsDelayCacheSize.getVal())
00141     itsDelayCache.setMaxSize(itsDelayCacheSize.getVal());
00142 
00143   if (itsMaxCacheSize.getVal())
00144     itsMaxCache.setMaxSize(itsMaxCacheSize.getVal());
00145 
00146   // open output file:
00147   if (itsOutFname.getVal().empty() == false) {
00148     if (itsOutFile) delete itsOutFile;
00149     itsOutFile = new std::ofstream(itsOutFname.getVal().c_str());
00150     if (itsOutFile->is_open() == false)
00151       LFATAL("Cannot open '%s' for writing", itsOutFname.getVal().c_str());
00152   }
00153 
00154   // open random file if one
00155   if (itsPriorRandomDistro.getVal().empty() == false) {
00156     if (itsRandFile) delete itsRandFile;
00157     itsRandFile = new std::ifstream(itsPriorRandomDistro.getVal().c_str());
00158     if (itsRandFile->is_open() == false)
00159       LFATAL("Cannot open '%s' for reading",
00160              itsPriorRandomDistro.getVal().c_str());
00161     else {
00162       while (! itsRandFile->eof()) {
00163         Point2D<int> pr;
00164         (*itsRandFile) >> pr.i >> pr.j;
00165         randPoints.push_back(pr);
00166       }
00167       itsRandFile->close();
00168     }
00169   }
00170   
00171   // set our SM history queue length:
00172   if (itsSMhistoryQlen.getVal())
00173     itsSMhistory.setMaxSize(itsSMhistoryQlen.getVal());
00174 
00175   SimulationViewer::start1();
00176 }
00177 
00178 // ######################################################################
00179 void SimulationViewerEyeHand::stop1()
00180 {
00181 GVX_TRACE(__PRETTY_FUNCTION__);
00182 
00183  if (itsOutFile)  {delete itsOutFile;  itsOutFile  = 0;}
00184  if (itsRandFile) {delete itsRandFile; itsRandFile = 0;}
00185 }
00186 
00187 // ######################################################################
00188 void SimulationViewerEyeHand::
00189 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect)
00190 {
00191   // get the current time (mostly for SVEyeRegion)
00192   itsCurrTime = q.now();
00193   // any new input frame from the retina? If so, let's initialize or
00194   // clear all our drawings:
00195   bool gotnewinput = false;
00196   if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this)) {
00197     itsDrawings.resize(e->frame().getDims(), true);
00198     gotnewinput = true;
00199     itsFrameNumber++; // used for derived class SVEyeRegion
00200     // this member is never used in SVEyeHand
00201   }
00202 
00203   // Let's update our sliding caches with the current SM/AGM/etc:
00204   const Image<float> currsm = getMap(q, false);
00205 
00206   // if we have a delay cache, we push the SM into it and push
00207   // what comes out of it into our max-computation cache;
00208   // otherwise, we directly push the SM into the max-computation
00209   // cache:
00210   if (itsDelayCacheSize.getVal())
00211     {
00212       // let's push the SM into our delay sliding cache:
00213       itsDelayCache.push_back(currsm);
00214 
00215       // transfer oldest delay image to our max computation cache:
00216       if (int(itsDelayCache.size()) >= itsDelayCacheSize.getVal())
00217           itsHeadSM = itsDelayCache.front();        
00218       else
00219         itsHeadSM.resize(currsm.getDims(), true);  // blank map
00220     }
00221   else 
00222     itsHeadSM = currsm;  // no delay cache
00223 
00224   // if we have a saliency history queue, and a new input, push the
00225   // current sm into it:
00226   if (itsSMhistoryQlen.getVal() && gotnewinput)
00227     itsSMhistory.push_back(itsHeadSM);
00228 
00229   // if we have a max cache, let's push the (possibly delayed) sm into it:
00230   if (itsMaxCacheSize.getVal()) itsMaxCache.push_back(itsHeadSM);
00231 
00232   // all right, get the latest retina image (whether new or not) as we
00233   // will need it to do coordinate transforms, coordinate clippings, etc:
00234   SeC<SimEventRetinaImage> retev = q.check<SimEventRetinaImage>(this, SEQ_ANY);
00235   if (retev.is_valid() == false)
00236     LFATAL("I need some SimEventRetinaImage in the queue to function.");
00237   // set the offset
00238   itsRawToRetOffset = retev->offset();
00239 
00240   // ## Hand-related stuffs
00241   //{@
00242   
00243   // any hand-tracker action?
00244   SeC<SimEventHandTrackerData> ee = q.check<SimEventHandTrackerData>(this);
00245   
00246   while(ee.is_valid()) {
00247     // parse that event:
00248     const rutz::shared_ptr<HandData> trackCurrPos = ee->data();
00249     const uint tnum = ee->trackerNum();
00250     const std::string tfn = ee->trackerFilename();
00251     // add entry to style vector when we get a new trackernum
00252     if (itsHandStyles.size() <= tnum) {
00253       itsHandStyles.resize(tnum+1);
00254       
00255       itsHandStyles[tnum].pSize = itsPatchSize.getVal();
00256       itsHandStyles[tnum].col = ee->trackerColor();
00257       
00258       // give distinct label, TODO: maybe write a switch for this
00259       //      itsHandStyles[tnum].label = convertToString(tnum);
00260       //huge hack right now, relies on directory tree data/<subject>/ pieces
00261       itsHandStyles[tnum].label = tfn.substr(tfn.find("data")+5,2);
00262     }
00263     
00264     // draw the conditions of current hand conditions:
00265     if (itsDisplayHand.getVal()) {
00266 
00267       // erase previous hand markers first
00268       if (itsEraseMarker.getVal()) itsDrawings.resize(retev->frame().getDims(), true);
00269       
00270       // draw marker at hand position
00271       drawHand(trackCurrPos, tnum);
00272     }
00273     
00274     // do we want to do any other sample processing? 
00275     // (for SVHandRegion or other derived classes)
00276     extraSampleProcessing(trackCurrPos);
00277     
00278     // any more events like that in the queue?
00279     ee = q.check<SimEventHandTrackerData>(this);
00280   }
00281   //@}
00282 
00283   // ## Eye-related Stuffs
00284 
00285   // any eye-tracker action?
00286   SeC<SimEventEyeTrackerData> e = q.check<SimEventEyeTrackerData>(this);
00287 
00288   while(e.is_valid()) {
00289     // parse that event:
00290     const rutz::shared_ptr<EyeData> trackCurrPos = e->data();
00291     const uint tnum = e->trackerNum();
00292     const std::string tfn = e->trackerFilename();
00293     // add entry to style vector when we get a new trackernum
00294     if (itsEyeStyles.size() <= tnum) {
00295       itsEyeStyles.resize(tnum+1);
00296          
00297       itsEyeStyles[tnum].pSize = itsPatchSize.getVal();
00298       itsEyeStyles[tnum].col = e->trackerColor();
00299 
00300       // give distinct label, TODO: maybe write a switch for this
00301       //      itsEyeStyles[tnum].label = convertToString(tnum);
00302       //huge hack right now, relies on directory tree data/<subject>/ pieces
00303       itsEyeStyles[tnum].label = tfn.substr(tfn.find("data")+5,2);
00304     }
00305 
00306     // draw small patch at current eye position:
00307     if (itsDisplayPatch.getVal()) 
00308       {
00309         // erase previous eye markers first
00310         if (itsEraseMarker.getVal()) itsDrawings.resize(retev->frame().getDims(), true);
00311 
00312         // draw marker at eye position
00313         drawEye(trackCurrPos, tnum);
00314       }
00315 
00316     // do we want to do any other sample processing? 
00317     // (for SVEyeRegion or other derived classes)
00318     extraSampleProcessing(trackCurrPos);
00319 
00320     bool isUsableTrace = true;
00321     // if we can't use saccades in blink and it's in blink, don't process the event
00322     if (itsUseSaccadeInBlink.getVal() == false && trackCurrPos->isInBlink() == true) isUsableTrace = false;
00323     // NB: what about combined saccades?
00324 
00325     // are we starting a saccade/special region with extra data?
00326     if (trackCurrPos->hasSaccadeTargetData() && isUsableTrace)
00327       {
00328         // extract the saccade destination for display:
00329         Point2D<int> dp = rawToRet(trackCurrPos->saccadeTarget());
00330 
00331         CLINFO("**** Tracker %03d: Saccade to (%d, %d) at %.1fms ****",
00332                tnum, dp.i, dp.j, q.now().msecs());
00333         // check that saccade target is okay:
00334         if (itsDrawings.coordsOk(dp) == false)
00335           CLINFO("Tracker %03d: Saccade target (%d, %d) out of bounds "
00336                  "-- IGNORING", tnum, dp.i, dp.j);
00337         else
00338           // do some saliency sampling at saccade target, but may
00339           // have to be delayed to end of a saccade... So for now
00340           // let's just push the eye data into a temporary storage:
00341           itsEyeTargets[tnum] = trackCurrPos;
00342       }
00343 
00344     // is it time to take a bunch of samples at the saccade target of
00345     // this tracker? Either we want to sample at the start of a
00346     // saccade and just pushed some saccade data, or we want to sample
00347     // at end and we are ending the saccade during which we pushed
00348     // some data:
00349     std::map<int, rutz::shared_ptr<EyeData> >::iterator
00350       itr = itsEyeTargets.find(tnum);
00351 
00352     if (itr != itsEyeTargets.end() && isUsableTrace &&
00353         (itsSampleAtStart.getVal() || trackCurrPos->isInSaccade() == false))
00354     {
00355       // extract the saccade target data:
00356       const rutz::shared_ptr<EyeData> trackEventSample = itr->second;
00357       itsEyeTargets.erase(itr); // we are now taking care of this one
00358 
00359       // changed from trackCurrPos->...();
00360       if(!(trackEventSample->hasSaccadeTargetData()) )
00361          LFATAL("Tracker %03d: Marked event has incomplete targeting data.", tnum);
00362  
00363       Point2D<int> trackerTarget = rawToRet(trackEventSample->saccadeTarget());
00364 
00365       LINFO("Tracker %03d [%s]: Taking saccade samples at (%d, %d) at time %.1fms",
00366             tnum, tfn.c_str(), trackerTarget.i, trackerTarget.j, q.now().msecs());
00367 
00368       // draw circle around target:
00369       if (itsDisplayFOA.getVal()) drawFOA(trackerTarget, tnum);
00370 
00371       // indicate saccade number?
00372       if (itsDisplaySacNum.getVal())
00373         {
00374           if (tnum == 0) {
00375             std::string fld("typenum"); //subject to change
00376             if (trackCurrPos->hasMetaData(fld.c_str()))
00377               writeText(itsDrawings, Point2D<int>(0, 0),
00378                         sformat(" %1g ", 
00379                                 trackEventSample->getMetaDataField("typenum")).c_str(),
00380                         PixRGB<byte>(1, 1, 1));
00381             else
00382               LFATAL("--svem-display-sacnum does not have numbered saccades available");
00383           }
00384           else
00385             LFATAL("--svem-display-sacnum only supported with 1 tracker");
00386         } 
00387 
00388       if (itsOutFile) {
00389         LINFO("writing to file %s........", itsOutFname.getVal().c_str());
00390         (*itsOutFile) << craftSVEMOutput(tfn, trackEventSample) << std::endl;
00391       }
00392     }//end sampling at saccade target
00393 
00394     // any more events like that in the queue?
00395     e = q.check<SimEventEyeTrackerData>(this);
00396   }
00397 
00398 
00399 }
00400 
00401 // ######################################################################
00402 void SimulationViewerEyeHand::drawEye(const rutz::shared_ptr<EyeData> rawPos, const uint tN)
00403 {
00404   // convert to retinal coordinates (accounting for any shifting,
00405   // embedding, etc):
00406   Point2D<int> currPos = rawToRet(rawPos->position());
00407 
00408   // note: these functions are tolerant to 
00409   // coordinates outside the actual image area
00410   PixRGB<byte> col;
00411   // select a drawing color:
00412   // note: we use 'almost black' (1,1,1) instead of black so that we can later
00413   // composite our drawings with various images, with pure black
00414   // then being treated as transparent):
00415   if (!itsUseDiagColor.getVal())
00416     {
00417       col = itsEyeStyles[tN].col;
00418       // dec/inc will clamp/saturate properly
00419       if (rawPos->isInSaccade()) col += byte(64);
00420       if (rawPos->isInBlink()) col -= byte(128);  
00421     }
00422   else //diagnostic colors
00423       {
00424         byte r = 1, g = 1, b = 1;
00425         if (rawPos->isInSaccade()) g = 255;  //green for saccade
00426         else if (rawPos->isInSmoothPursuit()) 
00427           {r = 255;b = 255;} // magenta
00428         else if (rawPos->isInFixation()) b = 255;                // blue
00429         if (rawPos->isInCombinedSaccade()) {b = 255;g = 255;}    //cyan
00430 
00431         col = PixRGB<byte>(r,g,b);    //default black
00432         if (rawPos->isInBlink()) col-=byte(128); // let's just make the cursor darker
00433       }
00434   drawPatchBB(itsDrawings, currPos, itsEyeStyles[tN].pSize, 
00435               col, PixRGB<byte>(1));
00436 
00437   //output the label we got next to the patch
00438   if(itsLabelEyePatch.getVal()) { 
00439     writeText(itsDrawings, currPos+itsEyeStyles[tN].pSize/2,
00440               itsEyeStyles[tN].label.c_str(),
00441               PixRGB<byte>(255,255,255), PixRGB<byte>(0,0,0),
00442               SimpleFont::FIXED(10), true);
00443   }
00444 }
00445 
00446 // ######################################################################
00447 void SimulationViewerEyeHand::drawHand(const rutz::shared_ptr<HandData> rawPos, const uint tN)
00448 {
00449   /* The output that we expect on screen:
00450    * .--------------------.
00451    * | |                  |
00452    * | |                  |
00453    * | Y                  |
00454    * | | B B B B ...      |
00455    * |  ------X--------   |
00456    * '--------------------'
00457    * X & Y = x & y (left-right & accl-brake) direction respectively
00458    *         It will show a line for range of movement and a box
00459    *         indicating the current position
00460    * B's   = buttons, there will be circles with numbers on it indicating
00461    *         whether the button is pressed(green) or not(red)
00462    *         Hopefully the B's on screen is wrapped around incase too many
00463    *         buttons are avail
00464    */
00465 
00466   // Our screen dimensions
00467   const Dims dims = itsDrawings.getDims();
00468   
00469   // Default one grid cell based on the screen dimensions
00470   // Assuming we always have 4:3 tv ratio to get a square grid
00471   // TODO: make this in the option
00472   const Dims numgrid (80,60);//Under 640x480, its expected to have 8x8 per grid
00473   const Dims grid (dims.w()/numgrid.w(),dims.h()/numgrid.h()); 
00474 
00475   PixRGB<byte> colTransp = PixRGB<byte> (  0, 0, 0);
00476   PixRGB<byte> colRed    = PixRGB<byte> (255, 0, 0);
00477   PixRGB<byte> colB;
00478 
00479 
00480   if (rawPos->isValid()) {
00481     // Get raw X and Y position
00482     int rawX = rawPos->getX();
00483     int rawY = rawPos->getY();
00484 
00485     // Get the dims of X and Y position
00486     /*! Assuming grid [1,1]'s coord is (8,8) --> [ 0, 0] is (  0,  0)
00487      *                                       --> [80,60] is (640,480)
00488      *  We only want the X to be in between grid [2,59] to [79,59]
00489      *  and Y in [1,1] to [1,58]. We set those instead of [1,59] to prevent
00490      *  collision incase both meet eachother (rawX=0/255, rawY=0/255)
00491      *  |----------------|-------[]------|
00492      *  X1               XC      X_      X2
00493      */
00494     Point2D<int> X1 (getGridCoord(grid,             2, numgrid.h()-1));
00495     Point2D<int> X2 (getGridCoord(grid, numgrid.w()-1, numgrid.h()-1));
00496     Point2D<int> XC ((127*(X2.i-X1.i)/255)+X1.i,X1.j);
00497     Point2D<int> XD (0,grid.h()/2);
00498     Point2D<int> Y1 (getGridCoord(grid,             1,             1));
00499     Point2D<int> Y2 (getGridCoord(grid,             1, numgrid.h()-2));
00500     Point2D<int> YC (grid.w(),(127*(Y2.j-Y1.j)/255)+Y1.j);
00501     Point2D<int> YD (grid.w()/2,0);
00502     // The patch coords
00503     Point2D<int> X_ ((rawX*(X2.i-X1.i)/255)+X1.i, dims.h()-grid.h());
00504     Point2D<int> Y_ (grid.w(), (rawY*(Y2.j-Y1.j)/255)+Y1.j);
00505 
00506     // First draw the white line for the X-Y coords
00507     const PixRGB<byte> colWhite (255,255,255);
00508     // The X-line
00509     drawLine(itsDrawings, X1, X2, colWhite);
00510     drawLine(itsDrawings, X1-XD, X1+XD, colWhite);
00511     drawLine(itsDrawings, XC-XD, XC+XD, colWhite);
00512     drawLine(itsDrawings, X2-XD, X2+XD, colWhite);
00513     // The Y-line
00514     drawLine(itsDrawings, Y1, Y2, colWhite);
00515     drawLine(itsDrawings, Y1-YD, Y1+YD, colWhite);
00516     drawLine(itsDrawings, YC-YD, YC+YD, colWhite);
00517     drawLine(itsDrawings, Y2-YD, Y2+YD, colWhite);
00518 
00519 
00520     // Draw the box for the X-Y
00521     drawPatchBB(itsDrawings, X_, itsHandStyles[tN].pSize,   
00522                 colWhite, PixRGB<byte>(1));
00523     drawPatchBB(itsDrawings, Y_, itsHandStyles[tN].pSize,   
00524                 colWhite, PixRGB<byte>(1));
00525   
00526     // For the buttons
00527     if (rawPos->isButtonEmpty() == false) {  
00528       for (uint i = 0; i < rawPos->numButton(); i++) {
00529         // Blue if pressed, red if not
00530         if (rawPos->isPressed(i))
00531           colB = colRed;
00532         else
00533           colB = colTransp;
00534 
00535         // For buttons position, we want them to be @ bottom of the screen
00536         // TODO: wrapped around in case the buttons grid over [80,_]
00537 
00538         // Write text of numbers into this thing
00539         writeText(itsDrawings,
00540                   Point2D<int>(getGridCoord(grid, 3*i+10, numgrid.h()-5)),
00541                   sformat("%2d",i+1).c_str(),
00542                   PixRGB<byte>(255), // Text color
00543                   colB, // Background color
00544                   SimpleFont::FIXED(10), // Font
00545                   false); // BG transparent?
00546   
00547         // Draw circle
00548         //drawCircle(itsDrawings, Point2D<int>(getGridCoord(grid, 3*i+3, 1)),6,colB,1);
00549       }
00550     }
00551   }//if rawPos->isValid()
00552 
00553   // For Mouse
00554   writeText(itsDrawings,
00555             Point2D<int>(getGridCoord(grid, 7, numgrid.h()-8)),
00556             sformat("Mouse: ").c_str(),
00557             PixRGB<byte>(255), // Text color
00558             PixRGB<byte>(0), // Background color
00559             SimpleFont::FIXED(10), // Font
00560             false); // BG transparent?
00561   if (rawPos->isMouseValid()) {
00562     // raw mouse position
00563     Point2D<int> mousePos;
00564 
00565     // computed mouse position
00566     if (rawPos->getNativeX() > 0 && rawPos->getNativeY() > 0) // valid dim
00567       // Absolute relation to the movie
00568       //mousePos=Point2D<int>(rawPos->getMouseX()*dims.w()/rawPos->getNativeX(),
00569       //                    rawPos->getMouseY()*dims.h()/rawPos->getNativeY());
00570 
00571       // Relative to the inside, fixed value
00572       if (rawPos->getNativeX() == 1024)
00573         mousePos=Point2D<int>((rawPos->getMouseX()*583/rawPos->getNativeX())+37,
00574                               (rawPos->getMouseY()*438/rawPos->getNativeY())+15);
00575       else if (rawPos->getNativeX() == 800)
00576         mousePos=Point2D<int>((rawPos->getMouseX()*602/rawPos->getNativeX())+11,
00577                               (rawPos->getMouseY()*439/rawPos->getNativeY())+11);
00578       else
00579         mousePos=Point2D<int>(rawPos->getMouseX()*dims.w()/rawPos->getNativeX(),
00580                               rawPos->getMouseY()*dims.h()/rawPos->getNativeY());
00581     else
00582       mousePos=Point2D<int>(rawPos->getMouseX(), rawPos->getMouseY());
00583 
00584     // draw the mouse position
00585     drawPatchBB(itsDrawings, mousePos, itsHandStyles[tN].pSize,
00586                 PixRGB<byte>(127), PixRGB<byte>(1));
00587     //}
00588     // drawing the letters: Mouse: L M R
00589     // the letter LMR will go red if pressed
00590     colB = rawPos->getMouseBL()?colRed:colTransp;
00591     writeText(itsDrawings,
00592               Point2D<int>(getGridCoord(grid, 15, numgrid.h()-8)),
00593               sformat("L").c_str(),
00594               PixRGB<byte>(255), // Text color
00595               colB,
00596               SimpleFont::FIXED(10), // Font
00597               false); // BG transparent?
00598     colB = rawPos->getMouseBM()?colRed:colTransp;
00599     writeText(itsDrawings,
00600               Point2D<int>(getGridCoord(grid, 18, numgrid.h()-8)),
00601               sformat("M").c_str(),
00602               PixRGB<byte>(255), // Text color
00603               colB,
00604               SimpleFont::FIXED(10), // Font
00605               false); // BG transparent?
00606     colB = rawPos->getMouseBR()?colRed:colTransp;
00607     writeText(itsDrawings,
00608               Point2D<int>(getGridCoord(grid, 21, numgrid.h()-8)),
00609               sformat("R").c_str(),
00610               PixRGB<byte>(255), // Text color
00611               colB,
00612               SimpleFont::FIXED(10), // Font
00613               false); // BG transparent?
00614   }
00615   else {
00616     writeText(itsDrawings,
00617               Point2D<int>(getGridCoord(grid, 15, numgrid.h()-8)),
00618               sformat("n/a").c_str(),
00619               PixRGB<byte>(255), // Text color
00620               PixRGB<byte>(0),
00621               SimpleFont::FIXED(10), // Font
00622               false); // BG transparent?
00623   }
00624 
00625   // For Keyboard
00626   writeText(itsDrawings,
00627             Point2D<int>(getGridCoord(grid,25,numgrid.h()-8)),
00628             sformat("Key:").c_str(),
00629             PixRGB<byte>(255),
00630             PixRGB<byte>(0),
00631             SimpleFont::FIXED(10),
00632             false);
00633   if (!rawPos->isKeyboardEmpty()) {
00634     //LINFO("%s", rawPos->getKeyboard());
00635     writeText(itsDrawings,
00636               Point2D<int>(getGridCoord(grid,31,numgrid.h()-8)),
00637               sformat(" %s ",rawPos->getKeyboard()).c_str(),
00638               //rawPos->getKeyboard(),
00639               PixRGB<byte>(255),
00640               PixRGB<byte>(0),
00641               SimpleFont::FIXED(10),
00642               false);
00643   }
00644   else {
00645     writeText(itsDrawings,
00646               Point2D<int>(getGridCoord(grid,31,numgrid.h()-8)),
00647               sformat("                         ").c_str(),
00648               PixRGB<byte>(255),
00649               PixRGB<byte>(0),
00650               SimpleFont::FIXED(10),
00651               false);
00652   }
00653 }
00654 
00655 
00656 // ######################################################################
00657 void SimulationViewerEyeHand::drawFOA(const Point2D<int> target, const uint tN)
00658 {
00659   const PixRGB<byte> col = itsEyeStyles[tN].col;
00660   const int radius = itsMetrics->getFoveaRadius();
00661   drawCircle(itsDrawings, target, radius, col, 2);
00662 }
00663 
00664 // ######################################################################
00665 std::string SimulationViewerEyeHand::craftSVEMOutput(const std::string tfn, 
00666                         const rutz::shared_ptr<EyeData> data)
00667 {
00668   // Write some measurements to a string. Here, we just put all the stats
00669   // into a text string, and we will send that off to be
00670   // written to disk.
00671   // The first time we run this, we build a header for the file 
00672   // to know what stats to read. This is done in the craft submodules.  
00673 
00674   std::string output = 
00675     sformat("%s ", tfn.c_str());
00676   if(!itsHeaderCrafted) itsOutFields.push_back("filename");
00677  
00678   // add model free output from the original eyetrack-data file 
00679   output += craftModelFreeOutput(data);
00680 
00681   // construct the saliency map, 
00682   // if requested from the max of several previous maps 
00683   Image<float> smap;
00684   if (itsMaxCacheSize.getVal()) 
00685     smap = itsMaxCache.getMax();
00686   else smap = itsHeadSM;
00687 
00688   // add samples of saliency map at the target / in the frame
00689   output += craftSMSamples(data, smap);
00690 
00691   // add history of saliency map across past frames for key locations
00692   if (itsSMhistoryQlen.getVal()) output += craftSMHistory(data, smap);
00693 
00694   // write the header line now - only once for each output 
00695   if(!itsHeaderCrafted)
00696     {
00697       writeHeader();
00698       itsHeaderCrafted = true;
00699     }
00700   return output;
00701 }
00702 
00703 // ######################################################################
00704 std::string SimulationViewerEyeHand::
00705 craftModelFreeOutput(const rutz::shared_ptr<EyeData> data)
00706 {
00707   // legacy behavior - this was the old eyesal output for the model-free numbers
00708   // retained for reference
00709   /*
00710     std::string output =
00711     sformat("%s %d %d  %d %d  %g %g %g %g", tfn.c_str(), rawdp.i, rawdp.j,
00712     itsCurrTarget.i, itsCurrTarget.j, d->pupilDiameter(), d->saccadeAmplitude(),
00713     d->saccadeDuration(), d->saccadeTime());
00714   */
00715 
00716   Point2D<int> target = rawToRet(data->saccadeTarget());
00717   std::string output = sformat("%d %d ", target.i, target.j);
00718   
00719   // start writing fields for the header
00720   if(!itsHeaderCrafted) 
00721     { 
00722       itsOutFields.push_back("ret_targetx"); 
00723       itsOutFields.push_back("ret_targety");
00724     }
00725   
00726   // output all the extra (now precomputed) parameters, 
00727   // default from Eye-markup is saccade target first, 
00728   // then amplitude and peak velocity, then time onset and offset of saccade.
00729   // then the interval between this saccade and the next, 
00730   // and lastly the number of the saccade (discriminates between saccades 
00731   // in blink, etc.) 
00732   std::string foo;
00733   for (ParamMap::key_iterator iter = data->getMetaData()->keys_begin();
00734        iter != data->getMetaData()->keys_end();
00735              ++iter)
00736     {
00737       // get rid of any leading asterisk
00738       foo = *iter;
00739       
00740       // add this to the queue of arguments
00741       if(!itsHeaderCrafted) itsOutFields.push_back(foo);
00742       output += sformat (" %g", data->getMetaDataField(foo));
00743     }
00744 
00745   return output;
00746 }
00747 
00748 // ######################################################################
00749 std::string SimulationViewerEyeHand::craftSMSamples(const rutz::shared_ptr<EyeData> data, 
00750                                                    const Image<float> smap)
00751 {
00752   std::string output;
00753   // get the map level to scale things down:
00754   int sml = itsLevelSpec.getVal().mapLevel();
00755 
00756   // get target
00757   Point2D<int> target = rawToRet(data->saccadeTarget());
00758   
00759   // get ready to take samples in the saliency map and in all
00760   // conspicuity maps, at the scale of the saliency map:
00761   const int radius = itsMetrics->getFoveaRadius();
00762   const int rad = radius >> sml;
00763   Point2D<int> p(target.i >> sml, target.j >> sml);
00764   
00765   // just in case the image has weird dims, make sure the sampling
00766   // location is in it:
00767 
00768   p.clampToDims(smap.getDims());
00769 
00770   // get the min/max/avg and saliency samples:
00771   float mi, ma, av; getMinMaxAvg(smap, mi, ma, av);
00772 
00773   // get a sample around the saccade target location:
00774   float val = getLocalMax(smap, p, rad);
00775 
00776   // add necessary headers
00777   if(!itsHeaderCrafted) 
00778     {
00779       itsOutFields.push_back("sal_val");
00780       itsOutFields.push_back("sal_min");
00781       itsOutFields.push_back("sal_max");
00782       itsOutFields.push_back("sal_av");
00783     }
00784   
00785   output = sformat("  %g %g %g %g ", val, mi, ma, av);
00786 
00787   // now get a bunch of random samples from the saliency map:
00788   if (itsNumRandomSamples.getVal() < smap.getSize())
00789     {
00790       // ok, we want fewer samples than are actually in the
00791       // saliency map, so choose them randomly:
00792       for (int k = 0; k < itsNumRandomSamples.getVal(); ++k) 
00793         {
00794           if (itsPriorRandomDistro.getVal().empty()) // uniform random
00795             {
00796               Point2D<int> randp(randomUpToNotIncluding(smap.getWidth()),
00797                                  randomUpToNotIncluding(smap.getHeight()));
00798               output += sformat(" %g %d %d", getLocalMax(smap, randp, rad),
00799                                 randp.i, randp.j);
00800             }
00801           else // take random points from file
00802             {
00803               const int randn = randomUpToNotIncluding(randPoints.size());
00804               Point2D<int> randp(randPoints[randn].i >> sml,
00805                                  randPoints[randn].j >> sml);
00806               output += sformat(" %g %d %d", getLocalMax(smap, randp, rad),
00807                                 randPoints[randn].i, randPoints[randn].j);
00808             }
00809           if(!itsHeaderCrafted) 
00810             {
00811               itsOutFields.push_back("rand_sal");
00812               itsOutFields.push_back("rand_x");
00813               itsOutFields.push_back("rand_y");
00814             }
00815         }
00816     }
00817   else
00818     {
00819       // ok, we want more or as many samples as are in the
00820       // saliency map, so just dump each map value once:
00821       Point2D<int> rp;
00822       for (rp.j = 0; rp.j < smap.getHeight(); ++rp.j)
00823         for (rp.i = 0; rp.i < smap.getWidth(); ++rp.i)
00824           {
00825             output += sformat(" %g %d %d", getLocalMax(smap, rp, rad),
00826                               rp.i, rp.j);
00827             if(!itsHeaderCrafted) 
00828               {
00829                 itsOutFields.push_back("rand_sal");
00830                 itsOutFields.push_back("rand_x");
00831                 itsOutFields.push_back("rand_y");
00832               }
00833           }
00834     }
00835   return output;
00836 }
00837 
00838 // ######################################################################
00839 std::string SimulationViewerEyeHand::craftSMHistory(const rutz::shared_ptr<EyeData> data,
00840                                                    Image<float> smap)
00841 {
00842   std::string output;
00843   // get the map level to scale things down:
00844   int sml = itsLevelSpec.getVal().mapLevel();
00845   
00846   // get target
00847   Point2D<int> target = rawToRet(data->saccadeTarget());
00848 
00849   // get ready to take samples in the saliency map and in all
00850   // conspicuity maps, at the scale of the saliency map:
00851   const int radius = itsMetrics->getFoveaRadius();
00852   const int rad = radius >> sml;
00853   Point2D<int> p(target.i >> sml, target.j >> sml);
00854   
00855   // save history of saliency values at saccade target?
00856   output += std::string("   ");
00857       
00858   // also get saccade start location, scaled down to SM size:
00859   Point2D<int> pp(data->position());
00860   pp.i >>= sml; pp.j >>= sml; pp.clampToDims(smap.getDims());
00861   
00862   // note: even though the queue may not be full yet, we
00863   // will always write itsSMhistoryQlen samples here, to
00864   // facilitate reading the data into matlab using
00865   // dlmread(). Saliency values for non-existent maps will
00866   // be set to 0:
00867   size_t ql = itsSMhistory.size();
00868   for (uint i = 0; i < itsSMhistoryQlen.getVal(); ++ i) 
00869     {
00870       if (i < ql)
00871         {
00872           // get min, max and avg saliency values over history:
00873           float mmi, mma, mmav;
00874           getMinMaxAvg(itsSMhistory[ql - 1 - i], mmi, mma, mmav);
00875           
00876           // and also value at one random location:
00877           Point2D<int> randp;
00878           if (itsPriorRandomDistro.getVal().empty()) // uniform random
00879             {
00880               randp.i = randomUpToNotIncluding(smap.getWidth());
00881               randp.j = randomUpToNotIncluding(smap.getHeight());
00882             }
00883           else // prior from file
00884             {
00885               const int randn =
00886                 randomUpToNotIncluding(randPoints.size());
00887               randp.i = randPoints[randn].i >> sml;
00888               randp.j = randPoints[randn].j >> sml;
00889             }
00890           
00891           output +=
00892             sformat(" %g %g %g %g %g",
00893                     getLocalMax(itsSMhistory[ql - 1 - i], p, rad),
00894                     getLocalMax(itsSMhistory[ql - 1 - i], pp, rad),
00895                     mma, mmav,
00896                     getLocalMax(itsSMhistory[ql - 1 - i], randp, rad));
00897         }
00898       else output += std::string(" 0.0 0.0 0.0 0.0 0.0");
00899       if(!itsHeaderCrafted) 
00900         {
00901           itsOutFields.push_back("hist_sacendsal");
00902           itsOutFields.push_back("hist_sacstartsal");
00903           itsOutFields.push_back("hist_avgsal");
00904           itsOutFields.push_back("hist_maxsal");
00905           itsOutFields.push_back("hist_randsal");      
00906         }
00907     }
00908   return output;
00909 }
00910 
00911 // ######################################################################
00912 void SimulationViewerEyeHand::writeHeader()
00913 {
00914   LINFO("writing header for file %s", itsOutFname.getVal().c_str());
00915   (*itsOutFile) << "#" << itsOutFields[0];
00916   for(uint i = 1; i < itsOutFields.size(); i++)
00917     (*itsOutFile) << " " << itsOutFields[i];
00918   (*itsOutFile) << std::endl;
00919 }
00920 
00921 // ######################################################################
00922 void SimulationViewerEyeHand::extraSampleProcessing(const rutz::shared_ptr<EyeData>) {}
00923 // empty here, written so that EyeRegion can do extra output on each eye position
00924 
00925 // ######################################################################
00926 void SimulationViewerEyeHand::extraSampleProcessing(const rutz::shared_ptr<HandData>) {}
00927 // empty here, written so that EyeRegion can do extra output on each eye position
00928 
00929 // ######################################################################
00930 Image< PixRGB<byte> > SimulationViewerEyeHand::getTraj(SimEventQueue& q)
00931 {
00932 GVX_TRACE(__PRETTY_FUNCTION__);
00933 
00934   // get the latest retina image:
00935   Image< PixRGB<byte> > input;
00936   if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this, SEQ_ANY))
00937     input = e->frame().colorByte();
00938   else
00939     LFATAL("Could not find required SimEventRetinaImage");
00940 
00941   // make a composite of the input + the drawings:
00942   Image< PixRGB<byte> > comp = composite(itsDrawings, input);
00943 
00944   // return a plain traj?
00945   if (itsSaveTraj.getVal()) return comp;
00946 
00947   // let's get the current saliency map (normalized):
00948   const Dims dims = input.getDims();
00949   Image<float> sm = getMap(q, false);
00950   if (sm.initialized()) sm = rescaleOpt(sm, dims, itsDisplayInterp.getVal());
00951   else sm.resize(dims, true); // blank
00952   Image< PixRGB<byte> > smc = toRGB(Image<byte>(sm));
00953 
00954   // make a composite of the instantaneous SM + the drawings:
00955   Image< PixRGB<byte> > smcomp = composite(itsDrawings, smc);
00956 
00957   // otherwise, return mega combo; we have two formats: if we have a
00958   // max-cache, it will be a 4-image format, otherwise a 2-image
00959   // format:
00960   Image< PixRGB<byte> > ret;
00961   if (itsMaxCacheSize.getVal())
00962     {
00963       // 4-image format
00964       Image<float> maxsm =
00965         rescaleOpt(itsMaxCache.getMax(), dims, itsDisplayInterp.getVal());
00966       Image< PixRGB<byte> > maxsmc = toRGB(Image<byte>(maxsm));
00967 
00968       ret = concatX(concatY(input, smcomp),
00969                     concatY(comp, composite(itsDrawings, maxsmc)));
00970 
00971       drawGrid(ret, dims.w()-1, dims.h()-1, 3, 3, PixRGB<byte>(128, 128, 128));
00972     }
00973   else
00974     {
00975       // 2-image format:
00976       ret = concatX(comp, smcomp);
00977       drawLine(ret, Point2D<int>(dims.w()-1, 0), Point2D<int>(dims.w()-1, dims.h()-1),
00978                PixRGB<byte>(255, 255, 0), 1);
00979       drawLine(ret, Point2D<int>(dims.w(), 0), Point2D<int>(dims.w(), dims.h()-1),
00980                PixRGB<byte>(255, 255, 0), 1);
00981     }
00982 
00983   // make sure image is not unreasonably large:
00984   while (ret.getWidth() > itsMaxComboWidth.getVal())
00985     ret = decY(lowPass3y(decX(lowPass3x(ret))));
00986 
00987   return ret;
00988 }
00989 
00990 // ######################################################################
00991 void SimulationViewerEyeHand::
00992 onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e)
00993 {
00994   this->save1(e->sinfo());
00995 }
00996 
00997 // ######################################################################
00998 void SimulationViewerEyeHand::save1(const ModelComponentSaveInfo& sinfo)
00999 {
01000 GVX_TRACE(__PRETTY_FUNCTION__);
01001   SimEventQueue *q = dynamic_cast<const SimModuleSaveInfo&>(sinfo).q;
01002 
01003   // update the trajectory:
01004   Image< PixRGB<byte> > res = getTraj(*q);
01005 
01006   // save results?
01007   if (itsSaveCombo.getVal() || itsSaveTraj.getVal())
01008     {
01009       // get the OFS to save to, assuming sinfo is of type
01010       // SimModuleSaveInfo (will throw a fatal exception otherwise):
01011       nub::ref<FrameOstream> ofs =
01012         dynamic_cast<const SimModuleSaveInfo&>(sinfo).ofs;
01013 
01014       ofs->writeRGB(res, "T",
01015                     FrameInfo("SimulationViewerEyeHand trajectory", SRC_POS));
01016 
01017     }
01018 }
01019 
01020 // ######################################################################
01021 /* So things look consistent in everyone's emacs... */
01022 /* Local Variables: */
01023 /* indent-tabs-mode: nil */
01024 /* End: */
Generated on Sun May 8 08:05:25 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3