00001 /*!@file Psycho/EyeTrace.C */ 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: John Shen <shenjohn@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Psycho/EyeTrace.C $ 00035 // $Id: EyeTrace.C 14593 2011-03-13 00:57:50Z dberg $ 00036 00037 00038 #ifndef PSYCHO_EYETRACE_C_DEFINED 00039 #define PSYCHO_EYETRACE_C_DEFINED 00040 00041 #include "Psycho/EyeTrace.H" 00042 #include "Util/StringConversions.H" 00043 #include "Util/StringUtil.H" 00044 #include "Util/log.H" 00045 #include "rutz/compat_cmath.h" // for isnan() 00046 00047 #include <fstream> 00048 00049 // ###################################################################### 00050 EyeTrace::EyeTrace(const std::string& filename, const PixRGB<byte>& color) : 00051 itsFilename(filename), itsColor(color), itsPeriod(SimTime::ZERO()), 00052 itsTrash(0), itsPPD(), itsNumEvents(0), itsData() 00053 { 00054 // let's read the entire file: 00055 const char *fn = filename.c_str(); 00056 std::ifstream fil(fn); 00057 if (fil.is_open() == false) PLFATAL("Cannot open '%s'", fn); 00058 00059 std::string line; int linenum = -1; 00060 const std::string delim(" \t"); 00061 bool gotperiod = false, gottrash = false, gotppd = false, gotcols = false; 00062 int ncols = 0; 00063 //itsInSac = false; 00064 uint samp_count = 0, trashed = 0; 00065 rutz::shared_ptr<ParamMap> lastSacData(new ParamMap); 00066 00067 //look in the header for metadata 00068 while (getline(fil, line)) 00069 { 00070 // one more line that we have read: 00071 ++linenum; 00072 00073 // skip initial whitespace: 00074 std::string::size_type pos = line.find_first_not_of(delim, 0); 00075 00076 if (pos == line.npos) continue; // line was all whitespace 00077 00078 if (line[pos] == '#') continue; // line is a comment 00079 00080 // is it some metadata: "key = value"? 00081 if (line.find('=') != line.npos) 00082 { 00083 // let's tokenize it: 00084 std::vector<std::string> tok; 00085 split(line, "= \t", std::back_inserter(tok)); 00086 00087 if (tok.size() != 2 && tok[0].compare("cols") != 0) 00088 { 00089 LFATAL("Error parsing '%s', line %d", fn, linenum); 00090 } 00091 // do we know that key? 00092 if (tok[0].compare("period") == 0) 00093 { itsPeriod = SimTime::fromString(tok[1]); gotperiod = true; } 00094 else if (tok[0].compare("trash") == 0) 00095 { itsTrash = fromStr<int>(tok[1]); gottrash = true; } 00096 else if (tok[0].compare("ppd") == 0) 00097 { 00098 00099 std::vector<std::string> ltok; 00100 split(tok[1], ",", std::back_inserter(ltok)); 00101 if (ltok.size() > 1) 00102 itsPPD = PixPerDeg(fromStr<float>(ltok[0]), 00103 fromStr<float>(ltok[1])); 00104 else 00105 itsPPD = PixPerDeg(fromStr<float>(ltok[0]), 0.0F); 00106 gotppd = true; 00107 } 00108 else if (tok[0].compare("cols") == 0) 00109 { 00110 tok.erase(tok.begin()); // get rid of "cols" 00111 itsFields = tok; 00112 gotcols = true; 00113 } 00114 else LFATAL("Unknown parameter '%s', file '%s' line %d", 00115 tok[0].c_str(), fn, linenum); 00116 00117 // Enforce that we should have all our parameters parsed before 00118 // the data starts: 00119 if (gotperiod && gottrash && gotppd) break; 00120 00121 // done with this line, let's keep going: 00122 continue; 00123 } 00124 // if we reach this point, either we have hit some junk, or a 00125 // data line but we are missing some parameters. Not good: 00126 LFATAL("I need to have period, trash, and ppd information before " 00127 "data starts, file '%s' line %d", fn, linenum); 00128 } 00129 00130 LINFO("%s: period = %.3fms, ppdx = %.1f ppdy = %.1f pix/deg, trash = %"ZU" samples.", 00131 fn, itsPeriod.msecs(), itsPPD.ppdx(), itsPPD.ppdy(), itsTrash); 00132 00133 // all right, we have all the metadata, let's now get the data. Note 00134 // that there is a bit of redundancy between the loop here and the 00135 // previous one, but it runs faster this way because from now on we 00136 // can assume that all the metadata is available: 00137 while (getline(fil, line)) 00138 { 00139 // one more line that we have read: 00140 ++linenum; 00141 00142 // skip initial whitespace: 00143 std::string::size_type pos = line.find_first_not_of(delim, 0); 00144 00145 if (pos == line.npos) continue; // line was all whitespace 00146 00147 if (line[pos] == '#') continue; // line is a comment 00148 00149 // is it the column metadata (that came last in the header): "cols = value"? 00150 if (line.find("cols =") != line.npos) 00151 { 00152 // let's tokenize it: 00153 std::vector<std::string> tok; 00154 split(line, "= \t", std::back_inserter(tok)); 00155 00156 tok.erase(tok.begin()); // get rid of "cols" 00157 itsFields = tok; 00158 gotcols = true; 00159 continue; 00160 } 00161 00162 // are we still unsure of the columns? then we first need to infer the columns 00163 if(!gotcols) 00164 { 00165 // extract values from line 00166 std::vector<std::string> values; 00167 split(line, " ", std::back_inserter(values)); 00168 ncols = values.size(); 00169 00170 std::string flds = ""; 00171 00172 // case out for the number of columns. 00173 // this is for legacy data with few # of columns 00174 switch(ncols) 00175 { 00176 case 2: {flds = "x y"; break;} 00177 case 3: {flds = "x y status"; break;} 00178 case 4: {flds = "x y pd status"; break;} 00179 case 7: {flds = "x y status *targetx *targety *ampl *interval"; break;} 00180 case 8: {flds = "x y pd status *targetx *targety *ampl *interval"; break;} 00181 default: 00182 LFATAL("Error parsing data in '%s',line %d: %d of columns gives unknown data", 00183 fn, linenum, ncols); 00184 } 00185 00186 split(flds, " ", std::back_inserter(itsFields)); 00187 gotcols = true; 00188 } 00189 00190 // maybe we want to trash it right here: 00191 if (trashed < itsTrash) { ++trashed; continue; } 00192 00193 // else let's read the data 00194 else {pushData(line);} 00195 00196 // update our sample count 00197 samp_count++; 00198 } // while (getline(fil,line)) 00199 00200 LINFO("%s: cols = %s", 00201 fn, join(itsFields.begin(),itsFields.end()," ").c_str() ); 00202 00203 LINFO("%s: %zu samples, %u events.", fn, itsData.size(), 00204 itsNumEvents); 00205 } 00206 00207 // ###################################################################### 00208 EyeTrace::~EyeTrace() 00209 { } 00210 00211 // ###################################################################### 00212 bool EyeTrace::hasData(const size_t index, const SimTime& t) const 00213 { return ( index < itsData.size() && itsPeriod * int(index) < t ); } 00214 00215 // ###################################################################### 00216 bool EyeTrace::hasData(const size_t index) const 00217 { return ( index < itsData.size() ); } 00218 00219 // ###################################################################### 00220 rutz::shared_ptr<EyeData> EyeTrace::data(const size_t index) const 00221 { 00222 if (index >= itsData.size()) LFATAL("Index past end of trace"); 00223 00224 // compute the saccade/blink states. Here is what David's matlab 00225 // code (see saliency/matlab/Eye-Markup/) spits out: 00226 // fixation: 0 blue 00227 // saccade: 1 green 00228 // blink/Artifact: 2 red 00229 // Saccade during Blink: 3 green 00230 // smooth pursuit: 4 magenta 00231 // drift/misclassification: 5 black 00232 // combined saccades 6 green 00233 00234 SaccadeState ss; bool bs; 00235 switch(itsData[index].status) 00236 { 00237 case 0: ss = SACSTATE_FIX; bs = false; break; 00238 case 1: ss = SACSTATE_SAC; bs = false; break; 00239 case 2: ss = SACSTATE_FIX; bs = true; break; 00240 case 3: ss = SACSTATE_SAC; bs = true; break; 00241 case 4: ss = SACSTATE_SMO; bs = false; break; 00242 case 5: ss = SACSTATE_UNK; bs = false; break; 00243 case 6: ss = SACSTATE_COM; bs = false; break; 00244 00245 default: 00246 LFATAL("Bogus status code '%d', file '%s' index %"ZU" (after trashing)", 00247 itsData[index].status, itsFilename.c_str(), index); 00248 ss = SACSTATE_UNK; bs = false; // compiler doesn't realize this won't run 00249 break; 00250 } 00251 00252 rutz::shared_ptr<EyeData> ret; 00253 00254 // correct this part 00255 00256 ret.reset(new EyeData(itsData[index].x, itsData[index].y, 00257 itsData[index].diam, ss, bs, 00258 itsData[index].extraData)); 00259 00260 return ret; 00261 } 00262 00263 // ###################################################################### 00264 const SaccadeState EyeTrace::getStatus(const int sta) const 00265 { 00266 SaccadeState ss; 00267 switch(sta) 00268 { 00269 case 0: ss = SACSTATE_FIX; break; 00270 case 1: ss = SACSTATE_SAC; break; 00271 case 2: ss = SACSTATE_FIX; break; 00272 case 3: ss = SACSTATE_SAC; break; 00273 case 4: ss = SACSTATE_SMO; break; 00274 case 5: ss = SACSTATE_UNK; break; 00275 case 6: ss = SACSTATE_COM; break; 00276 00277 default: 00278 LFATAL("Bogus status code '%d', file '%s'", 00279 sta, itsFilename.c_str()); 00280 ss = SACSTATE_UNK; // compiler doesn't realize this won't run 00281 break; 00282 } 00283 return ss; 00284 00285 } 00286 00287 bool EyeTrace::pushData(const std::string line) 00288 { 00289 // Parses the line from an EyeMarkup file 00290 // and decides whether or not to store extra data 00291 // There are three types of fields: 00292 // (1) the standard (key) fields (x,y,pdiam,status) 00293 // (2) event related fields: these fields receive a '*' designation 00294 // and will be treated as "events" (formerly saccades only) 00295 // (3) extra (non-key), non-event related fields 00296 00297 // NB: this code depends on some defaults that are set in EyeMarkup 00298 00299 ////////////////////////////////////// 00300 // Parse values from the line 00301 std::vector<std::string> strVals; 00302 split(line, " ", std::back_inserter(strVals)); 00303 00304 if(itsFields.size() != strVals.size()) 00305 {} // TODO: should throw some exception here for safety 00306 00307 // Parse data from line into doubles 00308 rutz::shared_ptr<ParamMap> dataBuffer(new ParamMap); 00309 00310 // sentinel value that Eye-Markup uses for when there is no event data 00311 const double SENTINEL = 0.0; 00312 uint i = 0; 00313 std::vector<double> values(strVals.size(),SENTINEL); 00314 for (i = 0; i < strVals.size(); i++) 00315 if(strVals[i].compare("NaN") != 0) // handle NaNs 00316 values[i] = fromStr<double>(strVals[i]); 00317 00318 ////////////////////////////////////// 00319 // pre-test if there is any event-related data 00320 // We have event data only when we see non-zero event marked fields 00321 bool hasEventData = false; 00322 for (i = 0; i < itsFields.size(); i++) 00323 // field should be indicative of an event and value should be valid 00324 if (isEventField(itsFields[i]) && values[i] != SENTINEL) 00325 { 00326 hasEventData = true; 00327 break; 00328 } 00329 00330 ////////////////////////////////////// 00331 // assign values to their fields 00332 // Special handling for standard data: 00333 // here we set up default values for x,y,pd, and status 00334 // Note: sensitive to the names used in "cols" header 00335 // TODO: generalize this so that key fields can be added 00336 00337 std::vector<std::string> key_fields; 00338 const std::string fieldnames("x y pd status"); 00339 split(fieldnames," ", std::back_inserter(key_fields)); 00340 std::vector<double> defaults(key_fields.size(),0); 00341 00342 // assign key fields their defaults in our buffer 00343 for (i = 0; i < key_fields.size(); i++) 00344 dataBuffer->putDoubleParam(key_fields[i], defaults[i]); 00345 00346 // assign values from the input line 00347 for (i = 0; i < itsFields.size(); i++) 00348 { 00349 // if this is an event-related field without data, skip it 00350 if (isEventField(itsFields[i]) && !hasEventData) continue; 00351 // see if this field is already in the set of key fields 00352 if(find(key_fields.begin(),key_fields.end(),itsFields[i]) 00353 !=key_fields.end() ) 00354 dataBuffer->replaceDoubleParam(itsFields[i], values[i]); 00355 else 00356 dataBuffer->putDoubleParam(itsFields[i],values[i]); 00357 } 00358 00359 ////////////////////////////////////// 00360 // package the data 00361 RawEyeData ed = {float(dataBuffer->getDoubleParam("x")), 00362 float(dataBuffer->getDoubleParam("y")), 00363 float(dataBuffer->getDoubleParam("pd")), 00364 int(dataBuffer->getDoubleParam("status")), 00365 dataBuffer}; 00366 // from the extra data structure, get rid of the standard data 00367 for (i = 0; i < key_fields.size(); i++) 00368 ed.extraData->erase(key_fields[i]); 00369 00370 // if we have event-related data 00371 if(hasEventData) 00372 { 00373 // add to list of events 00374 itsEvents.push_back(ed.extraData); 00375 itsNumEvents++; 00376 } 00377 00378 itsData.push_back(ed); 00379 return true; 00380 } 00381 00382 // ###################################################################### 00383 bool EyeTrace::isEventField(const std::string field) const 00384 { return field[0] == '*'; } 00385 00386 // ###################################################################### 00387 size_t EyeTrace::size() const 00388 { return itsData.size(); } 00389 00390 // ###################################################################### 00391 size_t EyeTrace::numEvents() const 00392 { return itsNumEvents; } 00393 00394 // ###################################################################### 00395 size_t EyeTrace::numSaccades() const 00396 { LERROR("This function is no longer accurate, use numEvents instead."); 00397 return itsNumEvents; } 00398 // NB: new markup means that we may not be indicating 00399 // saccades here 00400 00401 // ###################################################################### 00402 SimTime EyeTrace::period() const 00403 { return itsPeriod; } 00404 00405 // ###################################################################### 00406 std::string EyeTrace::filename() const 00407 { return itsFilename; } 00408 00409 // ###################################################################### 00410 PixRGB<byte> EyeTrace::color() const 00411 { return itsColor; } 00412 00413 // ###################################################################### 00414 std::string EyeTrace::basename() const 00415 { 00416 size_t idx = itsFilename.rfind('.'); 00417 if (idx != itsFilename.npos) return itsFilename.substr(0, idx); 00418 00419 return itsFilename; // no extension? 00420 } 00421 00422 // ###################################################################### 00423 PixPerDeg EyeTrace::ppd() const 00424 { return itsPPD; } 00425 00426 // ###################################################################### 00427 /* So things look consistent in everyone's emacs... */ 00428 /* Local Variables: */ 00429 /* mode: c++ */ 00430 /* indent-tabs-mode: nil */ 00431 /* End: */ 00432 00433 #endif // PSYCHO_EYETRACE_C_DEFINED