EyeHeadControllers.C

Go to the documentation of this file.
00001 /*!@file Neuro/EyeHeadControllers.C Eye/Head controllers */
00002 
00003 // //////////////////////////////////////////////////////////////////// //
00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00005 // by the University of Southern California (USC) and the iLab at USC.  //
00006 // See http://iLab.usc.edu for information about this project.          //
00007 // //////////////////////////////////////////////////////////////////// //
00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00010 // in Visual Environments, and Applications'' by Christof Koch and      //
00011 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00012 // pending; application number 09/912,225 filed July 23, 2001; see      //
00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status).     //
00014 // //////////////////////////////////////////////////////////////////// //
00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00016 //                                                                      //
00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00018 // redistribute it and/or modify it under the terms of the GNU General  //
00019 // Public License as published by the Free Software Foundation; either  //
00020 // version 2 of the License, or (at your option) any later version.     //
00021 //                                                                      //
00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00025 // PURPOSE.  See the GNU General Public License for more details.       //
00026 //                                                                      //
00027 // You should have received a copy of the GNU General Public License    //
00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00030 // Boston, MA 02111-1307 USA.                                           //
00031 // //////////////////////////////////////////////////////////////////// //
00032 //
00033 // Primary maintainer for this file: Laurent Itti <itti@usc.edu>
00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Neuro/EyeHeadControllers.C $
00035 // $Id: EyeHeadControllers.C 14376 2011-01-11 02:44:34Z pez $
00036 //
00037 
00038 #include "Neuro/EyeHeadControllers.H"
00039 #include "Neuro/SaccadeControllers.H"
00040 #include "Neuro/NeuroOpts.H"
00041 #include "Neuro/NeuroSimEvents.H"
00042 #include "Neuro/SpatialMetrics.H"
00043 #include "Component/OptionManager.H"
00044 #include "Psycho/EyeTrace.H"
00045 #include "Simulation/SimEventQueue.H"
00046 #include "Util/StringConversions.H"
00047 #include "Util/StringUtil.H"
00048 #include "Util/sformat.H"
00049 
00050 #include <vector>
00051 
00052 // ######################################################################
00053 // ######################################################################
00054 // ######################################################################
00055 StubEyeHeadController::StubEyeHeadController(OptionManager& mgr) :
00056   EyeHeadController(mgr, "Stub Eye/Head Controller",
00057                     "StubEyeHeadController")
00058 {  }
00059 
00060 // ######################################################################
00061 StubEyeHeadController::~StubEyeHeadController()
00062 {  }
00063 
00064 // ######################################################################
00065 // ######################################################################
00066 // ######################################################################
00067 SimpleEyeHeadController::SimpleEyeHeadController(OptionManager& mgr) :
00068   EyeHeadController(mgr, "Simple Eye/Head Controller",
00069                     "SimpleEyeHeadController"),
00070   SIMCALLBACK_INIT(SimEventClockTick),
00071   itsSCCeye(new SaccadeControllerEyeConfigurator(mgr)),
00072   itsSCChead(new SaccadeControllerHeadConfigurator(mgr)),
00073   itsSCeye(), itsSChead()
00074 {
00075   addSubComponent(itsSCCeye);
00076   addSubComponent(itsSCChead);
00077 }
00078 
00079 // ######################################################################
00080 SimpleEyeHeadController::~SimpleEyeHeadController()
00081 {  }
00082 
00083 // ######################################################################
00084 void SimpleEyeHeadController::start1()
00085 {
00086   // install our shortcuts:
00087   itsSCeye = itsSCCeye->getSC();
00088   itsSChead = itsSCChead->getSC();
00089 
00090   if (itsSCeye.is_invalid() || itsSChead.is_invalid())
00091     LFATAL("I need valid eye and head SaccadeControllers");
00092 
00093   EyeHeadController::start1();
00094 }
00095 
00096 // ######################################################################
00097 void SimpleEyeHeadController::
00098 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect)
00099 {
00100   // catch any WTAwinner and feed to eye:
00101   if (SeC<SimEventWTAwinner> e = q.check<SimEventWTAwinner>(this))
00102     itsSCeye->setPercept(e->winner(), q);
00103 
00104   // catch any SaccadeController eye status event:
00105   if (SeC<SimEventSaccadeStatusEye> e = q.check<SimEventSaccadeStatusEye>(this))
00106     {
00107       WTAwinner win(e->position(), q.now(), 0.0, false);
00108       itsSChead->setPercept(win, q);
00109     }
00110 
00111   // evolve our controllers; they will post events to let everyone
00112   // know what they are up to:
00113   itsSCeye->evolve(q);
00114   itsSChead->evolve(q);
00115 
00116   // force a computation of the new decisions and instruct our
00117   // controllers to post a status event to let everyone know what we
00118   // are up to:
00119   itsSCeye->getDecision(q, true);
00120   itsSChead->getDecision(q, true);
00121 }
00122 
00123 // ######################################################################
00124 // ######################################################################
00125 // ######################################################################
00126 EyeTrackerEyeHeadController::EyeTrackerEyeHeadController(OptionManager& mgr) :
00127   EyeHeadController(mgr, "EyeTracker Eye/Head Controller",
00128                     "EyeTrackerEyeHeadController"),
00129   SIMCALLBACK_INIT(SimEventClockTick),
00130   itsConfig(&OPT_EHCeyeTrackConfig, this),
00131   itsEyeTrace(), itsEyeSample()
00132 {  }
00133 
00134 // ######################################################################
00135 EyeTrackerEyeHeadController::~EyeTrackerEyeHeadController()
00136 {  }
00137 
00138 // ######################################################################
00139 void EyeTrackerEyeHeadController::start1()
00140 {
00141   // parse our config string and instantiate all our trackers:
00142   std::vector<std::string> tok;
00143   split(itsConfig.getVal(), ",", std::back_inserter(tok));
00144   if (tok.empty()) LFATAL("I cannot run without at least one eyetrace.");
00145 
00146   for (uint i = 0; i < tok.size(); i ++)
00147     {
00148       std::vector<std::string> tt;
00149       split(tok[i], ":", std::back_inserter(tt));
00150       if (tt.empty()) LFATAL("Invalid empty eye-tracker filename");
00151 
00152       std::string fname = tt[0];
00153       std::string extras = join(tt.begin() + 1, tt.end(), ":");
00154 
00155       LINFO("Instantiating Tracker %03d with file '%s', extras '%s'",
00156             i, fname.c_str(), extras.c_str());
00157 
00158       // the only extra we support for now is a color:
00159       PixRGB<byte> color(128, 255, 255);
00160       if (tt.size() > 1) convertFromString(tt[1], color);
00161 
00162       // instantiate a new EyeTrace object:
00163       rutz::shared_ptr<EyeTrace> et(new EyeTrace(fname, color));
00164 
00165       itsEyeTrace.push_back(et);
00166       itsEyeSample.push_back(0);
00167     }
00168 
00169   EyeHeadController::start1();
00170 }
00171 
00172 // ######################################################################
00173 void EyeTrackerEyeHeadController::
00174 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect)
00175 {
00176   const SimTime t = q.now();
00177 
00178   // evolve all our babies and post their data:
00179   bool keepgoing = true;
00180   while(keepgoing) // will go on until no tracker has any new data
00181     {
00182       keepgoing = false;
00183 
00184       // loop over our trackers:
00185       for (uint i = 0; i < itsEyeTrace.size(); i ++)
00186         if (itsEyeTrace[i]->hasData(itsEyeSample[i], t))
00187           {
00188             // ok, this warrants that we continue our while() loop:
00189             keepgoing = true;
00190 
00191             // get the next data sample:
00192             rutz::shared_ptr<EyeData> data = itsEyeTrace[i]->data(itsEyeSample[i]);
00193 
00194             CLDEBUG("Human eye %03u [%07"ZU"] (%d, %d) at %.1fms",
00195                     i, itsEyeSample[i], data->position().i, data->position().j,
00196                     itsEyeSample[i] * itsEyeTrace[i]->period().msecs());
00197 
00198             // post an event with it:
00199             q.post(rutz::make_shared(new SimEventEyeTrackerData(this, data, i, itsEyeTrace[i]->filename(), itsEyeTrace[i]->color(), itsEyeTrace[i]->ppd(),itsEyeTrace[i]->period())));
00200             
00201             // get the previous status info, if possible:
00202             SaccadeState prevSacState = SACSTATE_UNK;
00203             bool prevBlinkState = false;
00204             if (itsEyeSample[i] > 0) {
00205               rutz::shared_ptr<EyeData> prevEyeData = itsEyeTrace[i]->data(itsEyeSample[i] - 1);
00206               prevSacState = prevEyeData->saccadeState();
00207               prevBlinkState = prevEyeData->isInBlink();
00208             }
00209 
00210             // also send eye event; this one we will shift by any retinal input shift if available:
00211             Point2D<int> retinaleyepos = data->position();
00212             if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this, SEQ_ANY))
00213               retinaleyepos = e->rawToRetinal(retinaleyepos);
00214 
00215             q.post(rutz::make_shared(new SimEventSaccadeStatusEye(this, retinaleyepos, data->saccadeState(),
00216                                                                   prevSacState, data->isInBlink(), prevBlinkState)));
00217 
00218             // ready for next eye movement sample:
00219             ++ itsEyeSample[i];
00220           }
00221     }
00222 }
00223 
00224 // ######################################################################
00225 // ######################################################################
00226 // ######################################################################
00227 MonkeyEyeHeadController::
00228 MonkeyEyeHeadController(OptionManager& mgr) :
00229   EyeHeadController(mgr, "Monkey Eye/Head Controller", "MonkeyEyeHeadController"),
00230   SIMCALLBACK_INIT(SimEventClockTick),
00231   itsBlinkWait(&OPT_SCeyeBlinkWaitTime, this), // see Neuro/NeuroOpts.{H,C}
00232   itsBlinkDur(&OPT_SCeyeBlinkDuration, this), // see Neuro/NeuroOpts.{H,C}
00233   itsOdist(&OPT_SCeyeThreshMinOvert, this), // see Neuro/NeuroOpts.{H,C}
00234   itsMetrics(new SpatialMetrics(mgr)),
00235   itsTSC(new ThresholdSaccadeController(mgr, SaccadeBodyPartEye)),
00236   lastsbt(), blinkt()
00237 {
00238   this->addSubComponent(itsMetrics);
00239 
00240   // set default eye and head saccade controllers:
00241   /////  itsSCCeye->setModelParamString("SaccadeControllerEyeType", "Monkey");
00242   //////  itsSCChead->setModelParamString("SaccadeControllerHeadType", "Monkey");
00243 
00244   this->addSubComponent(itsTSC);
00245 
00246   lastsbt = SimTime::ZERO(); blinkt = SimTime::ZERO();
00247 }
00248 
00249 // ######################################################################
00250 MonkeyEyeHeadController::~MonkeyEyeHeadController()
00251 {  }
00252 
00253 // ######################################################################
00254 void MonkeyEyeHeadController::
00255 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect)
00256 {
00257   //const SimTime t = q.now();
00258 
00259   /*  // catch any WTAwinner and feed to eye and TSC:
00260   if (SeC<SimEventWTAwinner> e = q.check<SimEventWTAwinner>(this))
00261     this->setPercept(e->winner(), q);
00262 
00263   // evolve the thresholdfriction part of our business:
00264   itsTSC->evolve(q);
00265   Point2D<int> tsc = itsTSC->getDecision(q);
00266 
00267   // evolve the eye part of our business:
00268   itsSCeye->evolve(q);
00269   Point2D<int> eye = itsSCeye->getDecision(q);
00270 
00271 
00272   // did we just end our eye saccade?
00273   if (itsSCeye->saccadeStatus() == EVENT_END) lastsbt = t;
00274 
00275   // feed current eye position to head if it is different from the
00276   // last percept we have fed to the head:
00277   Point2D<int> headlast = itsSChead->getPreviousPercept(0).p;
00278   if (eye.isValid() && (eye.i != headlast.i || eye.j != headlast.j)) {
00279     WTAwinner win(eye, t, 0.0, false);
00280     itsSChead->setPercept(win, q);
00281   }
00282 
00283   // evolve the head part of our business:
00284   itsSChead->evolve(q);
00285   Point2D<int> head = itsSChead->getDecision(q);
00286 
00287   // are we blinking but time is up?
00288   if (itsSCeye->getBlinkState() == true && t - blinkt > itsBlinkDur.getVal())
00289     {
00290       LINFO("===== Ending eye blink at t=%.2fms =====", t.msecs());
00291       ////////////////      itsSCeye->endBlink(); lastsbt = t;
00292     }
00293 
00294   // do we want to blink?
00295   if (itsSCeye->getBlinkState() == false && itsSCeye->isInSaccade() == false &&
00296       t - lastsbt > itsBlinkWait.getVal())
00297     {
00298       // check whether our percepts are nicely clustered (no action going on)
00299       bool areclose; Point2D<int> avgp;
00300       itsTSC->checkPercepts(areclose, avgp);
00301       if (areclose)
00302         {
00303           // start a blink with very small probability, or with a higher
00304           // probability if the percepts are clustered, or for sure if it
00305           // has been a long time even though the percepts may not be
00306           // clustered:
00307           double r = randomDouble();
00308           if (r > 0.99995 ||
00309               (r > 0.9999 && areclose == true) ||
00310               (r > 0.999 && t - lastsbt > itsBlinkWait.getVal() * 2.0) ||
00311               (r > 0.9 && t - lastsbt > itsBlinkWait.getVal() * 3.0))
00312             {
00313               LINFO("===== Starting blink at t=%.2fms (last at %.2fms)=====",
00314                     t.msecs(), lastsbt.msecs());
00315               //////////////////////              itsSCeye->beginBlink(); blinkt = t;
00316             }
00317         }
00318     }
00319   */
00320 }
00321 
00322 // ######################################################################
00323 void MonkeyEyeHeadController::setPercept(const WTAwinner& fix,
00324                                          SimEventQueue& q)
00325 {
00326   // do the thresholdfriction part of our business:
00327   itsTSC->setPercept(fix, q);
00328   /*
00329   // get our current eye and head positions:
00330   Point2D<int> curreye = itsSCeye->getPreviousDecision(0).p;
00331   Point2D<int> currhead = itsSChead->getPreviousDecision(0).p;
00332 
00333   // if itsTSC recommends a saccade, let's execute it:
00334   Point2D<int> target = itsTSC->getDecision(q);
00335   if (target.i != -1)
00336     {
00337       const float sacthresh =
00338         float(itsMetrics->getFoveaRadius()) * itsOdist.getVal();
00339 
00340       // if the move recommended by itsTSC is large enough, and we are
00341       // not blinking, initiate a realistic saccade:
00342       if (target.distance(curreye) >= sacthresh &&
00343           itsSCeye->isInBlink() == false)
00344         {
00345           LINFO("=== Initiating saccade to (%d, %d) at %.2fms ===",
00346                 target.i, target.j, fix.t.msecs());
00347 
00348           // find the eye and head contributions:
00349           double eyecx, eyecy, headcx, headcy, tpx, tpy;
00350           itsMetrics->pix2deg(curreye, eyecx, eyecy);
00351           itsMetrics->pix2deg(currhead, headcx, headcy);
00352           itsMetrics->pix2deg(target, tpx, tpy);
00353 
00354           // get horizontal head displacement:
00355           const double hampx = headAmplitude(eyecx, headcx, tpx);
00356 
00357           // get vertical head displacement:
00358           const double hampy = headAmplitude(eyecy, headcy, tpy);
00359 
00360           // now, we'll send the eyes directly to the target and we'll
00361           // send the head to an angular displacement of (hampx, hampy):
00362           ///////////////itsSCeye->saccade(Point2DT(target, fix.t));
00363 
00364           Point2D<int> htarget;
00365           itsMetrics->deg2pix(hampx + headcx, hampy + headcy, htarget);
00366           ///////////////itsSChead->saccade(Point2DT(htarget, fix.t));
00367         }
00368       else
00369         {
00370           LINFO("=== Smooth pursuit to (%d, %d) at %.2fms ===",
00371                 target.i, target.j, fix.t.msecs());
00372 
00373           // we have a recommendation from itsTSC, but it's too small for
00374           // a realistic saccade. Just use it as friction targets:
00375           WTAwinner win(fix); // new target for our eye controller
00376           win.p = target;     // use target location from itsTSC as percept loc
00377           itsSCeye->setPercept(win, q);
00378 
00379           // the head follows the eye:
00380           win.p = itsSCeye->getPreviousDecision(0).p;
00381           itsSChead->setPercept(win, q);
00382         }
00383     }
00384   else
00385     {
00386       // we have no recommendation from itsTSC
00387 
00388       // set friction targets: the eye follows the moving average if
00389       // the recent percepts are nicely clustered, otherwise the eyes
00390       // just don't move:
00391       bool areclose; Point2D<int> avgp;
00392       WTAwinner win(fix);
00393       itsTSC->checkPercepts(areclose, avgp);
00394       if (areclose)
00395         {
00396           LINFO("=== Smooth pursuit to moving avg (%d, %d) at %.2fms ===",
00397                 avgp.i, avgp.j, win.t.msecs());
00398           win.p = avgp;     // use target location from itsTSC as percept loc
00399           itsSCeye->setPercept(win, q);
00400         }
00401 
00402       // the head follows the eye:
00403       win.p = itsSCeye->getPreviousDecision(0).p;
00404       itsSChead->setPercept(win, q);
00405     }
00406   */
00407 }
00408 /*
00409 // ######################################################################
00410 Point2D<int> MonkeyEyeHeadController::getEyeDecision(const SimTime& t)
00411 {
00412 
00413   // get current eye position:
00414   Point2D<int> eye = itsSCeye->getDecision(q);  // run decision on itsSCeye
00415 
00416   // feed the current eye position as new percept to the head
00417   // controller, if it is differemt from what the head controller
00418   // already has as its last percept:
00419   Point2D<int> headlast = itsSChead->getPreviousPercept(0).p;
00420   if (eye.i != -1 && (eye.i != headlast.i || eye.j != headlast.j)) {
00421     WTAwinner win(eye, t, 0.0, false);
00422     itsSChead->setPercept(win, q);
00423   }
00424 
00425   // we only return eye movements here, so it's whatever itsSCeye says:
00426   return eye;
00427 }
00428 
00429 // ######################################################################
00430 Point2D<int> MonkeyEyeHeadController::getHeadDecision(const SimTime& t)
00431 {
00432   // get current head position:
00433   Point2D<int> head = itsSChead->getDecision(q);   // run decision on itsSChead
00434 
00435   if (head.i != -1)
00436     LINFO("### head at (%d, %d) target (%d, %d) ###", head.i, head.j,
00437           itsSChead->getPreviousPercept(0).p.i,
00438           itsSChead->getPreviousPercept(0).p.j);
00439 
00440   return head;
00441 }
00442 */
00443 // ######################################################################
00444 double MonkeyEyeHeadController::headAmplitude(const double curreye,
00445                                                const double currhead,
00446                                                const double target)
00447 {
00448   // This implementation follows Freedman, Biol Cybern, 2001.
00449 
00450   // initial eye position with respect to the head. Sign is positive
00451   // if the eyes begin deviated in the direction of the subsequent
00452   // movement, negative otherwise:
00453   double iep = (curreye - currhead) * signOf(target - curreye);
00454 
00455   // compute desired gaze displacement:
00456   double hgazedd = target - curreye;
00457 
00458   // compute head displacement dead zone max boundary (note: we here
00459   // deviate from Freedman's formulation by clamping out negative
00460   // values):
00461   double hhdz = 0.56 * (20.0 - 0.5 * iep); if (hhdz < 0.0) hhdz = 0.0;
00462 
00463   // if desired gaze displacement in dead zone, do not move the head,
00464   // otherwise compute slope hslh of tradeoff between dead zone
00465   // boundary and actual gaze displacement amplitude, and derive head
00466   // displacement:
00467   double hheaddd = 0.0, hslh = 0.0;
00468 
00469   if (fabs(hgazedd) >= hhdz)
00470     {
00471       // I think Freedman forgot to take the absolute value of iep, so
00472       // we do that here. Also we'll make sure the slope is smaller
00473       // than 1, as otherwise Freeman's equation for hheaddd yields
00474       // weird results. Finally, let's also make sure both terms of
00475       // the tradeoff have the sign of hgazedd:
00476       hslh = 0.65 * (fabs(iep) / 35.0 + 1.1); if (hslh > 1.0) hslh = 1.0;
00477       hheaddd = hgazedd * hslh + hhdz * (1.0 - hslh) * signOf(hgazedd);
00478     }
00479 
00480   LINFO("curreye=%f currhead=%f target=%f iep=%f hgazedd=%f hhdz=%f "
00481         "hslh=%f hheaddd=%f", curreye, currhead, target, iep, hgazedd, hhdz,
00482         hslh, hheaddd);
00483   return hheaddd;
00484 }
00485 
00486 
00487 
00488 // ######################################################################
00489 /* So things look consistent in everyone's emacs... */
00490 /* Local Variables: */
00491 /* mode: c++ */
00492 /* indent-tabs-mode: nil */
00493 /* End: */
Generated on Sun May 8 08:41:03 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3