function s = VignetCalibration(filename, screenId) % s = VignetCalibration([filename] [, screenId]) % % Vignetted Luminance calibration procedure for undistortion of distorted % display luminance. % % This code was created by Jorrit S. Montijn of the Laboratoire Psychologie % de la Perception at Universite Paris Descartes. % % This function allows the user to create a calibration for a vignetted % luminance distortion. It enables the user to choose from either a % Gaussian or Exponential approximation of the luminance vignetting. Users % can change the position and steepness of the luminance fall-off in % horizontal and vertical direction independently. It is also possible to % adjust the minimal luminance. % % Once the calibration procedure is finished, the gain matrix (and other % possibly useful parameters and variables) are saved to disk for use by a % vignetting compensation program (see 'VignettingCorrectionDemo' for an % example of how to do this). % % How to use: % % Start the script, providing a filename and screennumber: % % 'filename' is the name of the file to which calibration results will be % saved. If none is provided, a default name will be used. The default file % will be stored in the directory returned by PsychtoolboxConfigDir('ShadingCalibration'). % % 'screenId' is the optional index of the screen that you want to % calibrate. If only one screen is present, it is not necessary to include % this parameter. % % When started, the program will show either a gaussian or exponential % approximation of the required gain function. You can adjust the % parameters so that the screen will look approximately isoluminant. It is % recommended to use a photometer, since human eyes are far from perfect when % doing this. % % You can use the following keys to issue commands: % % Escape - use this key to save and exit % Tab - show/hide help text % Space - change the parameter you wish to adjust % '<' and '>' - use these keys to change the increment size of the % parameter adjustments % Arrow Keys - adjust parameters (depending on selected parameter) % 1. Luminance/Function type % 2. Width/Steepness of function % 3. Position of centre % % History: % % 16.03.2010 Interactive Vignetting Calibration function created % by Jorrit S. Montijn. % % 18.03.2010 mk Minimal cleanup and merge into distribution. % 27.03.2010 mk Switch to multiplicative inverse. Use gainCorrection of a % 50% grey background via PsychColorCorretion(). This allows % for > 1.0 gain values. % 19.04.2010 jm Removed Lmin restrictions allowing for gain values to take % any value (-inf < Lmin < inf), added display of % current parameter values in on-screen help text % % Check for proper installation: PsychDefaultSetup(1); %create variable if needed if ~exist('screenId','var') screenId = []; end % Fetch connected screens: screens = Screen('Screens'); %if no screen nr was provided if isempty(screenId) %ask which one to calibrate if more than 1 is present if length(screens)>1, screenId = str2double(input(['Which screen should we calibrate? [' num2str(screens) '] '],'s')); else screenId = screens; end end if ~ismember(screenId, screens) error('No such screen. Please restart program with valid screenid.'); end %put screenId number into structure s.scrNr = screenId; %fetch screen resolution [w, h] = Screen('WindowSize', screenId); %create variable if needed if ~exist('filename', 'var') filename = []; end %if no filename was provided if isempty(filename) %assign filename filename = [PsychtoolboxConfigDir('ShadingCalibration') 'VignetCalibration' sprintf('_%i_%i_%i', screenId, w, h) '.mat']; fprintf('\nNo filename defined... Using default\n'); end %show name of output file and check to overwrite fprintf('Name of outputfile: %s\n\n', filename); if exist(filename, 'file') answer = input('File already exists. Overwrite? [y/n]','s'); if ~strcmp(lower(answer), 'y') %#ok fprintf('\n\nCalibration aborted. Please restart program and specify another filename.\n\n'); return; end end %disable synctests oldsynclevel = Screen('Preference', 'SkipSyncTests', 2); %change verbosity oldverbosity = Screen('Preference', 'Verbosity', 1); % Open window on desired screenId: % Use imaging pipeline for visual processing and display: PsychImaging('PrepareConfiguration'); % Request per-pixel 2D gain correction for display: PsychImaging('AddTask', 'FinalFormatting', 'DisplayColorCorrection', 'GainMatrix'); % Open window with gray background (128 aka 50% gray), return handle 'win': [s.window, rect] = PsychImaging('OpenWindow', s.scrNr, 128); %change text appearance Screen('TextSize',s.window, 18); Screen('TextStyle', s.window, 1+2); Screen('TextColor', s.window, [255 0 0]); %put rect into structure s.rect = rect; try %hide cursor and disable input HideCursor; ListenChar(2); %initialize parameters [beta,xGrid,yGrid] = InitVignetCalibration(s.rect); %put values into structure s.quitNow = 0; s.beta = beta; s.xGrid = xGrid; s.yGrid = yGrid; %start calibration s = CalibrateVignetNow(s); %collect variables from structure for easy access gainMatrix = s.textureGrid; %#ok parameters = s.beta; %#ok functionType = s.functionType; %#ok % Strip redundant data from 's': bs = s; s.xGrid = []; s.yGrid = []; s.textureGrid = []; %save variables and structure to file save(filename, 'gainMatrix', 'parameters', 'functionType', 's', '-mat', '-V6'); % Restore unstripped 's' before return: s = bs; fprintf('The calibration is finished! \n\n'); fprintf('Your calibration matrix, parameters and function type have been saved to\n%s\n', filename); fprintf('Also included in the file is a structure that contains most settings you used.\n'); fprintf('For an example on how to use the generated gain matrix to reduce vignetting,\n'); fprintf('please take a look at the PsychToolbox demo ''VignettingCorrectionDemo''\n\n'); catch %show cursor ShowCursor('Arrow'); %reset screen values sca; Screen('Preference', 'SkipSyncTests', oldsynclevel); Screen('Preference', 'Verbosity', oldverbosity); %reenable listenchar ListenChar(0); %rethrow error psychrethrow(psychlasterror); end %reenable listenchar ListenChar(0); %restore cursor ShowCursor('Arrow'); %reset screen values sca; Screen('Preference', 'SkipSyncTests', oldsynclevel); Screen('Preference', 'Verbosity', oldverbosity); return; end function [beta,xGrid,yGrid] = InitVignetCalibration(rect) rLeft = rect(1); rTop = rect(2); rRight = rect(3); rBot = rect(4); xPixelSpace = rLeft+1:rRight; %horizontal pixel space yPixelSpace = rTop+1:rBot; %vertical pixel space xPixNum = length(xPixelSpace); %number of pixels on x-axis yPixNum = length(yPixelSpace); %number of pixels on y-axis Lmin = 0; %minimal luminance level wX = xPixNum/2; %initial horizontal gaussian width is quarter of screen wY = yPixNum/2; %initial vertical gaussian width is quarter of screen muX = xPixNum/2; %initial horizontal gaussian position is at middle of screen muY = yPixNum/2; %initial vertical gaussian position is at middle of screen %put parameters into beta variable beta(1) = Lmin; beta(2) = wX; beta(3) = wY; beta(4) = muX; beta(5) = muY; %create meshgrids from pixel spaces [xGrid,yGrid] = meshgrid(xPixelSpace,yPixelSpace); end function s = CalibrateVignetNow(s) %define initial variables useFunction = 1; drawText = 1; paramNow = 1; beta = s.beta; xGrid = s.xGrid; yGrid = s.yGrid; switchVar = [2 1]; switchVar3 = [2 3 1]; redrawTexture = 1; functionList = {'Gaussian','Exponential'}; paramList = {'Minimum Luminance','Width','Position'}; %create Lmin variables Lmin = beta(1); LminIncrement = 0.1; %create width variables wX = beta(2); wY = beta(3); wIncrement = wX/20; %create position variables muX = beta(4); muY = beta(5); muIncrement = muX/20; while s.quitNow == 0 %if texture whould be (re)drawn if redrawTexture == 1 %reset redraw var redrawTexture = 0; %create grid from parameters beta(1) = Lmin; beta(2) = wX; beta(3) = wY; beta(4) = muX; beta(5) = muY; if useFunction == 1 %use gaussian approximation textureGrid = gaussianGrid(beta, xGrid, yGrid); else %use exponential approximation textureGrid = exponentialGrid(beta,xGrid,yGrid); end % Apply gain correction matrix to display: This will take effect at the % next Screen('Flip'): PsychColorCorrection('SetGainMatrix', s.window, textureGrid); end %if text should be drawn if drawText == 1 %draw help s.functionType = functionList{useFunction}; if paramNow == 1 thisIncrement = num2str(LminIncrement); upDown = 'increase/decrease min Luminance factor'; curValUD = num2str(beta(1)); leftRight = 'Shift to other function'; curValLR = s.functionType; elseif paramNow == 2 thisIncrement = num2str(wIncrement); upDown = 'increase/decrease vertical sigma'; curValUD = num2str(beta(3)); leftRight = 'decrease/increase horizontal sigma'; curValLR = num2str(beta(2)); else thisIncrement = num2str(muIncrement); upDown = 'move centre up/down'; curValUD = num2str(beta(5)); leftRight = 'move centre left/right'; curValLR = num2str(beta(4)); end text = ['You can use the following commands:\n\n'... 'TAB: show/hide this text\n'... 'Escape: save&exit\n'... 'Space: switch parameters :: Current: ' paramList{paramNow} '\n'... '"<" and ">": decrease/increase increment: ' thisIncrement '\n'... 'Up/Down: ' upDown ' :: Current: ' curValUD '\n'... 'Left/Right: ' leftRight ' :: Current: ' curValLR '\n']; DrawFormattedText(s.window,text,20,20); end %flip screen Screen('Flip',s.window); %check for input inputReceived = 0; keyPressed = 0; while inputReceived == 0 && s.quitNow == 0 KbWait; %wait till keypress [keyIsDown,secs,keyCode]=KbCheck; if keyIsDown if keyPressed == 0 keyPressed = 1; inputReceived = 1; if keyCode(KbName('Escape')) %done! Put output texture and parameters in %structure to return to master function and break %loop s.textureGrid = textureGrid; s.beta = beta; s.quitNow = 1; break; elseif keyCode(KbName('space')) %shift adjustment parameters %1: strength (Lmin) %2: width (wX and xY) %3: position (muX and muY) paramNow = switchVar3(paramNow); elseif keyCode(KbName('tab')) %toggle show/hide text drawText = switchVar(drawText); else if paramNow == 1 %Lmin valueX = Lmin; valueY = Lmin; increment = LminIncrement; elseif paramNow == 2 %width valueX = wX; valueY = wY; increment = wIncrement; elseif paramNow == 3 %position valueX = muX; valueY = muY; increment = muIncrement; end if keyCode(KbName('LeftArrow')) %adjust left redrawTexture = 1; valueX = valueX - increment; if paramNow == 1 useFunction = switchVar(useFunction); end elseif keyCode(KbName('RightArrow')) %adjust right redrawTexture = 1; valueX = valueX + increment; if paramNow == 1 useFunction = switchVar(useFunction); end elseif keyCode(KbName('UpArrow')) %adjust up redrawTexture = 1; if paramNow == 3 valueY = valueY - increment; else valueY = valueY + increment; end elseif keyCode(KbName('DownArrow')) %adjust down redrawTexture = 1; if paramNow == 3 valueY = valueY + increment; else valueY = valueY - increment; end elseif keyCode(KbName(',<')) %decrease increment increment = increment / 2; elseif keyCode(KbName('.>')) %increase increment increment = increment * 2; else %false alarm: no input received, just a random keypress inputReceived = 0; end if paramNow == 1 %Lmin LminIncrement = increment; Lmin = valueY; elseif paramNow == 2 %width wIncrement = increment; wX = valueX; wY = valueY; elseif paramNow == 3 %position muIncrement = increment; muX = valueX; muY = valueY; end end end else keyPressed = 0; end end KbReleaseWait; end end function gaussGrid = gaussianGrid(beta, xGrid, yGrid) Lmin = beta(1); %minimal luminance level (0-1) wX = beta(2); %width on x-axis (pixels) wY = beta(3); %width on y-axis (pixels) muX = beta(4); %centre on x-axis (pixels) muY = beta(5); %centre on y-axis (pixels) gaussX = exp(- (((xGrid-muX).^2) / ((2*wX).^2) ) ); %create horizontal gaussian gaussY = exp(- (((yGrid-muY).^2) / ((2*wY).^2) ) ); %create vertical gaussian comb = gaussX.*gaussY; %combine into one grid % Invert grid: Multiplicative inverse should compensate for % multiplicative attenuation of display. Add offset Lmin to shift the % gainfield around: The final matrix stores floating point gain values % greater or equal to 0.0: gaussGrid = (1 ./ comb) + Lmin; fprintf('Mingain = %f, Maxgain = %f\n', min(min(gaussGrid)), max(max(gaussGrid))); end function expoGrid = exponentialGrid(beta, xGrid,yGrid) Lmin = beta(1); %minimal luminance level (0-1) xF = beta(2); %width on x-axis (pixels) yF = beta(3); %width on y-axis (pixels) muX = beta(4); %centre on x-axis (pixels) muY = beta(5); %centre on y-axis (pixels) expX = min(exp(xGrid/xF-muX/xF),exp(-xGrid/xF+muX/xF) ); %horizontal part expY = min(exp(yGrid/yF-muY/yF),exp(-yGrid/yF+muY/yF) ); %vertical part comb = expX.*expY; %combine into one grid % Invert grid: Multiplicative inverse should compensate for % multiplicative attenuation of display. Add offset Lmin to shift the % gainfield around: The final matrix stores floating point gain values % greater or equal to 0.0: expoGrid = (1 ./ comb) + Lmin; fprintf('Mingain = %f, Maxgain = %f\n', min(min(expoGrid)), max(max(expoGrid))); end