00001 /*!@file AppPsycho/psycho-narrator.C Psychophysics interactive display of 00002 still images or movies with speech recording */ 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00006 // University of Southern California (USC) and the iLab at USC. // 00007 // See http://iLab.usc.edu for information about this project. // 00008 // //////////////////////////////////////////////////////////////////// // 00009 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00010 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00011 // in Visual Environments, and Applications'' by Christof Koch and // 00012 // Laurent Itti, California Institute of Technology, 2001 (patent // 00013 // pending; application number 09/912,225 filed July 23, 2001; see // 00014 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00015 // //////////////////////////////////////////////////////////////////// // 00016 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00017 // // 00018 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00019 // redistribute it and/or modify it under the terms of the GNU General // 00020 // Public License as published by the Free Software Foundation; either // 00021 // version 2 of the License, or (at your option) any later version. // 00022 // // 00023 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00024 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00025 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00026 // PURPOSE. See the GNU General Public License for more details. // 00027 // // 00028 // You should have received a copy of the GNU General Public License // 00029 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00030 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00031 // Boston, MA 02111-1307 USA. // 00032 // //////////////////////////////////////////////////////////////////// // 00033 // 00034 // Primary maintainer for this file: Jinyong Lee <jinyongl@usc.edu> 00035 // $HeadURL: 00036 // $Id: psycho-narrator.C 00037 // 00038 00039 #include "Component/ModelManager.H" 00040 #include "Component/ComponentOpts.H" 00041 #include "Component/ModelOptionDef.H" 00042 #include "Component/EventLog.H" 00043 00044 #include "Image/Image.H" 00045 #include "Raster/Raster.H" 00046 #include "Video/VideoFrame.H" 00047 #include "Util/Types.H" 00048 #include "Util/MathFunctions.H" 00049 #include "Util/FileUtil.H" 00050 #include "Util/sformat.H" 00051 #include "rutz/time.h" 00052 00053 #include "Psycho/PsychoDisplay.H" 00054 #include "Psycho/EyeTrackerConfigurator.H" 00055 #include "Psycho/EyeTracker.H" 00056 #include "Psycho/PsychoOpts.H" 00057 #include "GUI/GUIOpts.H" 00058 00059 #include "Devices/AudioGrabber.H" 00060 #include "Devices/AudioMixer.H" 00061 #include "Devices/DeviceOpts.H" 00062 #include "Audio/AudioWavFile.H" 00063 00064 #include "Media/MPEGStream.H" 00065 #include "Media/MediaOpts.H" 00066 #include "Neuro/NeuroOpts.H" 00067 00068 #include <vector> 00069 #include <pthread.h> 00070 #include <signal.h> 00071 00072 00073 // ###################################################################### 00074 // Definitions & Constants 00075 00076 #define CACHELEN 150 00077 00078 // supported visual stimulus file extensions (adopted from "Media/MediaOpts.C") 00079 // needed to be constantly updated unless there's a way to check automatically 00080 // 00081 const char *imageExtensions[] = { ".pnm", ".pgm", ".ppm", ".pbm", ".pfm", ".png", ".jpeg", ".jpg", ".dpx", NULL }; 00082 const char *movieExtensions[] = { ".avi", ".mpg", ".mpeg", ".m4v", ".m2v", ".mov", ".flv", ".dv", NULL }; 00083 00084 // experiment procedure type 00085 #define PROC_NORMAL 0 00086 #define PROC_EXP1 1 // online-offline switch 00087 #define PROC_EXP2 2 // scene pairs & online-offline switch 00088 #define PROC_EXP3 3 // change blindness paradigm 00089 #define PROC_EXP4 4 // scene pairs & low-high threshold switch 00090 #define PROC_INVALID -1 00091 00092 // input stimulus type 00093 #define STIM_IMAGE 0 00094 #define STIM_MOVIE 1 00095 #define STIM_UNKNOWN -1 00096 00097 00098 // recording styles 00099 #define REC_NONE 0 00100 #define REC_DURING (1 << 0) 00101 #define REC_AFTER (1 << 1) 00102 #define REC_ALL (REC_DURING | REC_AFTER) 00103 00104 00105 // session structures 00106 struct SESSION_EXP12 00107 { 00108 uint index; 00109 uint staticPeriod; 00110 uint blankPeriod; 00111 00112 const char* flag; 00113 const char* message; 00114 }; 00115 00116 struct SESSION_EXP3 00117 { 00118 uint index; 00119 uint style; 00120 00121 const char* flag; 00122 }; 00123 00124 struct SESSION_EXP4 00125 { 00126 uint index; 00127 uint task; 00128 bool practice; 00129 00130 const char* flag; 00131 const char* reminder; 00132 }; 00133 00134 // 00135 // psycho-narrator options 00136 // 00137 00138 static const ModelOptionDef OPT_ProcedureType = 00139 { MODOPT_ARG_STRING, "ProcedureType", &MOC_DISPLAY, OPTEXP_CORE, 00140 "Use experiment specific procedure types that would override relevant parameter settings.", 00141 "proc-type", '\0', "<Normal|Exp1|Exp2|Exp3|Exp4>", "Normal" }; 00142 00143 static const ModelOptionDef OPT_EyeTrackerRecalib = 00144 { MODOPT_ARG(uint), "EyeTrackerRecalibration", &MOC_EYETRACK, OPTEXP_CORE, 00145 "Recalibration frequency of EyeTracker. Set 0 if you don't want recalibration at all." 00146 "If you set to 1, then recalibration will be done after every single image session.", 00147 "et-recalib", '\0', "<int>", "0" }; 00148 00149 static const ModelOptionDef OPT_ShuffleOrder = 00150 { MODOPT_FLAG, "ShuffleOrder", &MOC_DISPLAY, OPTEXP_CORE, 00151 "Whether shuffle the order of input stimuli or not.", 00152 "shuffle", '\0', "<bool>", "false" }; 00153 00154 static const ModelOptionDef OPT_MouseInput = 00155 { MODOPT_FLAG, "MouseInput", &MOC_DISPLAY, OPTEXP_CORE, 00156 "Make mouse input available and use it instead of key presses.", 00157 "mouse-input", '\0', "<bool>", "false" }; 00158 00159 static const ModelOptionDef OPT_BlankPeriod = 00160 { MODOPT_ARG(uint), "BlankPeriod", &MOC_DISPLAY, OPTEXP_CORE, 00161 "The period (in sec) of the blank sessions between the visual stimulus presentations. " 00162 "If set to 0, no blank session will be presented. If set bigger than 999," 00163 "blank session continues until the user presses mouse button or a key.", 00164 "blank-period", '\0', "<int>", "5" }; 00165 00166 static const ModelOptionDef OPT_StaticPeriod = 00167 { MODOPT_ARG(uint), "StaticImagePeriod", &MOC_DISPLAY, OPTEXP_CORE, 00168 "The period (in sec) of static images (if the currently showing image is a raster file) " 00169 "during which they are presented on the screen. For video clips, this option is ignored.", 00170 "static-period", '\0', "<int>", "5" }; 00171 00172 static const ModelOptionDef OPT_EyeTrackerRec = 00173 { MODOPT_ARG_STRING, "EyeTrackerRecordStyle", &MOC_EYETRACK, OPTEXP_SAVE, 00174 "During when eye-tracker data should be grabbed and recorded. " 00175 "'During' means the recording is done only during the stimulus presentation whereas " 00176 "'All' is done even after the presentation - i.e. including the blank presentation.", 00177 "et-rec", '\0', "<During|All>", "During" }; 00178 00179 static const ModelOptionDef OPT_AudRec = 00180 { MODOPT_ARG_STRING, "AudioRecordStyle", &MOC_DISPLAY, OPTEXP_SAVE, 00181 "During when audio(speech) should be grabbed and recorded. " 00182 "'During' means the recording is done only during the stimulus presentation whereas " 00183 "'After' is done only after the presentation - i.e. during the blank sessions.", 00184 "aud-rec", '\0', "<None|During|After|All>", "All" }; 00185 /* 00186 static const ModelOptionDef OPT_ExpName = 00187 { MODOPT_ARG_STRING, "ExperimentName", &MOC_DISPLAY, OPTEXP_SAVE, 00188 "The name of experiment. " 00189 "Event log and recorded speech will be saved as the name specified by this option.", 00190 "exp-name", '\0', "<name>", "exp_narrator" }; 00191 */ 00192 00193 00194 // thread mutex variable key for audio recording 00195 static pthread_mutex_t audMutexKey = PTHREAD_MUTEX_INITIALIZER; 00196 volatile bool audExit = false; // audio thread exit flag 00197 volatile bool audRec = false; // grabbed audio recording flag 00198 00199 std::vector< AudioBuffer<byte> > rec; // grabbed audio buffer 00200 00201 00202 // ###################################################################### 00203 // Submodules 00204 00205 // case insensitive conversion of record style 00206 int readRecordStyle( const std::string& recStr ) 00207 { 00208 if( strcasecmp( recStr.c_str(), "During" ) == 0 ) 00209 return REC_DURING; 00210 00211 if( strcasecmp( recStr.c_str(), "After" ) == 0 ) 00212 return REC_AFTER; 00213 00214 if( strcasecmp( recStr.c_str(), "All" ) == 0 ) 00215 return REC_ALL; 00216 00217 return REC_NONE; // no-recording by default 00218 } 00219 00220 // case insensitive conversion of procedure type 00221 int readProcType( const std::string& procType ) 00222 { 00223 if( strcasecmp( procType.c_str(), "Normal" ) == 0 ) 00224 return PROC_NORMAL; 00225 00226 if( strcasecmp( procType.c_str(), "Exp1" ) == 0 ) 00227 return PROC_EXP1; 00228 00229 if( strcasecmp( procType.c_str(), "Exp2" ) == 0 ) 00230 return PROC_EXP2; 00231 00232 if( strcasecmp( procType.c_str(), "Exp3" ) == 0 ) 00233 return PROC_EXP3; 00234 00235 if( strcasecmp( procType.c_str(), "Exp4" ) == 0 ) 00236 return PROC_EXP4; 00237 00238 return PROC_INVALID; // invalid procedure type 00239 } 00240 00241 // get stimulus file type according to the extension 00242 int getStimulusType( const std::string& fname ) 00243 { 00244 // check if it is a raster file 00245 for( int i = 0; imageExtensions[i]; i ++ ) 00246 if( hasExtension( fname, imageExtensions[i] )) 00247 return STIM_IMAGE; 00248 00249 // check if it is a movie file 00250 for( int i = 0; movieExtensions[i]; i ++ ) 00251 if( hasExtension( fname, movieExtensions[i] )) 00252 return STIM_MOVIE; 00253 00254 return STIM_UNKNOWN; // unknown file type 00255 } 00256 00257 // extract stimulus name field only 00258 void getStimulusName( const std::string &stimPath, std::string &stimName ) 00259 { 00260 std::string name, path; 00261 splitPath( stimPath, path, name ); 00262 std::string::size_type dot = name.rfind( '.' ); 00263 if( dot != name.npos ) name.erase( dot ); 00264 00265 stimName = name; 00266 } 00267 00268 // pause until a key/mouse button pressed 00269 void pause( bool mouse, nub::soft_ref<PsychoDisplay>& d ) 00270 { 00271 if( mouse ) 00272 d->waitForMouseClick(); 00273 else 00274 d->waitForKey(); 00275 } 00276 00277 // sleep while checking key press 00278 void snooze( uint sec, nub::soft_ref<PsychoDisplay>& d ) 00279 { 00280 if( sec < 1 ) return; 00281 00282 rutz::time start = rutz::time::wall_clock_now(); 00283 rutz::time stop; 00284 00285 do { 00286 d->checkForKey(); 00287 usleep( 50000 ); // wait for 0.05 sec 00288 stop = rutz::time::wall_clock_now(); 00289 00290 } while( (uint)(stop-start).sec() < sec ); 00291 } 00292 00293 // start/stop eye-tracking 00294 void trackEyes( bool trk, nub::soft_ref<EyeTracker>& et, nub::soft_ref<PsychoDisplay>& d ) 00295 { 00296 if( trk ) 00297 { 00298 if( !et->isTracking() ) 00299 { 00300 // start the eye tracker 00301 et->track( true ); 00302 00303 // blink the fixation point at the center 00304 d->displayFixationBlink(); 00305 } 00306 } else 00307 { 00308 if( et->isTracking() ) 00309 { 00310 // stop the eye tracker 00311 usleep( 50000 ); // wait for 0.05 sec 00312 et->track( false ); 00313 } 00314 } 00315 } 00316 00317 // start/stop audio recording 00318 void recordAudio( bool rec, nub::soft_ref<PsychoDisplay>& d ) 00319 { 00320 if( rec ) 00321 { 00322 if( !audRec ) d->pushEvent( "---- Audio Recording Start ----" ); 00323 audRec = true; 00324 } else 00325 { 00326 if( audRec ) d->pushEvent( "---- Audio Recording Stop ----" ); 00327 audRec = false; 00328 } 00329 } 00330 00331 // calibrate ISCAN 00332 void calibrateISCAN( bool mouse, nub::soft_ref<PsychoDisplay>& d ) 00333 { 00334 /* 00335 et->calibrate(d); 00336 d->clearScreen(); 00337 */ 00338 00339 d->clearScreen(); 00340 d->displayText( "ISCAN calibration" ); 00341 pause( mouse, d ); 00342 00343 // display ISCAN calibration grid 00344 d->clearScreen(); 00345 d->displayISCANcalib(); 00346 pause( mouse, d ); 00347 00348 // run 15-point calibration 00349 d->clearScreen(); 00350 00351 if( mouse ) 00352 { 00353 d->displayText( "click LEFT button to calibrate or RIGHT button to skip" ); 00354 int ret = d->waitForMouseClick(); 00355 if( ret == 1 ) d->displayEyeTrackerCalibration( 3, 5, 1, true ); 00356 } else 00357 { 00358 d->displayText( "press SPACE key to calibrate or other key to skip" ); 00359 int ret = d->waitForKey(); 00360 if( ret == ' ' ) d->displayEyeTrackerCalibration( 3, 5, 1 ); 00361 } 00362 00363 d->clearScreen(); 00364 } 00365 00366 // buffering movie frames 00367 static bool cacheFrame( nub::soft_ref<InputMPEGStream>& mp, std::deque<VideoFrame>& cache ) 00368 { 00369 const VideoFrame frame = mp->readVideoFrame(); 00370 if( !frame.initialized() ) 00371 return false; // end of stream 00372 00373 cache.push_front( frame ); 00374 return true; 00375 } 00376 00377 // save recorded speech into a wave file 00378 void saveAudioRecord( const std::string &wavname, nub::soft_ref<PsychoDisplay>& d ) 00379 { 00380 pthread_mutex_lock( &audMutexKey ); 00381 00382 if( rec.size() > 0 ) // if there is some record to write 00383 { 00384 LINFO("Saving '%s'...", wavname.c_str()); 00385 00386 // write audio file 00387 d->pushEventBegin( std::string("writeAudioFile: '") + wavname + "'" ); 00388 writeAudioWavFile( wavname, rec ); 00389 d->pushEventEnd( "writeAudioFile" ); 00390 00391 rec.clear(); // reset recorded audio 00392 } 00393 00394 pthread_mutex_unlock( &audMutexKey ); 00395 } 00396 00397 // audio grabbing thread function 00398 static void *grabAudio( void *arg ) 00399 { 00400 LINFO("Initiating the audio-grabbing thread..."); 00401 00402 AudioGrabber *pAgb = (AudioGrabber*)arg; 00403 00404 // mask all allowed signals 00405 sigset_t mask; 00406 sigfillset( &mask ); 00407 if( pthread_sigmask( SIG_BLOCK, &mask, NULL ) != 0 ) 00408 LINFO("Failed to mask signals for the audio-grabbing thread!"); 00409 00410 // grab audio data endlessly 00411 while( !audExit ) 00412 { 00413 // grab audio data from input buffer 00414 AudioBuffer<byte> data; 00415 pAgb->grab( data ); 00416 00417 // record grabbed audio 00418 if( audRec ) 00419 { 00420 pthread_mutex_lock( &audMutexKey ); 00421 rec.push_back( data ); 00422 pthread_mutex_unlock( &audMutexKey ); 00423 } 00424 } 00425 00426 LINFO("Quitting the audio-grabbing thread..."); 00427 00428 return NULL; 00429 } 00430 00431 00432 // ###################################################################### 00433 // psycho-narrator main function 00434 // 00435 00436 static int submain( const int argc, char** argv ) 00437 { 00438 MYLOGVERB = LOG_INFO; // suppress debug messages 00439 00440 // create ModelManager 00441 ModelManager manager( "Psycho Narrator" ); 00442 00443 // hook up newly created psycho-narrator options 00444 OModelParam<std::string> procTypeStr( &OPT_ProcedureType, &manager ); 00445 OModelParam<uint> etRecalib( &OPT_EyeTrackerRecalib, &manager ); 00446 OModelParam<bool> shuffle( &OPT_ShuffleOrder, &manager ); 00447 OModelParam<bool> mouseInput( &OPT_MouseInput, &manager ); 00448 OModelParam<uint> blankPeriod( &OPT_BlankPeriod, &manager ); 00449 OModelParam<uint> staticPeriod( &OPT_StaticPeriod, &manager ); 00450 OModelParam<std::string> audRecStr( &OPT_AudRec, &manager ); 00451 OModelParam<std::string> etRecStr( &OPT_EyeTrackerRec, &manager ); 00452 // OModelParam<std::string> expName( &OPT_ExpName, &manager ); 00453 00454 // 00455 // create various ModelComponents 00456 // 00457 00458 // display 00459 nub::soft_ref<PsychoDisplay> d( new PsychoDisplay(manager) ); 00460 manager.addSubComponent( d ); 00461 00462 // event log 00463 nub::soft_ref<EventLog> el( new EventLog(manager) ); 00464 manager.addSubComponent( el ); 00465 00466 // eye-tracker configurator 00467 nub::soft_ref<EyeTrackerConfigurator> etc( new EyeTrackerConfigurator(manager) ); 00468 manager.addSubComponent( etc ); 00469 00470 // MPEG stream 00471 nub::soft_ref<InputMPEGStream> mp( new InputMPEGStream(manager, "Input MPEG Stream", "InputMPEGStream") ); 00472 manager.addSubComponent( mp ); 00473 00474 // audio mixer 00475 nub::soft_ref<AudioMixer> amx( new AudioMixer(manager) ); 00476 manager.addSubComponent( amx ); 00477 00478 // audio grabber 00479 nub::soft_ref<AudioGrabber> agb( new AudioGrabber(manager) ); 00480 manager.addSubComponent( agb ); 00481 00482 // audio buffer 00483 std::vector< AudioBuffer<byte> > rec; 00484 00485 // set option defaults 00486 manager.setOptionValString( &OPT_SDLdisplayDims, "1920x1080" ); 00487 manager.setOptionValString( &OPT_EventLogFileName, "narrator.psy" ); 00488 manager.setOptionValString( &OPT_EyeTrackerType, "ISCAN" ); 00489 manager.setOptionValString( &OPT_InputMPEGStreamPreload, "true" ); 00490 manager.setOptionValString( &OPT_AudioMixerLineIn, "false" ); 00491 manager.setOptionValString( &OPT_AudioMixerCdIn, "false" ); 00492 manager.setOptionValString( &OPT_AudioMixerMicIn, "true" ); 00493 manager.setOptionValString( &OPT_AudioGrabberBits, "8" ); 00494 manager.setOptionValString( &OPT_AudioGrabberFreq, "11025" ); 00495 manager.setOptionValString( &OPT_AudioGrabberBufSamples, "256" ); 00496 manager.setOptionValString( &OPT_AudioGrabberChans, "1" ); 00497 00498 // parse command-line 00499 if( manager.parseCommandLine( argc, argv, "<stimulus 1> ... <stimulus N>", 1, -1 ) == false ) 00500 return 1; 00501 00502 // manager.setOptionValString( &OPT_EventLogFileName, expName.getVal() + ".psy" ); 00503 00504 int etRecStyle = readRecordStyle( etRecStr.getVal() ); 00505 int audRecStyle = readRecordStyle( audRecStr.getVal() ); 00506 00507 int procType = readProcType( procTypeStr.getVal() ); 00508 if( procType == PROC_INVALID ) 00509 LFATAL("Invalid procedure type '%s'", procTypeStr.getVal().c_str()); 00510 00511 // hook up ModelComponents 00512 nub::soft_ref<EyeTracker> et = etc->getET(); 00513 d->setEyeTracker( et ); 00514 d->setEventLog( el ); 00515 et->setEventLog( el ); 00516 00517 if( !(audRecStyle & REC_ALL) ) // if no audio recording, remove audio components 00518 { 00519 manager.removeSubComponent( *amx, true ); 00520 manager.removeSubComponent( *agb, true ); 00521 } 00522 00523 // initiate ModelComponent instances 00524 manager.start(); 00525 00526 // create audio-grabbing thread 00527 // NOTE: audio thread should start here right after the grabber started 00528 pthread_t audGrbId; 00529 if( audRecStyle & REC_ALL ) // if audio recording, initiate the audio thread 00530 { 00531 pthread_create( &audGrbId, NULL, &grabAudio, (void*)agb.get() ); 00532 } 00533 00534 // 00535 // begin experiment 00536 // 00537 00538 // eye tracker calibration 00539 calibrateISCAN( mouseInput.getVal(), d ); 00540 00541 // 00542 // start experiment procedure 00543 // 00544 switch( procType ) 00545 { 00546 00547 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 00548 case PROC_NORMAL: 00549 LINFO("Normal experiment procedure..."); 00550 00551 { 00552 uint recalibCount = 0; 00553 uint stimNum = manager.numExtraArgs(); 00554 uint stimIndices[stimNum]; 00555 for( uint i = 0; i < stimNum; i ++ ) stimIndices[i] = i; 00556 if( shuffle.getVal() ) 00557 { 00558 LINFO("Shuffling stimulus display order..."); 00559 randShuffle( stimIndices, stimNum ); 00560 } 00561 00562 // session loop 00563 for( uint i = 0; i < stimNum; i ++ ) 00564 { 00565 // get the type of stimulus file according to its extension 00566 std::string stimPath = manager.getExtraArg(stimIndices[i]); 00567 std::string stimName; 00568 getStimulusName( stimPath, stimName ); 00569 int stimType = getStimulusType( stimPath ); 00570 if( stimType == STIM_UNKNOWN ) 00571 LFATAL("Unknown stimulus file extension '%s'", stimPath.c_str()); 00572 00573 SDL_Surface *surf = NULL; 00574 std::deque<VideoFrame> cache; 00575 bool streaming = true; 00576 00577 d->clearScreen(); 00578 00579 // load visual stimulus 00580 if( stimType == STIM_IMAGE ) 00581 { 00582 // load raster image 00583 LINFO("Loading '%s'...", stimPath.c_str()); 00584 Image< PixRGB<byte> > image = Raster::ReadRGB( stimPath ); 00585 surf = d->makeBlittableSurface( image, true ); 00586 } else 00587 { 00588 // cache initial movie frames 00589 LINFO("Buffering '%s'...", stimPath.c_str()); 00590 mp->setFileName( stimPath ); 00591 for( uint j = 0; j < CACHELEN; j ++ ) 00592 { 00593 streaming = cacheFrame( mp, cache ); 00594 if( streaming == false ) break; // all movie frames got cached! 00595 } 00596 } 00597 LINFO("'%s' ready.", stimPath.c_str()); 00598 00599 // give a chance to other processes (useful on single-CPU machines) 00600 snooze( 1, d ); 00601 system( "sync" ); 00602 00603 // start session 00604 d->displayText( sformat( "Session %04d", i + 1 ) ); 00605 pause( mouseInput.getVal(), d ); 00606 00607 d->waitNextRequestedVsync( false, true ); 00608 if( stimType == STIM_IMAGE ) 00609 d->pushEvent( std::string("===== Showing image: ") + stimPath + " =====" ); 00610 else 00611 d->pushEvent( std::string("===== Playing movie: ") + stimPath + " =====" ); 00612 00613 // start eye tracking 00614 trackEyes( true, et, d ); 00615 00616 // show visual stimulus 00617 if( stimType == STIM_IMAGE ) 00618 { 00619 // display image 00620 d->displaySurface( surf, -2 ); 00621 00622 // speech recording during image presentation 00623 recordAudio( audRecStyle & REC_DURING, d ); 00624 00625 // wait for a while as image is being displayed 00626 snooze( staticPeriod.getVal(), d ); 00627 00628 SDL_FreeSurface( surf ); 00629 00630 } else 00631 { 00632 // create an overlay 00633 d->createVideoOverlay( VIDFMT_YUV420P, mp->getWidth(), mp->getHeight() ); 00634 00635 // play movie 00636 uint frame = 0; 00637 rutz::time start = rutz::time::wall_clock_now(); // start time 00638 00639 while( cache.size() ) 00640 { 00641 d->checkForKey(); 00642 00643 // cache one more frame 00644 if( streaming ) 00645 streaming = cacheFrame( mp, cache ); 00646 00647 // get next frame to display and put it into overlay 00648 VideoFrame vidframe = cache.back(); 00649 d->displayVideoOverlay( vidframe, frame, SDLdisplay::NEXT_VSYNC ); 00650 cache.pop_back(); 00651 00652 // speech recording during video play 00653 recordAudio( audRecStyle & REC_DURING, d ); 00654 00655 frame ++; 00656 } 00657 rutz::time stop = rutz::time::wall_clock_now(); // end time 00658 double secs = (stop - start).sec(); 00659 LINFO("%d frames in %.02f sec (~%.02f fps)", frame, secs, frame / secs); 00660 00661 // destroy the overlay. Somehow, mixing overlay displays and 00662 // normal displays does not work. With a single overlay created 00663 // before this loop and never destroyed, the first movie plays 00664 // ok but the other ones don't show up 00665 d->destroyYUVoverlay(); 00666 d->clearScreen(); // sometimes 2 clearScreen() are necessary 00667 } 00668 00669 d->clearScreen(); 00670 00671 // display blank 00672 if( blankPeriod.getVal() > 0 ) 00673 { 00674 d->pushEvent( std::string("===== Presenting blank =====") ); 00675 00676 // eye tracking during blank 00677 trackEyes( etRecStyle & REC_AFTER, et, d ); 00678 00679 // speech recording during blank 00680 recordAudio( audRecStyle & REC_AFTER, d ); 00681 00682 // wait for a while as blank is being presented 00683 if( blankPeriod.getVal() < 1000 ) 00684 { 00685 snooze( blankPeriod.getVal(), d ); 00686 } else 00687 { 00688 pause( mouseInput.getVal(), d ); 00689 } 00690 } 00691 00692 // stop eye tracking 00693 trackEyes( false, et, d ); 00694 00695 // stop speech recording 00696 recordAudio( false, d ); 00697 00698 // save recorded speech to a wave file 00699 // std::string wavname( sformat("%s-%04d.wav", expName.getVal().c_str(), i + 1) ); 00700 saveAudioRecord( stimName + ".wav", d ); 00701 00702 // eye tracker recalibration 00703 if( etRecalib.getVal() > 0 ) 00704 { 00705 recalibCount ++; 00706 if( recalibCount == etRecalib.getVal() ) 00707 { 00708 recalibCount = 0; 00709 calibrateISCAN( mouseInput.getVal(), d ); 00710 } 00711 } 00712 } 00713 } 00714 break; 00715 00716 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 00717 case PROC_EXP1: 00718 case PROC_EXP2: 00719 if( procType == PROC_EXP1 ) 00720 LINFO("Experiment 1 procedure..."); 00721 else 00722 LINFO("Experiment 2 procedure..."); 00723 00724 { 00725 uint sessionNum; 00726 uint pairNum[2]; 00727 00728 if( procType == PROC_EXP1 ) 00729 { 00730 sessionNum = manager.numExtraArgs(); 00731 } else 00732 { 00733 for( int i = 0; i < 2; i ++ ) 00734 { 00735 pairNum[i] = (uint)atoi( manager.getExtraArg(i).c_str() ); 00736 if( pairNum[i] < 1 ) 00737 LFATAL("Invalid stimulus information '%s'", manager.getExtraArg(i).c_str()); 00738 } 00739 sessionNum = pairNum[0] + pairNum[1]; 00740 } 00741 00742 SESSION_EXP12 sessions[sessionNum]; 00743 00744 if( procType == PROC_EXP1 ) 00745 { 00746 // switching between online and offline for a pair of scenes 00747 for( uint i = 0; i < sessionNum; i += 2 ) 00748 { 00749 uint i1, i2; 00750 if( randomUpToNotIncluding( 2 ) ) 00751 { 00752 i1 = i; 00753 i2 = i + 1; 00754 } else 00755 { 00756 i1 = i + 1; 00757 i2 = i; 00758 } 00759 00760 sessions[i1].index = i1; 00761 sessions[i1].staticPeriod = 15; 00762 sessions[i1].blankPeriod = 5; 00763 sessions[i1].flag = "ON"; 00764 sessions[i1].message = "On-line Description"; 00765 00766 sessions[i2].index = i2; 00767 sessions[i2].staticPeriod = 10; 00768 sessions[i2].blankPeriod = 15; 00769 sessions[i2].flag = "OFF"; 00770 sessions[i2].message = "Post-scene Description"; 00771 } 00772 } else 00773 { 00774 // select one among a pair of scenes and switch between online and offline 00775 uint idx = 0; 00776 for( int i = 0; i < 2; i ++ ) 00777 { 00778 int offset[pairNum[i]]; 00779 int task[pairNum[i]]; 00780 for( uint j = 0; j < pairNum[i]; j ++ ) 00781 { 00782 if( j < pairNum[i] / 2 ) 00783 offset[j] = task[j] = 0; 00784 else 00785 offset[j] = task[j] = 1; 00786 } 00787 randShuffle( offset, pairNum[i] ); 00788 randShuffle( task, pairNum[i] ); 00789 00790 for( uint j = 0; j < pairNum[i]; j ++ ) 00791 { 00792 sessions[idx].index = 2 + idx * 2 + offset[j]; 00793 if( task[j] == 0 ) 00794 { 00795 sessions[idx].staticPeriod = 12; 00796 sessions[idx].blankPeriod = 5; 00797 sessions[idx].flag = "ON"; 00798 sessions[idx].message = "Describe as soon as possible"; 00799 } else 00800 { 00801 sessions[idx].staticPeriod = 7; 00802 sessions[idx].blankPeriod = 10; 00803 sessions[idx].flag = "OFF"; 00804 sessions[idx].message = "Describe in one sentence"; 00805 } 00806 00807 idx ++; 00808 } 00809 } 00810 } 00811 00812 // randomize session order 00813 randShuffle( sessions, sessionNum ); 00814 00815 // session loop 00816 for( uint i = 0; i < sessionNum; i ++ ) 00817 { 00818 d->clearScreen(); 00819 00820 // load raster image 00821 std::string stimPath = manager.getExtraArg(sessions[i].index); 00822 std::string stimName; 00823 getStimulusName( stimPath, stimName ); 00824 LINFO("Loading '%s'...", stimPath.c_str()); 00825 Image< PixRGB<byte> > image = Raster::ReadRGB( stimPath ); 00826 SDL_Surface *surf = d->makeBlittableSurface( image, true ); 00827 LINFO("'%s' ready.", stimPath.c_str()); 00828 00829 // give a chance to other processes (useful on single-CPU machines) 00830 snooze( 1, d ); 00831 system( "sync" ); 00832 00833 // start session 00834 d->displayText( sformat( "%s", sessions[i].message ) ); 00835 pause( mouseInput.getVal(), d ); 00836 00837 d->waitNextRequestedVsync( false, true ); 00838 d->pushEvent( std::string("===== Showing image: ") + stimPath + " " + sessions[i].flag + " =====" ); 00839 00840 // start eye tracking 00841 trackEyes( true, et, d ); 00842 00843 // start speech recording 00844 recordAudio( true, d ); 00845 00846 // display image 00847 d->displaySurface( surf, -2 ); 00848 00849 // wait for a while as image is being displayed 00850 snooze( sessions[i].staticPeriod, d ); 00851 00852 SDL_FreeSurface( surf ); 00853 d->clearScreen(); 00854 00855 // display blank 00856 d->pushEvent( std::string("===== Presenting blank =====") ); 00857 00858 // wait for a while as blank is being presented 00859 snooze( sessions[i].blankPeriod, d ); 00860 00861 // stop eye tracking 00862 trackEyes( false, et, d ); 00863 00864 // stop speech recording 00865 recordAudio( false, d ); 00866 00867 // save recorded speech to a wave file 00868 saveAudioRecord( stimName + ".wav", d ); 00869 } 00870 } 00871 break; 00872 00873 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 00874 case PROC_EXP3: 00875 LINFO("Experiment 3 procedure..."); 00876 00877 { 00878 uint sessionNum; 00879 uint pairNum[3]; 00880 00881 for( int i = 0; i < 3; i ++ ) 00882 { 00883 pairNum[i] = (uint)atoi( manager.getExtraArg(i).c_str() ); 00884 if( pairNum[i] < 1 ) 00885 LFATAL("Invalid stimulus information '%s'", manager.getExtraArg(i).c_str()); 00886 } 00887 sessionNum = pairNum[0] + pairNum[1] + pairNum[2]; 00888 00889 SESSION_EXP3 sessions[sessionNum]; 00890 00891 uint idx = 0; 00892 for( int i = 0; i < 3; i ++ ) 00893 { 00894 int offset[pairNum[i]]; 00895 for( uint j = 0; j < pairNum[i]; j ++ ) 00896 { 00897 if( j < pairNum[i] / 2 ) 00898 offset[j] = 0; // normal scene 00899 else 00900 offset[j] = 1; // faded scene 00901 } 00902 randShuffle( offset, pairNum[i] ); 00903 00904 for( uint j = 0; j < pairNum[i]; j ++ ) 00905 { 00906 sessions[idx].style = i; 00907 switch( sessions[idx].style ) 00908 { 00909 case 0: // practice 00910 sessions[idx].index = 3 + j; 00911 sessions[idx].flag = "PRACTICE"; 00912 break; 00913 00914 case 1: // standard 00915 sessions[idx].index = 3 + pairNum[0] + j * 2; 00916 if( offset[j] == 1 ) sessions[idx].index ++; 00917 sessions[idx].flag = "STD"; 00918 break; 00919 00920 case 2: // change blindness 00921 sessions[idx].index = 3 + pairNum[0] + pairNum[1] * 2 + j * 4; 00922 if( offset[j] == 1 ) sessions[idx].index += 2; 00923 sessions[idx].flag = "CHG"; 00924 break; 00925 } 00926 00927 idx ++; 00928 } 00929 } 00930 00931 // randomize session order (practice sessions excluded) 00932 randShuffle( &sessions[pairNum[0]], sessionNum - pairNum[0] ); 00933 00934 // 00935 // session loop 00936 // 00937 uint sessCnt = 1; 00938 uint pracCnt = 1; 00939 for( uint i = 0; i < sessionNum; i ++ ) 00940 { 00941 d->clearScreen(); 00942 00943 // load raster image(s) 00944 std::string stimPath = manager.getExtraArg(sessions[i].index); 00945 std::string stimName; 00946 getStimulusName( stimPath, stimName ); 00947 LINFO("Loading '%s'...", stimPath.c_str()); 00948 Image< PixRGB<byte> > image = Raster::ReadRGB( stimPath ); 00949 SDL_Surface *surf = d->makeBlittableSurface( image, true ); 00950 SDL_Surface *surf_ch = NULL; 00951 if( sessions[i].style == 2 ) 00952 { 00953 Image< PixRGB<byte> > image_ch = Raster::ReadRGB( manager.getExtraArg(sessions[i].index + 1) ); 00954 surf_ch = d->makeBlittableSurface( image_ch, true ); 00955 } 00956 LINFO("'%s' ready.", stimPath.c_str()); 00957 00958 // give a chance to other processes (useful on single-CPU machines) 00959 snooze( 1, d ); 00960 system( "sync" ); 00961 00962 // start session 00963 if( sessions[i].style == 0 ) 00964 d->displayText( sformat( "Pactice %04d", pracCnt ) ); 00965 else 00966 d->displayText( sformat( "Session %04d", sessCnt ) ); 00967 pause( mouseInput.getVal(), d ); 00968 00969 d->displayText( "Describe the event(s) of scene as quickly as possible." ); 00970 pause( mouseInput.getVal(), d ); 00971 00972 d->waitNextRequestedVsync( false, true ); 00973 d->pushEvent( std::string("===== Showing image: ") + stimPath + " " + sessions[i].flag + " =====" ); 00974 00975 if( sessions[i].style != 0 ) 00976 { 00977 // start eye tracking 00978 trackEyes( true, et, d ); 00979 00980 // start speech recording 00981 recordAudio( true, d ); 00982 } else 00983 { 00984 // blink the fixation point at the center 00985 d->displayFixationBlink(); 00986 } 00987 00988 for( int j = 0; j < 10; j ++ ) 00989 { 00990 // display image 00991 if( sessions[i].style == 2 && j >= 3 && j <= 6 ) // show changed scene from 4.5 sec to 10.5 sec (for 6 sec) 00992 d->displaySurface( surf_ch ); // apply change 00993 else 00994 d->displaySurface( surf, j == 0 ? -2 : -1 ); 00995 usleep( 1000000 ); // wait for 1 sec 00996 00997 // display field blank 00998 d->SDLdisplay::clearScreen( PixRGB<byte>(0, 0, 0), true ); 00999 usleep( 500000 ); // wait for 0.5 sec 01000 01001 d->checkForKey(); 01002 } 01003 01004 SDL_FreeSurface( surf ); 01005 if( surf_ch ) SDL_FreeSurface( surf_ch ); 01006 d->SDLdisplay::clearScreen( PixRGB<byte>(0, 0, 0), true ); 01007 01008 // display blank 01009 d->pushEvent( std::string("===== Presenting blank =====") ); 01010 01011 // wait for a while as blank is being presented 01012 snooze( 5, d ); 01013 01014 if( sessions[i].style != 0 ) 01015 { 01016 // stop eye tracking 01017 trackEyes( false, et, d ); 01018 01019 // stop speech recording 01020 recordAudio( false, d ); 01021 01022 // save recorded speech to a wave file 01023 saveAudioRecord( stimName + ".wav", d ); 01024 } 01025 01026 d->displayText( "Describe what you have seen with as much detail as possible." ); 01027 pause( mouseInput.getVal(), d ); 01028 d->clearScreen(); 01029 01030 if( sessions[i].style != 0 ) 01031 { 01032 // start speech recording 01033 recordAudio( true, d ); 01034 } 01035 snooze( 3, d ); // 3 sec of grace time for press mistakes 01036 pause( mouseInput.getVal(), d ); 01037 01038 if( sessions[i].style != 0 ) 01039 { 01040 // stop speech recording 01041 recordAudio( false, d ); 01042 01043 // save recorded speech to a wave file 01044 saveAudioRecord( stimName + "_post.wav", d ); 01045 } 01046 01047 if( sessions[i].style != 0 ) 01048 sessCnt ++; 01049 else 01050 pracCnt ++; 01051 } 01052 } 01053 break; 01054 01055 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 01056 case PROC_EXP4: 01057 LINFO("Experiment 4 procedure..."); 01058 01059 { 01060 uint sessionNum; 01061 uint qckSessionNum, frmSessionNum; 01062 uint qckPracNum, frmPracNum; 01063 uint stimNum[3]; 01064 01065 for( int i = 0; i < 3; i ++ ) 01066 { 01067 stimNum[i] = (uint)atoi( manager.getExtraArg(i).c_str() ); 01068 if( stimNum[i] < 1 ) 01069 LFATAL("Invalid stimulus information '%s'", manager.getExtraArg(i).c_str()); 01070 } 01071 sessionNum = stimNum[0] + stimNum[1] + stimNum[2]; 01072 qckSessionNum = (stimNum[0] / 2) + (stimNum[1] / 2) + (stimNum[2] / 2); 01073 frmSessionNum = sessionNum - qckSessionNum; 01074 qckPracNum = stimNum[0] / 2; 01075 frmPracNum = stimNum[0] - qckPracNum; 01076 01077 SESSION_EXP4 sessions[sessionNum]; 01078 01079 uint qckIdx = 0; 01080 uint frmIdx = qckSessionNum; 01081 for( int i = 0; i < 3; i ++ ) 01082 { 01083 int offset = 3; 01084 for( int j = 0; j < i; j ++ ) 01085 offset += stimNum[j]; 01086 01087 int task[stimNum[i]]; 01088 for( uint j = 0; j < stimNum[i]; j ++ ) 01089 task[j] = (j < stimNum[i] / 2) ? 0 : 1; 01090 randShuffle( task, stimNum[i] ); 01091 01092 for( uint j = 0; j < stimNum[i]; j ++ ) 01093 { 01094 if( task[j] == 0 ) 01095 { 01096 // as quickly as possible 01097 sessions[qckIdx].index = j + offset; 01098 sessions[qckIdx].practice = (i == 0); 01099 sessions[qckIdx].task = 0; 01100 sessions[qckIdx].flag = "QCK"; 01101 sessions[qckIdx].reminder = "Describe the event(s) of the scene AS QUICKLY AS POSSIBLE while watching."; 01102 01103 qckIdx ++; 01104 } else 01105 { 01106 // complete and well-formed 01107 sessions[frmIdx].index = j + offset; 01108 sessions[frmIdx].practice = (i == 0); 01109 sessions[frmIdx].task = 1; 01110 sessions[frmIdx].flag = "FRM"; 01111 sessions[frmIdx].reminder = "Describe the event(s) of the scene in COMPLETE AND WELL-FORMED SENTENCES while watching."; 01112 01113 frmIdx ++; 01114 } 01115 } 01116 } 01117 01118 // randomize session order (practice sessions excluded) 01119 randShuffle( &sessions[qckPracNum], qckSessionNum - qckPracNum ); 01120 randShuffle( &sessions[qckSessionNum + frmPracNum], frmSessionNum - frmPracNum ); 01121 01122 // 01123 // session loop 01124 // 01125 uint curTask = 100; 01126 for( uint i = 0; i < sessionNum; i ++ ) 01127 { 01128 d->clearScreen(); 01129 01130 // load raster image(s) 01131 std::string stimPath = manager.getExtraArg(sessions[i].index); 01132 std::string stimName; 01133 getStimulusName( stimPath, stimName ); 01134 LINFO("Loading '%s'...", stimPath.c_str()); 01135 Image< PixRGB<byte> > image = Raster::ReadRGB( stimPath ); 01136 SDL_Surface *surf = d->makeBlittableSurface( image, true ); 01137 LINFO("'%s' ready.", stimPath.c_str()); 01138 01139 // give a chance to other processes (useful on single-CPU machines) 01140 snooze( 1, d ); 01141 system( "sync" ); 01142 01143 // task description 01144 if( curTask != sessions[i].task ) 01145 { 01146 d->displayText( sformat( "Task Description") ); 01147 pause( mouseInput.getVal(), d ); 01148 switch( sessions[i].task ) 01149 { 01150 case 0: 01151 d->displayText( "The task is to describe the event(s) of the scene AS QUICKLY AS POSSIBLE." ); 01152 pause( mouseInput.getVal(), d ); 01153 d->displayText( "Note that in this task, you don't have to worry about" ); 01154 pause( mouseInput.getVal(), d ); 01155 d->displayText( "the well-formedness or grammatical correctness of the sentence." ); 01156 pause( mouseInput.getVal(), d ); 01157 break; 01158 01159 case 1: 01160 d->displayText( "The task is to describe the event(s) of the scene in COMPLETE AND WELL-FORMED SENTENCES." ); 01161 pause( mouseInput.getVal(), d ); 01162 d->displayText( "Note that in this task, you don't have to speak quickly." ); 01163 pause( mouseInput.getVal(), d ); 01164 d->displayText( "Focus on the sentence structure while taking as much time as you want." ); 01165 pause( mouseInput.getVal(), d ); 01166 break; 01167 } 01168 curTask = sessions[i].task; 01169 } 01170 01171 // start session 01172 if( sessions[i].practice ) 01173 d->displayText( sformat( "Session %04d (Practice)", i + 1 ) ); 01174 else 01175 d->displayText( sformat( "Session %04d", i + 1 ) ); 01176 pause( mouseInput.getVal(), d ); 01177 01178 // task reminder 01179 d->displayText( sessions[i].reminder ); 01180 pause( mouseInput.getVal(), d ); 01181 01182 d->waitNextRequestedVsync( false, true ); 01183 if( sessions[i].practice == false ) 01184 { 01185 d->pushEvent( std::string("===== Showing image: ") + stimPath + " " + sessions[i].flag + " =====" ); 01186 01187 // start eye tracking 01188 trackEyes( true, et, d ); 01189 01190 // start speech recording 01191 recordAudio( true, d ); 01192 } else 01193 { 01194 d->pushEvent( std::string("===== Showing image: ") + stimPath + " PRACTICE =====" ); 01195 01196 // blink the fixation point at the center 01197 d->displayFixationBlink(); 01198 } 01199 01200 // display image 01201 d->displaySurface( surf, -2 ); 01202 01203 // wait until description speech is done 01204 snooze( 3, d ); // 3 sec of grace time for press mistakes 01205 pause( mouseInput.getVal(), d ); 01206 01207 SDL_FreeSurface( surf ); 01208 d->clearScreen(); 01209 01210 if( sessions[i].practice == false ) 01211 { 01212 // stop eye tracking 01213 trackEyes( false, et, d ); 01214 01215 // stop speech recording 01216 recordAudio( false, d ); 01217 01218 // save recorded speech to a wave file 01219 saveAudioRecord( stimName + ".wav", d ); 01220 } 01221 01222 // display blank 01223 d->pushEvent( std::string("===== Presenting blank =====") ); 01224 01225 // wait for a while as blank is being presented 01226 snooze( 2, d ); 01227 } 01228 } 01229 break; 01230 01231 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 01232 } 01233 01234 // 01235 // finish experiment 01236 // 01237 d->clearScreen(); 01238 d->displayText( "Experiment complete." ); 01239 pause( mouseInput.getVal(), d ); 01240 01241 // kill audio grabbing thread 01242 audExit = true; 01243 if( audRecStyle & REC_ALL ) 01244 pthread_join( audGrbId, NULL ); 01245 01246 // stop all ModelComponents 01247 manager.stop(); 01248 01249 return 0; 01250 } 01251 01252 01253 01254 // ###################################################################### 01255 // psycho-narrator dummy main function 01256 // 01257 01258 extern "C" int main(const int argc, char** argv) 01259 { 01260 // simple wrapper around submain() to catch exceptions (because we 01261 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 01262 // abort while SDL is in fullscreen mode, the X server won't return 01263 // to its original resolution) 01264 01265 try 01266 { 01267 return submain( argc, argv ); 01268 } 01269 catch (...) 01270 { 01271 REPORT_CURRENT_EXCEPTION; 01272 } 01273 01274 return 1; 01275 } 01276