00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
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
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
00094 if (itsParTrig.getVal())
00095 itsParPort->WriteData(255, 255);
00096
00097 EyeTracker::start2();
00098 }
00099
00100
00101 void EyeTrackerISCAN::calibrate(nub::soft_ref<PsychoDisplay> d)
00102 {
00103
00104 d->clearScreen();
00105 d->displayISCANcalib();
00106 d->waitForKey(true);
00107
00108
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
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
00165 itsParPort->WriteData(255, 0);
00166 }
00167 else
00168 {
00169 LINFO("requestEyeS was false");
00170 itsParPort->WriteData(255, 0);
00171 }
00172
00173 }
00174 else
00175 {
00176
00177 LINFO("using serial port so screwed");
00178 char cmd[1]; cmd[0] = 132;
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
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
00199 itsParPort->WriteData(255, 255);
00200
00201
00202
00203
00204
00205
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
00239
00240
00241 }
00242 else
00243 {
00244
00245 itsParPort->WriteData(255, 255);
00246 }
00247
00248
00249
00250 }
00251 else
00252 {
00253
00254 char cmd[1]; cmd[0] = 136;
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
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286 unsigned char buf[256];
00287 int n;
00288 Point2D<int> eyePos(-1,-1);
00289
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)
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
00329
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
00334 return Point2D<int>(itsCurrentCalibEyePosX.atomic_get(),itsCurrentCalibEyePosY.atomic_get());
00335
00336
00337
00338 }
00339
00340
00341 CalibrationTransform::Data EyeTrackerISCAN::getCalibrationSet(nub::soft_ref<PsychoDisplay> d) const
00342 {
00343
00344
00345 LINFO("\n getting calibration set...");
00346
00347
00348
00349
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
00358
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
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;
00382 double diffX=0.0F,diffY=0.0F;
00383 AffineTransform tempAffine;
00384 CalibrationTransform::Data finalPts;
00385
00386
00387 while(!happy)
00388 {
00389
00390
00391
00392 d->pushEventBegin("EyeTrackerCalibration");
00393
00394 CalibrationTransform::Data firstFix;
00395
00396 for (int j=0;j<npts;j++)
00397 {
00398 d->clearScreen();
00399 tempPt = pts[j];
00400
00401
00402 d->displayFixation();
00403 d->waitForKey();
00404 d->displayFixationBlink();
00405 d->clearScreen();
00406 fixated = false;
00407 d->drawCalibPoint(tempPt);
00408
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
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
00451
00452
00453 d->pushEventEnd("EyeTrackerCalibration");
00454
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
00507 Point2D<int> raw;
00508
00509
00510 unsigned char buf[256];
00511 int n;
00512
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)
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
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
00552
00553
00554
00555
00556
00557 #endif // PSYCHO_EYETRACKERISCAN_C_DEFINED