function GratingDemo
% GratingDemo
%
% Displays a stationary grating.  See also DriftDemo, DriftDemo2, DriftDemo3 and DriftWaitDemo.

% ---------- Program History ----------

% 07/01/1999 dgp Added arbitrary orientation.
% 12/10/2001 awi Added font conditional.
% 02/21/2002 dgp Mentioned DriftDemo.
% 04/03/2002 awi Merged OS9 and Win versions, which had fallen out of sync. 
% 04/13/2002 dgp Used Arial, eliminating need for conditional.
% 07/15/2003 dgp Added comments explaining f and lambda.
% 08/16/2006 rhh Added user-friendly parameters, such as tiltInDegrees,
%                pixelsPerPeriod, periodsCoveredByOneStandardDeviation and widthOfGrid.
% 08/18/2006 rhh Expanded comments and created comment sections.
% 10/04/2006 dhb Minimize warnings.
% 10/11/2006 dhb Use maximum available screen.
% 10/14/2006 dhb Save and restore altered prefs, more extensive comments for them
% 07/12/2006 prf Changed method of rotating the grating

% ---------- Parameter Setup ----------
% Initializes the program's parameters.

% Prevents MATLAB from reprinting the source code when the program runs.
echo off

% *** To rotate the grating, set tiltInDegrees to a new value.
tiltInDegrees = 7; % The tilt of the grating in degrees.
tiltInRadians = tiltInDegrees * pi / 180; % The tilt of the grating in radians.

% *** To lengthen the period of the grating, increase pixelsPerPeriod.
pixelsPerPeriod = 33; % How many pixels will each period/cycle occupy?
spatialFrequency = 1 / pixelsPerPeriod; % How many periods/cycles are there in a pixel?
radiansPerPixel = spatialFrequency * (2 * pi); % = (periods per pixel) * (2 pi radians per period)

% *** To enlarge the gaussian mask, increase periodsCoveredByOneStandardDeviation.
% The parameter "periodsCoveredByOneStandardDeviation" is approximately
% equal to
% the number of periods/cycles covered by one standard deviation of the radius of
% the gaussian mask.
periodsCoveredByOneStandardDeviation = 1.5;
% The parameter "gaussianSpaceConstant" is approximately equal to the
% number of pixels covered by one standard deviation of the radius of
% the gaussian mask.
gaussianSpaceConstant = periodsCoveredByOneStandardDeviation  * pixelsPerPeriod;

% *** If the grating is clipped on the sides, increase widthOfGrid.
widthOfGrid = 400;
halfWidthOfGrid = widthOfGrid / 2;
widthArray = (-halfWidthOfGrid) : halfWidthOfGrid;  % widthArray is used in creating the meshgrid.

% For an explanation of the try-catch block, see the section "Error Handling"
% at the end of this document.
try
  % ---------- Window Setup ----------
  % Opens a window.

  % Screen is able to do a lot of configuration and performance checks on
  % open, and will print out a fair amount of detailed information when
  % it does.  These commands supress that checking behavior and just let
  % the demo go straight into action.  See ScreenTest for an example of
  % how to do detailed checking.
  oldVisualDebugLevel = Screen('Preference', 'VisualDebugLevel', 3);
  oldSupressAllWarnings = Screen('Preference', 'SuppressAllWarnings', 1);

  % Find out how many screens and use largest screen number.
  whichScreen = max(Screen('Screens'));

  % Opens a graphics window on the main monitor (screen 0).  If you have
  % multiple monitors connected to your computer, then you can specify
  % a different monitor by supplying a different number in the second
  % argument to OpenWindow, e.g. Screen('OpenWindow', 2).
  window = Screen('OpenWindow', whichScreen);

  % Hides the mouse cursor
  HideCursor;

  % ---------- Color Setup ----------
  % Gets color values.

  % Retrieves color codes for black and white and gray.
  black = BlackIndex(window);  % Retrieves the CLUT color code for black.
  white = WhiteIndex(window);  % Retrieves the CLUT color code for white.
  gray = (black + white) / 2;  % Computes the CLUT color code for gray.
  if round(gray)==white
    gray=black;
  end

  % Taking the absolute value of the difference between white and gray will
  % help keep the grating consistent regardless of whether the CLUT color
  % code for white is less or greater than the CLUT color code for black.
  absoluteDifferenceBetweenWhiteAndGray = abs(white - gray);

  % ---------- Image Setup ----------
  % Stores the image in a two dimensional matrix.

  % Creates a two-dimensional square grid.  For each element i = i(x0, y0) of
  % the grid, x = x(x0, y0) corresponds to the x-coordinate of element "i"
  % and y = y(x0, y0) corresponds to the y-coordinate of element "i"
  [x y] = meshgrid(widthArray, widthArray);

  % Replaced original method of changing the orientation of the grating
  % (gradient = y - tan(tiltInRadians) .* x) with sine and cosine (adapted from DriftDemo). 
  % Use of tangent was breakable because it is undefined for theta near pi/2 and the period
  % of the grating changed with change in theta.  

  a=cos(tiltInRadians)*radiansPerPixel;
  b=sin(tiltInRadians)*radiansPerPixel;

  % Converts meshgrid into a sinusoidal grating, where elements
  % along a line with angle theta have the same value and where the
  % period of the sinusoid is equal to "pixelsPerPeriod" pixels.
  % Note that each entry of gratingMatrix varies between minus one and
  % one; -1 <= gratingMatrix(x0, y0)  <= 1
  gratingMatrix = sin(a*x+b*y);

  % Creates a circular Gaussian mask centered at the origin, where the number
  % of pixels covered by one standard deviation of the radius is
  % approximately equal to "gaussianSpaceConstant."
  % For more information on circular and elliptical Gaussian distributions, please see
  % http://mathworld.wolfram.com/GaussianFunction.html
  % Note that since each entry of circularGaussianMaskMatrix is "e"
  % raised to a negative exponent, each entry of
  % circularGaussianMaskMatrix is one over "e" raised to a positive
  % exponent, which is always between zero and one;
  % 0 < circularGaussianMaskMatrix(x0, y0) <= 1
  circularGaussianMaskMatrix = exp(-((x .^ 2) + (y .^ 2)) / (gaussianSpaceConstant ^ 2));

  % Since each entry of gratingMatrix varies between minus one and one and each entry of
  % circularGaussianMaskMatrix vary between zero and one, each entry of
  % imageMatrix varies between minus one and one.
  % -1 <= imageMatrix(x0, y0) <= 1
  imageMatrix = gratingMatrix .* circularGaussianMaskMatrix;

  % Since each entry of imageMatrix is a fraction between minus one and
  % one, multiplying imageMatrix by absoluteDifferenceBetweenWhiteAndGray
  % and adding the gray CLUT color code baseline
  % converts each entry of imageMatrix into a shade of gray:
  % if an entry of "m" is minus one, then the corresponding pixel is black;
  % if an entry of "m" is zero, then the corresponding pixel is gray;
  % if an entry of "m" is one, then the corresponding pixel is white.
  grayscaleImageMatrix = gray + absoluteDifferenceBetweenWhiteAndGray * imageMatrix;

  % ---------- Image Display ---------- 
  % Displays the image in the window.

  % Colors the entire window gray.
  Screen('FillRect', window, gray);

  % Writes the image to the window.
  Screen('PutImage', window, grayscaleImageMatrix);

  % Writes text to the window.
  currentTextRow = 0;
  Screen('DrawText', window, sprintf('black = %d, white = %d', black, white), 0, currentTextRow, black);
  currentTextRow = currentTextRow + 20;
  Screen('DrawText', window, 'Press any key to exit.', 0, currentTextRow, black);

  % Updates the screen to reflect our changes to the window.
  Screen('Flip', window);

  % Waits for the user to press a key.
  KbWait;

  % ---------- Window Cleanup ---------- 

  % Closes all windows.
  sca;

  % Restores the mouse cursor.
  ShowCursor;

  % Restore preferences
  Screen('Preference', 'VisualDebugLevel', oldVisualDebugLevel);
  Screen('Preference', 'SuppressAllWarnings', oldSupressAllWarnings);
catch
  % ---------- Error Handling ---------- 
  % If there is an error in our code, we will end up here.

  % The try-catch block ensures that Screen will restore the display and return us
  % to the MATLAB prompt even if there is an error in our code.  Without this try-catch
  % block, Screen could still have control of the display when MATLAB throws an error, in
  % which case the user will not see the MATLAB prompt.
  sca;

  % Restores the mouse cursor.
  ShowCursor;

  % Restore preferences
  Screen('Preference', 'VisualDebugLevel', oldVisualDebugLevel);
  Screen('Preference', 'SuppressAllWarnings', oldSupressAllWarnings);

  % We throw the error again so the user sees the error description.
  psychrethrow(psychlasterror);
end
