00001 /*!@file Psycho/EyeTrackerISCAN.C Abstraction for an ISCAN eye-tracker */ 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/Psycho/EyeTrackerISCAN.C $ 00035 // $Id: EyeTrackerISCAN.C 14159 2010-10-22 04:04:17Z ilink $ 00036 // 00037 00038 #ifndef PSYCHO_EYETRACKERISCAN_C_DEFINED 00039 #define PSYCHO_EYETRACKERISCAN_C_DEFINED 00040 00041 #include "Psycho/EyeTrackerISCAN.H" 00042 00043 #include "Component/OptionManager.H" 00044 #include "Devices/ParPort.H" 00045 #include "Devices/Serial.H" 00046 #include "Psycho/PsychoOpts.H" 00047 #include "Psycho/PsychoDisplay.H" 00048 #include "Util/sformat.H" 00049 #include "Util/stats.H" 00050 #include <cmath> 00051 #include "Image/Point2D.H" 00052 #include "Image/DrawOps.H" 00053 #include "Image/AffineTransform.H" 00054 #include <fstream> 00055 00056 // ###################################################################### 00057 EyeTrackerISCAN::EyeTrackerISCAN(OptionManager& mgr, 00058 const std::string& descrName, 00059 const std::string& tagName) : 00060 EyeTracker(mgr, descrName, tagName), 00061 itsParTrig(&OPT_EyeTrackerParTrig, this), 00062 itsSerDev(&OPT_EyeTrackerSerDev, this), 00063 itsParDev(&OPT_EyeTrackerParDev, this), 00064 itsSerial(new Serial(mgr, "ISCAN Serial Port", "ISCANSerial")), 00065 itsParPort(new ParPort(mgr, "ISCAN Parallel Port", "ISCANParPort")), 00066 itsRecalibCount(0) 00067 { 00068 addSubComponent(itsSerial); 00069 addSubComponent(itsParPort); 00070 itsRequestQuickEyeS = false; 00071 00072 } 00073 00074 // ###################################################################### 00075 EyeTrackerISCAN::~EyeTrackerISCAN() 00076 { } 00077 00078 // ###################################################################### 00079 void EyeTrackerISCAN::start1() 00080 { 00081 // configure our serial and parallel ports: 00082 itsSerial->setModelParamVal("DevName", itsSerDev.getVal()); 00083 itsParPort->setModelParamVal("ISCANParPortDevName", itsParDev.getVal()); 00084 itsSerial->configure(itsSerDev.getVal().c_str(), 00085 115200, "8N1", false, false, 0); 00086 00087 EyeTracker::start1(); 00088 } 00089 00090 // ###################################################################### 00091 void EyeTrackerISCAN::start2() 00092 { 00093 // if using parallel trigger, be sure to initialize in non-tracking mode: 00094 if (itsParTrig.getVal()) 00095 itsParPort->WriteData(255, 255); // turn all data bits to on 00096 00097 EyeTracker::start2(); 00098 } 00099 00100 // ###################################################################### 00101 void EyeTrackerISCAN::calibrate(nub::soft_ref<PsychoDisplay> d) 00102 { 00103 // let's display an ISCAN calibration grid: 00104 d->clearScreen(); 00105 d->displayISCANcalib(); 00106 d->waitForKey(true); 00107 00108 // now run a 9-point calibration: 00109 d->clearScreen(); 00110 d->displayText("<SPACE> to calibrate; other key to skip"); 00111 int c = d->waitForKey(true); 00112 if (c == ' ') d->displayEyeTrackerCalibration(3, 3); 00113 } 00114 00115 // ###################################################################### 00116 void EyeTrackerISCAN::recalibrate(nub::soft_ref<PsychoDisplay> d, int repeats) 00117 { 00118 ++itsRecalibCount; 00119 if (itsRecalibCount == repeats) 00120 { 00121 itsRecalibCount = 0; 00122 d->clearScreen(); 00123 d->displayText("Ready for quick recalibration"); 00124 d->waitForKey(true); 00125 d->clearScreen(); 00126 d->displayISCANcalib(); 00127 d->waitForKey(true); 00128 d->displayEyeTrackerCalibration(3, 3); 00129 d->clearScreen(); 00130 d->displayText("Ready to continue"); 00131 d->waitForKey(true); 00132 } 00133 } 00134 00135 // ###################################################################### 00136 void EyeTrackerISCAN::calibrateOnline(nub::soft_ref<PsychoDisplay> d) 00137 { 00138 00139 00140 CalibrationTransform::Data pts; 00141 pts = getCalibrationSet(d); 00142 LINFO("got calibration set computing transform"); 00143 itsAffine.computeTransform(pts); 00144 } 00145 00146 00147 // ###################################################################### 00148 void EyeTrackerISCAN::startTracking() 00149 { 00150 if (itsParTrig.getVal()) 00151 { 00152 if(itsRequestQuickEyeS) 00153 { 00154 LINFO("\n creating new Thread now"); 00155 //clear the eyePosEvents buffer and create the thread 00156 itsEyePosEvents.resize(0); 00157 itsEyePosEvents.reserve(240 * 60 * 60); 00158 00159 if (0 != pthread_create(&itsEyePosPollThread, NULL, 00160 &EyeTrackerISCAN::eyePosPollThread, 00161 (void*)(this))) 00162 LFATAL("Cannot create thread"); 00163 00164 // start tracker using parallel port: 00165 itsParPort->WriteData(255, 0); // turn all data bits to off 00166 } 00167 else 00168 { // start tracker using parallel port: 00169 LINFO("requestEyeS was false"); 00170 itsParPort->WriteData(255, 0); // turn all data bits to off 00171 } 00172 00173 } 00174 else 00175 { 00176 // start tracker using serial port: 00177 LINFO("using serial port so screwed"); 00178 char cmd[1]; cmd[0] = 132; // ISCAN start command 00179 itsSerial->write(cmd, 1); 00180 } 00181 } 00182 00183 // ###################################################################### 00184 void EyeTrackerISCAN::stopTracking() 00185 { 00186 if (itsParTrig.getVal()) 00187 { 00188 if(itsRequestQuickEyeS) 00189 { 00190 00191 //stop thread recording live eyePos 00192 if (0 != pthread_cancel(itsEyePosPollThread)) 00193 LFATAL("pthread_cancel failed"); 00194 00195 if (0 != pthread_join(itsEyePosPollThread, NULL)) 00196 LFATAL("pthread_join failed"); 00197 00198 // stop tracker using parallel port: 00199 itsParPort->WriteData(255, 255); // turn all data bits to on 00200 00201 //#####################theEyeSFiledump##################/ 00202 00203 //TODO:dumpdata to eyeS file 00204 //iterate through eyePosevents 00205 //format time write to eyeData class use IO for eyeS 00206 00207 00208 if (itsEyePosEvents.empty() == false) 00209 { 00210 const char *fname = getCurrentStimFile().c_str(); 00211 std::ofstream ofs(fname); 00212 if (!ofs.is_open()) 00213 LERROR("Couldn't open file '%s' for writing.", fname); 00214 else 00215 { 00216 std::vector<EyePosEvent>::const_iterator itr = itsEyePosEvents.begin(); 00217 00218 while (itr != itsEyePosEvents.end()) 00219 { 00220 const uint64 t = itr->tim; 00221 const int usec = int(t % 1000ULL); 00222 const int msec = int((t / 1000ULL) % 1000ULL); 00223 const int sec = int((t / 1000000ULL) % 60ULL); 00224 const int minu = int((t / 60000000ULL) % 60ULL); 00225 const int hour = int(t / 3600000000ULL); 00226 ofs << sformat("%03d:%02d:%02d.%03d.%03d", 00227 hour, minu, sec, msec, usec) 00228 << " " << itr->pt.i << " " << itr->pt.j << std::endl; 00229 ++ itr; 00230 } 00231 ofs.close(); 00232 LINFO("Saved log to '%s'", fname); 00233 } 00234 } 00235 00236 00237 00238 //################endfiledump###################### 00239 00240 00241 } 00242 else 00243 { 00244 // stop tracker using parallel port: 00245 itsParPort->WriteData(255, 255); // turn all data bits to on 00246 } 00247 00248 00249 00250 } 00251 else 00252 { 00253 // stop tracker using serial port: 00254 char cmd[1]; cmd[0] = 136; // ISCAN stop command 00255 itsSerial->write(cmd, 1); 00256 } 00257 } 00258 00259 // ###################################################################### 00260 bool EyeTrackerISCAN::isFixating() 00261 { 00262 LFATAL("Unimplemented for now"); 00263 return false; 00264 } 00265 00266 // ###################################################################### 00267 bool EyeTrackerISCAN::isSaccade() 00268 { 00269 LFATAL("Unimplemented for now"); 00270 return false; 00271 } 00272 00273 // ###################################################################### 00274 Point2D<int> EyeTrackerISCAN::getEyePos() const 00275 { 00276 /*if (!itsParTrig.getVal()) 00277 LFATAL("must use parallel-port triggering"); 00278 00279 int val = itsCurrentRawEyePos.atomic_get(); 00280 // return itsCurrentRawEyePos.set(val & 0xfff, 00281 // (val & 0xfff000) >> 12); 00282 00283 return Point2D<int>(val & 0xfff,(val & 0xfff000 >> 12)); 00284 */ 00285 00286 unsigned char buf[256]; 00287 int n; 00288 Point2D<int> eyePos(-1,-1); 00289 //prefix byte 0 = prefix byte 1 = hex value = 0x44 00290 00291 n = itsSerial->read(buf,256); 00292 bool gotHeader =false; 00293 00294 for(int i=0;i<n-1;i++) 00295 { 00296 00297 if(gotHeader && n-i>5) //ensure enough data to read 00298 { 00299 eyePos.i = buf[i+2] <<8; 00300 eyePos.i += buf[i+1]; 00301 eyePos.j = buf[i+4] <<8; 00302 eyePos.j += buf[i+3]; 00303 break; 00304 00305 } 00306 00307 if(buf[i] == 0x44 && buf[i+1] == 0x44) 00308 gotHeader = true; 00309 } 00310 00311 return eyePos; 00312 } 00313 00314 // ###################################################################### 00315 Point2D<int> EyeTrackerISCAN::getFixationPos() const 00316 { 00317 LFATAL("Unavailable on DML tracker, sorry."); 00318 return Point2D<int>(0, 0); 00319 } 00320 00321 // ###################################################################### 00322 Point2D<int> EyeTrackerISCAN::getCalibEyePos() 00323 { 00324 if (!itsParTrig.getVal()) 00325 LFATAL("must use parallel-port triggering"); 00326 00327 int val = itsCurrentCalibEyePos.atomic_get(); 00328 //return itsCurrentCalibEyePos.set(val & 0xfff, 00329 // (val & 0xfff000) >> 12); 00330 int val2 = itsCurrentRawEyePos.atomic_get(); 00331 LINFO("eye pos at getRawEyePos %d,%d",val2 & 0xfff,(val2 & 0xfff000 >> 12)); 00332 LINFO("Eye Pos at thread calib = (%d,%d)",val & 0xfff,(val & 0xfff000 >> 12)); 00333 //return Point2D<int>(val & 0xfff,(val & 0xfff000 >> 12)); 00334 return Point2D<int>(itsCurrentCalibEyePosX.atomic_get(),itsCurrentCalibEyePosY.atomic_get()); 00335 00336 //Point2D<double> rawPos = Point2D<double>(getEyePos()); 00337 //return Point2D<int>(itsAffine.getCalibrated(rawPos)); 00338 } 00339 00340 // ###################################################################### 00341 CalibrationTransform::Data EyeTrackerISCAN::getCalibrationSet(nub::soft_ref<PsychoDisplay> d) const 00342 { 00343 /*get the calibration data for online calibration*/ 00344 00345 LINFO("\n getting calibration set..."); 00346 00347 00348 //start with simple test to see if someone is fixating then 00349 //say fixating with location 00350 00351 int fixWindow = 75; 00352 std::vector<Point2D<int> > temp(fixWindow); 00353 std::vector<float> tempX(fixWindow); 00354 std::vector<float> tempY(fixWindow); 00355 float xMean,yMean,xStd,yStd,xVar,yVar; 00356 00357 //you need a minimum of 4 calibration points to ensure inverse matrix 00358 //computations are legal 00359 00360 const int nptshoriz=3, nptsvertic=3; 00361 const int npts = nptshoriz*nptsvertic; 00362 Image<double> displayImage(1920,1080,ZEROS); 00363 Dims dispDims = displayImage.getDims(); 00364 int w=dispDims.w(), h = dispDims.h(); 00365 00366 00367 int deltax = w / (nptshoriz + 1), deltay = h / (nptsvertic + 1); 00368 00369 // list all the points we want: 00370 std::vector<Point2D<int> > pts; 00371 for (int j = deltay-1; j < h - deltay; j += deltay) 00372 for (int i = deltax-1; i < w - deltax; i += deltax) 00373 pts.push_back(Point2D<int>(i, j)); 00374 00375 Point2D<int> tempPt,tempCalibPt; 00376 std::vector<Point2D<int> > eyeMeans(npts); 00377 stats<float> Stats; 00378 bool fixated; 00379 d->clearScreen(); 00380 char tmp[50]; 00381 bool happy=false; //happy with calib or not 00382 double diffX=0.0F,diffY=0.0F; 00383 AffineTransform tempAffine; 00384 CalibrationTransform::Data finalPts; 00385 00386 00387 while(!happy) 00388 { 00389 //startTracking(); 00390 //event log code marked as EL-code eventually should be removed 00391 //EL-code 00392 d->pushEventBegin("EyeTrackerCalibration"); 00393 //~EL-code 00394 CalibrationTransform::Data firstFix; 00395 00396 for (int j=0;j<npts;j++) 00397 { 00398 d->clearScreen(); 00399 tempPt = pts[j]; 00400 //d->drawCalibPoint(tempPt); 00401 //rintf(tmp,"press key when ready"); 00402 d->displayFixation(); 00403 d->waitForKey(); 00404 d->displayFixationBlink(); 00405 d->clearScreen(); 00406 fixated = false; 00407 d->drawCalibPoint(tempPt); 00408 //EL-code 00409 d->pushEventBegin(sformat("eyeTrackerCalibration at (%d, %d)", tempPt.i, tempPt.j)); 00410 while(fixated==false) 00411 { 00412 00413 00414 for(int i=0; i < fixWindow; i++) 00415 { 00416 temp[i] = getEyePos(); 00417 if((temp[i].i < 0) || (temp[i].j<0)) 00418 i--; 00419 else 00420 { 00421 LINFO("I got eyePos %d,%d",temp[i].i, temp[i].j); 00422 tempX[i] = temp[i].i; 00423 tempY[i] = temp[i].j; 00424 } 00425 } 00426 00427 xMean = Stats.mean(tempX); 00428 yMean = Stats.mean(tempY); 00429 xStd = Stats.findS(tempX,xMean); 00430 xVar = Stats.S2; 00431 yStd = Stats.findS(tempY,yMean); 00432 yVar = Stats.S2; 00433 00434 00435 if((xVar < 5.0) && (yVar < 5.0) && (xMean > 0) && (yMean >0)) 00436 { 00437 //EL-code 00438 d->pushEventEnd(sformat("eyeTrackerCalibration at (%d, %d)", tempPt.i, tempPt.j)); 00439 fixated = true; 00440 d->displayText("Found stable fixation"); 00441 LINFO("####Calibration Point %d #######",j+1); 00442 LINFO("found fixation varX = %f, var Y =%f....meanX = %f meanY = %f ",xStd,yStd,xMean,yMean); 00443 LINFO("raw (%f,%f) scr(%d,%d)",xMean,yMean,pts[j].i,pts[j].j); 00444 eyeMeans[j] = Point2D<int>((int)xMean,(int)yMean); 00445 firstFix.addData(Point2D<double>(xMean,yMean),Point2D<double>(tempPt)); 00446 } 00447 00448 } 00449 } 00450 //lets display a grid show original screen points in red squares and calib pts 00451 //in blue 00452 //EL-Code 00453 d->pushEventEnd("EyeTrackerCalibration"); 00454 // stopTracking(); 00455 tempAffine.computeTransform(firstFix); 00456 for (int j=0;j<npts;j++) 00457 { 00458 tempPt = pts[j]; 00459 d->drawCalibPoint(tempPt); 00460 tempCalibPt = Point2D<int>(tempAffine.getCalibrated(Point2D<double>(eyeMeans[j]))); 00461 d->drawPointColor(tempCalibPt,PixRGB<byte>(0,0,255)); 00462 diffX += abs(tempPt.i - tempCalibPt.i); 00463 diffY += abs(tempPt.j - tempCalibPt.j); 00464 LINFO("raw(%d,%d),calib(%d,%d)",tempPt.i,tempPt.j,tempCalibPt.i,tempCalibPt.j); 00465 00466 } 00467 d->waitForKey(); 00468 sprintf(tmp,"avg X difference = %f, avg Y difference = %f \n--press 5 to repeat Calibration",diffX/npts,diffY/npts); 00469 d->displayText(tmp); 00470 int c=d->waitForKey(true); 00471 if(c != '5') 00472 { 00473 happy = true; 00474 finalPts= firstFix; 00475 LINFO("setfirstfix"); 00476 00477 } 00478 } 00479 00480 d->clearScreen(); 00481 d->displayText("Calibrated!"); 00482 LINFO("calibration ended"); 00483 return finalPts; 00484 } 00485 00486 00487 // #################################################################### 00488 void EyeTrackerISCAN::requestQuickEyeS() 00489 { 00490 itsRequestQuickEyeS=true; 00491 } 00492 00493 //##################################################################### 00494 00495 00496 void* EyeTrackerISCAN::eyePosPollThread(void* p) 00497 { 00498 EyeTrackerISCAN* et = static_cast<EyeTrackerISCAN*>(p); 00499 00500 Timer timer(1000000); 00501 00502 timer.reset(); 00503 00504 while (true) 00505 { 00506 // read one sample from the serial port 00507 Point2D<int> raw; 00508 00509 //###############serial reading code###################/ 00510 unsigned char buf[256]; 00511 int n; 00512 //prefix byte 0 = prefix byte 1 = hex value = 0x44 00513 00514 n = et->itsSerial->read(buf,256); 00515 bool gotHeader =false; 00516 00517 for(int i=0;i<n-1;i++) 00518 { 00519 00520 if(gotHeader && n-i>5) //ensure enough data to read 00521 { 00522 raw.i = buf[i+2] <<8; 00523 raw.i += buf[i+1]; 00524 raw.j = buf[i+4] <<8; 00525 raw.j += buf[i+3]; 00526 break; 00527 } 00528 00529 if(buf[i] == 0x44 && buf[i+1] == 0x44) 00530 gotHeader = true; 00531 } 00532 00533 00534 EyePosEvent ev; 00535 ev.tim = timer.get(); 00536 ev.pt = Point2D<int16>(et->itsAffine.getCalibrated(Point2D<double>(raw))); 00537 //LINFO("Eye Pos at thread raw = (%d,%d) calib=(%d,%d)",raw.i,raw.j, ev.pt.i,ev.pt.j); 00538 et->itsEyePosEvents.push_back(ev); 00539 et->itsCurrentRawEyePos.atomic_set(raw.i + (raw.j << 12)); 00540 et->itsCurrentCalibEyePos.atomic_set(ev.pt.i + (ev.pt.j << 12)); 00541 et->itsCurrentCalibEyePosX.atomic_set(ev.pt.i); 00542 et->itsCurrentCalibEyePosY.atomic_set(ev.pt.j); 00543 } 00544 00545 return NULL; 00546 } 00547 00548 // ##################################################################### 00549 00550 00551 /* So things look consistent in everyone's emacs... */ 00552 /* Local Variables: */ 00553 /* mode: c++ */ 00554 /* indent-tabs-mode: nil */ 00555 /* End: */ 00556 00557 #endif // PSYCHO_EYETRACKERISCAN_C_DEFINED