function BitsPlusImagingPipelineTest(whichScreen, plotdiffs, forcesuccess, winrect, bitsSharpPortName, acceptableerror) % BitsPlusImagingPipelineTest([whichScreen=max][,plotdiffs=0][, forcesuccess=0][, winrect=[]][, bitsSharpPortName][, acceptableerror=1]) % % Tests correct function of Mono++ and Color++ mode with imaging pipeline... % This test script needs to be run once after each graphics card or % graphics driver or Psychtoolbox upgrade. % % This test tests if the Psychtoolbox image processing pipeline is capable % to correctly convert a high dynamic range image for the Cambridge % Research Systems Bits++ / Bits# box for Mono++ and Color++ mode. This % test script can be also used to verify proper operation with VPixx Inc. % devices like the DataPixx, ViewPixx, and ProPixx, as they expect the same % type of color encoding of pixeldata as the devices from CRS. % % It does so by generating a test stimulus, converting it into a Bits++ % image via the Matlab BitsPlusPlus toolbox and by use of the imaging % pipeline. Then it reads back and compares the conversion results of both % to verify that the imaging pipeline produces exactly the same results % as the Matlab routines. % % If the results are the same, it will write some info file to the % filesystem to confirm this test was successfully run. % % Optional parameters: % % whichScreen = Screen id of display to test on. Will be the secondardy % display if none provided. % % plotdiffs = If set to one, plot diagnostic difference images, if any % differences are detected. By default no such images are plotted. No % images will be plotted if no differences exist. % % forcesuccess = Set this to one if you want to force the test to succeed, % despite detected errors, ie., if you want the GPU % conversion to be used. Only use this if you really know % what you are doing! % % winrect = Optional placement rectangle for window. Defaults to fullscreen. % % bitsSharpPortName = Optional name of the serial port to which a Bits# device % is connected. If omitted, the portName will be fetched % from the global Bits# config file, or auto-detected. % % acceptableerror = Maximum acceptable error in absolute output intensity % difference. Differences less than this value are % considered a successful "pass" of the test by the GPU. % The default is 1, ie., 1 unit out of 65535. On CRS % Bits+ and Bits# this would cause no measureable error, % as these devices only use 14 bits of the 16 bit range, % ie., an error in the least significant bit of intensity % won't show up anyway. On VPixx Data/View/ProPixx % devices, the error will be so small that it will just % disappear in the noise/error caused by other sources of % error, e.g., inaccurate display calibration or gamma % correction, lighting conditions, variation in display % hardware due to operating temperature, voltage % fluctuations etc. The default setting of 1 should be % perfectly fine, even higher values (maybe up to 10) % would be ok in practice. % % Please note that this test script can only test if the correct output to % your systems framebuffer is generated by Psychtoolbox. It can't detect if % the Bits++ box itself is working correctly with this data. Only visual % inspection and a photometer/colorimeter test can really tell you if the % whole system is working correctly! answer = input('Test with DataPixx/ViewPixx/ProPixx (d), Bits+ (b) or Bits# (s)? ', 's'); if answer == 'd' % Tell BitsPlusPlus driver that this is operating on a DataPixx: BitsPlusPlus('SetTargetDeviceType', 1); else % Tell BitsPlusPlus driver that this is operating on a Bits+ or Bits# : BitsPlusPlus('SetTargetDeviceType', 0); end % If it is a Bits#, open a connection to it: if answer == 's' if nargin < 5 bitsSharpPortName = []; end BitsPlusPlus('OpenBits#', bitsSharpPortName); end oldverbosity = Screen('Preference', 'Verbosity', 2); oldsynclevel = Screen('Preference', 'SkipSyncTests', 2); % Define screen: if nargin < 1 || isempty(whichScreen) whichScreen=max(Screen('Screens')); end if nargin < 2 || isempty(plotdiffs) plotdiffs = 0; end if nargin < 3 || isempty(forcesuccess) forcesuccess = 0; end if nargin < 4 winrect = []; end % Default to 1 absolute intensity unit of acceptable error, if none % specified: if nargin < 6 || isempty(acceptableerror) acceptableerror = 1; end % Generate a synthetic grating that covers the whole % color intensity range from 0 to 16384, mapped to the 0.0 - 1.0 range: theImage=zeros(256,256,3); theImage(:,:,1)=reshape(double(linspace(0, 2^16 - 1, 2^16)), 256, 256)' / (2^16 - 1); theImage(:,:,2)=theImage(:,:,1); theImage(:,:,3)=theImage(:,:,1); % Convert input image via Bits++ toolbox routines (Matlab code): fprintf('Converting test stim to color++ format\n'); packedImage = BitsPlusPackColorImage(theImage, 0, 1); % Show the image % Make sure we run with our default color correction mode for this test: % 'ClampOnly' is the default, but we set it here explicitely, so no state % from previously running scripts can bleed through: PsychColorCorrection('ChooseColorCorrection', 'ClampOnly'); % Open a double buffered fullscreen window with black background, configured for the Bits++ % Color++ Mode, i.e., with proper setup of imaging pipeline and conversion shaders: BitsPlusPlus('ForceUnvalidatedRun'); % The correctness test for Color++/C48 mode is written for classic mode, % ie., mode 0, so request that: BitsPlusPlus('SetColorConversionMode', 0); [window, screenRect] = BitsPlusPlus('OpenWindowColor++', whichScreen, 0, winrect); % Build HDR texture: hdrtexIndex= Screen('MakeTexture', window, theImage, [], [], 2); % Draw HDR image via imaging pipeline: % Enable Bits++ Color++ output formatter: Screen('HookFunction', window, 'Enable', 'FinalOutputFormattingBlit'); % Draw Color++ image as generated by PTB GPU imaging pipeline: dstRect = Screen('Rect', hdrtexIndex); Screen('DrawTexture',window,hdrtexIndex, [], OffsetRect(dstRect, 1, 1), [], 0); % Finalize image before we take a screenshot: Screen('DrawingFinished', window, 0, 1); % Scanning pattern for testing for possible offsets between rasterization % path and readback path due to graphics driver bugs. We start with the % most likely hypothesis [0 ; 0] aka no offsets: spo = [ 0, -1, -1, -1, 0, 0, +1, +1, +1 ; ... 0, -1, 0, +1, -1, +1, -1, 0, +1]; fprintf('\n\n'); minerror = inf; for idx = 1:size(spo, 2) dx = spo(1, idx); dy = spo(2, idx); % Take screenshot of GPU converted image: convImage=Screen('GetImage', window, OffsetRect(ScaleRect(dstRect, 2, 1), dx + 2, dy + 1),'backBuffer'); % Compute difference images between Matlab converted packedImage and GPU converted % HDR image: diffred = abs(double(packedImage(:,:,1)) - double(convImage(:,:,1))); diffgreen = abs(double(packedImage(:,:,2)) - double(convImage(:,:,2))); diffblue = abs(double(packedImage(:,:,3)) - double(convImage(:,:,3))); % Compute maximum deviation of framebuffer raw data: mdr = max(max(diffred)); mdg = max(max(diffgreen)); mdb = max(max(diffblue)); fprintf('ReadbackOffset (%i, %i): Maximum raw data difference: red= %f green = %f blue = %f\n', dx, dy, mdr, mdg, mdb); % If there is a difference, show plotted difference if requested: if (mdr>0 || mdg>0 || mdb>0) && plotdiffs % Differences detected! close all; imagesc(diffred); title('Undecoded pixel error in red channel (even/odd cols = high/low 8-Bits):'); figure; imagesc(diffgreen); title('Undecoded pixel error in green channel (even/odd cols = high/low 8-Bits):'); figure; imagesc(diffblue); title('Undecoded pixel error in blue channel (even/odd cols = high/low 8-Bits):'); end if (mdr>0 || mdg>0 || mdb>0) % Now compute a more meaningful difference: The difference between the % stimulus as the Bits++ box would see it (i.e. how much do the 16 bit % intensity values of each color channel differ?): convImage = double(convImage); packedImage = double(packedImage); % For each color channel do... for c=1:3 % Invert conversion: Compute 16 bpc color values from high/low byte % pixel data: deconvImage = (zeros(size(convImage,1), size(convImage,2)/2)); deconvImage(:,:) = 256 * convImage(:, 1:2:end-1, c) + convImage(:, 2:2:end, c); depackImage = (zeros(size(packedImage,1), size(packedImage,2)/2)); depackImage(:,:) = 256 * packedImage(:, 1:2:end-1, c) + packedImage(:, 2:2:end, c); % Difference image: diffImage = (deconvImage - depackImage); % Find locations where pixels differ: idxdiff = find(abs(diffImage) > 0); numdiff(c) = length(idxdiff); %#ok numtot(c) = size(diffImage,1)*size(diffImage,2); %#ok maxdiff(c) = max(max(abs(diffImage))); %#ok [row col] = ind2sub(size(diffImage), idxdiff); % Print out all pixels values which differ, and their difference: if plotdiffs figure; imagesc(diffImage); title(sprintf('Effective error in channel %i: Units of device color intensity:', c)); if plotdiffs > 1 for j=1:length(row) fprintf('Diff: %.2f Input Value: %.20f\n', diffImage(row(j), col(j)), theImage(row(j), col(j), c) * 65535); end end end end for c=1:3 % Summarize for this color channel: fprintf('Channel %i: %i out of %i pixels differ. The maximum absolute difference is %i.\n', c, numdiff(c), numtot(c), maxdiff(c)); end else % No error: Set maxdiff to zero. maxdiff = 0; end % Compute absolute effective maximum error in color intensity over all % values, pixels and channels. This is the true "human visible" error. maxerror = max(maxdiff); % Track minimum error and the settings that lead to it: if maxerror < minerror minerror = maxerror; mindx = dx; mindy = dy; end % Error small enough to be acceptable for practical purposes? if maxerror <= acceptableerror % Yes. Skip further tests at different rasterizer offsets: break; end end % Move ahead with the dx,dy offsets that gave the best results: dx = mindx; dy = mindy; % Show GPU converted image. Should obviously not make any visual difference if % it is the same as the Matlab converted image. vbl = Screen('Flip', window); % Disable Bits++ Color++ output formatter: Screen('HookFunction', window, 'Disable', 'FinalOutputFormattingBlit'); % Build and draw texture from packed image: texpacked= Screen('MakeTexture', window, packedImage); dstRect = Screen('Rect', texpacked); Screen('DrawTexture', window, texpacked, [], OffsetRect(dstRect, 2, 1), [], 0); % Show it: vbl = Screen('Flip', window, vbl + 1); % Keep it onscreen for 2 seconds, then blank screen: Screen('Flip', window, vbl + 2); % Done. Close everything down: Screen('CloseAll'); RestoreCluts; % Best fit differs from expected result? That would be "game over": if (mdr>0 || mdg>0 || mdb>0) fprintf('\n\n------------------ DIFFERENCE IN COLOR++ CONVERSION DETECTED ------------------\n'); % Effective maximum error below acceptance threshold? Then we just warn % mildly but do not fail: if maxerror <= acceptableerror fprintf('However, the effective absolute maximal error is within the rejection threshold of %f units.\n', acceptableerror); fprintf('This means the error of %f intensity units out of 65535 is small enough to not matter in reality,\n', maxerror); fprintf('given the limited precision of your display device, calibration errors and other sources of error.\n'); fprintf('\nTherefore i will let this test pass. You can use the "plotdiffs" flag to show a difference image\n'); fprintf('for further assessment if you want.\n\n'); else % Error too big to be acceptable. fprintf('The effective absolute maximal error of %f is bigger than the\nrejection threshold of %f units.\n', maxerror, acceptableerror); fprintf('The best case absolute minimal error of %f is bigger than the\nrejection threshold of %f units.\n', minerror, acceptableerror); fprintf('This should not happen on properly and accurately working graphics hardware.\n'); fprintf('Either there is a bug in the graphics driver, or something is misconfigured or\n'); fprintf('your hardware is too old and not capable of performing the calculations in sufficient\n'); fprintf('precision.\nYou may want to check your configuration and upgrade your driver. If that\n'); fprintf('does not help, upgrade your graphics hardware. Alternatively you may want to use the old\n'); fprintf('Matlab-based BitsPlusPackColorImage() function for slow conversion of color images.\n\n'); fprintf('Please report this failure with a description of your hardware setup to the Psychtoolbox\n'); fprintf('forum (http://psychtoolbox.org --> Link to the forum.)\n\n'); if ~forcesuccess fprintf('You can force this test to succeed if you set the optional "forcesuccess" flag for this\n'); fprintf('script to one and rerun it.\n\n'); Screen('Preference', 'Verbosity', oldverbosity); Screen('Preference', 'SkipSyncTests', oldsynclevel); BitsPlusPlus('ResetOnWindowClose'); error('Bits++ Color++ test failed. Results of Matlab code and GPU conversion differ too much!'); else fprintf('Will continue anyway, because you set the "forcesuccess" flag to force me to continue.\n'); end end end % Non zero readback offsets needed? if ((dx ~= 0) || (dy ~= 0)) % Not good. fprintf('\n\n----------------------------------- CAUTION -----------------------------------------\n'); fprintf('There is an offset of dx = %i and dy = %i pixels between the detected\n', dx, dy); fprintf('and expected location of the pixel data in the framebuffer! This means that\n'); fprintf('your graphics driver has a bug either in the pixel rendering path or the pixel\n'); fprintf('readback path. A bug in the readback path would be problematic for other applications\n'); fprintf('but not for high precision color or luminance output.\n'); fprintf('A bug in the rendering path would be bad for high precision visual stimulus display.\n'); fprintf('This test cannot find out by itself which of both bugs is present, the "good" one or\n'); fprintf('the "bad" one. I will display a test pattern now after a key press. With your Bits+ box\n'); fprintf('or Datapixx in Color++ mode, you should see a 50%% intensity gray rectangle with a 100%% white\n'); fprintf('frame around it, in front of a black background.\n'); fprintf('Press a key for presentation, then the key again after you verified\n'); fprintf('if you saw what you were expected to see.\n'); fprintf('\n'); fprintf('Press any key to start presentation.\n'); KbStrokeWait; PsychColorCorrection('ChooseColorCorrection', 'ClampOnly'); % Open a double buffered fullscreen window with black background, configured for the Bits++ % Color++ Mode, i.e., with proper setup of imaging pipeline and conversion shaders: BitsPlusPlus('ForceUnvalidatedRun'); % The correctness test for Color++/C48 mode is written for classic mode, % ie., mode 0, so request that: BitsPlusPlus('SetColorConversionMode', 0); window = BitsPlusPlus('OpenWindowColor++', whichScreen, 0, winrect); Screen('FillRect', window, 0.5, CenterRect([0 0 200 200], screenRect)); Screen('FrameRect', window, 1.0, CenterRect([0 0 200 200], screenRect), 20); DrawFormattedText(window, 'Press any key when done.', 'center', 100, [1 1 0]); Screen('Flip', window); KbStrokeWait; Screen('CloseAll'); RestoreCluts; fprintf('\n\n\n'); answer = input('Did you see a gray filled rectangle, with a white frame on a black background? [y/n] ', 's'); if answer ~= 'y' fprintf('\n\nOk, the graphics driver bug is in the rendering path. Sad, very sad :-(\n'); fprintf('This will not work for precision stimulus presentation. Test failed.\n\n'); fprintf('You may want to check your configuration and upgrade your driver. If that\n'); fprintf('does not help, upgrade your graphics hardware. Alternatively you may want to use the old\n'); fprintf('Matlab-based BitsPlusPackColorImage() function for slow conversion of color images.\n\n'); fprintf('Please report this failure with a description of your hardware setup to the Psychtoolbox\n'); fprintf('forum (http://psychtoolbox.org --> Link to the forum.)\n\n'); fprintf('You can force this test to succeed if you set the optional "forcesuccess" flag for this\n'); fprintf('script to one and rerun it.\n\n'); Screen('Preference', 'Verbosity', oldverbosity); Screen('Preference', 'SkipSyncTests', oldsynclevel); error('Bits++ Color++ test failed. Hardware rasterizer malfunction / graphics driver bug!'); else fprintf('\n\nOk, the graphics driver bug is in the pixel readback path. That is tolerable for\n'); fprintf('high precision stimulus display, but look out for other problems on your system\n'); fprintf('with visual stimulus presentation etc.\n'); fprintf('Might be a good idea to update your graphics drivers. I will classify the test so far as succeess...\n\n'); fprintf('\n'); fprintf('Press any key to continue to part II of the test.\n'); KbStrokeWait; end end fprintf('\n\n------------------- Color++ test success! -------------------------------------\n\n'); % Now test Mono++ formatter: % Reset color conversion mode to "undefined" to prevent cascading % errors if usercode doesn't set this itself: BitsPlusPlus('SetColorConversionMode', []); % Generate a synthetic grating that covers the whole % intensity range from 0 to 16384, mapped to the 0.0 - 1.0 range: theImage=zeros(256,256,1); theImage(:,:)=reshape(double(linspace(0, 2^16 - 1, 2^16)), 256, 256)' / (2^16 - 1); % Convert input image via Bits++ toolbox routines (Matlab code): fprintf('Converting test stim to mono++ format\n'); packedImage = BitsPlusPackMonoImage(theImage * (2^16 - 1)); % Show the image % Open a double buffered fullscreen window with black background, configured for the Bits++ % Mono++ Mode, i.e., with proper setup of imaging pipeline and conversion shaders: BitsPlusPlus('ForceUnvalidatedRun'); window = BitsPlusPlus('OpenWindowMono++', whichScreen, 0, winrect); % Build HDR texture: hdrtexIndex= Screen('MakeTexture', window, theImage, [], [], 2); % Draw HDR image via imaging pipeline: % Enable Bits++ Mono++ output formatter: Screen('HookFunction', window, 'Enable', 'FinalOutputFormattingBlit'); % Draw Mono++ image as generated by PTB GPU imaging pipeline: dstRect = Screen('Rect', hdrtexIndex); Screen('DrawTexture',window,hdrtexIndex, [], OffsetRect(dstRect, 1, 1), [], 0); % Finalize image before we take a screenshot: Screen('DrawingFinished', window, 0, 1); % Take screenshot of GPU converted image: convImage=Screen('GetImage', window, OffsetRect(dstRect, dx + 1, dy + 1), 'backBuffer'); % Show GPU converted image. Should obviously not make any visual difference if % it is the same as the Matlab converted image. vbl = Screen('Flip', window); % Disable Bits++ Mono++ output formatter: Screen('HookFunction', window, 'Disable', 'FinalOutputFormattingBlit'); % Build and draw texture from packed image: texpacked= Screen('MakeTexture', window, packedImage); dstRect = Screen('Rect', texpacked); Screen('DrawTexture', window, texpacked, [], OffsetRect(dstRect, dx + 1, dy + 1), [], 0); % Keep it onscreen for 2 seconds, then blank screen: vbl = Screen('Flip', window, vbl + 2); % Keep it onscreen for 2 seconds, then blank screen: Screen('Flip', window, vbl + 2); Screen('Preference', 'Verbosity', oldverbosity); Screen('Preference', 'SkipSyncTests', oldsynclevel); % Compute difference images between Matlab converted packedImage and GPU converted % HDR image: diffred = abs(double(packedImage(:,:,1)) - double(convImage(:,:,1))); diffgreen = abs(double(packedImage(:,:,2)) - double(convImage(:,:,2))); diffblue = abs(double(packedImage(:,:,3)) - double(convImage(:,:,3))); % Compute maximum deviation of framebuffer raw data: mdr = max(max(diffred)); mdg = max(max(diffgreen)); mdb = max(max(diffblue)); fprintf('\n\nMaximum raw data difference: red= %f green = %f blue = %f\n', mdr, mdg, mdb); % If there is a difference, show plotted difference if requested: if (mdr>0 || mdg>0 || mdb>0) && plotdiffs % Differences detected! close all; imagesc(diffred); title('Undecoded pixel error in red channel (high 8-Bits):'); figure; imagesc(diffgreen); title('Undecoded pixel error in green channel (low 8-Bits):'); figure; imagesc(diffblue); title('Undecoded pixel error in blue channel (8-Bit overlay clut index):'); end if (mdr>0 || mdg>0 || mdb>0) % Now compute a more meaningful difference: The difference between the % stimulus as the Bits++ box would see it (i.e. how much do the 16 bit % intensity values of each color channel differ?): convImage = double(convImage); packedImage = double(packedImage); % Invert conversion: Compute 16 bpc intensity values from high/low byte % pixel data: deconvImage = zeros(size(convImage,1), size(convImage,2)); deconvImage(:,:) = 256 * convImage(:, :, 1) + convImage(:, :, 2); depackImage = zeros(size(packedImage,1), size(packedImage,2)); depackImage(:,:) = 256 * packedImage(:, :, 1) + packedImage(:, :, 2); % Difference image: diffImage = (deconvImage - depackImage); % Find locations where pixels differ: idxdiff = find(abs(diffImage) > 0); numdiff = length(idxdiff); numtot = size(diffImage,1)*size(diffImage,2); maxdiff = max(max(abs(diffImage))); [row col] = ind2sub(size(diffImage), idxdiff); % Print out all pixels values which differ, and their difference: if plotdiffs figure; imagesc(diffImage); title('Effective error in luminance: Units of device intensity:'); if plotdiffs > 1 for j=1:length(row) fprintf('Diff: %.2f Input Value: %.20f\n', diffImage(row(j), col(j)), theImage(row(j), col(j)) * 65535); end end end % Summarize: fprintf('%i out of %i pixels differ. The maximum absolute difference is %i.\n', numdiff, numtot, maxdiff); else % No error: Set maxdiff to zero. maxdiff = 0; end % Compute absolute effective maximum error in luminance intensity values, % pixels and channels. This is the true "human visible" error. maxerror = max(maxdiff); if (mdr>0 || mdg>0 || mdb>0) fprintf('\n\n------------------ DIFFERENCE IN MONO++ CONVERSION DETECTED ------------------\n'); % Effective maximum error below acceptance threshold? Then we just warn % mildly but do not fail: if maxerror <= acceptableerror fprintf('However, the effective absolute maximal error is within the rejection threshold of %f units.\n', acceptableerror); fprintf('This means the error of %f intensity units out of 65535 is small enough to not matter in reality,\n', maxerror); fprintf('given the limited precision of your display device, calibration errors and other sources of error.\n'); fprintf('\nTherefore i will let this test pass. You can use the "plotdiffs" flag to show a difference image\n'); fprintf('for further assessment if you want.\n\n'); else % Error too big to be acceptable. fprintf('The effective absolute maximal error of %f is bigger than the\nrejection threshold of %f units.\n', maxerror, acceptableerror); fprintf('This should not happen on properly and accurately working graphics hardware.\n'); fprintf('Either there is a bug in the graphics driver, or something is misconfigured or\n'); fprintf('your hardware is too old and not capable of performing the calculations in sufficient\n'); fprintf('precision.\nYou may want to check your configuration and upgrade your driver. If that\n'); fprintf('does not help, upgrade your graphics hardware. Alternatively you may want to use the old\n'); fprintf('Matlab-based BitsPlusPackMonoImage() function for slow conversion of luminance images.\n\n'); fprintf('Please report this failure with a description of your hardware setup to the Psychtoolbox\n'); fprintf('forum (http://psychtoolbox.org --> Link to the forum.)\n\n'); if ~forcesuccess fprintf('You can force this test to succeed if you set the optional "forcesuccess" flag for this\n'); fprintf('script to one and rerun it.\n\n'); Screen('Preference', 'Verbosity', oldverbosity); Screen('Preference', 'SkipSyncTests', oldsynclevel); BitsPlusPlus('ResetOnWindowClose'); sca; error('Bits++ Mono++ test failed. Results of Matlab code and GPU conversion differ too much!'); else fprintf('Will continue anyway, because you set the "forcesuccess" flag to force me to continue.\n'); end end end fprintf('\n\n------------------- Mono++ test success! -------------------------------------\n\n'); % All tests successful: Write this configuration to file as the imaging pipeline from usercode -> framebuffer being validated: BitsPlusPlus('StoreValidation', window, 1); % Done. Close everything down: BitsPlusPlus('ResetOnWindowClose'); Screen('CloseAll'); RestoreCluts; fprintf('\n\nSUMMARY: BitsPlusPlus imaging pipeline verified to work correctly. Validation info stored.\n'); fprintf('You may want to run BitsPlusIdentityClutTest next to test the path from framebuffer to output device.\n'); % Done for now. return;