00001 /*!@file AppPsycho/psycho-math-op.C Psychophysics test to measure the influence of eyemovement on memory task performance */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // 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/AppPsycho/psycho-math-op.C $ 00035 00036 00037 #include "Component/ModelManager.H" 00038 #include "Image/Image.H" 00039 #include "Psycho/PsychoDisplay.H" 00040 #include "Psycho/EyeTrackerConfigurator.H" 00041 #include "Psycho/EyeTracker.H" 00042 #include "Psycho/PsychoOpts.H" 00043 #include "Component/EventLog.H" 00044 #include "Component/ComponentOpts.H" 00045 #include "Raster/Raster.H" 00046 #include "Util/MathFunctions.H" 00047 #include "Util/Types.H" 00048 #include "GameBoard/basic-graphics.H" 00049 #include <sys/types.h> 00050 #include <dirent.h> 00051 #include <errno.h> 00052 #include <vector> 00053 #include <string> 00054 #include <iostream> 00055 #include <SDL/SDL.h> 00056 #include <SDL/SDL_image.h> 00057 #include <SDL/SDL_mixer.h> 00058 #include <stdio.h> 00059 #include <stdlib.h> 00060 #include <sstream> 00061 #include <time.h> 00062 #include "Image/DrawOps.H" 00063 #include "GameBoard/resize.h" 00064 #include <iostream> 00065 #include <fstream> 00066 #include <set> 00067 #include <algorithm> 00068 #include <ctime> 00069 00070 #ifndef INVT_HAVE_LIBSDL_IMAGE 00071 #include <cstdio> 00072 int main() 00073 { 00074 fprintf(stderr, "The SDL_image library must be installed to use this program\n"); 00075 return 1; 00076 } 00077 00078 #else 00079 00080 00081 00082 using namespace std; 00083 00084 // ###################################################################### 00085 00086 ModelManager manager("Psycho-Math-Op"); 00087 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00088 map<uint,uint> testMap ; 00089 map<string,string> argMap ; 00090 map<string,vector<SDL_Rect*>*> clipsmap; 00091 00092 ////////////////////////////////////////////// 00093 // a functionf for stringigying things 00094 ////////////////////////////////////////////// 00095 template <class T> std::string stringify(T i) 00096 { 00097 ostringstream o ; 00098 o << i ; 00099 return o.str(); 00100 } 00101 00102 bool itIsInThere(int x , vector<int> bag){ 00103 for( uint i=0 ; i < bag.size(); i++ ){ 00104 if(x == bag[i]) return true ; 00105 } 00106 return false ; 00107 } 00108 00109 00110 //pushes back the name of wav files in the directory into the given vector 00111 int getdir (string dir, vector<string> &files) 00112 { 00113 DIR *dp; 00114 struct dirent *dirp; 00115 if((dp = opendir(dir.c_str())) == NULL) { 00116 cout << "Error(" << errno << ") opening " << dir << endl; 00117 return errno; 00118 } 00119 string fn = "" ; 00120 size_t found; 00121 string extension = "" ; 00122 while ((dirp = readdir(dp)) != NULL) { 00123 fn = string(dirp->d_name) ; 00124 found = fn.find_last_of("."); 00125 if(found > 0 && found <1000){ 00126 extension = fn.substr(found) ; 00127 if(extension.compare(".wav")== 0 ) 00128 files.push_back(dir+"/"+fn); 00129 } 00130 } 00131 closedir(dp); 00132 return 0; 00133 } 00134 00135 00136 //////////////////////////////////////////////////////////////// 00137 ////This is our button factory 00138 //////////////////////////////////////////////////////////////// 00139 SDL_Surface* getButtonImage(string label , PixRGB<byte> txtcolor=PixRGB<byte>(0,0,0) , PixRGB<byte> bgcolor=PixRGB<byte>(255,255,255) ,Point2D<int> size = Point2D<int>(100,100) ,PixRGB<byte> bordercolor=PixRGB<byte>(0,0,0) , int border=3){ 00140 Image<PixRGB<byte> > textIm(d->getWidth(),d->getHeight(),ZEROS); 00141 textIm.clear(bgcolor); 00142 writeText(textIm, Point2D<int>((size.i - label.length()*10)/2,(size.j-20) /2),label.c_str(),txtcolor,bgcolor); 00143 SDL_Surface *surf = d->makeBlittableSurface(textIm , true); 00144 Uint32 bc = d->getUint32color(bordercolor); 00145 drawRectangle(surf,bc,0,0,size.i -1,size.j -1 ,border); 00146 SDL_Surface* blank =getABlankSurface(size.i , size.j); 00147 SDL_Rect clip; 00148 clip.x = 0 ; 00149 clip.y = 0 ; 00150 clip.w = size.i ; 00151 clip.h = size.j ; 00152 apply_surface(0,0,*surf,*blank,clip); 00153 dumpSurface(surf) ; 00154 return blank ; 00155 } 00156 00157 //////////////////////////////////////////////////////////////////////// 00158 ////This is the function for creating the keypad, in fact it generates 00159 ////12 buttons and associates the actions to the region for each button 00160 //////////////////////////////////////////////////////////////////////// 00161 00162 SDL_Surface* getKeyPad(string alphabet,map<string , SDL_Rect>& buttmap){ 00163 SDL_Surface* pad= getABlankSurface(d->getWidth()/4,d->getHeight()/3); 00164 SDL_Rect clip; 00165 clip.x=0; 00166 clip.y=0; 00167 int numofrows = alphabet.size()/3 +1; 00168 if(alphabet.size()%3 != 0 ) numofrows++ ; 00169 int numofcolumns = 3 ; 00170 clip.w= pad->w / numofcolumns ; 00171 clip.h = pad->h / numofrows ; 00172 00173 //keys for 1 to 9 00174 for( int i = 0 ; i < numofrows*3 ; i++){ 00175 SDL_Surface* but ; 00176 if((uint)i < alphabet.size()){ 00177 but = getButtonImage(alphabet.substr(i,1),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / numofcolumns , pad->h / numofrows),PixRGB<byte>(255, 98 , 25),3); 00178 }else{ 00179 but = getButtonImage(" ",PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / numofcolumns , pad->h / numofrows),PixRGB<byte>(255, 98 , 25),3); 00180 } 00181 00182 SDL_Rect cl ; 00183 cl.x = ((i)%numofcolumns)*(pad->w)/numofcolumns ; cl.y= ((i)/numofcolumns)*((pad->h)/numofrows) ; 00184 cl.w = clip.w ; 00185 cl.h = clip.h ; 00186 apply_surface( cl.x , cl.y ,*but,*pad,clip); 00187 if((uint)i < alphabet.size()) buttmap[alphabet.substr(i,1)] = cl ; 00188 dumpSurface(but); 00189 } 00190 SDL_Rect cl1 ; 00191 cl1.x = 0 ; cl1.y= (numofrows-1)*((pad->h)/numofrows) ; 00192 cl1.w = clip.w ; 00193 cl1.h = clip.h ; 00194 buttmap["!"] = cl1 ; 00195 SDL_Surface* but = getButtonImage(string("<-"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / numofcolumns , pad->h / numofrows),PixRGB<byte>(255, 98 , 25),3); 00196 apply_surface(0, (numofrows-1)*((pad->h)/numofrows),*but,*pad,clip); 00197 dumpSurface(but); 00198 SDL_Rect cl2 ; 00199 cl2.x = (pad->w)/numofcolumns ; cl2.y= (numofrows-1)*((pad->h)/numofrows) ; 00200 cl2.w = clip.w ; 00201 cl2.h = clip.h ; 00202 buttmap[" "] = cl2 ; 00203 but = getButtonImage(string("spc"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / numofcolumns , pad->h / numofrows),PixRGB<byte>(255, 98 , 25),3); 00204 apply_surface((pad->w)/numofcolumns, (numofrows-1)*((pad->h)/numofrows),*but,*pad,clip); 00205 dumpSurface(but); 00206 SDL_Rect cl3 ; 00207 cl3.x = 2*(pad->w)/numofcolumns ; cl3.y= (numofrows-1)*((pad->h)/numofrows) ; 00208 cl3.w = clip.w ; 00209 cl3.h = clip.h ; 00210 buttmap["*"] = cl3 ; 00211 but = getButtonImage(string("Ok"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / numofcolumns , pad->h / numofrows),PixRGB<byte>(255, 98 , 25),3); 00212 apply_surface(2*(pad->w)/numofcolumns, (numofrows-1)*((pad->h)/numofrows),*but,*pad,clip); 00213 dumpSurface(but); 00214 return pad ; 00215 } 00216 00217 00218 00219 00220 /////////////////////////////////////////////////////////////////////////// 00221 /////this function listens to mouse clicks and then finds the region of the screen 00222 /////associated with the action, buttmap is the map of the region, offset is the offset of 00223 /////buttons 00224 /////////////////////////////////////////////////////////////////////////// 00225 string getPressedButtonCommand(map<string , SDL_Rect>& buttmap,Point2D<int> offset=Point2D<int>(0,0)){ 00226 int quit = 0 ; 00227 string s ; 00228 SDL_Event event ; 00229 while( quit!=2 ){ 00230 while( SDL_PollEvent( &event ) ) { 00231 if(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT ){ 00232 for( map<string , SDL_Rect>::iterator it = buttmap.begin() ; it!=buttmap.end() ; ++it){ 00233 if(event.button.x >= (it->second).x + offset.i && event.button.x <= (it->second).x + (it->second).w + offset.i && event.button.y >= (it->second).y+ offset.j && event.button.y <= (it->second).y + (it->second).h + offset.j) { 00234 quit = 2 ; 00235 s = it->first ; 00236 break; 00237 } 00238 00239 } 00240 } 00241 00242 } 00243 } 00244 return s ; 00245 00246 } 00247 00248 00249 //////////////////////////////////////////////////// 00250 ////This function creates a virtual keypad, creates a map of buttons 00251 ////and their representation area and listens to the button press and at 00252 ////the end returns the keyed digits 00253 //////////////////////////////////////////////////// 00254 string getDigitSequenceFromSubject(string alphabet="0123456789" , uint maxl = 7 ){ 00255 d->showCursor(true) ; 00256 //let's creat a map to map actions to regions of the screen, each region is represented as an SDL_Rect 00257 map<string , SDL_Rect>* buttmap = new map<string , SDL_Rect>(); 00258 //now let's get the keypad surface while we get the actions map to regions 00259 SDL_Surface * keypad = getKeyPad(alphabet,*buttmap); 00260 //this will be the offset of displaying the keypad on the screen 00261 SDL_Rect offset ; 00262 offset.x = (d->getWidth() - keypad->w) /2; 00263 offset.y = (d-> getHeight() - keypad->h) /2; 00264 //now let's display the keypad 00265 d->displaySDLSurfacePatch(keypad , &offset,NULL , -2,false, true); 00266 //this will hold the final string keyed be the subject 00267 string p = string("") ; 00268 //this is a temporary string holding the last action related to the pressed key 00269 string tp = string(""); 00270 //now let's record subject's key press 00271 while( tp.compare("*")!=0 ){ 00272 //this button is actually the display for the current string 00273 SDL_Surface* dp = getButtonImage(p ,PixRGB<byte>(195,60,12) ,PixRGB<byte>(255,255,255) ,Point2D<int>(d->getWidth()/6,d->getHeight() /15) ,PixRGB<byte>(0,25,180) , 4) ; 00274 SDL_Rect offs ; offs.x = (d->getWidth() - dp->w) /2 ; offs.y = d->getHeight()/6 ; 00275 d->displaySDLSurfacePatch(dp , &offs , NULL , -2 , false ,true ) ; 00276 //now let's listen to button events 00277 tp = getPressedButtonCommand(*buttmap,Point2D<int>(offset.x,offset.y)) ; 00278 dumpSurface(dp) ; 00279 if(tp.compare("!")==0 && p.size()>=0 ) { 00280 if (p.size()>0) p = p.substr(0,p.size()-1) ; 00281 }else{ 00282 if(p.size() < maxl && tp.compare("*")!=0) { 00283 p +=tp ; 00284 } 00285 00286 } 00287 00288 } 00289 buttmap = 0 ; 00290 dumpSurface(keypad) ; 00291 d->clearScreen() ; 00292 return p ; 00293 00294 } 00295 00296 00297 00298 int addArgument(const string st,const string delim="="){ 00299 int i = st.find(delim) ; 00300 argMap[st.substr(0,i)] = st.substr(i+1); 00301 00302 return 0 ; 00303 } 00304 00305 std::string getArgumentValue(string arg){ 00306 return argMap[arg] ; 00307 } 00308 00309 std::vector<int> getDigits(int n , string zs="n" , string rs="n" ){ 00310 if(rs.compare("n")==0){ 00311 if(zs.compare("n")==0 && n >9 ) {LINFO( "come on! what do you expect?!") ; exit(-1) ;} 00312 if(zs.compare("y")==0 && n >10 ) {LINFO( "come on! what do you expect?!") ; exit(-1) ;} 00313 } 00314 vector<int> digits ; 00315 int dig = 0 ; 00316 while( digits.size() < (uint)n ){ 00317 if(zs.compare("n")==0) {dig = 1+(random()%9);}else{dig = random()%10;} 00318 if(rs.compare("y")==0){digits.push_back(dig);}else{if(!itIsInThere(dig,digits)) digits.push_back(dig);} 00319 } 00320 return digits ; 00321 } 00322 00323 std::string getUsageComment(){ 00324 00325 string com = string("\nlist of arguments : \n"); 00326 00327 com += "\nlogfile=[logfilename.psy] {default = psycho-stroop-concurrent.psy}\n" ; 00328 com += "\nmemo=[a_string_without_white_space]\n"; 00329 com += "\nnum-of-digits=[>0](the size of string){default=} \n"; 00330 com += "\nsubject=[subject_name] \n" ; 00331 com += "\ntest-rounds=[>1] (number of tests ) {default=10}\n"; 00332 com += "\nalphabet=[a string of characters](a string of characters){default=0123456789}\n"; 00333 com += "\nrecall-prb=[a number between 0 to 1], probability of challenging the subject with a memorization task, default=0.4\n" ; 00334 com += "\ncue-wait-frames=[<0](number of frames to show the cue){default=0}\n"; 00335 com += "\ncue-onset-frames=[<0](number of frames to show the cue onset){default=3}\n"; 00336 com += "\nsound-dir=[path to wav files directory]{default=..}\n"; 00337 com += "\ndigit-repeat=[y/n](whether digits can repeat, y for yes , n for no){default=n}\n" ; 00338 com += "\ninclude-zero=[y/n](whether zero be included in the presented digits, y for yes , n for no){default=n}\n"; 00339 return com ; 00340 } 00341 00342 00343 extern "C" int main(const int argc, char** argv) 00344 { 00345 00346 MYLOGVERB = LOG_INFO; // suppress debug messages 00347 //let's push the initial value for the parameters 00348 argMap["experiment"]="math-operation-with-played-numbers"; 00349 argMap["logfile"]="psycho-math-op.psy" ; 00350 argMap["num-of-digits"]="3" ; 00351 argMap["test-rounds"]="10"; 00352 argMap["subject"]="" ; 00353 argMap["memo"]="" ; 00354 argMap["alphabet"]="0123456789"; 00355 argMap["recall-prb"]="0.4" ; 00356 argMap["cue-wait-frames"]="0" ; 00357 argMap["cue-onset-frames"] = "3" ; 00358 argMap["white-space"] = "20" ; 00359 argMap["sound-dir"]=".."; 00360 argMap["include-zero"]="n"; 00361 argMap["digit-repeat"]="n" ; 00362 00363 manager.addSubComponent(d); 00364 nub::soft_ref<EventLog> el(new EventLog(manager)); 00365 manager.addSubComponent(el); 00366 d->setEventLog(el); 00367 nub::soft_ref<EyeTrackerConfigurator> 00368 etc(new EyeTrackerConfigurator(manager)); 00369 manager.addSubComponent(etc); 00370 00371 if (manager.parseCommandLine(argc, argv, 00372 "at least one argument needed", 1, -1)==false){ 00373 cout<<getUsageComment()<<endl; 00374 return(1); 00375 } 00376 00377 for(uint i = 0 ; i < manager.numExtraArgs() ; i++){ 00378 addArgument(manager.getExtraArg(i),std::string("=")) ; 00379 } 00380 00381 manager.setOptionValString(&OPT_EventLogFileName, argMap["logfile"]); 00382 manager.setOptionValString(&OPT_EyeTrackerType, "ISCAN"); 00383 nub::soft_ref<EyeTracker> eyet = etc->getET(); 00384 d->setEyeTracker(eyet); 00385 eyet->setEventLog(el); 00386 00387 //now let's open the audio channel 00388 if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 ){ 00389 LINFO( "did not open the mix-audio") ; 00390 return -1 ; 00391 } 00392 //let's load the 10 audio files off the director of audios and put them in a map 00393 map<int,Mix_Music*> audioMap ; 00394 for( int i = 0 ; i < 10 ; i++ ){ 00395 string str = argMap["sound-dir"]+"/"+stringify(i)+".wav" ; 00396 audioMap[i] = Mix_LoadMUS(str.c_str()); 00397 } 00398 00399 int numOfDigits = atoi(argMap["num-of-digits"].c_str()); 00400 int cue_onset_frames = atoi(argMap["cue-onset-frames"].c_str()) ; 00401 int cue_wait_frames = atoi(argMap["cue-wait-frames"].c_str()) ; 00402 00403 // let's get all our ModelComponent instances started: 00404 manager.start(); 00405 for(map<string,string>::iterator it= argMap.begin(); it!= argMap.end() ; ++it) d->pushEvent("arg:"+ it->first+" value:"+it->second ) ; 00406 // let's display an ISCAN calibration grid: 00407 d->clearScreen(); 00408 d->displayISCANcalib(); 00409 d->waitForMouseClick(); 00410 d->displayText("Here the experiment starts! click to start!"); 00411 d->waitForMouseClick(); 00412 d->clearScreen(); 00413 //let's see in what mode the user like to run the program 00414 int numOfTests = atoi(argMap["test-rounds"].c_str()) ; 00415 00416 float memPrb = atof(argMap["recall-prb"].c_str()) ; 00417 00418 //let's do calibration 00419 d->displayText("CLICK LEFT button to calibrate; RIGHT to skip"); 00420 int cl = d->waitForMouseClick(); 00421 if (cl == 1) d->displayEyeTrackerCalibration(3,5,1 , true); 00422 d->clearScreen(); 00423 //let's count the rounds 00424 int cr = 0 ; 00425 while( cr <numOfTests ){ 00426 d->showCursor(true); 00427 d->displayText("click one of the mouse buttons to start!"); 00428 d->waitForMouseClick() ; 00429 d->showCursor(false) ; 00430 d->clearScreen() ; 00431 d->displayFixationBlink(); 00432 float mP = (float) rand()/RAND_MAX ; 00433 vector<int> digits = getDigits(numOfDigits,argMap["include-zero"],argMap["digit-repeat"]); 00434 int counter = 0 ; 00435 while( counter < numOfDigits ){ 00436 if(Mix_PlayingMusic() == 0 ){ 00437 //Play the music 00438 if( Mix_PlayMusic( audioMap[digits[counter]], 0 ) == -1 ) { return 1; } 00439 d->pushEvent("the "+stringify(counter)+"th : " +stringify(digits[counter])); 00440 counter++ ; 00441 } 00442 } 00443 //just hold it there to the end of the audio playing 00444 while( Mix_PlayingMusic() == 1 ){} 00445 00446 00447 if(mP > memPrb){ 00448 cr++; 00449 d->pushEvent("**************************************") ; 00450 if(cue_wait_frames != 0) d->waitFrames(cue_wait_frames) ; 00451 std::string imst = "===== Showing image: def"+ stringify(cr)+ ".png ====="; 00452 d->pushEvent(imst); 00453 eyet->track(true); 00454 d->displayRedDotFixation(); 00455 d->waitFrames(cue_onset_frames); 00456 long st = d->getTimerValue() ;//we take this time as the start time of the operation, right after the signal for sorting 00457 d->clearScreen() ; 00458 d->pushEvent("manipulation starts"); 00459 d->waitForMouseClick() ; 00460 long et = d->getTimerValue();//we take this time as the end time of the operation, right after recieving the mouse click 00461 eyet->track(false); 00462 d->pushEvent("manipulation ends") ; 00463 d->pushEvent("manipulation time:"+stringify(et-st)); 00464 string answer = getDigitSequenceFromSubject(argMap["alphabet"] , numOfDigits); 00465 d->pushEvent("subject keyed : "+answer); 00466 00467 }else{ 00468 d->pushEvent("+++++++++++++++++++++++++++++++++++++++") ; 00469 if(cue_wait_frames != 0) d->waitFrames(cue_wait_frames) ; 00470 d->displayFixationBlink(d->getWidth()/2,d->getHeight()/2,1,cue_onset_frames); 00471 d->pushEvent("subject is being challenged for simple momorization"); 00472 string answer = getDigitSequenceFromSubject(argMap["alphabet"] , numOfDigits); 00473 d->pushEvent("subject keyed : "+answer); 00474 } 00475 } 00476 00477 d->clearScreen(); 00478 d->displayText("Experiment complete. Thank you!"); 00479 d->waitForMouseClick(); 00480 00481 00482 // stop all our ModelComponents 00483 manager.stop(); 00484 00485 //let's free up the audio files 00486 for( int i = 0 ; i < 10 ; i++ ){ 00487 Mix_FreeMusic(audioMap[i]); 00488 } 00489 //time to close the audio channel 00490 Mix_CloseAudio(); 00491 // all done! 00492 return 0; 00493 } 00494 00495 #endif // INVT_HAVE_LIBSDL_IMAGE 00496