00001 /*!@file AppPsycho/psycho-mem-capacity.C Psychophysics measuring the digit memorization capacity */ 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-mem-capacity-test.C $ 00035 // $Id: psycho-mem-capacity-test.C 10794 2009-02-08 06:21:09Z itti $ 00036 // 00037 00038 #include "Component/ModelManager.H" 00039 #include "Image/Image.H" 00040 #include "Psycho/PsychoDisplay.H" 00041 #include "Psycho/EyeTrackerConfigurator.H" 00042 #include "Psycho/EyeTracker.H" 00043 #include "Psycho/PsychoOpts.H" 00044 #include "Component/EventLog.H" 00045 #include "Component/ComponentOpts.H" 00046 #include "Raster/Raster.H" 00047 #include "Util/MathFunctions.H" 00048 #include "Util/Types.H" 00049 #include "GameBoard/basic-graphics.H" 00050 #include <sys/types.h> 00051 #include <dirent.h> 00052 #include <errno.h> 00053 #include <vector> 00054 #include <string> 00055 #include <iostream> 00056 #include <SDL/SDL.h> 00057 #include <SDL/SDL_image.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 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-Concurrent-Digit"); 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 00094 // a functionf for stringigying things 00095 template <class T> std::string stringify(T i) 00096 { 00097 ostringstream o ; 00098 o << i ; 00099 return o.str(); 00100 } 00101 00102 00103 00104 00105 bool itIsInThere(int x , vector<int> bag){ 00106 for( uint i=0 ; i < bag.size(); i++ ){ 00107 if(x == bag[i]) return true ; 00108 } 00109 return false ; 00110 } 00111 00112 //////////////////////////////////////////////////////// 00113 ///// simply generates a sequence of digits displayed on random places on the screen 00114 //////////////////////////////////////////////////////// 00115 string digitMemorizationTask(uint l, int maxForDigit=10 ,float wp = 1.0f, float hp = 1.0f, int displayFrame = 10 , int delayFrame = 30 ){ 00116 d->clearScreen() ; 00117 vector<int> pickedones = vector<int>() ; 00118 string test = string("") ; 00119 string tp = string("") ; 00120 int widthRange = (int)((float)(d->getWidth()) * wp); 00121 int heightRange = (int)((float)(d->getHeight()) * hp); 00122 int wMargin = (d->getWidth() - widthRange)/2 ; 00123 int hMargin = (d->getHeight() - heightRange) / 2 ; 00124 for(uint i = 0 ; i < l ; i++){ 00125 int nd; 00126 do{ nd= rand()% maxForDigit ; }while(itIsInThere(nd,pickedones) && pickedones.size() < 11) ; 00127 pickedones.push_back(nd); 00128 tp = stringify(nd) ; 00129 test += tp ; 00130 } 00131 00132 int x = (rand()%widthRange) + wMargin; 00133 int y = (rand()%heightRange ) + hMargin ; 00134 d->displayText(test,Point2D<int>(x,y),PixRGB<byte>(0,0,0),PixRGB<byte>(128,128,128),true) ; 00135 d->waitFrames(displayFrame*l) ; 00136 d->clearScreen() ; 00137 return test ; 00138 } 00139 00140 00141 //////////////////////////////////////////////////////// 00142 ///////this will change the order of elements in a vector to a random order 00143 //////////////////////////////////////////////////////// 00144 void scramble(vector<string>& v){ 00145 vector<string> tv = vector<string>() ; 00146 while(v.size()>0){ 00147 tv.push_back(v[0]); 00148 v.erase(v.begin()); 00149 } 00150 int i = 0 ; 00151 while(tv.size()>0){ 00152 i = rand()%tv.size() ; 00153 v.push_back(tv[i]); 00154 tv.erase(tv.begin()+i); 00155 } 00156 } 00157 00158 00159 //////////////////////////////////////////////////////////////// 00160 ////This is our button factory 00161 //////////////////////////////////////////////////////////////// 00162 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){ 00163 Image<PixRGB<byte> > textIm(d->getWidth(),d->getHeight(),ZEROS); 00164 textIm.clear(bgcolor); 00165 writeText(textIm, Point2D<int>((size.i - label.length()*10)/2,(size.j-20) /2),label.c_str(),txtcolor,bgcolor); 00166 SDL_Surface *surf = d->makeBlittableSurface(textIm , true); 00167 Uint32 bc = d->getUint32color(bordercolor); 00168 drawRectangle(surf,bc,0,0,size.i -1,size.j -1 ,border); 00169 SDL_Surface* blank =getABlankSurface(size.i , size.j); 00170 SDL_Rect clip; 00171 clip.x = 0 ; 00172 clip.y = 0 ; 00173 clip.w = size.i ; 00174 clip.h = size.j ; 00175 apply_surface(0,0,*surf,*blank,clip); 00176 dumpSurface(surf) ; 00177 return blank ; 00178 } 00179 00180 //////////////////////////////////////////////////////////////////////// 00181 ////This is the function for creating the keypad, in fact it generates 00182 ////12 buttons and associates the actions to the region for each button 00183 //////////////////////////////////////////////////////////////////////// 00184 00185 SDL_Surface* getKeyPad(map<string , SDL_Rect>& buttmap){ 00186 SDL_Surface* pad= getABlankSurface(d->getWidth()/4,d->getHeight()/3); 00187 SDL_Rect clip; 00188 clip.x=0; 00189 clip.y=0; 00190 clip.w= pad->w / 3 ; 00191 clip.h = pad->h / 4 ; 00192 //keys for 1 to 9 00193 for( int i = 1 ; i < 10 ; i++){ 00194 SDL_Surface* but = getButtonImage(stringify(i),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / 3 , pad->h / 4),PixRGB<byte>(255, 98 , 25),3); 00195 SDL_Rect cl ; 00196 cl.x = ((i-1)%3)*(pad->w)/3 ; cl.y= ((i-1)/3)*((pad->h)/4) ; 00197 cl.w = clip.w ; 00198 cl.h = clip.h ; 00199 apply_surface( cl.x , cl.y ,*but,*pad,clip); 00200 buttmap[stringify(i)] = cl ; 00201 dumpSurface(but); 00202 } 00203 SDL_Rect cl1 ; 00204 cl1.x = 0 ; cl1.y= 3*((pad->h)/4) ; 00205 cl1.w = clip.w ; 00206 cl1.h = clip.h ; 00207 buttmap["z"] = cl1 ; 00208 SDL_Surface* but = getButtonImage(string("<-"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / 3 , pad->h / 4),PixRGB<byte>(255, 98 , 25),3); 00209 apply_surface(0, 3*((pad->h)/4),*but,*pad,clip); 00210 dumpSurface(but); 00211 SDL_Rect cl2 ; 00212 cl2.x = (pad->w)/3 ; cl2.y= 3*((pad->h)/4) ; 00213 cl2.w = clip.w ; 00214 cl2.h = clip.h ; 00215 buttmap["0"] = cl2 ; 00216 but = getButtonImage(string("0"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / 3 , pad->h / 4),PixRGB<byte>(255, 98 , 25),3); 00217 apply_surface((pad->w)/3, 3*((pad->h)/4),*but,*pad,clip); 00218 dumpSurface(but); 00219 SDL_Rect cl3 ; 00220 cl3.x = 2*(pad->w)/3 ; cl3.y= 3*((pad->h)/4) ; 00221 cl3.w = clip.w ; 00222 cl3.h = clip.h ; 00223 buttmap["o"] = cl3 ; 00224 but = getButtonImage(string("Ok"),PixRGB<byte>(0,0,0),PixRGB<byte>(255,255,255),Point2D<int>(pad->w / 3 , pad->h / 4),PixRGB<byte>(255, 98 , 25),3); 00225 apply_surface(2*(pad->w)/3, 3*((pad->h)/4),*but,*pad,clip); 00226 dumpSurface(but); 00227 return pad ; 00228 } 00229 00230 00231 00232 00233 /////////////////////////////////////////////////////////////////////////// 00234 /////this function listens to mouse clicks and then finds the region of the screen 00235 /////associated with the action, buttmap is the map of the region, offset is the offset of 00236 /////buttons 00237 /////////////////////////////////////////////////////////////////////////// 00238 string getPressedButtonCommand(map<string , SDL_Rect>& buttmap,Point2D<int> offset=Point2D<int>(0,0)){ 00239 int quit = 0 ; 00240 string s ; 00241 SDL_Event event ; 00242 while( quit!=2 ){ 00243 while( SDL_PollEvent( &event ) ) { 00244 if(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT ){ 00245 for( map<string , SDL_Rect>::iterator it = buttmap.begin() ; it!=buttmap.end() ; ++it){ 00246 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) { 00247 quit = 2 ; 00248 s = it->first ; 00249 break; 00250 } 00251 00252 } 00253 } 00254 00255 } 00256 } 00257 return s ; 00258 00259 } 00260 00261 00262 //////////////////////////////////////////////////// 00263 ////This function creates a virtual keypad, creates a map of buttons 00264 ////and their representation area and listens to the button press and at 00265 ////the end returns the keyed digits 00266 //////////////////////////////////////////////////// 00267 string getDigitSequenceFromSubject(uint maxl = 7 ){ 00268 d->showCursor(true) ; 00269 //let's creat a map to map actions to regions of the screen, each region is represented as an SDL_Rect 00270 map<string , SDL_Rect>* buttmap = new map<string , SDL_Rect>(); 00271 //now let's get the keypad surface while we get the actions map to regions 00272 SDL_Surface * keypad = getKeyPad(*buttmap); 00273 //this will be the offset of displaying the keypad on the screen 00274 SDL_Rect offset ; 00275 offset.x = (d->getWidth() - keypad->w) /2; 00276 offset.y = (d-> getHeight() - keypad->h) /2; 00277 //now let's display the keypad 00278 d->displaySDLSurfacePatch(keypad , &offset,NULL , -2,false, true); 00279 //this will hold the final string keyed be the subject 00280 string p = string("") ; 00281 //this is a temporary string holding the last action related to the pressed key 00282 string tp = string(""); 00283 //now let's record subject's key press 00284 while( tp.compare("o")!=0 ){ 00285 //this button is actually the display for the current string 00286 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) ; 00287 SDL_Rect offs ; offs.x = (d->getWidth() - dp->w) /2 ; offs.y = d->getHeight()/6 ; 00288 d->displaySDLSurfacePatch(dp , &offs , NULL , -2 , false ,true ) ; 00289 //now let's listen to button events 00290 tp = getPressedButtonCommand(*buttmap,Point2D<int>(offset.x,offset.y)) ; 00291 dumpSurface(dp) ; 00292 if(tp.compare("z")==0 && p.size()>=0 ) { 00293 if (p.size()>0) p = p.substr(0,p.size()-1) ; 00294 }else{ 00295 if(p.size() < maxl && tp.compare("o")!=0) { 00296 p +=tp ; 00297 } 00298 00299 } 00300 00301 } 00302 buttmap = 0 ; 00303 dumpSurface(keypad) ; 00304 d->clearScreen() ; 00305 return p ; 00306 00307 } 00308 00309 00310 00311 //////////////////////////////////////////////////////////////////// 00312 //// gets a string as the argument and returns a string composed of 00313 //// characters of the first string sorted in the ascending order 00314 /////////////////////////////////////////////////////////////////// 00315 string ascSort(string st) 00316 { 00317 string res = "" ; 00318 vector<string> v = vector<string>(); 00319 for(uint i = 0 ; i < st.size() ; i++) v.push_back(st.substr(i,1)) ; 00320 00321 std::sort(v.begin(), v.end()); 00322 00323 for ( uint i = 0 ; i < v.size() ; i++ ){ 00324 res += v[i] ; 00325 } 00326 return res; 00327 } 00328 00329 //////////////////////////////////////////////////////////////////// 00330 //// gets a string as the argument and returns a string composed of 00331 //// characters of the first string sorted in the descending order 00332 /////////////////////////////////////////////////////////////////// 00333 string desSort(string st) 00334 { 00335 string res = "" ; 00336 vector<string> v = vector<string>(); 00337 for(uint i = 0 ; i < st.size() ; i++) v.push_back(st.substr(i,1)) ; 00338 std::sort(v.begin(), v.end()); 00339 std::reverse(v.begin(), v.end()); 00340 for ( uint i = 0 ; i < v.size() ; i++ ){ 00341 res += v[i] ; 00342 } 00343 return res; 00344 } 00345 00346 /////////////////////////////////////////////////////////////// 00347 //////gets the test string, answer and the mode and identifies if 00348 //////the answer matches the thing it should be, mode=0 checks if 00349 //////if the answer and test string simply the same, mode=1 matches 00350 //////the answer against the ascending sorted string of the test string 00351 //////mode=2 compares the answer against the descending sorted of 00352 //////the test string 00353 /////////////////////////////////////////////////////////////// 00354 bool isAnswerCorrect(string test , string answer , int mode){ 00355 00356 if(mode == 0 && answer.compare(test)==0) return true ; 00357 00358 if(mode == 1 && answer.compare(ascSort(test))==0) return true ; 00359 00360 if(mode == 2 && answer.compare(desSort(test))==0) return true ; 00361 00362 return false; 00363 } 00364 00365 00366 00367 int addArgument(const string st,const string delim="="){ 00368 int i = st.find(delim) ; 00369 argMap[st.substr(0,i)] = st.substr(i+1); 00370 00371 return 0 ; 00372 } 00373 00374 std::string getArgumentValue(string arg){ 00375 return argMap[arg] ; 00376 } 00377 00378 std::string getUsageComment(){ 00379 00380 string com = string("\nlist of arguments : \n"); 00381 00382 com += "\ndelay-interval=[>1]:[>1] (the interval for random delay after stroop task) {default=10}\n"; 00383 com += "\nfixation-blink=[y/n] (show the blink for fixation after stroop task or no) {defaule y}\n"; 00384 com += "\nlogfile=[logfilename.psy] {default = psycho-stroop-concurrent.psy}\n" ; 00385 com += "\nmemo=[a_string_without_white_space]\n"; 00386 com += "\nmode=[1] {default = 1}\n"; 00387 com += "\nstart-digit-size=[<1](number of words in the stroop task){default=6} \n" ; 00388 com += "\nmax-digit-size=[>6](the maximum digits that will be used for test){default=10} \n"; 00389 com += "\naccuracy-threshold=[>0 and <1.0] (the threshold for accuracy){default=.85}\n" ; 00390 com += "\nsubject=[subject_name] \n" ; 00391 com += "\ntest-rounds=[>1] (number of tests for each string size) {default=10}\n"; 00392 com += "\ndigit-onset=[>1] (number of frames that the digit will remain onset for each digit){default=10}\n"; 00393 com += "\ndigit-offset=[>1](number of frames that between two consequent digit onset){default=30}\n"; 00394 com += "\nwidth-range=[1..100](the percentage of the width of screen for showing the digits){default=100}\n"; 00395 com += "\nheight-range=[1..100](the percentage of the height of screen for showing the digits){default=100}\n"; 00396 return com ; 00397 } 00398 00399 00400 extern "C" int main(const int argc, char** argv) 00401 { 00402 00403 MYLOGVERB = LOG_INFO; // suppress debug messages 00404 //let's push the initial value for the parameters 00405 argMap["mode"] = "1" ; 00406 argMap["logfile"]="psycho-digit-concurrent.psy" ; 00407 argMap["start-digit-size"]="6" ; 00408 argMap["max-digit-size"]="10" ; 00409 argMap["accuracy-threshold"]=".85"; 00410 argMap["test-rounds"]="10"; 00411 argMap["delay-interval"]="60:160" ; 00412 argMap["subject"]="" ; 00413 argMap["memo"]="" ; 00414 argMap["fixation-blink"]="y" ; 00415 argMap["digit-onset"]="10" ; 00416 argMap["digit-offset"]="30" ; 00417 argMap["width-range"]="100" ; 00418 argMap["height-range"]="100" ; 00419 00420 manager.addSubComponent(d); 00421 nub::soft_ref<EventLog> el(new EventLog(manager)); 00422 manager.addSubComponent(el); 00423 d->setEventLog(el); 00424 00425 if (manager.parseCommandLine(argc, argv, 00426 "at least one argument needed", 1, -1)==false){ 00427 cout<<getUsageComment()<<endl; 00428 return(1); 00429 } 00430 00431 for(uint i = 0 ; i < manager.numExtraArgs() ; i++){ 00432 addArgument(manager.getExtraArg(i),std::string("=")) ; 00433 } 00434 00435 manager.setOptionValString(&OPT_EventLogFileName, argMap["logfile"]); 00436 00437 00438 00439 // let's get all our ModelComponent instances started: 00440 manager.start(); 00441 for(map<string,string>::iterator it= argMap.begin(); it!= argMap.end() ; ++it) d->pushEvent("arg:"+ it->first+" value:"+it->second ) ; 00442 // let's display an ISCAN calibration grid: 00443 d->clearScreen(); 00444 d->displayISCANcalib(); 00445 d->waitForMouseClick(); 00446 00447 // let's do an eye tracker calibration: 00448 d->displayText("Here the experiment starts! click to start!"); 00449 d->waitForMouseClick(); 00450 d->clearScreen(); 00451 //let's see in what mode the user like to run the program 00452 int mode = atoi(argMap["mode"].c_str()) ; 00453 float accuracyThreshold = atof(argMap["accuracy-threshold"].c_str()); 00454 //mode 1 and 2 inlude single memorization task, 1 for just memorization and 2 for dynamic memorization 00455 //in dynamic memorization the subject has to sort the string in ascending order when the last digit is even 00456 //and descending order if the last digit is odd 00457 if(mode==1 || true){ 00458 int numOfTests = atoi(argMap["test-rounds"].c_str()) ; 00459 int startStringSize = atoi(argMap["start-digit-size"].c_str()) ; 00460 int maxStringSize = atoi(argMap["max-digit-size"].c_str()); 00461 string::size_type position = argMap["delay-interval"].find(":"); 00462 int minDel = atoi(argMap["delay-interval"].substr(0,position).c_str()) ; 00463 int maxDel = atoi(argMap["delay-interval"].substr(position+1).c_str()) ; 00464 float wr = ((float)(atoi(argMap["width-range"].c_str())))/100.0f; 00465 float hr = ((float)(atoi(argMap["height-range"].c_str())))/100.0f; 00466 int onsetDel = atoi(argMap["digit-onset"].c_str()) ; 00467 int offsetDel = atoi(argMap["digit-offset"].c_str()) ; 00468 float accuracy=1.0f; 00469 int currentStringSize = startStringSize ; 00470 while(accuracy>accuracyThreshold && currentStringSize <= maxStringSize){ 00471 00472 int correctCount = 0 ; 00473 for(int i = 0 ; i < numOfTests ; i++){ 00474 d->pushEvent("**************************************") ; 00475 d->showCursor(true); 00476 d->displayText("click one of the mouse buttons to start!"); 00477 d->waitForMouseClick() ; 00478 d->showCursor(false); 00479 // d->clearScreen(); 00480 string testString ; 00481 testString = digitMemorizationTask(currentStringSize, 10 , wr , hr , onsetDel , offsetDel) ; 00482 // 00483 d->pushEvent("the memorization sequence is : "+testString) ; 00484 d->waitFrames((rand()%(maxDel - minDel)) +minDel); 00485 string answer = getDigitSequenceFromSubject(testString.size()); 00486 bool af = false ; 00487 af = isAnswerCorrect(testString,answer,0); 00488 d->pushEvent("subject keyed : "+answer); 00489 if(af){ 00490 d->pushEvent("answer was correct"); 00491 correctCount++ ; 00492 }else{ 00493 d->pushEvent("answer was incorrect"); 00494 } 00495 00496 } 00497 accuracy = float(correctCount)/float(numOfTests); 00498 d->pushEvent("string size:" + stringify(currentStringSize)+" accuracy :"+stringify(accuracy)); 00499 currentStringSize++ ; 00500 00501 } 00502 d->pushEvent("the appropriate test size :" + stringify((currentStringSize-1))); 00503 } 00504 00505 00506 00507 d->clearScreen(); 00508 d->displayText("Experiment complete. Thank you!"); 00509 d->waitForMouseClick(); 00510 00511 // stop all our ModelComponents 00512 manager.stop(); 00513 00514 00515 // all done! 00516 return 0; 00517 } 00518 00519 #endif // INVT_HAVE_LIBSDL_IMAGE 00520