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: */