Hello everyone,
I am currently using MATLAB’s Psychtoolbox to conduct a visual stimulation experiment, aiming to achieve CFF (Critical Flicker Fusion) presentation. The refresh rate of the monitor I am using is 240Hz. However, during the actual operation, I found that the flicker effect is very uneven and appears unstable.
Here is a brief description of my experimental setup:
- I am generating flicker stimuli using Psychtoolbox in the MATLAB environment.
- I use the
Screen('Flip')
function to control the screen flip timing and adjust the flicker frame interval withwaitframes
. - The refresh rate of the monitor is 240Hz, and I want to achieve a flicker frequency of at least 30Hz(requiring the monitor’s refresh rate is 60Hz), to ensure stable high-frequency visual stimulation and achieve CFF.
- Psychtoolbox version: 3.0.19 - Flavor: Manual Install
- MATLAB version: 2023b.
*Window11
However, when testing at 240Hz refresh rate, the flicker stimuli are uneven, with noticeable jumps or discontinuities. I have tried the following methods, but the issue persists:
- Adjust
waitframes
Value: I adjustedwaitframes
according to different flicker frequencies, but jitter still occurs. - Check Refresh Rate and Timestamps: I controlled the flip timing using
ifi
andvbl
timestamps to reduce errors. - Ensure Priority Level and Drawing Finished: I set the maximum priority level with
MaxPriority(window)
and calledScreen('DrawingFinished', window)
to ensure drawing operations complete before flipping.
I suspect this might be related to the synchronization mechanism of high refresh rate monitors or due to insufficient synchronization between MATLAB and the hardware.
I would like to ask if anyone has experience achieving high flicker frequencies (e.g., 60Hz or above) on high refresh rate monitors to achieve CFF? Are there any optimization suggestions for MATLAB and Psychtoolbox in a Windows environment?
Any help or suggestions would be greatly appreciated!
%% Initialize Psychtoolbox
clear
Screen('Preference', 'SkipSyncTests', 1); % Skip sync tests for debugging
PsychDefaultSetup(2);
KbName('UnifyKeyNames'); % Unify key names
% Specify screen number
screenNumber = 1; % Use the first display
% Calculate color values
white = WhiteIndex(screenNumber);
black = BlackIndex(screenNumber);
grey = white / 2;
% Open a window and set it to grey background
[window, windowRect] = PsychImaging('OpenWindow', screenNumber, grey);
% Define screen center position
[xCenter, yCenter] = RectCenter(windowRect);
% Set square size and position
squareSize = 300; % Size of the square
squareRect = CenterRectOnPointd([0, 0, squareSize, squareSize], xCenter, yCenter);
% Define initial colors and frame rate parameters
red = [0.5, 0, 0];
green = [0, 0.5, 0];
waitframes = 5; % Initial wait frames
ifi = Screen('GetFlipInterval', window);
% Initialize control variable
currentFrame = 1; % Used to alternate between red and green squares
% Record initial flip time
vbl = Screen('Flip', window);
[texture1, texture2, GrayImage] = processImage(window, 'house.png', red, green);
scaleFactor = 1.0; % Initial scaling factor
imageRect = CenterRectOnPointd([0, 0, size(GrayImage, 2) * scaleFactor, size(GrayImage, 1) * scaleFactor], xCenter, yCenter);
lastKeyTime = GetSecs; % Initialize last key press time
responseInterval = 0.1; % Keyboard response interval (seconds)
topPriorityLevel = MaxPriority(window);
Priority(topPriorityLevel);
% Enter main loop
while true
% Check keyboard input
[keyIsDown, ~, keyCode] = KbCheck;
currentTime = GetSecs;
if currentTime - lastKeyTime > responseInterval
[keyIsDown, secs, keyCode] = KbCheck;
if keyIsDown
lastKeyTime = secs; % Update last key press time
if keyCode(KbName('DownArrow')) % Press down arrow to increase waitframes
waitframes = waitframes + 1;
elseif keyCode(KbName('UpArrow')) % Press up arrow to decrease waitframes
waitframes = max(1, waitframes - 1); % Ensure waitframes is at least 1
elseif keyCode(KbName('ESCAPE')) % Press ESC to exit loop
break;
end
end
end
% Calculate flicker frequency
flashFrequency = Screen('NominalFrameRate', screenNumber) / (2 * waitframes);
% Select color to draw based on currentFrame variable
if currentFrame == 1
Screen('DrawTexture', window, texture1, [], imageRect);
currentFrame = 0;
else
Screen('DrawTexture', window, texture2, [], imageRect);
currentFrame = 1;
end
% Display current waitframes and flicker frequency on the screen
textString = sprintf('Waitframes: %d\nFlash Frequency: %.2f Hz', waitframes, flashFrequency);
Screen('DrawText', window, textString, 50, 50, white);
% Define fixation point color as blue
fixationColor = [0, 0, 1]; % RGB color [0, 0, 1] corresponds to blue
% Define fixation point size
fixationSize = 15; % Radius of 15 pixels
% Define fixation point position (center of the screen)
fixationRect = CenterRectOnPointd([0 0 fixationSize fixationSize], xCenter, yCenter);
% Draw blue circular fixation point
Screen('FillOval', window, fixationColor, fixationRect);
Screen('DrawingFinished', window);
% Flip screen, using waitframes to control flip timing
vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);
end
% Close window
sca;
function [texture1, texture2, GrayImage] = processImage(window, ImagePath, red, green, blurRadius)
% Load and process image
Image = imread(ImagePath); % Load image
% Check the type of image
if size(Image, 3) == 1
% Image is grayscale
GrayImage = im2double(Image);
elseif size(Image, 3) == 3
% Image is RGB
GrayImage = im2double(rgb2gray(Image));
else
% If the image is indexed, convert it to RGB
[X, map] = imread(ImagePath);
Image = ind2rgb(X, map);
GrayImage = im2double(rgb2gray(Image));
end
% Resize the image to 300x300 pixels
targetSize = [300, 300];
GrayImage = imresize(GrayImage, targetSize); % Resize grayscale image
% Create mask
lineMask = GrayImage < 0.5; % Assume lines are darker than background, threshold 0.5 can be adjusted
lineMask = double(lineMask); % Keep range in [0, 1]
% Apply Gaussian blur to lineMask if blurRadius is provided
if nargin == 5 && ~isempty(blurRadius) && blurRadius > 0
% Create Gaussian filter using fspecial
h = fspecial('gaussian', [5 5], blurRadius);
lineMask = imfilter(lineMask, h, 'replicate');
end
% Expand lineMask to 3 channels to match color matrix dimensions
lineMask = repmat(lineMask, [1, 1, 3]);
% Define colors (red lines and green background)
lineColor1 = reshape(red, [1, 1, 3]); % Red lines
bgColor1 = reshape(green, [1, 1, 3]); % Green background
% Generate image with red lines and green background
colorImage1 = lineMask .* lineColor1 + (1 - lineMask) .* bgColor1;
% Define colors (green lines and red background)
lineColor2 = reshape(green, [1, 1, 3]); % Green lines
bgColor2 = reshape(red, [1, 1, 3]); % Red background
% Generate image with green lines and red background
colorImage2 = lineMask .* lineColor2 + (1 - lineMask) .* bgColor2;
% Create textures
texture1 = Screen('MakeTexture', window, colorImage1);
texture2 = Screen('MakeTexture', window, colorImage2);
end
If anyone has any suggestions or experiences, especially regarding how to achieve precise high flicker frequency visual stimulation on high refresh rate monitors to achieve CFF, I would greatly appreciate it!