function MinimumMotionExp(writeResult)
% MinimumMotionExp([writeResult=0])
%
% Minimum motion luminance measurement (Anstis and Cavanagh). Rapid,
% precise, and works well down to 1hz, or 1  cpd. Self-contained script,
% with optional gamma file input or default.
%
% Measures red/green and blue/green foveal or extrafoveal equiluminance
% using the Cavanagh, MacLeod and Anstis (1987) sinusoidal minimum motion
% stimulus, here drawn as a windmill.
%
% One component of the test stimulus is a rotary sine wave where peaks of 1
% phosphor coincide with troughs of a second phosphor. This is counterphase
% modulated in cosine phase is space and time. A "luminance lure" grating
% is added in spatial and temporal quadrature (sine phase). The stimulus
% appears in clockwise or anticlockwise motion, depending on the relative
% luminance of two phosphors. The ratio of the two phosphor intensities is
% adjusted using the mouse. The isoluminant point is the precise point
% where the motion direction changes, and there the motion is replaced by
% counterphase flicker. User selects whether to measure the red phosphor
% luminance relative to the green, the blue phosphor luminance relative to
% the green, or the blue phosphor luminance relative to the red. Reported
% values are the ratios of phosphor luminances when each phosphor is at
% maximum intensity. Average stimulus chromaticity and luminance are kept
% constant during the adjustment.
%
% The stimulus is produced by clut animation (palette mode). If BPP is
% nonzero, we assume that DVI video output is relayed to the monitor via a
% CRS BitsPlusPlus device, or via a DataPixx/ViewPixx device allowing fine
% control of modulation depths and corresponding precision in luminance
% estimates. Otherwise set BPP to zero. In either case, Psychophysics
% Toolbox 3 and Matlab 7.4 or later (or Octave 3.2 or later) are required.
%
% Note: Writing of result files is disabled, but can be enabled in a
% "production setting" by providing the optional argument 'writeResult' as
% 1.
%
% This script is contributed by Don MacLeod, UCSD. It was modified by Mario
% Kleiner for portability across all supported operating systems and for
% optimized speed.
%

% History:
% 03/03/08  Initial checking by MK on behalf of Don MacLeod.
% 12/20/09  Add support for VPixx DataPixx. (MK)
% 08/19/12  Use PsychImaging CLUT mapping mode instead of moglClutBlit().
%           This simplifies code somewhat. (MK)

clc;

disp('============================ Minimum Motion Windmill ==============================================')
disp('==A fast, precise, versatile and eternally fascinating photometric technique for the computer age==');
fprintf('\n');

% Set writeResult to 1 if you want to actually really write result files.
if nargin < 1 || isempty(writeResult)
    % It is == 0 by default to suppress writing when running as PTB demo.
    writeResult = 0;
end

useBPPString = input('Do you want to use the Bits++ box or DataPixx/ViewPixx for stimulation [y/n]? ', 's');
if strcmpi(useBPPString, 'y')
    BPP = 1;  % Use high precision display device in CLUT mode.
else
    BPP = 0;  % Use standard 8 bpc graphics card.
end

if (writeResult > 0) && ~exist('data','dir')
    mkdir(data);  % create this subdirectory if it doesn't already exist
end

% Abort script if it isn't executed on Psychtoolbox-3:
AssertOpenGL;

% Use common key mapping for all operating systems and define the escape
% key as abort key:
KbName('UnifyKeyNames');
escape = KbName('ESCAPE');

%***************************************************
%           -- Gamma compensation preparations --
% __________________________________________________
% define inverse gamma table...Note, this is what PTB calls the gamma table
% Entries are video signal levels, indexed by intensity
GAMMA = 2.7;

if BPP
    disp('Using CRS Bits++ in Bits++ CLUT mode or DataPixx/ViewPixx in L48 mode.')
    GTBLENGTH = 65536;
else
    disp('Using standard graphics card with 8 bpc framebuffer.')
    GTBLENGTH = 256;
end

disp('For default gamma compensation (gamma = 2.7), just hit enter at the following prompt: ');
invgammafilename = input...
    (['File with Nx3 InvGammaTablevariable listing the video outputs (0 to'...
    num2str(GTBLENGTH) ') producing r,g,b intensities ~ row index; will  add .mat extension)'...
    '\nOr, for default gamma compensation (gamma = ' num2str(GAMMA) '), just hit enter: '], 's');

if (~isempty(invgammafilename))
    eval(['load ' invgammafilename '.mat -mat']);
else
    if BPP
        InvGammaTable=  repmat(   floor((GTBLENGTH-.01)*(linspace(0,1,GTBLENGTH)'.^(1/GAMMA))), 1,3); % 0:65535 integer entries
    else
        InvGammaTable=  repmat(   (linspace(0,1,GTBLENGTH)'.^(1/GAMMA)), 1,3); % normalized entries, 0 to 1
    end
end

%***************************************
%       -- Parameter setting --
% ______________________________________

nblocks =input('Number of settings per condition: ');
subjectsname = input('Name of subject (only internal use!): ','s');
fname = input('File name prefix for storing new data (relative to data subdirectory, will  add .mat and .txt extensions):  ','s');
sectorspercycle = 80;  % per cycle, 20 or so is generally plenty; 254 is limit for 256 long Clut (1 is background and 256 is fixation)
ncycles = 18;   % number of windmill segments; spatial frequency in cpd is sectorspercycle/(pi*(avg of inner, outer diameters))
% ...might alternatively define spatial frequency first, derive ncycles, or else make these and/or drift rate condition-dependent. 
% ncycles must be integer (to allow the stimulus to be drawn only once, repeated in frame-dependent colorings.) 
DateOfExperiment = date; %#ok<*NASGU>
MAXCOL = 1; % Palette rgb intensity values will range from zero to MAXCOL
ADAPTRGB = MAXCOL*[ 0.5000  0.31   0.24 ]
lurecontrast = 0.08; % Michelson contrast of lure; .05 for precision, more for easy capture
% reasonable guess for CRT, half of max possible equal energy white, on
% scale from 0 to 1 for phosphor intensity; alternatively, request input or
% use your color calibrations etc. Palette entries are normalized below to
% max of 1 before reference to gamma table.
% Condition-dependent parameters in this script: innerdiameter, outerdiam, whichluminance (r/g:1, b/g:2, b/r:3)
conditionset = [ 11 15 3; 0 2 3]; 
nconds = size(conditionset, 1);
disp('condition-dependent parameters in this script: innerdiameter, outerdiam, whichluminance (r:1, b:2, both:3); values, 1 row per condition  are');
conditionset
VIEWDIST = 40; % cm
SCREENCM = 40; % Width in centimeters of full screen image on monitor....40 for Iiyama
disp('Spatial dimensions displayed and quoted assume that monitor screen width = viewing distance; if not, modify constants in script.')
disp('Move trackball or mouse leftward if motion is clockwise, rightward if anticlockwise, to find the balance point.');
disp('Hit escape to save and quit early; to register a setting, press any other keyboard key or a mouse button. Setting is acknowledged by a beep');
disp('Press any key to continue...'); % to avoid overwriting with PTB warnings/stimulus screen 
pause;

%***************************************************
% -- Initialise BitsPlusPlus/ install gamma table, set screen  onstants--
% -- For BPP, the actual gamma compensation comes when defining currentcols below --
% __________________________________________________

try
    % Choose screen for display: As usual, we assume the secondary display
    % is the stimulation display:
    screenid = max(Screen('Screens')); %   usual guess % 1+BPP; % if 2 screens with BPP on 2nd;     

    % Backup current graphics card gammatable, so we can restore it at
    % the end of the session:
    BackupCluts(screenid);

    % Prepare imaging pipeline:
    PsychImaging('PrepareConfiguration');
    
    % Use Bits++ for stimulation?
    if BPP
        % Add virtual framebuffer to make the "dontclear" after Flip more
        % efficient in the main animation loop:
        PsychImaging('AddTask', 'General', 'UseVirtualFramebuffer');

        % Use PTB's built-in setup code for Bits++ or DataPixx/Viewpixx
        % mode: This will load an identity gammatable into the graphics
        % card, so Bits++ T-Lock codes pass through unmodified. It will
        % also enable automatic updates of the Bits++ / DataPixx CLUT via
        % Screen('LoadNormalizedGammatable', ...) - PTB will automatically
        % convert CLUT's into proper T-Lock codes and draw them in the top
        % line of the frame at each Screen('Flip') - or perform the
        % equivalent action for a DataPixx or ViewPixx.
        useDPIXXString = input('Do you want to use the DataPixx box instead of Bits++ box for stimulation [y/n]? ', 's');
        if strcmpi(useDPIXXString, 'y')
            PsychImaging('AddTask', 'General', 'EnableDataPixxL48Output');
        else
            PsychImaging('AddTask', 'General', 'EnableBits++Bits++Output');
        end
    else
        % Enable CLUT animation by CLUT mapping, using a 8bpc, 256 slot clut:
        PsychImaging('AddTask', 'AllViews', 'EnableCLUTMapping');        

        % Load InvGammaTable immediately into graphics card for gamma correction:
        Screen('LoadNormalizedGammaTable', screenid, InvGammaTable);
    end

    % Open screen window with default background and imaging pipeline setup
    % for Bits+/Datapixx/Regular CLUT animation:
    wptr = PsychImaging('OpenWindow', screenid);
    
    % Open the offscreen window into which we draw the "index color image"
    % which defines the appearance of the "color wheel":
    [offwptr,screenRect] = Screen('OpenOffscreenWindow',wptr, 0); % open offscreen window

    % Perform initial flip to set display to well defined initial display
    % with background color:
    Screen('Flip', wptr);
    
    % Query duration of a video refresh interval: Technically it's the
    % minimum possible time delta between two flips, but in all setups,
    % except a few special frame-sequential stereo setups, this is the same
    % as the duration of a video refresh interval:
    flipinterval = Screen('GetFlipInterval',wptr);
    
    driftratehz = 2      % hz
    ASSUMEDREFRESHRATE = 1/flipinterval %#ok<*NOPRT> % assumedrefresh rate
    period = 1/driftratehz; % sec
    nframes = round(ASSUMEDREFRESHRATE*period); % frames (vertical retraces) per cycle
    if mod(ASSUMEDREFRESHRATE,driftratehz)
        warning('Drift period is not a multiple of monitor refresh interval: rounding'); %#ok<*WNTAG> % avoidable if each frame were drawn freshly
        fprintf('Stipulated drift rate: %f Hz, actual %f Hz\n', driftratehz, 1/(flipinterval*nframes));
    end
    
    % Hide mouse cursor:
    HideCursor;
    
    % Compute display dimensions:
    Width=RectWidth(screenRect);
    Height=RectHeight(screenRect);
    ppd=(Width/2)/(atan(SCREENCM/2/VIEWDIST) * 180.0 / pi); % pixels per degree, averaged over screen width;  24 ppd for Iiyama 1280x1024) at 40cm
    BACKINDEX = 1;  % Clut index for background

    %***************************************************
    %           -- Schedule conditions and begin experiment --
    % __________________________________________________
    datalist = [];
    for i=1:nblocks % create a list of condition indexes (to the conditionset list) in random order
        condindex(:,i)=randperm(nconds)'; %#ok<*AGROW> % =(1:nconds);%
    end
    
    % Preallocate some matrices needed later to speedup Matlabs processing:
    rphosvals = zeros(nframes, 256);
    gphosvals = zeros(nframes, 256);
    bphosvals = zeros(nframes, 256);
    currentdvivals = zeros(256,3);

    % Switch to realtime priority:
    Priority(MaxPriority(wptr));
    
    NTrials=nblocks*nconds;
    % nblocks = Number of settings per condition
    % nconds = Number of conditions
    for trial = 1:nblocks*nconds  % each 'trial' ends with a setting
        inrad = conditionset(condindex(trial),1)/2; % first condition-dependent parameter is inner diamete
        outrad = conditionset(condindex(trial),2)/2;
        whichluminance = conditionset(condindex(trial),3);
        keyisdown = 0; keycode=0;
        disp(['Trial ' ,num2str(trial),' of ', num2str(NTrials)]);
        disp(['Outer Radius: ', num2str(outrad), 'degrees,  Mean spatial frequency in cpd: ', num2str(ncycles/(outrad+inrad)/pi)])

        % Compute the spatiotemporal cosine for test stimuli, sine for lure
        sectorindex = 1:sectorspercycle; % index for the segments in the annulus
        spacesine = sin(2*pi*sectorindex./sectorspercycle);
        spacecosine = cos(2*pi*sectorindex./sectorspercycle);
        frameindex = 1:nframes; % index for time frames of stimulus
        timesine = sin(2*pi*frameindex./nframes);
        timecosine = cos(2*pi*frameindex./nframes);
        stsine = timesine'*spacesine;       % luminance lure amplitude, = spatiotemporal standing sine wave
        stcosine = timecosine'*spacecosine; % test amplitude, spatiotemporal standing cosine wave
        luredeltas(:,:,1) = (lurecontrast*stsine).*ADAPTRGB(1);  % nframe by sectorspercycle matrix of r values
        luredeltas(:,:,2) = (lurecontrast*stsine).*ADAPTRGB(2);  % g values
        luredeltas(:,:,3) = (lurecontrast*stsine).*ADAPTRGB(3);  % b values
        testdeltas(:,:,1) = ADAPTRGB(1)*ones(size(stcosine));  % nframe by sectorspercycle matrix of r values; phase compensation, if needed, could go here
        testdeltas(:,:,2) = ADAPTRGB(2)*ones(size(stcosine));  % nframe by sectorspercycle matrix of g values
        testdeltas(:,:,3) = ADAPTRGB(3)*ones(size(stcosine));  % nframe by sectorspercycle matrix of b values

        % Draw stimuli to offscreen window once: about 12ms for 180 arcs.
        Screen('FillRect', offwptr, BACKINDEX-1, screenRect); 
        Arc = 360/(ncycles*sectorspercycle);
        for i = 0: round(360/Arc) - 1 % =ncycl*nsect/2-1 rounding should not be needed if ncycles*sectorspercycle is submultiple of 360
            Screen('FillArc', offwptr, (mod(i,sectorspercycle)+1), [Width/2-(outrad*ppd) Height/2-(outrad*ppd) Width/2+(outrad*ppd) Height/2+(outrad*ppd)], Arc*i, Arc); 
        end
        
        % a small inner region of radius ncycles/pi pixels will show aliasing, so take it out (reduce ncycles if you need it smaller):
        centerradius = max(inrad*ppd, ncycles/pi);  
        Screen('FillOval', offwptr, BACKINDEX-1, [Width/2-(centerradius) Height/2-(centerradius) Width/2+(centerradius) Height/2+(centerradius)]); % inner gray circle
        Screen('FillRect', offwptr, 255, [Width/2-(.1*ppd) Height/2-(.1*ppd) Width/2+(.1*ppd) Height/2+(.1*ppd)]); % 6 min arc fixation point
        
        % Query mouse x position 'xi' to get initial setting for first
        % animation frame:
        loopcount=0; whichframe=0;
        [xi, yi, button] = GetMouse(wptr);

        % Perform flip at start of trial to sync us to retrace and get a
        % timestamp 'vbl' of start of trial:
        vbl = Screen('Flip', wptr);

        while ~keyisdown && ~any(button) % loop to display motion, with adjustable luminances, until a key or mouse button is pressed: about 1 msec
            loopcount=loopcount+1;
            whichframe=mod(whichframe+1,nframes);
            tstart = GetSecs;
            
            % Mouse and keyboard queries:
            oldxi = xi;
            [xi, yi, button] = GetMouse(wptr);       % gets mouse coordinates and button state.
            
            % We check the keyboard only every 10'th redraw, as KbCheck is
            % relatively expensive at least on OS/X (about 1 msec):
            if mod(loopcount, 10) == 0
                [keyisdown, secs, keycode] = KbCheck;    % is a key pressed of the keyboard, keyisdown is a logical variable if key is pressed
            end
            
            % if mouse has moved, get new lum value, update colors (not
            % just the current frame) - recompute all CLUT's for all
            % frames:
            if( loopcount == 1 || xi ~= oldxi)
                if whichluminance==1    % rlum (fraction of max g that is equiluminous with max r)
                    incphos = 2;    % g is incremented with mouse xi
                    refphos = 1;    % red phosphor is "standard", generally maximal modulation but can be reduced if necessary
                    staticphos = 3; % third phosphor is unmodulated, just used to make adaptation stimulus white
                elseif whichluminance==2 % blum (fraction of max g that is equiluminous with max b)
                    incphos = 3;    % b is incremented with mouse xi
                    refphos = 2;    % green phosphor is "standard", generally maximal modulation but can be reduced if necessary
                    staticphos = 1; % third phosphor is unmodulated, just used to make adaptation stimulus white
                elseif whichluminance==3 % compare red with blue to get fraction of max r that is equiluminous with max b
                    incphos = 1;    % r is incremented with mouse xi
                    refphos = 3;    % blue phosphor is "standard", generally maximal modulation but can be reduced if necessary
                    staticphos = 2; % third phosphor is unmodulated, just used to make adaptation stimulus white
                end

                refdeltalimit = min([MAXCOL-ADAPTRGB(refphos) ADAPTRGB(refphos)]); % maximum available modulation around ADAPTRGB for "ref" phosphor
                incdeltalimit = min([MAXCOL-ADAPTRGB(incphos) ADAPTRGB(incphos)]); % maximum available modulation around ADAPTRGB for "inc" phosphor

                % lum is the luminance of the full intensity of red or
                % blue phosphor as a fraction of that of the green
                % phosphor, or (if rorblum = 3) of the blue relative to
                % the red
                lum = exp(8*(xi./Width -.5));  % new rlum or blum value from mouse, range exp(-4) to exp(4) or about .02: 50, log spacing

                rangescaler = min(refdeltalimit, incdeltalimit/lum);    % neither phosphor will go out of range
                testdeltas(:,:,refphos) = (rangescaler*stcosine);       % nframe by sectorspercycle matrix of 'refphos' values
                testdeltas(:,:,incphos) = (-lum*rangescaler*stcosine);  % incphos values, OK for low lum but can exceed range, so...
                testdeltas(:,:,staticphos) = zeros(size(stcosine));     % nframe by sectorspercycle matrix of b values: put outside loop?

                rphosvals(:,BACKINDEX+[1:sectorspercycle]) =   max(0,ADAPTRGB(1) + testdeltas(:,:,1) + luredeltas(:,:,1)); %#ok<*NBRAK> % '-lure' to make direction of response to mouse intuitive
                gphosvals(:,BACKINDEX+[1:sectorspercycle]) =   max(0,ADAPTRGB(2) + testdeltas(:,:,2) + luredeltas(:,:,2));
                bphosvals(:,BACKINDEX+[1:sectorspercycle]) =   max(0,ADAPTRGB(3) + testdeltas(:,:,3) + luredeltas(:,:,3));
                rphosvals(:,256) = 0; % fixation point
                gphosvals(:,256) = 0;
                bphosvals(:,256) = 0;
                rphosvals(:,BACKINDEX) = ADAPTRGB(1); % background
                gphosvals(:,BACKINDEX) = ADAPTRGB(2);
                bphosvals(:,BACKINDEX) = ADAPTRGB(3);
            end

            % Select CLUT to use for next video refresh:
            currentcols = [rphosvals(whichframe+1,:)'  gphosvals(whichframe+1,:)'  bphosvals(whichframe+1,:)']; % list of rgb triplets for current frame

            % Timing check:
            if whichframe == 0 && loopcount > 1   % report actual cycletime if not nframes*flipinterval
                t_end = GetSecs;
                cycletime = t_end - tstart;
                if cycletime > (nframes +1) * flipinterval
                    fprintf('Requested drift rate not attained on cycle %f: reduce sectorspercycle (per spatial cycle?) %fHz vs. %fHz\n', loopcount, 1/cycletime, driftratehz)
                end
            end

            % Drawing of windmill image is only needed during first two
            % draw cycles to put the image into the two buffers of our
            % double-buffered window. As the window isn't cleared after
            % a flip, no need to redraw at all following cycles:
            if loopcount <= 2
                % Draw offscreen window with stimulus "windmill" index image to framebuffer.
                % Set filterMode == 0 to disable any kind of interpolation.
                % We want the pixels exactly as defined in the offscreen
                % window, so CLUT palette animation works:
                Screen('DrawTexture', wptr, offwptr, [], [], [], 0);
            end
            
            % now transform the desired intensities (0...1) into digital
            % video card output (DVI) values for R,G,B, using the provided
            % gammatable or else the default one
            
            % Different methods for Bits++ vs. standard graphics:
            if BPP
                % Bits++ CLUT animation: One unified Bits++ CLUT for both,
                % CLUT animation and display gamma correction:
                
                %-------------------------------------------
                % Derive needed DVI values, currentdvivals, by
                % Gamma-compensating the currentcols palette, and draw it as a
                % Clut to line 1 of the on-screen window of the frame buffer
                %-------------------------------------------
                tmpcols = max(ceil(65536*currentcols/MAXCOL),1); % currentcols, rescaled 1 to 65536
                
                % Gamma correction via table lookup in InvGammaTable:
                for gun = 1:3
                    currentdvivals(:,gun) = floor(InvGammaTable(tmpcols(:,gun),gun)); % 0 to 65535; use uint16 table, intensities?
                end
                
                % Final Clut needs to be in normalized range [0.0 - 1.0] for Screen's
                % builtin Bits++ CLUT update function to work:
                Clut = currentdvivals / 65535;

                % Tell Screen() to upload this 'Clut' to the Bits++ box at
                % next invocation of Screen('Flip') -- it will draw the
                % proper T-Lock line at the top of the stimulus image. This
                % is faster than doing it with the old Matlab encoding
                % functions! The flag '2' enables this special mode,
                % whereas a flag of 0 or 1 would affect the standard gamma
                % tables of the graphics card:
                Screen('LoadNormalizedGammaTable', wptr, Clut, 2);                
            else
                % Standard graphics: Gamma correction is already done by
                % the hardware gammatables of the graphics card (see setup
                % at top of script). We just need to apply the current CLUT
                % 'currentcols' to the color index image in the onscreen
                % window to convert it into the final true-color RGB
                % stimulus image for this frame.
                %
                % We ask the imaging pipeline to apply itto the framebuffer
                % at next 'Flip', similar to what the Bits++ path does:
                Screen('LoadNormalizedGammaTable', wptr, currentcols, 2);
            end
            
            % Show updated stimulus image (or same image with updated
            % Bits++ CLUT) at next display vertical retrace (when = []),
            % disable clearing of framebuffer after flip ( == 1), because
            % we totally overwrite the framebuffer anyway at next draw
            % cycle:
            vbl = Screen('Flip', wptr, vbl, 1);

            % End of this redraw cycle...
        end % exit from 'while' loop on mouse or keyboard keypress

        % acknowledge keypress received, setting will be recorded.
        beep;

        % Need a drawnow, so beep() works properly with all Matlav versions.
        drawnow; 

        % Abort if ESCape key pressed:
        if keyisdown && keycode(escape)
            break;
        end
        
        % wait until key is released, to avoid multiple saves of one setting
        while keyisdown || any(button)
            keyisdown = KbCheck;
            [xi, yi, button] = GetMouse(wptr); %#ok<*ASGLU>
        end
   
        % Modification for PTB demo: Only write results if writeResult = 1:
        if writeResult
            % Save results of this trial to file:
            datalist(trial,:)=[conditionset(condindex(trial),:) lum]; % append the luminosity implied by this setting to the list in datalist
            if whichluminance==1
                disp(['rlum, luminance of red phosphor relative to green =' num2str(lum)]) % show result for this setting
            elseif whichluminance==2
                disp(['blum, luminance of blue phosphor relative to green =' num2str(lum)])
            elseif whichluminance==3
                disp(['Luminance of blue phosphor relative to red =' num2str(lum)])
            end
            % just about everything to .mat file
            eval(['save ./data/' fname '.mat', ' whichluminance', ' VIEWDIST', ' subjectsname', ... 
                 ' datalist', ' screenRect', ' ADAPTRGB', ' NTrials', ' conditionset' , ' ASSUMEDREFRESHRATE',...
                 ' period', ' sectorspercycle', ' nframes', ' ncycles', ' nconds', ' nblocks', ' DateOfExperiment',...
                 ' ppd', ' rangescaler', ' refdeltalimit',  ...
                 ' fname', ' driftratehz', ' lurecontrast', ' inrad', ' outrad', ' incdeltalimit']);
            eval(['save ./data/' fname '.txt datalist /ascii']); % save datalist matrix to chosen .txt ascii file
        end

        keyisdown=0;
        
        % Reposition invisible mouse cursor to set new random offset:
        SetMouse(round(Width*rand),round(Height*rand),wptr) % random offset for next setting
        
        % End of this trial - Next trial...
    end

    %***************************************************
    %                 -- Clean up --
    % __________________________________________________
    
    if BPP
        % Load identity mapping CLUT into Bits++ / DataPixx, so it works
        % again as a normal display:
        BitsPlusPlus('LoadIdentityClut', wptr);
    end
    
    % Do a single flip to clear out display to background color:
    Screen('Flip', wptr);
    
    % Switch back to normal priority:
    Priority(0);

    % Restore pre-session gammatable into graphics card:
    RestoreCluts;

    % Close window and release all ressources:
    sca;

    % Done!
    return;
    
catch %#ok<*CTCH>
    % Cleanup in case of error and error handling:
    
    % Switch back to normal priority:
    Priority(0);

    % Load identity mapping CLUT into Bits++, so it works as a normal
    % display:
    if BPP && exist('wptr', 'var')
        BitsPlusPlus('LoadIdentityClut', wptr);
    end
    
    % Restore pre-session gammatable into graphics card:
    RestoreCluts;
    
    % Close window and release all ressources:
    sca;

    % Rethrow the error for Matlabs error reporting to kick in:
    psychrethrow(psychlasterror);
end

return;
