function results = MullerLyerIllusion(subID) % results = MullerLyerIllusion(subID) % % Presenting the Muller-Lyer Illusion with the Psychtoolbox, complete experiment. % % Input: % % subID = subject Id (scalar), defaults to 66 % % Output: % % 'results' is a numerical matrix with one row per trial and columns: % colHeaders = {'subID', 'trial no', 'trial ID', 'length l1', 'headdir l1',... % 'length l2', 'headdir l2', 'length ratio', 'correct', 'rt PTB', 'rt ML'} % % In this experiment two lines (l1 and l2) are presented, where each line can have % inward or outward pointing arrow heads at its ends. % subjects are asked to judge the relative length of the basic lines % and to respond by pressing 's' for same or 'd' for different % % correctness and reaction times are recorded, the latter by two % different methods in order to compare precision: % rt PTB relies on PTB commands which are supposedly more precise % rt ML uses the Matlab's native tic/toc stopwatch % % This experiment is a counterbalanced 2x2x2x2 design with % the factors line length (l1, l2) and head direction (l1, l2) % The 16 test trials are presented in randomized order, following 3 % randomly drawn training trials % The exact design is defined in condtable (see below), where each line % stands for one trial and the columns for the four factors % % line length can be either 1 or 2, these values will be taken as % indices into the 1 x 2 vector hll (half line length, difference of 10%) % using indices allows the stimuli to be scaled by a random factor in each trial % % head direction can be -1 (outward pointing), 0 (no head) or 1 % (inward pointing); 0 is not used in the present design % % % Written by N. Ruh, 15/02/2008. % % Exemplary reference: % % Restle F & Decker J (1977) Size of the Mueller-Lyer illusion as a % function of its dimensions: Theory and data. Perception & Psychophysics 21:489 503 % % History: % 3/3/2008 Included in Psychtoolbox (MK). % Make sure the script is running on Psychtoolbox-3: AssertOpenGL; %set default values for input arguments if ~exist('subID','var') subID=66; end %warn if duplicate sub ID fileName=['MLIexpSubj' num2str(subID) '.txt']; if exist(fileName,'file') if ~IsOctave resp=questdlg({['the file ' fileName 'already exists']; 'do you want to overwrite it?'},... 'duplicate warning','cancel','ok','ok'); else resp=input(['the file ' fileName ' already exists. do you want to overwrite it? [Type ok for overwrite]'], 's'); end if ~strcmp(resp,'ok') %abort experiment if overwriting was not confirmed disp('experiment aborted') return end end %prepare conditions; %the first two elements in each row code for linelength, %the last two for head orientation condtable=[ 1 1 1 1; 1 2 1 1; 2 1 1 1; 2 2 1 1; 1 1 -1 1; 1 2 -1 1; 2 1 -1 1; 2 2 -1 1; 1 1 1 -1; 1 2 1 -1; 2 1 1 -1; 2 2 1 -1; 1 1 -1 -1; 1 2 -1 -1; 2 1 -1 -1; 2 2 -1 -1]; ntrials=size(condtable,1); %re-order the 16 trials randomly shuffeldTrialIDs=randperm(ntrials); %number of training trials ntrain=3; %attach a randomly drawn subsample as training trials order=[ceil(rand(1,ntrain)*ntrials), shuffeldTrialIDs]; %Prepare output colHeaders = {'subID', 'trial no', 'trial ID', 'length l1', 'headdir l1',... 'length l2', 'headdir l2', 'length ratio', 'correct', 'rt PTB', 'rt ML'}; results=NaN * ones(length(order),length(colHeaders)); %preallocate results matrix %head=[-1 0 1]; %we are currently using only +/- 1, see condtable lw=3; %line width of stimuli %when working with the PTB it is a good idea to enclose the whole body of your program %in a try ... catch ... end construct. This will often prevent you from getting stuck %in the PTB full screen mode try % Enable unified mode of KbName, so KbName accepts identical key names on % all operating systems (not absolutely necessary, but good practice): KbName('UnifyKeyNames'); %funnily enough, the very first call to KbCheck takes itself some %time - after this it is in the cache and very fast %to make absolutely sure, we thus call it here once for no other %reason than to get it cached. This btw. is true for all major %functions in Matlab, so calling each of them once before entering the %trial loop will make sure that the 1st trial goes smooth wrt. timing. KbCheck; %disable output of keypresses to Matlab. !!!use with care!!!!!! %if the program gets stuck you might end up with a dead keyboard %if this happens, press CTRL-C to reenable keyboard handling -- it is %the only key still recognized. ListenChar(2); %Set higher DebugLevel, so that you don't get all kinds of messages flashed %at you each time you start the experiment: olddebuglevel=Screen('Preference', 'VisualDebuglevel', 3); %Choosing the display with the highest display number is %a best guess about where you want the stimulus displayed. %usually there will be only one screen with id = 0, unless you use a %multi-display setup: screens=Screen('Screens'); screenNumber=max(screens); %open an (the only) onscreen Window, if you give only two input arguments %this will make the full screen white (=default) [expWin,rect]=Screen('OpenWindow',screenNumber); %alternative: replace the above with smaller window for testing % [expWin,rect]=Screen('OpenWindow',screenNumber,[],[10 20 1200 700]); %NOTE that smaller windows can induce synchronisation problems %and other issues, so they're not suitable for running real experiment %sessions. See >> help SyncTrouble %get the midpoint (mx, my) of this window, x and y [mx, my] = RectCenter(rect); %get rid of the mouse cursor, we don't have anything to click at anyway HideCursor; % Preparing and displaying the welcome screen % We choose a text size of 24 pixels - Well readable on most screens: Screen('TextSize', expWin, 24); % This is our intro text. The '\n' sequence creates a line-feed: myText = ['In this experiment you are asked to judge\n' ... 'the relative length of two horizontal lines\n' ... ' Press s if the lines have the same length \n' ... ' Press d if the lines have different length\n' ... 'You will begin with ' num2str(ntrain) ' training trials\n' ... ' (Press any key to start training)\n' ]; % Draw 'myText', centered in the display window: DrawFormattedText(expWin, myText, 'center', 'center'); % This chunk of code would do roughly the same: It uses the lower-level % 'DrawText' subcommand to draw text. DrawFormattedText is a % convenience wrapper that provides basic text formatting functions. % See 'help DrawFormattedTextDemo' for a demo of its capabilities... % % lm=150; %left margin, adjust to suit the size of your screen % Screen('DrawText', expWin, 'In this experiment you are asked to judge', lm, 50); % Screen('DrawText', expWin, 'the relative length of two horizontal lines', lm, 80); % Screen('DrawText', expWin, ' press s if the lines have the same length', lm, 110); % Screen('DrawText', expWin, ' press d if the lines have different lengths', lm, 140); % Screen('DrawText', expWin, ['you will begin with ' num2str(ntrain) ' training trials'], lm, 170); % Screen('DrawText', expWin, ' (press any key to start training)', lm, 250); % Show the drawn text at next display refresh cycle: Screen('Flip', expWin); % Wait for key stroke. This will first make sure all keys are % released, then wait for a keypress and release: KbWait([], 3); % %an example of preparing an offScreenwindow (for repeated use and fast drawing) % [fixcross,rect2]=Screen('OpenOffscreenWindow',screenNumber,[],[0 0 20 20]); % Screen('drawline',fixcross,[0 0 0],10,0,10,20,2);%mx-10,my,mx+10,my % Screen('drawline',fixcross,[0 0 0],0,10,20,10,2);%mx,my-10,mx,my+10 % Another way to create a fixation cross: Doing the above with textures, % by preparing a little Matlab matrix with the image of a fixation % cross: --> Choose whatever you like more. FixCr=ones(20,20)*255; FixCr(10:11,:)=0; FixCr(:,10:11)=0; %try imagesc(FixCr) to display the result in Matlab fixcross = Screen('MakeTexture',expWin,FixCr); %start trials loop (over training and test trials) for i=1:length(order) %prepare and display end of training/start experiment screen if i==ntrain+1 %before the first test trial DrawFormattedText(expWin, 'Are you ready for the experiment?\n(Press any key to start experiment)', 'center', 'center'); Screen('Flip', expWin); KbWait([], 3); end % Copy the content of the previously prepared texture or offscreenWindow % into the backbuffer of the onscreen window, then flip it to the front % NOTE that offscreen windows and textures are almost the same % thing. Btw. although we specify the target region, this is not % strictly neccessary. A Screen('DrawTexture', expWin, fixcross); % would yield exactly the same result, as all textures are centered % in the target window by default. Screen('DrawTexture', expWin, fixcross,[],[mx-10,my-10,mx+10,my+10]); % We show the fixation cross at next display refresh cycle and % store the onset time of the fixation cross in 'tfixation'. Later % on we will use that as baseline to make sure the actual Mueller % Lyer test stim is shown 0.5 secs after onset of fixation: tfixation = Screen('Flip', expWin); %Prepare stimulus characteristics, make all aspects of the stimuli %proportional to stimsize so it can be dynamically changed stimsize=rand*100+50; %between 50 and 150 pixels hll=[stimsize, stimsize*0.9]; %halflinelength, difference of 10% hos=stimsize*.8; %headoffset voff=stimsize*1.5; %vertical distance between lines %look up the index in the correct line of current trial and find the %corresponding item in hll (half line length) %the identity of the current line is governed by the scrambled indices in order l_hll = hll(condtable(order(i),(1))); %look up the direction of the header in the current line of condtable l_head = condtable(order(i),(3)); %find horizontal offset according to this line's hearer direction l_hos=hos*l_head; %same again for the second stimulus/line u_hll = hll(condtable(order(i),(2))); u_head = condtable(order(i),(4)); u_hos=hos*u_head; %draw stimuli into backbuffer Screen('DrawLine', expWin , 0, mx-l_hll, my-voff, mx+l_hll, my-voff, lw); if l_hos~=0 Screen('DrawLine', expWin , 0, mx-l_hll, my-voff, mx-l_hll+l_hos, my-voff+hos/2, lw); Screen('DrawLine', expWin , 0, mx-l_hll, my-voff, mx-l_hll+l_hos, my-voff-hos/2, lw); Screen('DrawLine', expWin , 0, mx+l_hll, my-voff, mx+l_hll-l_hos, my-voff+hos/2, lw); Screen('DrawLine', expWin , 0, mx+l_hll, my-voff, mx+l_hll-l_hos, my-voff-hos/2, lw); end Screen('DrawLine', expWin , 0, mx-u_hll, my+voff, mx+u_hll, my+voff, lw); if u_hos~=0 Screen('DrawLine', expWin , 0, mx-u_hll, my+voff, mx-u_hll+u_hos, my+voff+hos/2, lw); Screen('DrawLine', expWin , 0, mx-u_hll, my+voff, mx-u_hll+u_hos, my+voff-hos/2, lw); Screen('DrawLine', expWin , 0, mx+u_hll, my+voff, mx+u_hll-u_hos, my+voff+hos/2, lw); Screen('DrawLine', expWin , 0, mx+u_hll, my+voff, mx+u_hll-u_hos, my+voff-hos/2, lw); end %this would tell PTB that no further drawing commands will occur %before the next screen 'flip'. Apparently this can improve performance %telapsed is the time since the last flip command; use this if %you want to test how long it takes to draw into the backbuffer % telapsed = Screen('DrawingFinished', expWin, [], 1); %However, its not needed here... %display stimuli and get onset time (two alternative ways). We ask %to show the stim 0.5 seconds after onset 'tfixation' of the %fixation cross: [VBLTimestamp, StimulusOnsetTime, FlipTimestamp]=Screen('Flip', expWin, tfixation + 0.5); tic; %these different timestamps are not exactly the same, e.g.: % plot([VBLTimestamp StimulusOnsetTime FlipTimestamp tic]) %the difference is negligible for most experiments %record response time, two methods again %this is just to compare between Matlab and PTB timing. %In your experiment, you should settle for one method --> %the Psychtoolbox method of using 'StimulusOnsetTime' seems to be %the more reliable solution, specifically on varying hardware %setups or under suboptimal conditions [resptime, keyCode] = KbWait; MLrt=toc; rt=resptime-StimulusOnsetTime; %find out which key was pressed cc=KbName(keyCode); %translate code into letter (string) %calculate performance or detect forced exit if isempty(cc) || strcmp(cc,'ESCAPE') break; %break out of trials loop, but perform all the cleanup things %and give back results collected so far elseif ~any(strcmp(cc,'s') || strcmp(cc,'d')) anscorrect = 66; elseif u_hll==l_hll && strcmp(cc,'s') anscorrect = 1; elseif u_hll~=l_hll && strcmp(cc,'d') anscorrect = 1; else anscorrect = 0; end %enter results in matrix results(i,:) = [subID, i-ntrain, order(i), l_hll*2, l_head, u_hll*2, u_head, u_hll/l_hll, anscorrect, rt, MLrt]; %beep if the response was incorrect if anscorrect ~=1 beep; end %show between trial prompt and wait for button press DrawFormattedText(expWin, 'Press any key to start next trial', 'center', 'center'); Screen('Flip', expWin); KbWait([], 3); %wait for keystroke end %of trials loop %write results to comma delimited text file (use '\t' for tabs) dlmwrite(fileName, results, 'delimiter', ',', 'precision', 6); % %alternative: write to excel format % xlswrite(['MLIexpSubj' num2str(subID) '.xls'],[colHeaders; num2cell(results)]); %calculate and display performance feedback performance_errors=mean(results(results(:,9)~=66,9)); DrawFormattedText(expWin, ['You were correct in ' num2str(performance_errors*100,2) '% of trials.\nThank you for participating!'], 'center', 'center'); Screen('Flip', expWin); KbWait([], 2); %wait for keystroke %clean up before exit ShowCursor; sca; %or sca; ListenChar(0); %return to olddebuglevel Screen('Preference', 'VisualDebuglevel', olddebuglevel); catch % This section is executed only in case an error happens in the % experiment code implemented between try and catch... ShowCursor; sca; %or sca ListenChar(0); Screen('Preference', 'VisualDebuglevel', olddebuglevel); %output the error message psychrethrow(psychlasterror); end