Hi,
I am trying to present anaglyph gratings through a circular apperture subtending 3 degrees of the visual angle on any conventional monitor. The general idea is that no matter which monitor I use, the apperture will always subtend ~3 degree of visual angle both veritcally and horizontally (from 3m).
I've tried manipulating the Driftdemos with much success. However no matter what I do the gratings are presented in a oval apperture, in a way that matches the dimensions of the screen (horizontally stretched).
Could someone guide me in the right direction? I'm sort of at a loss here. Does it have to do with the aspect ratio or am I totally off track?
My code is split into 2 m-files: visualangle.m and high_strength_AOC_anaglyphs_v4.m (run this file):
visualangle.m
function [pixelWidth pixelHeight spatialFrequencyPixelWidth spatialFrequencyPixelHeight] = visualangle (visualAngleWidth, visualAngleHeight, spatialFrequency, resolutionWidth, resolutionHeight, screenWidth, screenHeight, observerdistance)
% visualAngleWidth: horizontal visual angle of grating
% visualAngleHeight: vertical visual angle of grating
% spatialFrequency: spatial frequency
% observerdistance: distance between eyes and monitor
% resolutionWidth: horizontal resolution
% resolutionHeight: vertical resolution
% screenWidth: width of screen (needs to be adjusted for different screens)
% screenHeight: height of screen (needs to be adjusted for different screens)
% type monitor we are using
% 1 AOC: 1920x1080 509.2x286.4mm
% 2 True3Di 19 inch: 1280x1024 376.32x301.056mm
% 3 True3Di 22 inch: 1280x1024 473.76x296.1mm
monitorType = 1;
if nargin > 8 || nargin < 8
visualAngleWidth = 1.5;
visualAngleHeight = 1.5;
spatialFrequency = 8;
observerdistance = 3000;
if monitorType == 1
resolutionWidth = 1920;
resolutionHeight = 1080;
screenWidth = 509.2;
screenHeight = 286.4;
elseif monitorType == 2
resolutionWidth = 1280;
resolutionHeight = 1024;
screenWidth = 376.32;
screenHeight = 301.056;
elseif monitorType == 3
resolutionWidth = 1280;
resolutionHeight = 1024;
screenWidth = 473.76;
screenHeight = 296.1;
end
end
pixelWidth = round(tan(degtorad(visualAngleWidth/2))*2*observerdistance*resolutionWidth/screenWidth);
pixelHeight = round(tan(degtorad(visualAngleHeight/2))*2*observerdistance*resolutionHeight/screenHeight);
spatialFrequencyPixelWidth = spatialFrequency/(pixelWidth*2);
spatialFrequencyPixelHeight = spatialFrequency/(pixelHeight*2);
high_strength_AOC_anaglyphs_v4.m
function high_strength_AOC_anaglyphs_v4
%%%%%%%%%%%%%%%%%%%
% Observer parameters
% inches from screen = 118.11
%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%
% Screen parameters
% resolution (22') = 1680 x 1024
% active display size (22') = 473.76(h) x 296.1(w)
% resolution (19') = 1280 x 1024
% active display size (19') = 376.32(h) x 301.056(w)
%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%
% Stimuli paramters
% aperture size (visual angle) = 1.5 deg
% grating pixel size (22') = 278.526 (width) & 271.629 (height)
% grating pixel size (19') = 267.158 (width & height)
%%%%%%%%%%%%%%%%%%%
% high strength: s.f. = 8 cycles/deg; drift speed = 4 cycles/sec
% low strength: s.f. = 4 cycles/deg; drift speed = 0 cycles/sec
Screen('Preference', 'SkipSyncTests', 1);
try
% This script calls Psychtoolbox commands available only in OpenGL-based
% versions of the Psychtoolbox. (So far, the OS X Psychtoolbox is the
% only OpenGL-base Psychtoolbox.) The Psychtoolbox command AssertPsychOpenGL will issue
% an error message if someone tries to execute this script on a computer without
% an OpenGL Psychtoolbox
AssertOpenGL;
% Get the list of screens and choose the one with the highest screen number.
% Screen 0 is, by definition, the display with the menu bar. Often when
% two monitors are connected the one without the menu bar is used as
% the stimulus display. Chosing the display with the highest dislay number is
% a best guess about where you want the stimulus displayed.
screens=Screen('Screens');
screenNumber=0;%max(screens);
% Below values are the predefined parameters of the stimulus
visualAngleWidth = 1.5;
visualAngleHeight = 1.5;
spatialFrequency = 8;
observerdistance = 3000;
[pixelWidth pixelHeight spatialFrequencyPixelWidth spatialFrequencyPixelHeight] = visualangle;
gratingsize = 534; %534 is double of 267.158 roundedup, 534.316 is double of 267.158. By default the visible grating is 400 pixels by 400 pixels in size:
texsize=gratingsize / 2;% Define Half-Size of the grating image (i.e. radius).
texsize_horiz = pixelWidth;
texsize_vert = pixelHeight;
f=8/gratingsize; % Set at 0.015. Grating frequency in cycles/pixel: By default 0.05 cycles per pixel.
f_horiz=spatialFrequencyPixelWidth; % red
f_vert=spatialFrequencyPixelHeight; % blue
cyclespersecond = 4;% Speed of grating in cycles per second: 1 cycle per second by default.
% Find the color values which correspond to white and black: Usually
% black is always 0 and white 255, but this rule is not true if one of
% the high precision framebuffer modes is enabled via the
% PsychImaging() commmand, so we query the true values via the
% functions WhiteIndex and BlackIndex:
white=WhiteIndex(screenNumber);
black=BlackIndex(screenNumber);
% Round gray to integral number, to avoid roundoff artifacts with some
% graphics cards:
gray=round((white+black)/2);
% This makes sure that on floating point framebuffers we still get a
% well defined gray. It isn't strictly neccessary in this demo:
if gray == white
gray=white / 2;
end
% Contrast 'inc'rement range for given white and gray values:
inc=white-gray;
% Open a double buffered fullscreen window and set default background
% color to black:
%[w screenRect]=Screen('OpenWindow',screenNumber, black);
PsychImaging('PrepareConfiguration');
[w screenRect]=PsychImaging('OpenWindow', screenNumber, 0, [], [], [], 8);
%Obtain width & height of screen
[width, height] = WindowSize(w);
SetAnaglyphStereoParameters('LeftGains', w, [0.4 0.0 0.0]);
SetAnaglyphStereoParameters('RightGains', w, [0.0 0.2 0.7]);
% Initially fill left- and right-eye image buffer with black background
% color:
Screen('SelectStereoDrawBuffer', w, 0);
Screen('FillRect', w, BlackIndex(screenNumber));
Screen('SelectStereoDrawBuffer', w, 1);
Screen('FillRect', w, BlackIndex(screenNumber));
% Enable alpha blending for proper combination of the gaussian aperture
% with the drifting sine grating:
Screen('BlendFunction', w, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
% Calculate parameters of the grating:
%
% First we compute pixels per cycle, rounded up to full pixels, as we
% need this to create a grating of proper size below:
%p=ceil(1/f)
p_vert=ceil(1/f_vert)
p_horiz=ceil(1/f_horiz)
% Also need frequency in radians:
%fr=f*2*pi;
fr_horiz=f_horiz*2*pi;
fr_vert=f_vert*2*pi;
% This is the visible size of the grating. It is twice the half-width
% of the texture plus one pixel to make sure it has an odd number of
% pixels and is therefore symmetric around the center of the texture:
%visiblesize=2*texsize+1
visiblesize_horiz=2*texsize_horiz+1
visiblesize_vert=2*texsize_vert+1
% Create one single static grating image:
%
% We only need a texture with a single row of pixels(i.e. 1 pixel in height) to
% define the whole grating! If the 'srcRect' in the 'Drawtexture' call
% below is "higher" than that (i.e. visibleSize >> 1), the GPU will
% automatically replicate pixel rows. This 1 pixel height saves memory
% and memory bandwith, ie. it is potentially faster on some GPUs.
%
% However it does need 2 * texsize + p columns, i.e. the visible size
% of the grating extended by the length of 1 period (repetition) of the
% sine-wave in pixels 'p':
%x = meshgrid(-texsize:texsize + p, 1);
x_horiz = meshgrid(-texsize_horiz:texsize_horiz + p_horiz, 1);
x_vert = meshgrid(-texsize_vert:texsize_vert + p_vert, 1);
% Compute actual cosine grating:
%grating=gray + inc*cos(fr*x);
% Compute actual square-wave grating:
%grating=gray + inc*floor(cos(fr*x+1)); %gray + inc*cos(fr*x);
grating_horiz=gray + inc*floor(cos(fr_horiz*x_horiz+1)); %gray + inc*cos(fr*x);
grating_vert=gray + inc*floor(cos(fr_vert*x_vert+1)); %gray + inc*cos(fr*x);
% Store 1-D single row grating in texture:
%gratingtex=Screen('MakeTexture', w, grating);
gratingtex_horiz=Screen('MakeTexture', w, grating_horiz);
gratingtex_vert=Screen('MakeTexture', w, grating_vert);
% Create a single gaussian transparency mask and store it to a texture:
% The mask must have the same size as the visible size of the grating
% to fully cover it. Here we must define it in 2 dimensions and can't
% get easily away with one single row of pixels.
%
% We create a two-layer texture: One unused luminance channel which we
% just fill with the same color as the background color of the screen
% 'gray'. The transparency (aka alpha) channel is filled with a
% gaussian (exp()) aperture mask:
%mask=ones(2*texsize+1, 2*texsize+1, 2) * gray;
%[x,y]=meshgrid(-1*texsize:1*texsize,-1*texsize:1*texsize);
%mask(:, :, 2)=0;%white * (1 - exp(-((x/90).^2)-((y/90).^2)));
%masktex=Screen('MakeTexture', w, mask);
mask_horiz=ones(2*texsize_horiz+1, 2*texsize_horiz+1, 2) * gray;
[x,y]=meshgrid(-1*texsize_horiz:1*texsize_horiz,-1*texsize_horiz:1*texsize_horiz);
mask_horiz(:, :, 2)=0;
masktex_horiz=Screen('MakeTexture', w, mask_horiz);
mask_vert=ones(2*texsize_vert+1, 2*texsize_vert+1, 2) * gray;
[x,y]=meshgrid(-1*texsize_vert:1*texsize_vert,-1*texsize_vert:1*texsize_vert);
mask_vert(:, :, 2)=0;
masktex_vert=Screen('MakeTexture', w, mask_vert);
% Query maximum useable priorityLevel on this system:
priorityLevel=MaxPriority(w) %priority set to 1
% We don't use Priority() in order to not accidentally overload older
% machines that can't handle a redraw every 40 ms. If your machine is
% fast enough, uncomment this to get more accurate timing.
Priority(priorityLevel);
% Definition of the drawn rectangle on the screen:
% Compute it to be the visible size of the grating, centered on the
% screen:
dstRect=[0 0 visiblesize_vert visiblesize_horiz];
dstRect=CenterRect(dstRect, screenRect);
% Query duration of one monitor refresh interval:
ifi=Screen('GetFlipInterval', w);
% Translate that into the amount of seconds to wait between screen
% redraws/updates:
% waitframes = 1 means: Redraw every monitor refresh. If your GPU is
% not fast enough to do this, you can increment this to only redraw
% every n'th refresh. All animation paramters will adapt to still
% provide the proper grating. However, if you have a fine grating
% drifting at a high speed, the refresh rate must exceed that
% "effective" grating speed to avoid aliasing artifacts in time, i.e.,
% to make sure to satisfy the constraints of the sampling theorem
% (See Wikipedia: "Nyquist?Shannon sampling theorem" for a starter, if
% you don't know what this means):
waitframes = 1;
% Translate frames into seconds for screen update interval:
waitduration = waitframes * ifi;
% Recompute p, this time without the ceil() operation from above.
% Otherwise we will get wrong drift speed due to rounding errors!
p=1/f; % pixels/cycle
p_horiz=1/f_horiz;
p_vert=1/f_vert;
% Translate requested speed of the grating (in cycles per second) into
% a shift value in "pixels per frame", for given waitduration: This is
% the amount of pixels to shift our srcRect "aperture" in horizontal
% directionat each redraw:
%shiftperframe= cyclespersecond * p * waitduration;
shiftperframe_horiz= cyclespersecond * p_horiz * waitduration;
shiftperframe_vert= cyclespersecond * p_vert * waitduration;
% Perform initial Flip to sync us to the VBL and for getting an initial
% VBL-Timestamp as timing baseline for our redraw loop:
vbl=Screen('Flip', w);
%destinationRect for the 2 gratings
%dst1Rect = [(width/2) - (texsize/2), (height/2) - (texsize/2), (width/2) + (texsize/2), (height/2) + (texsize/2)];
dstRect = [(width/2) - (texsize_horiz/2), (height/2) - (texsize_vert/2), (width/2) + (texsize_horiz/2), (height/2) + (texsize_vert/2)];
dstRect_rect = [(width/2) - (texsize/2)-(width/2), (height/2) - (texsize/2)-(width/2), (width/2) + (texsize/2)+(width/2), (height/2) + (texsize/2)+(width/2)];
%Set index for drifting
i=0;
% Our framecounter, we love stats ;-)
fcount = 0;
hidecursor;
% Animationloop:
while ~KbCheck %(vbl < vblendtime)
Screen('SelectStereoDrawBuffer', w, 0);
% Shift the grating by "shiftperframe" pixels per frame:
% the mod'ulo operation makes sure that our "aperture" will snap
% back to the beginning of the grating, once the border is reached.
% Fractional values of 'xoffset' are fine here. The GPU will
% perform proper interpolation of color values in the grating
% texture image to draw a grating that corresponds as closely as
% technical possible to that fractional 'xoffset'. GPU's use
% bilinear interpolation whose accuracy depends on the GPU at hand.
% Consumer ATI hardware usually resolves 1/64 of a pixel, whereas
% consumer NVidia hardware usually resolves 1/256 of a pixel. You
% can run the script "DriftTexturePrecisionTest" to test your
% hardware...
%xoffset = mod(i*shiftperframe,p);
xoffset_horiz = mod(i*shiftperframe_horiz,p_horiz);
xoffset_vert = mod(i*shiftperframe_vert,p_vert);
i=i+1;
% Define shifted srcRect that cuts out the properly shifted rectangular
% area from the texture: We cut out the range 0 to visiblesize in
% the vertical direction although the texture is only 1 pixel in
% height! This works because the hardware will automatically
% replicate pixels in one dimension if we exceed the real borders
% of the stored texture. This allows us to save storage space here,
% as our 2-D grating is essentially only defined in 1-D:
%srcRect=[xoffset 0 xoffset + visiblesize_vert visiblesize_horiz];
srcRect_horiz=[xoffset_horiz 0 xoffset_horiz + visiblesize_vert visiblesize_horiz];
srcRect_vert=[xoffset_vert 0 xoffset_vert + visiblesize_vert visiblesize_horiz];
% Fill circular 'dstRect' region with an alpha value of 255:
%Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('SelectStereoDrawBuffer', w, 0);
Screen('Blendfunction', w, GL_ONE, GL_ZERO, [0 0 0 1]);
Screen('FillRect', w, [0 0 0 0], dstRect_rect);
Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('Blendfunction', w, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, [1 1 1 1]);
%Screen('DrawTexture', w, gratingtex, srcRect, dst1Rect, 180, [], [], [255 255 255]); %draw left vertical grating
Screen('DrawTexture', w, gratingtex_horiz, srcRect_horiz, dstRect, 180, [], [], [255 255 255]); %draw left vertical grating
Screen('SelectStereoDrawBuffer', w, 1);
Screen('Blendfunction', w, GL_ONE, GL_ZERO, [0 0 0 1]);
Screen('FillRect', w, [0 0 0 0], dstRect_rect);
Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('Blendfunction', w, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, [1 1 1 1]);
%Screen('DrawTexture', w, gratingtex, srcRect, dst1Rect, 270, [], [], [255 255 255]); %draw right horizontal grating
Screen('DrawTexture', w, gratingtex_vert, srcRect_vert, dstRect, 270, [], [], [255 255 255]); %draw right horizontal grating
%if drawmask==1
% Draw gaussian mask over grating:
%Screen('DrawTexture', w, masktex, [0 0 visiblesize visiblesize], dstRect, angle);
%end;
% Restore alpha blending mode for next draw iteration:
Screen('Blendfunction', w, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
% Flip 'waitframes' monitor refresh intervals after last redraw.
% Providing this 'when' timestamp allows for optimal timing
% precision in stimulus onset, a stable animation framerate and at
% the same time allows the built-in "skipped frames" detector to
% work optimally and report skipped frames due to hardware
% overload:
vbl = Screen('Flip', w, vbl + (waitframes - 0.5) * ifi);
fcount = fcount + 1;
% Abort demo if any key is pressed:
%if KbCheck
%break;
%end;
end;
% Restore normal priority scheduling in case something else was set
% before:
Priority(0);
showcursor;
%The same commands wich close onscreen and offscreen windows also close
%textures.
Screen('CloseAll');
catch
%this "catch" section executes in case of an error in the "try" section
%above. Importantly, it closes the onscreen window if its open.
%Screen('CloseAll');
Priority(0);
psychrethrow(psychlasterror);
end %try..catch..
% We're done!
return;
I am trying to present anaglyph gratings through a circular apperture subtending 3 degrees of the visual angle on any conventional monitor. The general idea is that no matter which monitor I use, the apperture will always subtend ~3 degree of visual angle both veritcally and horizontally (from 3m).
I've tried manipulating the Driftdemos with much success. However no matter what I do the gratings are presented in a oval apperture, in a way that matches the dimensions of the screen (horizontally stretched).
Could someone guide me in the right direction? I'm sort of at a loss here. Does it have to do with the aspect ratio or am I totally off track?
My code is split into 2 m-files: visualangle.m and high_strength_AOC_anaglyphs_v4.m (run this file):
visualangle.m
function [pixelWidth pixelHeight spatialFrequencyPixelWidth spatialFrequencyPixelHeight] = visualangle (visualAngleWidth, visualAngleHeight, spatialFrequency, resolutionWidth, resolutionHeight, screenWidth, screenHeight, observerdistance)
% visualAngleWidth: horizontal visual angle of grating
% visualAngleHeight: vertical visual angle of grating
% spatialFrequency: spatial frequency
% observerdistance: distance between eyes and monitor
% resolutionWidth: horizontal resolution
% resolutionHeight: vertical resolution
% screenWidth: width of screen (needs to be adjusted for different screens)
% screenHeight: height of screen (needs to be adjusted for different screens)
% type monitor we are using
% 1 AOC: 1920x1080 509.2x286.4mm
% 2 True3Di 19 inch: 1280x1024 376.32x301.056mm
% 3 True3Di 22 inch: 1280x1024 473.76x296.1mm
monitorType = 1;
if nargin > 8 || nargin < 8
visualAngleWidth = 1.5;
visualAngleHeight = 1.5;
spatialFrequency = 8;
observerdistance = 3000;
if monitorType == 1
resolutionWidth = 1920;
resolutionHeight = 1080;
screenWidth = 509.2;
screenHeight = 286.4;
elseif monitorType == 2
resolutionWidth = 1280;
resolutionHeight = 1024;
screenWidth = 376.32;
screenHeight = 301.056;
elseif monitorType == 3
resolutionWidth = 1280;
resolutionHeight = 1024;
screenWidth = 473.76;
screenHeight = 296.1;
end
end
pixelWidth = round(tan(degtorad(visualAngleWidth/2))*2*observerdistance*resolutionWidth/screenWidth);
pixelHeight = round(tan(degtorad(visualAngleHeight/2))*2*observerdistance*resolutionHeight/screenHeight);
spatialFrequencyPixelWidth = spatialFrequency/(pixelWidth*2);
spatialFrequencyPixelHeight = spatialFrequency/(pixelHeight*2);
high_strength_AOC_anaglyphs_v4.m
function high_strength_AOC_anaglyphs_v4
%%%%%%%%%%%%%%%%%%%
% Observer parameters
% inches from screen = 118.11
%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%
% Screen parameters
% resolution (22') = 1680 x 1024
% active display size (22') = 473.76(h) x 296.1(w)
% resolution (19') = 1280 x 1024
% active display size (19') = 376.32(h) x 301.056(w)
%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%
% Stimuli paramters
% aperture size (visual angle) = 1.5 deg
% grating pixel size (22') = 278.526 (width) & 271.629 (height)
% grating pixel size (19') = 267.158 (width & height)
%%%%%%%%%%%%%%%%%%%
% high strength: s.f. = 8 cycles/deg; drift speed = 4 cycles/sec
% low strength: s.f. = 4 cycles/deg; drift speed = 0 cycles/sec
Screen('Preference', 'SkipSyncTests', 1);
try
% This script calls Psychtoolbox commands available only in OpenGL-based
% versions of the Psychtoolbox. (So far, the OS X Psychtoolbox is the
% only OpenGL-base Psychtoolbox.) The Psychtoolbox command AssertPsychOpenGL will issue
% an error message if someone tries to execute this script on a computer without
% an OpenGL Psychtoolbox
AssertOpenGL;
% Get the list of screens and choose the one with the highest screen number.
% Screen 0 is, by definition, the display with the menu bar. Often when
% two monitors are connected the one without the menu bar is used as
% the stimulus display. Chosing the display with the highest dislay number is
% a best guess about where you want the stimulus displayed.
screens=Screen('Screens');
screenNumber=0;%max(screens);
% Below values are the predefined parameters of the stimulus
visualAngleWidth = 1.5;
visualAngleHeight = 1.5;
spatialFrequency = 8;
observerdistance = 3000;
[pixelWidth pixelHeight spatialFrequencyPixelWidth spatialFrequencyPixelHeight] = visualangle;
gratingsize = 534; %534 is double of 267.158 roundedup, 534.316 is double of 267.158. By default the visible grating is 400 pixels by 400 pixels in size:
texsize=gratingsize / 2;% Define Half-Size of the grating image (i.e. radius).
texsize_horiz = pixelWidth;
texsize_vert = pixelHeight;
f=8/gratingsize; % Set at 0.015. Grating frequency in cycles/pixel: By default 0.05 cycles per pixel.
f_horiz=spatialFrequencyPixelWidth; % red
f_vert=spatialFrequencyPixelHeight; % blue
cyclespersecond = 4;% Speed of grating in cycles per second: 1 cycle per second by default.
% Find the color values which correspond to white and black: Usually
% black is always 0 and white 255, but this rule is not true if one of
% the high precision framebuffer modes is enabled via the
% PsychImaging() commmand, so we query the true values via the
% functions WhiteIndex and BlackIndex:
white=WhiteIndex(screenNumber);
black=BlackIndex(screenNumber);
% Round gray to integral number, to avoid roundoff artifacts with some
% graphics cards:
gray=round((white+black)/2);
% This makes sure that on floating point framebuffers we still get a
% well defined gray. It isn't strictly neccessary in this demo:
if gray == white
gray=white / 2;
end
% Contrast 'inc'rement range for given white and gray values:
inc=white-gray;
% Open a double buffered fullscreen window and set default background
% color to black:
%[w screenRect]=Screen('OpenWindow',screenNumber, black);
PsychImaging('PrepareConfiguration');
[w screenRect]=PsychImaging('OpenWindow', screenNumber, 0, [], [], [], 8);
%Obtain width & height of screen
[width, height] = WindowSize(w);
SetAnaglyphStereoParameters('LeftGains', w, [0.4 0.0 0.0]);
SetAnaglyphStereoParameters('RightGains', w, [0.0 0.2 0.7]);
% Initially fill left- and right-eye image buffer with black background
% color:
Screen('SelectStereoDrawBuffer', w, 0);
Screen('FillRect', w, BlackIndex(screenNumber));
Screen('SelectStereoDrawBuffer', w, 1);
Screen('FillRect', w, BlackIndex(screenNumber));
% Enable alpha blending for proper combination of the gaussian aperture
% with the drifting sine grating:
Screen('BlendFunction', w, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
% Calculate parameters of the grating:
%
% First we compute pixels per cycle, rounded up to full pixels, as we
% need this to create a grating of proper size below:
%p=ceil(1/f)
p_vert=ceil(1/f_vert)
p_horiz=ceil(1/f_horiz)
% Also need frequency in radians:
%fr=f*2*pi;
fr_horiz=f_horiz*2*pi;
fr_vert=f_vert*2*pi;
% This is the visible size of the grating. It is twice the half-width
% of the texture plus one pixel to make sure it has an odd number of
% pixels and is therefore symmetric around the center of the texture:
%visiblesize=2*texsize+1
visiblesize_horiz=2*texsize_horiz+1
visiblesize_vert=2*texsize_vert+1
% Create one single static grating image:
%
% We only need a texture with a single row of pixels(i.e. 1 pixel in height) to
% define the whole grating! If the 'srcRect' in the 'Drawtexture' call
% below is "higher" than that (i.e. visibleSize >> 1), the GPU will
% automatically replicate pixel rows. This 1 pixel height saves memory
% and memory bandwith, ie. it is potentially faster on some GPUs.
%
% However it does need 2 * texsize + p columns, i.e. the visible size
% of the grating extended by the length of 1 period (repetition) of the
% sine-wave in pixels 'p':
%x = meshgrid(-texsize:texsize + p, 1);
x_horiz = meshgrid(-texsize_horiz:texsize_horiz + p_horiz, 1);
x_vert = meshgrid(-texsize_vert:texsize_vert + p_vert, 1);
% Compute actual cosine grating:
%grating=gray + inc*cos(fr*x);
% Compute actual square-wave grating:
%grating=gray + inc*floor(cos(fr*x+1)); %gray + inc*cos(fr*x);
grating_horiz=gray + inc*floor(cos(fr_horiz*x_horiz+1)); %gray + inc*cos(fr*x);
grating_vert=gray + inc*floor(cos(fr_vert*x_vert+1)); %gray + inc*cos(fr*x);
% Store 1-D single row grating in texture:
%gratingtex=Screen('MakeTexture', w, grating);
gratingtex_horiz=Screen('MakeTexture', w, grating_horiz);
gratingtex_vert=Screen('MakeTexture', w, grating_vert);
% Create a single gaussian transparency mask and store it to a texture:
% The mask must have the same size as the visible size of the grating
% to fully cover it. Here we must define it in 2 dimensions and can't
% get easily away with one single row of pixels.
%
% We create a two-layer texture: One unused luminance channel which we
% just fill with the same color as the background color of the screen
% 'gray'. The transparency (aka alpha) channel is filled with a
% gaussian (exp()) aperture mask:
%mask=ones(2*texsize+1, 2*texsize+1, 2) * gray;
%[x,y]=meshgrid(-1*texsize:1*texsize,-1*texsize:1*texsize);
%mask(:, :, 2)=0;%white * (1 - exp(-((x/90).^2)-((y/90).^2)));
%masktex=Screen('MakeTexture', w, mask);
mask_horiz=ones(2*texsize_horiz+1, 2*texsize_horiz+1, 2) * gray;
[x,y]=meshgrid(-1*texsize_horiz:1*texsize_horiz,-1*texsize_horiz:1*texsize_horiz);
mask_horiz(:, :, 2)=0;
masktex_horiz=Screen('MakeTexture', w, mask_horiz);
mask_vert=ones(2*texsize_vert+1, 2*texsize_vert+1, 2) * gray;
[x,y]=meshgrid(-1*texsize_vert:1*texsize_vert,-1*texsize_vert:1*texsize_vert);
mask_vert(:, :, 2)=0;
masktex_vert=Screen('MakeTexture', w, mask_vert);
% Query maximum useable priorityLevel on this system:
priorityLevel=MaxPriority(w) %priority set to 1
% We don't use Priority() in order to not accidentally overload older
% machines that can't handle a redraw every 40 ms. If your machine is
% fast enough, uncomment this to get more accurate timing.
Priority(priorityLevel);
% Definition of the drawn rectangle on the screen:
% Compute it to be the visible size of the grating, centered on the
% screen:
dstRect=[0 0 visiblesize_vert visiblesize_horiz];
dstRect=CenterRect(dstRect, screenRect);
% Query duration of one monitor refresh interval:
ifi=Screen('GetFlipInterval', w);
% Translate that into the amount of seconds to wait between screen
% redraws/updates:
% waitframes = 1 means: Redraw every monitor refresh. If your GPU is
% not fast enough to do this, you can increment this to only redraw
% every n'th refresh. All animation paramters will adapt to still
% provide the proper grating. However, if you have a fine grating
% drifting at a high speed, the refresh rate must exceed that
% "effective" grating speed to avoid aliasing artifacts in time, i.e.,
% to make sure to satisfy the constraints of the sampling theorem
% (See Wikipedia: "Nyquist?Shannon sampling theorem" for a starter, if
% you don't know what this means):
waitframes = 1;
% Translate frames into seconds for screen update interval:
waitduration = waitframes * ifi;
% Recompute p, this time without the ceil() operation from above.
% Otherwise we will get wrong drift speed due to rounding errors!
p=1/f; % pixels/cycle
p_horiz=1/f_horiz;
p_vert=1/f_vert;
% Translate requested speed of the grating (in cycles per second) into
% a shift value in "pixels per frame", for given waitduration: This is
% the amount of pixels to shift our srcRect "aperture" in horizontal
% directionat each redraw:
%shiftperframe= cyclespersecond * p * waitduration;
shiftperframe_horiz= cyclespersecond * p_horiz * waitduration;
shiftperframe_vert= cyclespersecond * p_vert * waitduration;
% Perform initial Flip to sync us to the VBL and for getting an initial
% VBL-Timestamp as timing baseline for our redraw loop:
vbl=Screen('Flip', w);
%destinationRect for the 2 gratings
%dst1Rect = [(width/2) - (texsize/2), (height/2) - (texsize/2), (width/2) + (texsize/2), (height/2) + (texsize/2)];
dstRect = [(width/2) - (texsize_horiz/2), (height/2) - (texsize_vert/2), (width/2) + (texsize_horiz/2), (height/2) + (texsize_vert/2)];
dstRect_rect = [(width/2) - (texsize/2)-(width/2), (height/2) - (texsize/2)-(width/2), (width/2) + (texsize/2)+(width/2), (height/2) + (texsize/2)+(width/2)];
%Set index for drifting
i=0;
% Our framecounter, we love stats ;-)
fcount = 0;
hidecursor;
% Animationloop:
while ~KbCheck %(vbl < vblendtime)
Screen('SelectStereoDrawBuffer', w, 0);
% Shift the grating by "shiftperframe" pixels per frame:
% the mod'ulo operation makes sure that our "aperture" will snap
% back to the beginning of the grating, once the border is reached.
% Fractional values of 'xoffset' are fine here. The GPU will
% perform proper interpolation of color values in the grating
% texture image to draw a grating that corresponds as closely as
% technical possible to that fractional 'xoffset'. GPU's use
% bilinear interpolation whose accuracy depends on the GPU at hand.
% Consumer ATI hardware usually resolves 1/64 of a pixel, whereas
% consumer NVidia hardware usually resolves 1/256 of a pixel. You
% can run the script "DriftTexturePrecisionTest" to test your
% hardware...
%xoffset = mod(i*shiftperframe,p);
xoffset_horiz = mod(i*shiftperframe_horiz,p_horiz);
xoffset_vert = mod(i*shiftperframe_vert,p_vert);
i=i+1;
% Define shifted srcRect that cuts out the properly shifted rectangular
% area from the texture: We cut out the range 0 to visiblesize in
% the vertical direction although the texture is only 1 pixel in
% height! This works because the hardware will automatically
% replicate pixels in one dimension if we exceed the real borders
% of the stored texture. This allows us to save storage space here,
% as our 2-D grating is essentially only defined in 1-D:
%srcRect=[xoffset 0 xoffset + visiblesize_vert visiblesize_horiz];
srcRect_horiz=[xoffset_horiz 0 xoffset_horiz + visiblesize_vert visiblesize_horiz];
srcRect_vert=[xoffset_vert 0 xoffset_vert + visiblesize_vert visiblesize_horiz];
% Fill circular 'dstRect' region with an alpha value of 255:
%Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('SelectStereoDrawBuffer', w, 0);
Screen('Blendfunction', w, GL_ONE, GL_ZERO, [0 0 0 1]);
Screen('FillRect', w, [0 0 0 0], dstRect_rect);
Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('Blendfunction', w, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, [1 1 1 1]);
%Screen('DrawTexture', w, gratingtex, srcRect, dst1Rect, 180, [], [], [255 255 255]); %draw left vertical grating
Screen('DrawTexture', w, gratingtex_horiz, srcRect_horiz, dstRect, 180, [], [], [255 255 255]); %draw left vertical grating
Screen('SelectStereoDrawBuffer', w, 1);
Screen('Blendfunction', w, GL_ONE, GL_ZERO, [0 0 0 1]);
Screen('FillRect', w, [0 0 0 0], dstRect_rect);
Screen('FillOval', w, [0 0 0 255], dstRect);
Screen('Blendfunction', w, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, [1 1 1 1]);
%Screen('DrawTexture', w, gratingtex, srcRect, dst1Rect, 270, [], [], [255 255 255]); %draw right horizontal grating
Screen('DrawTexture', w, gratingtex_vert, srcRect_vert, dstRect, 270, [], [], [255 255 255]); %draw right horizontal grating
%if drawmask==1
% Draw gaussian mask over grating:
%Screen('DrawTexture', w, masktex, [0 0 visiblesize visiblesize], dstRect, angle);
%end;
% Restore alpha blending mode for next draw iteration:
Screen('Blendfunction', w, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
% Flip 'waitframes' monitor refresh intervals after last redraw.
% Providing this 'when' timestamp allows for optimal timing
% precision in stimulus onset, a stable animation framerate and at
% the same time allows the built-in "skipped frames" detector to
% work optimally and report skipped frames due to hardware
% overload:
vbl = Screen('Flip', w, vbl + (waitframes - 0.5) * ifi);
fcount = fcount + 1;
% Abort demo if any key is pressed:
%if KbCheck
%break;
%end;
end;
% Restore normal priority scheduling in case something else was set
% before:
Priority(0);
showcursor;
%The same commands wich close onscreen and offscreen windows also close
%textures.
Screen('CloseAll');
catch
%this "catch" section executes in case of an error in the "try" section
%above. Importantly, it closes the onscreen window if its open.
%Screen('CloseAll');
Priority(0);
psychrethrow(psychlasterror);
end %try..catch..
% We're done!
return;