function EyelinkGazeContingentDemo(mode) % % ___________________________________________________________________ % % Demo implementation of a generic gaze-contingent display. % We take one input image and create - via image processing - two images % out of it: An image to show at the screen location were the subject % fixates (According to the eye-tracker). A second image to show in the % peripery of the subjects field of view. These two images are blended into % each other via a gaussian weight mask (an aperture). The mask is centered % at the center of gaze and allows for a smooth transition between the two % images. % % This illustrates an application of OpenGL Alpha blending by compositing % two images based on a spatial gaussian weight mask. Compositing is done % by the graphics hardware. % Mode can have the following values: % Mode 1: % Fovea contains original image data: % Periphery contains grayscale-version: % Mode 2: % Fovea contains original image data: % Periphery contains blurred-version: % Mode 3 % Fovea contains color-inverted image data: % Periphery contains original data: % Mode 4 % Test-case: One shouldn't see any foveated region on the % screen - this is a basic correctness test for blending. % % _________________________________________________________________________ % % see also: EyelinkExample, EyelinkPicture % HISTORY % % mm/dd/yy % % 7/23/05 mk Derived it from Frans Cornelissen's AlphaImageDemoOSX. % 29/06/06 fwc Derived from Mario Kleiner's GazeContingentDemoOSX ;-) % 07/13/10 fwc made to work with new toolbox with callback and updated to % enable eye image display, added "cleanup" function, % PsychDefaultSetup(1); dummymode=0; try fprintf('%s (%s)\n', mfilename, datestr(now)); fprintf('Press space or click on mouse to stop demo.\n'); stopkey=KbName('space'); if 1; Screen('Preference', 'SkipSyncTests', 1); end; % Set hurryup = 1 for benchmarking - Syncing to retrace is disabled % in that case so we'll get the maximum refresh rate. hurryup=0; % Setup default mode to color vs. gray. if nargin < 1 mode = 1; end; % Setup default aperture size to 2*200+1 x 2*200+1 pixels. ms=200; % Set default demo images. basepath = [ PsychtoolboxRoot 'PsychDemos' filesep ]; myimgfile= [basepath 'konijntjes1024x768.jpg']; myblurimgfile= [basepath 'konijntjes1024x768blur.jpg']; mygrayimgfile= [basepath 'konijntjes1024x768gray.jpg']; % Get the list of screens and choose the one with the highest screen number. % Screen 0 is, by definition, the display with the menu bar. Often when % two monitors are connected the one without the menu bar is used as % the stimulus display. Chosing the display with the highest display number is % a best guess about where you want the stimulus displayed. screenNumber=max(Screen('Screens')); % Open a fullscreen window. [w, wRect]=Screen('OpenWindow',screenNumber); % Set background color to gray. backgroundcolor=GrayIndex(w); % returns as default the mean gray value of screen % Provide Eyelink with details about the graphics environment % and perform some initializations. The information is returned % in a structure that also contains useful defaults % and control codes (e.g. tracker state bit and Eyelink key values). el=EyelinkInitDefaults(w); % Initialization of the connection with the Eyelink Gazetracker. % exit program if this fails. if ~EyelinkInit(dummymode) fprintf('Eyelink Init aborted.\n'); cleanup; % cleanup function return; end % Load image file: fprintf('Using image ''%s''\n', myimgfile); imdata=imread(myimgfile); imdatablur=imread(myblurimgfile); imdatagray=imread(mygrayimgfile); % crop image if it is larger then screen size. There's no image scaling % in maketexture (Note: no longer true as Sceen's Drawtexture does scaling) [iy, ix, id]=size(imdata); [wW, wH]=WindowSize(w); if ix>wW || iy>wH disp('Image size exceeds screen size'); disp('Image will be cropped'); end if ix>wW cl=round((ix-wW)/2); cr=(ix-wW)-cl; else cl=0; cr=0; end if iy>wH ct=round((iy-wH)/2); cb=(iy-wH)-ct; else ct=0; cb=0; end % imdata is the cropped version of the image. imdata=imdata(1+ct:iy-cb, 1+cl:ix-cr,:); imdatablur=imdatablur(1+ct:iy-cb, 1+cl:ix-cr,:); imdatagray=imdatagray(1+ct:iy-cb, 1+cl:ix-cr,:); % Compute image for foveated region and periphery: switch (mode) case 1 % Mode 1: % Fovea contains original image data: foveaimdata = imdata; % Periphery contains grayscale-version: peripheryimdata = imdatagray; case 2 % Fovea contains original image data: foveaimdata = imdata; % Periphery contains blurred-version: peripheryimdata = imdatablur; case 3 % Fovea contains color-inverted image data: foveaimdata(:,:,:) = 255 - imdata(:,:,:); % Periphery contains original data: peripheryimdata = imdata; case 4 % Test-case: One shouldn't see any foveated region on the % screen - this is a basic correctness test for blending. foveaimdata = imdata; peripheryimdata = imdata; otherwise % Unknown mode! We force abortion: fprintf('Invalid mode provided!'); abortthisbeast end; % Build texture for foveated region: foveatex=Screen('MakeTexture', w, foveaimdata); tRect=Screen('Rect', foveatex); % Build texture for peripheral (non-foveated) region: nonfoveatex=Screen('MakeTexture', w, peripheryimdata); [ctRect, dx, dy]=CenterRect(tRect, wRect); % We create a Luminance+Alpha matrix for use as transparency mask: % Layer 1 (Luminance) is filled with 'backgroundcolor'. transLayer=2; [x,y]=meshgrid(-ms:ms, -ms:ms); maskblob=ones(2*ms+1, 2*ms+1, transLayer) * backgroundcolor; % Layer 2 (Transparency aka Alpha) is filled with gaussian transparency % mask. xsd=ms/2.2; ysd=ms/2.2; maskblob(:,:,transLayer)=round(255 - exp(-((x/xsd).^2)-((y/ysd).^2))*255); % Build a single transparency mask texture: masktex=Screen('MakeTexture', w, maskblob); mRect=Screen('Rect', masktex); fprintf('Size image texture: %d x %d\n', RectWidth(tRect), RectHeight(tRect)); fprintf('Size mask texture: %d x %d\n', RectWidth(mRect), RectHeight(mRect)); % Set background color to 'backgroundcolor' and do initial flip to show % blank screen: Screen('FillRect', w, backgroundcolor); Screen('Flip', w); % make sure that we get gaze data from the Eyelink Eyelink('command', 'link_sample_data = LEFT,RIGHT,GAZE,AREA'); % open file to record data to Eyelink('openfile', 'demo.edf'); % STEP 4 % Calibrate the eye tracker EyelinkDoTrackerSetup(el); % do a final check of calibration using driftcorrection EyelinkDoDriftCorrection(el); WaitSecs(0.1); Eyelink('StartRecording'); eye_used = Eyelink('EyeAvailable'); % get eye that's tracked if eye_used == el.BINOCULAR; % if both eyes are tracked eye_used = el.LEFT_EYE; % use left eye end % Set background color to 'backgroundcolor' and do flip to show % blank screen: Screen('FillRect', w, backgroundcolor); Screen('Flip', w); % The mouse-cursor position will define gaze-position (center of % fixation) to simulate (x,y) input from an eyetracker. Set cursor % initially to center of screen: [a,b]=RectCenter(wRect); WaitSetMouse(a,b,screenNumber); % set cursor and wait for it to take effect HideCursor; buttons=0; priorityLevel=MaxPriority(w); Priority(priorityLevel); % Wait until all keys on keyboard are released: while KbCheck; WaitSecs(0.1); end; mxold=0; myold=0; oldvbl=Screen('Flip', w); tavg = 0; ncount = 0; % Infinite display loop: Whenever "gaze position" changes, we update % the display accordingly. Loop aborts on keyboard press or mouse % click or after 10000 frames... while (ncount < 10000) if dummymode==0 % error=Eyelink('CheckRecording'); if(error~=0) break; end if Eyelink( 'NewFloatSampleAvailable') > 0 % get the sample in the form of an event structure evt = Eyelink( 'NewestFloatSample'); if eye_used ~= -1 % do we know which eye to use yet? % if we do, get current gaze position from sample x = evt.gx(eye_used+1); % +1 as we're accessing MATLAB array y = evt.gy(eye_used+1); % do we have valid data and is the pupil visible? if x~=el.MISSING_DATA && y~=el.MISSING_DATA && evt.pa(eye_used+1)>0 mx=x; my=y; end end end else % Query current mouse cursor position (our "pseudo-eyetracker") - % (mx,my) is our gaze position. if (hurryup==0) [mx, my, buttons]=GetMouse; %(w); else % In benchmark mode, we just do a quick sinusoidal motion % without query of the mouse: mx=500 + 500*sin(ncount/10); my=300; end; end % We only redraw if gazepos. has changed: if (mx~=mxold || my~=myold) % Compute position and size of source- and destinationrect and % clip it, if necessary... myrect=[mx-ms my-ms mx+ms+1 my+ms+1]; % center dRect on current mouseposition dRect = ClipRect(myrect,ctRect); sRect=OffsetRect(dRect, -dx, -dy); % Valid destination rectangle? if ~IsEmptyRect(dRect) % Yes! Draw image for current frame: % Step 1: Draw the alpha-mask into the backbuffer. It % defines the aperture for foveation: The center of gaze % has zero alpha value. Alpha values increase with distance from % center of gaze according to a gaussian function and % approach 255 at the border of the aperture... Screen('BlendFunction', w, GL_ONE, GL_ZERO); Screen('DrawTexture', w, masktex, [], myrect); % Step 2: Draw peripheral image. It is only drawn where % the alpha-value in the backbuffer is 255 or high, leaving % the foveated area (low or zero alpha values) alone: % This is done by weighting each color value of each pixel % with the corresponding alpha-value in the backbuffer % (GL_DST_ALPHA). Screen('BlendFunction', w, GL_DST_ALPHA, GL_ZERO); Screen('DrawTexture', w, nonfoveatex, [], ctRect); % Step 3: Draw foveated image, but only where the % alpha-value in the backbuffer is zero or low: This is % done by weighting each color value with one minus the % corresponding alpha-value in the backbuffer % (GL_ONE_MINUS_DST_ALPHA). Screen('BlendFunction', w, GL_ONE_MINUS_DST_ALPHA, GL_ONE); Screen('DrawTexture', w, foveatex, sRect, dRect); % Show final result on screen. This also clears the drawing % surface back to black background color and a zero alpha % value. % Actually... We use clearmode=2: This doesn't clear the % backbuffer, but we don't need to clear it for this kind % of stimulus and it gives us 2 msecs extra headroom for % higher refresh rates! For benchmark purpose, we disable % syncing to retrace if hurryup is == 1. vbl = Screen('Flip', w, 0, 2, 2*hurryup); vbl = GetSecs; tavg = tavg + (vbl-oldvbl); oldvbl=vbl; ncount = ncount + 1; end; end; % Keep track of last gaze position: mxold=mx; myold=my; % We wait 1 ms each loop-iteration so that we % don't overload the system in realtime-priority: WaitSecs(0.001); % Abort demo on keypress our mouse-click: [mx, my, buttons]=GetMouse; %(w); if any(buttons) % break out of loop break; end % check for keyboard press [keyIsDown,secs,keyCode] = KbCheck; % if spacebar was pressed stop display if keyCode(stopkey) Eyelink('Message', 'Key pressed') break; end end % stop eyelink Eyelink('StopRecording'); % Display full image a last time, just for fun... Screen('BlendFunction', w, GL_ONE, GL_ZERO); Screen('DrawTexture', w, foveatex); Screen('Flip', w); WaitSecs(1); cleanup; tavg = tavg / ncount * 1000; fprintf('End of %s. Avg. redraw time is %f ms = %f Hz.\n\n', mfilename, tavg, 1000 / tavg); return; catch lasterror %this "catch" section executes in case of an error in the "try" section %above. Importantly, it closes the onscreen window if its open. cleanup; fprintf('%s: some error occured\n', mfilename); psychrethrow(lasterror); end %try..catch.. % Cleanup routine: function cleanup % Shutdown Eyelink: Eyelink('Shutdown'); % Close window: sca; Priority(0); commandwindow; % Restore keyboard output to Matlab: % % ListenChar(0);