EyeTrace.C

Go to the documentation of this file.
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
Generated on Sun May 8 08:41:13 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3