00001 /*!@file Psycho/EyeTrackerEyeLink.C Abstraction for an EyeLink 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/EyeTrackerEyeLink.C $ 00035 // $Id: EyeTrackerEyeLink.C 14444 2011-01-21 17:04:13Z jshen $ 00036 // 00037 00038 #include "Psycho/EyeTrackerEyeLink.H" 00039 00040 #include "Component/OptionManager.H" 00041 #include "GUI/GUIOpts.H" 00042 #include "Psycho/PsychoOpts.H" 00043 #include "Util/sformat.H" 00044 #include "Component/EventLog.H" 00045 #include "Psycho/PsychoDisplay.H" 00046 #include <SDL/SDL.h> 00047 #include "Raster/Raster.H" 00048 00049 #ifdef HAVE_EYELINK 00050 #include <eyelink/eyelink.h> 00051 #include <eyelink/sdl_expt.h> 00052 #include <eyelink/sdl_text_support.h> 00053 ALLF_DATA evt; //buffer to hold sample and event data 00054 #endif 00055 00056 SDL_Color target_background_color = { 0, 0, 0}; 00057 SDL_Color target_foreground_color = { 192, 192, 192 }; 00058 00059 // ###################################################################### 00060 EyeTrackerEyeLink::EyeTrackerEyeLink(OptionManager& mgr, 00061 const std::string& descrName, 00062 const std::string& tagName) : 00063 EyeTracker(mgr, descrName, tagName), 00064 itsEDFfname(&OPT_EyeTrackerEDFfname, this), 00065 itsDims(&OPT_SDLdisplayDims, this) 00066 { } 00067 00068 // ###################################################################### 00069 EyeTrackerEyeLink::~EyeTrackerEyeLink() 00070 { } 00071 00072 // ###################################################################### 00073 void EyeTrackerEyeLink::start1() 00074 { 00075 #ifndef HAVE_EYELINK 00076 LFATAL("Proprietary EyeLink developer API not installed"); 00077 #else 00078 // get the link to the eye-tracker going: 00079 if (open_eyelink_connection(0)) 00080 LFATAL("Cannot open link to EyeLink tracker - make sure your " 00081 "hostname resolves to 100.1.1.2 (check /etc/hosts)"); 00082 00083 // Basic initializations: 00084 set_offline_mode(); 00085 flush_getkey_queue();/* initialize getkey() system */ 00086 00087 // Open EyeLink version of SDL 00088 openSDL(); 00089 00090 // set the display characteristics: 00091 DISPLAYINFO di; // defined in eyelink/core_expt.h 00092 di.width = itsDims.getVal().w(); di.height = itsDims.getVal().h(); 00093 di.left = 0; di.top = 0; di.right = di.width-1; di.bottom = di.height-1; 00094 di.bits = 24; di.palsize = 0; di.pages = 0; di.refresh = 60.0F; 00095 di.winnt = 0; 00096 00097 set_calibration_colors(&target_foreground_color, &target_background_color); 00098 00099 // open EDF file if set: 00100 if (itsEDFfname.getVal().empty() == false) 00101 { 00102 if (open_data_file((char *)(itsEDFfname.getVal().c_str())) != 0) 00103 LFATAL("Cannot open EDF file '%s'", itsEDFfname.getVal().c_str()); 00104 00105 eyecmd_printf(const_cast<char*>("add_file_preamble_text 'RECORDED BY iLab code' ")); 00106 } 00107 00108 // Add resolution to EDF file: 00109 eyemsg_printf(const_cast<char*>("DISPLAY_COORDS %ld %ld %ld %ld"), 00110 di.left, di.top, di.right, di.bottom); 00111 00112 /* SET UP TRACKER CONFIGURATION */ 00113 /* set parser saccade thresholds (conservative settings) */ 00114 /* 0 = standard sensitivity */ 00115 eyecmd_printf(const_cast<char*>("select_parser_configuration 0")); 00116 00117 // set EDF file contents: 00118 eyecmd_printf(const_cast<char*>("file_event_filter = LEFT,RIGHT,FIXATION,SACCADE," 00119 "BLINK,MESSAGE,BUTTON")); 00120 eyecmd_printf(const_cast<char*>("file_sample_data = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS")); 00121 00122 // set link data (used for gaze cursor): 00123 eyecmd_printf(const_cast<char*>("link_event_filter = LEFT,RIGHT,FIXATION,FIXUPDATE,SACCADE,BLINK,BUTTON")); 00124 eyecmd_printf(const_cast<char*>("link_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS")); 00125 00126 // Program button #5 for use in drift correction: 00127 eyecmd_printf(const_cast<char*>("button_function 5 'accept_target_fixation'")); 00128 00129 // setup eyelink filtering: default is "1 2" 00130 eyecmd_printf(const_cast<char*>("heuristic_filter = 1 2")); 00131 eyelink_wait_for_mode_ready(500); 00132 00133 // make sure we're still alive: 00134 if (!eyelink_is_connected() || break_pressed()) 00135 LFATAL("Connection to EyeLink broken or aborted"); 00136 00137 /* TRIAL_VAR_LABELS message is recorded for EyeLink Data Viewer analysis 00138 It specifies the list of trial variables for the trial 00139 This should be written once only and put before the recording of 00140 individual trials */ 00141 eyemsg_printf(const_cast<char*>("TRIAL_VAR_LABELS CONDITION")); 00142 00143 // Configure EyeLink to send fixation updates every 50 msec: 00144 //eyecmd_printf("link_event_filter = LEFT,RIGHT,FIXUPDATE"); 00145 //eyecmd_printf("fixation_update_interval = 50"); 00146 //eyecmd_printf("fixation_update_accumulate = 50"); 00147 00148 EyeTracker::start1(); 00149 #endif 00150 } 00151 00152 // ###################################################################### 00153 void EyeTrackerEyeLink::openSDL() 00154 { 00155 #ifdef HAVE_EYELINK 00156 // set the display characteristics: 00157 DISPLAYINFO di; 00158 di.width = itsDims.getVal().w(); di.height = itsDims.getVal().h(); 00159 di.left = 0; di.top = 0; di.right = di.width-1; di.bottom = di.height-1; 00160 di.bits = 24; di.palsize = 0; di.pages = 0; di.refresh = 60.0F; 00161 di.winnt = 0; 00162 00163 // open the display with our wishes: 00164 if (init_expt_graphics(NULL, &di)) 00165 LFATAL("Cannot open display"); 00166 00167 // see what we actually got: 00168 get_display_information(&di); 00169 LINFO("DISPLAYINFO: [%dx%d}: (%d,%d)-(%d,%d) %dbpp %.1fHz", 00170 int(di.width), int(di.height), int(di.left), int(di.top), 00171 int(di.right), int(di.bottom), int(di.bits), di.refresh); 00172 if (di.palsize) LFATAL("Paletized color modes not supported"); 00173 00174 // Prepare for calibration and other settings: 00175 set_target_size(10, 2); 00176 set_calibration_colors(&target_foreground_color, &target_background_color); 00177 set_cal_sounds(const_cast<char*>(""), const_cast<char*>(""), const_cast<char*>("")); 00178 set_dcorr_sounds(const_cast<char*>(""), const_cast<char*>("off"), const_cast<char*>("off")); 00179 00180 // Setup calibration type: 00181 eyecmd_printf(const_cast<char*>("calibration_type = HV9")); 00182 00183 // Configure tracker for display: 00184 eyecmd_printf(const_cast<char*>("screen_pixel_coords = %ld %ld %ld %ld"), 00185 di.left, di.top, di.right, di.bottom); 00186 00187 if (di.refresh > 40.0F) 00188 eyemsg_printf(const_cast<char*>("FRAMERATE %1.2f Hz."), di.refresh); 00189 #endif 00190 } 00191 00192 // ###################################################################### 00193 void EyeTrackerEyeLink::stop1() 00194 { 00195 #ifndef HAVE_EYELINK 00196 LFATAL("Proprietary EyeLink developer API not installed"); 00197 #else 00198 set_offline_mode(); 00199 pump_delay(500); // give some time to tracker to stop 00200 00201 // close EDF data file if any: 00202 if (itsEDFfname.getVal().empty() == false) 00203 eyecmd_printf(const_cast<char*>("close_data_file")); 00204 00205 00206 // shutdown EyeLink properly: 00207 close_expt_graphics(); 00208 00209 // transfer the EDF file if any: 00210 if (itsEDFfname.getVal().empty() == false) 00211 { 00212 LINFO("Transferring EDF file to '%s'", itsEDFfname.getVal().c_str()); 00213 receive_data_file((char *)itsEDFfname.getVal().c_str(), 00214 (char *)itsEDFfname.getVal().c_str(), 0); 00215 } 00216 00217 // Disconnect from eyelink: 00218 close_eyelink_connection(); 00219 00220 EyeTracker::stop1(); 00221 #endif 00222 } 00223 00224 // ###################################################################### 00225 void EyeTrackerEyeLink::calibrate(nub::soft_ref<PsychoDisplay> d) 00226 { 00227 #ifndef HAVE_EYELINK 00228 LFATAL("Proprietary EyeLink developer API not installed"); 00229 #else 00230 //create SDL_Surface of a image that replaces calibration point 00231 if(!d->getModelParamString("PsychoDisplayFixationIcon").empty()){ 00232 Image< PixRGB<byte> > fixicon = 00233 Raster::ReadRGB(d->getModelParamString("PsychoDisplayFixationIcon")); 00234 SDL_Surface *img = d->makeBlittableSurface(fixicon, false, 00235 PixRGB<byte>(target_background_color.r, target_background_color.g, target_background_color.b)); 00236 set_cal_target_surface(img); 00237 } 00238 00239 // Do camera setup and calibration: 00240 do_tracker_setup(); 00241 00242 // Get calibration result 00243 char message[256]; 00244 eyelink_cal_message(message); 00245 eyemsg_printf(message); 00246 #endif 00247 } 00248 00249 // ###################################################################### 00250 void EyeTrackerEyeLink::calibrate2(nub::soft_ref<PsychoDisplay> d) 00251 { 00252 #ifndef HAVE_EYELINK 00253 LFATAL("Proprietary EyeLink developer API not installed"); 00254 #else 00255 //create SDL_Surface of a image that replaces calibration point 00256 if(!d->getModelParamString("PsychoDisplayFixationIcon").empty()){ 00257 Image< PixRGB<byte> > fixicon = 00258 Raster::ReadRGB(d->getModelParamString("PsychoDisplayFixationIcon")); 00259 SDL_Surface *img = d->makeBlittableSurface(fixicon, false, 00260 PixRGB<byte>(target_background_color.r, target_background_color.g, target_background_color.b)); 00261 set_cal_target_surface(img); 00262 } 00263 00264 // Do camera setup and calibration: 00265 do_tracker_setup(); 00266 00267 // INT16 x = 1024; 00268 // INT16 y = 768; 00269 // draw_cal_target(x, y); 00270 00271 // Get calibration result 00272 char message[256]; 00273 eyelink_cal_message(message); 00274 eyemsg_printf(message); 00275 #endif 00276 } 00277 00278 // ###################################################################### 00279 void EyeTrackerEyeLink::setBackgroundColor(nub::soft_ref<PsychoDisplay> d) 00280 { 00281 #ifndef HAVE_EYELINK 00282 LFATAL("Proprietary EyeLink developer API not installed"); 00283 #else 00284 SDL_Color bgcolor = { d->getGrey().red(), d->getGrey().green(), d->getGrey().blue()}; 00285 SDL_Color fgcolor = { 192, 192, 192}; 00286 00287 set_calibration_colors(&fgcolor, &bgcolor); 00288 LINFO("RGB: %i %i %i", d->getGrey().red(), d->getGrey().green(), d->getGrey().blue()); 00289 #endif 00290 } 00291 00292 // ###################################################################### 00293 void EyeTrackerEyeLink::manualDriftCorrection(Point2D<double> eyepos, 00294 Point2D<double> targetpos) 00295 { 00296 #ifndef HAVE_EYELINK 00297 LFATAL("Proprietary EyeLink developer API not installed"); 00298 #else 00299 char message[256]; 00300 eyecmd_printf(const_cast<char*>("drift_correction %ld %ld %ld %ld"), 00301 targetpos.i-eyepos.i, targetpos.j-eyepos.j, 00302 targetpos.i, targetpos.j); 00303 00304 // Get drift correction result 00305 eyelink_cal_message(message); 00306 eyemsg_printf(message); 00307 #endif 00308 } 00309 00310 // ###################################################################### 00311 void EyeTrackerEyeLink::recalibrate(nub::soft_ref<PsychoDisplay> d,int repeats) 00312 { 00313 #ifndef HAVE_EYELINK 00314 LFATAL("Proprietary EyeLink developer API not installed"); 00315 #else 00316 //create SDL_Surface of a image that replaces calibration point 00317 if(!d->getModelParamString("PsychoDisplayFixationIcon").empty()){ 00318 Image< PixRGB<byte> > fixicon = 00319 Raster::ReadRGB(d->getModelParamString("PsychoDisplayFixationIcon")); 00320 SDL_Surface *img = d->makeBlittableSurface(fixicon, false, 00321 PixRGB<byte>(target_background_color.r, target_background_color.g, target_background_color.b)); 00322 set_cal_target_surface(img); 00323 } 00324 00325 do_drift_correct(d->getWidth()/2, d->getHeight()/2, 1, 1); 00326 00327 // Get calibration result 00328 char message[256]; 00329 eyelink_cal_message(message); 00330 eyemsg_printf(message); 00331 00332 // set offline mode to ensure trackign 00333 set_offline_mode(); 00334 #endif 00335 } 00336 00337 // ###################################################################### 00338 void EyeTrackerEyeLink::closeSDL() 00339 { 00340 #ifdef HAVE_EYELINK 00341 close_expt_graphics(); 00342 #endif 00343 } 00344 00345 00346 // ###################################################################### 00347 void EyeTrackerEyeLink::startTracking() 00348 { 00349 #ifndef HAVE_EYELINK 00350 LFATAL("Proprietary EyeLink developer API not installed"); 00351 #else 00352 // Show message at bottom of tracker display: 00353 eyecmd_printf(const_cast<char*>("record_status_message 'Recording [%d]...' "), getSession()); 00354 00355 // set FIXUPDATE event property 00356 eyecmd_printf(const_cast<char*>("fixation_update_interval = 50")); 00357 eyecmd_printf(const_cast<char*>("fixation_update_accumulate = 50")); 00358 00359 // Always send a TRIALID message before starting to record. It 00360 // should contain trial condition data required for analysis: 00361 eyemsg_printf(const_cast<char*>("TRIALID EYETRACK")); 00362 00363 // TRIAL_VAR_DATA message is recorded for EyeLink Data Viewer 00364 // analysis It specifies the list of trial variables value for the 00365 // trial This must be specified within the scope of an individual 00366 // trial (i.e., after "TRIALID" and before "TRIAL_RESULT"): 00367 eyemsg_printf(const_cast<char*>("!V TRIAL_VAR_DATA EYETRACKING")); 00368 00369 // Log eye tracker time at onset of clip: 00370 if (itsEventLog.isValid()) 00371 itsEventLog->pushEvent(sformat("Eye Tracker Time = %d", int(current_msec()))); 00372 00373 // Actually start the recording: 00374 if (start_recording(1, 1, 1, 1)) 00375 LFATAL("Error trying to start recording"); 00376 #endif 00377 } 00378 00379 // ###################################################################### 00380 void EyeTrackerEyeLink::stopTracking() 00381 { 00382 #ifndef HAVE_EYELINK 00383 LFATAL("Proprietary EyeLink developer API not installed"); 00384 #else 00385 // report response result: 0=timeout, else button number: 00386 eyemsg_printf(const_cast<char*>("TRIAL_RESULT 1")); 00387 00388 // stop the tracker: 00389 stop_recording(); 00390 00391 // set FIXUPDATE event property (close it) 00392 eyecmd_printf(const_cast<char*>("fixation_update_interval = 0")); 00393 eyecmd_printf(const_cast<char*>("fixation_update_accumulate = 0")); 00394 00395 set_offline_mode(); 00396 #endif 00397 } 00398 00399 // ###################################################################### 00400 bool EyeTrackerEyeLink::isFixating() 00401 { 00402 LFATAL("Unimplemented for now"); 00403 return false; 00404 } 00405 00406 // ###################################################################### 00407 bool EyeTrackerEyeLink::isSaccade() 00408 { 00409 LFATAL("Unimplemented for now"); 00410 return false; 00411 } 00412 00413 // ###################################################################### 00414 Point2D<int> EyeTrackerEyeLink::getEyePos() const 00415 { 00416 #ifndef HAVE_EYELINK 00417 LFATAL("Proprietary EyeLink developer API not installed"); 00418 return Point2D<int>(-1,-1); 00419 #else 00420 // get gaze position 00421 eyelink_newest_float_sample(&evt); 00422 00423 // make sure pupil is present 00424 if(evt.fs.gx[RIGHT_EYE]!=MISSING_DATA && 00425 evt.fs.gy[RIGHT_EYE]!=MISSING_DATA && evt.fs.pa[RIGHT_EYE]>0){ 00426 return Point2D<int>((int)evt.fs.gx[RIGHT_EYE], (int)evt.fs.gy[RIGHT_EYE]); 00427 } else { 00428 return Point2D<int>(-1, -1); 00429 } 00430 #endif 00431 } 00432 00433 // ###################################################################### 00434 Point2D<int> EyeTrackerEyeLink::getFixationPos() const 00435 { 00436 #ifndef HAVE_EYELINK 00437 LFATAL("Proprietary EyeLink developer API not installed"); 00438 return Point2D<int>(-1,-1); 00439 #else 00440 int i, x, y; 00441 00442 while (1) { // using while loop to consume the eye data queue 00443 i = eyelink_get_next_data(NULL); // check for new data item 00444 if (i == FIXUPDATE) { 00445 eyelink_get_float_data(&evt); 00446 x = (int)evt.fe.gavx; 00447 y = (int)evt.fe.gavy; 00448 //LINFO("Eyelink Time (FIXUPDATE) : %i (%i, %i)\n", (int)evt.fe.time, x, y); 00449 break; 00450 } 00451 } 00452 00453 return Point2D<int>(x, y); 00454 #endif 00455 } 00456 00457 // ###################################################################### 00458 CalibrationTransform::Data EyeTrackerEyeLink::getCalibrationSet(nub::soft_ref<PsychoDisplay> d) const 00459 { 00460 LINFO("\n getting calibration set..."); 00461 CalibrationTransform::Data dummy; 00462 dummy.addData(Point2D<double>(-1.0,-1.0),Point2D<double>(-1.0,-1.0)); 00463 return dummy; 00464 } 00465 00466 00467 // ###################################################################### 00468 /* So things look consistent in everyone's emacs... */ 00469 /* Local Variables: */ 00470 /* mode: c++ */ 00471 /* indent-tabs-mode: nil */ 00472 /* End: */