Psychtoolbox Crashing Unpredictably

PsychtoolboxVersion Output:
‘3.0.18 – Flavor: Manual Install, 26-Oct-2021 18:56:38.
I am running this on Windows 10.

I am using Psychtoolbox for a method of constant stimuli task that involves displaying images. A floating adapter image is displayed first for 30 seconds, and then the participant goes through 90 trials. Each trial involves the following: a 2 second blank period showing a black screen and small fixation cross. Then an image (the fixation cross is constantly present) is shown for 0.2 seconds. Then the image disappears, the participant responds with a keypress, and finally, there is a 5 second adaptation period with the same floating adapter image each time. Once the 90th trial is finished, an exit screen is shown, and when the participant presses any key, the Psychtoolbox window closes.
Sometimes the experiment works fine, with all 90 trials completed with no issue. Sometimes, though, unpredictably, MATLAB or Psychtoolbox seems to freeze on one of the last trials. Sometimes it’s the 88th trial, sometimes the 89th, sometimes the 87th; there is no pattern I can discern. Anyway, what happens is that after the participant responds with a keypress, the floating adapter image never appears. The screen just remains black, with the fixation cross in the middle of the screen. The only way to exit that I’ve found is by hitting the “Window” key to bring up the Start Menu and then attempting to close the Psychtoolbox window from the Taskbar. MATLAB itself then quits and has to be restarted. Because of that, there’s no error message left behind.
This problem happens both on my laptop running Windows 10, and a desktop computer running Windows 10.
I saw a very similar issue on this forum that was solved by ensuring that each texture was cleared before a new one was created. I modified my code accordingly, and the experiment actually ran successfully three times in a row (this was remarkable), but then I tried running it a fourth time, and the experiment froze on the 87th trial.
Here is the main function that runs the experiment, along with a couple of smaller functions I wrote that may possibly be relevant.

function drawImage

arguments
image {mustBeNumeric};
location (1,4) double;
window(1,1) double;
end

Screen(‘Close’);
image_texture = Screen(‘MakeTexture’,window,image);
Screen(‘DrawTexture’,window,image_texture,[],location,0);
end

function drawFixationCross

arguments
window (1,1) double;
window_rect (1,4) double;
color double;
arm_length (1,1) double;
line_width (1,1) double;
end

[x_center,y_center] = RectCenter(window_rect);

x_coordinates = [-arm_length arm_length 0 0];
y_coordinates = [0 0 -arm_length arm_length];
coordinates = [x_coordinates;y_coordinates];

Screen(‘DrawLines’,window,coordinates,line_width,color,…
[x_center y_center],0);
end

function RunExperiment

arguments
participant (1,1) string;
object_number (1,1) double;
adapter_view (1,1) string;
end

%% Sync tests

%{
NOTE: The third argument of Screen(‘Preference’,‘SkipSyncTests’,[]) must
be either 0 or 1 (0 = don’t skip; 1 = skip).

If possible, this argument should be set to 0, since doing the sync tests
will ensure that stimulus presentation timing is super accurate and
precise.

However, sometimes sync tests fail on certain computers, and if they
fail, Psychtoolbox can’t run at all (the function will terminate
immediately and an error message titled “PTB - Error: Synchronization
Failure!” will be displayed). If this happens, and you’re in a situation
where extremely accurate and precise timing is not required (experimenting
with or revising the code, debugging, etc.), then the argument can be set
to 1.
%}
Screen(‘Preference’,‘SkipSyncTests’,0);

%% Load the experiment stimuli and other variables from file

load Variables.mat; %#ok
clear tutorial_text;

%% Extra input argument validation

if participant == “”
error(‘participant cannot be an empty string!’);
elseif object_number < 1 || object_number > length(objects) - 1
error(‘object_number must be a number from 1 to %d!’,…
length(objects) - 1);
elseif adapter_view == “”
error(‘adapter_view cannot be an empty string!’);
end

%% Set the raw data filename

timestamp = sprintf(’%04d-%02d-%02d_%02d-%02d-%02d’,round(clock));
raw_data_filename = sprintf(‘RAW_%s_obj-%d_%s_%s.mat’,…
participant,object_number,adapter_view,timestamp);

%% Get the object’s adapter images and test images

adapter_images = objects(object_number).adapters;
test_images = objects(object_number).test;
if isempty(adapter_images)
error(‘No adapter images found for this object!’);
elseif isempty(test_images)
error(‘No test images found for this object!’);
end
clear objects;

%% Get the size (in pixels) of the images

%{
NOTE: The size function’s output has the dimensions in the wrong order,
so the dimensions have to be switched around.
%}
image_size = size(adapter_images{1});
image_size = [image_size(2) image_size(1)];

%% Calculate the number of times each test image must be presented

max_image_presentations = trial_number / length(test_images);

%% Preallocate arrays to track test image presentations and hold data

tracker = zeros(1,length(test_images));
test_view_data = strings(1,trial_number);
response_data = zeros(1,trial_number);
reaction_time_data = zeros(1,trial_number);

%% Set up Psychtoolbox to display images and text

%{
NOTE: This also involves establishing the boundaries of the floating area
that the adapter and test images will be restricted to.
%}
PsychDefaultSetup(2);
screens = Screen(‘Screens’);
screen_number = min(screens);
black = BlackIndex(screen_number);
white = WhiteIndex(screen_number);
[window,window_rect] = PsychImaging(‘OpenWindow’,screen_number,black);
try
image_rect = [0 0 image_size(1) image_size(2)];
[x_range,y_range] = calculateLocationRange(window_rect,…
floating_area_size,image_size);
Screen(‘TextSize’,window,text_size);
Screen(‘TextFont’,window,‘Courier’);
HideCursor(window);
clear screens screen_number floating_area_size image_size text_size black;

%% Display the start screen

%{
NOTE: 65 and 186 are the key codes for the ‘a’ and ‘;’ keys. These key
codes must be modified if and when the input method is changed.
%}
DrawFormattedText(window,start_screen_text,‘center’,‘center’,white);
Screen(‘Flip’,window);
KbStrokeWait;
clear start_screen_text;
RestrictKeysForKbCheck([65 186]);

%% Initial adaptation period

adapter_index = strcmp(adapter_view,adapter_views);
adapter = adapter_images{adapter_index};
clear adapter_views adapter_images adapter_index;
[location,current_edge] = chooseRandomLocation(image_rect,x_range,y_range);
[target_edge,target_location] = chooseTargetEdge(location,x_range,…
y_range,current_edge);
drawImage(adapter,location,window);
drawFixationCross(window,window_rect,fixation_cross_color,arm_length,…
line_width);
Screen(‘Flip’,window);
seconds_passed = 0;
tic;
while seconds_passed < initial_adaptation_period
[location,arrived] = moveTowardLocation(location,target_location,1);
if arrived
[target_edge,target_location] = chooseTargetEdge(location,…
x_range,y_range,target_edge);
end
drawImage(adapter,location,window);
drawFixationCross(window,window_rect,fixation_cross_color,…
arm_length,line_width);
Screen(‘Flip’,window);
WaitSecs(time_to_travel_one_pixel);
seconds_passed = toc;
end

for i = 1:trial_number
%% Blank period

drawFixationCross(window,window_rect,fixation_cross_color,...
    arm_length,line_width);
Screen('Flip',window);
WaitSecs(blank_period);

%% Test period

%{
NOTE: Keeping track of the image that was presented during the previous
trial (previous_test_index) is necessary to prevent the same image
being displayed twice in succession.
%}
image_chosen = false;
while image_chosen == false
    test_index = randi(length(test_images));
    if i == 1
        image_chosen = true;
    elseif test_index ~= previous_test_index &&...
            tracker(test_index) < max_image_presentations
        image_chosen = true;
    end
end
previous_test_index = test_index;
tracker(test_index) = tracker(test_index) + 1;
test_view_data(i) = test_views(test_index);
current_test = test_images{test_index};
[location,~] = chooseRandomLocation(image_rect,x_range,y_range);
drawImage(current_test,location,window);
drawFixationCross(window,window_rect,fixation_cross_color,...
    arm_length,line_width);
Screen('Flip',window);
WaitSecs(test_period);

%% Response period

%{
NOTE: I inserted a pause of 0.2 seconds because without it, the top_up
adaptation period would start the instant I made my response, and the
speed was actually a little unnerving.
%}
drawFixationCross(window,window_rect,fixation_cross_color,...
    arm_length,line_width);
Screen('Flip',window);
response_made = false; 
start_time = GetSecs;
while response_made == false
    [key_is_pressed,~,key_code] = KbCheck;
    if key_is_pressed
        response_made = true;
    end
end
end_time = GetSecs;
WaitSecs(0.2);
response_data(i) = find(key_code);
reaction_time_data(i) = end_time - start_time;
clc;

%% Save the raw data

saveData(raw_data_filename,participant,object_number,adapter_view,...
    timestamp,test_view_data,response_data,reaction_time_data);

%% Top-up adaptation period

if i == trial_number
    break;
else
    [location,current_edge] = chooseRandomLocation(image_rect,...
        x_range,y_range);
    [target_edge,target_location] = chooseTargetEdge(location,...
        x_range,y_range,current_edge);
    drawImage(adapter,location,window);
    drawFixationCross(window,window_rect,fixation_cross_color,...
        arm_length,line_width);
    Screen('Flip',window);
    seconds_passed = 0;
    tic;
    while seconds_passed < top_up_adaptation_period
        [location,arrived] = moveTowardLocation(location,...
            target_location,1);
        if arrived
            [target_edge,target_location] = ...
                chooseTargetEdge(location,x_range,y_range,target_edge);
        end
        drawImage(adapter,location,window);
        drawFixationCross(window,window_rect,fixation_cross_color,...
            arm_length,line_width);
        Screen('Flip',window);
        WaitSecs(time_to_travel_one_pixel);
        seconds_passed = toc;
    end
end
Screen('Close');

end

%% Display the exit screen

RestrictKeysForKbCheck([]);
DrawFormattedText(window,exit_screen_text,‘center’,‘center’,white);
Screen(‘Flip’,window);
KbStrokeWait;
Screen(‘Close’);
sca;
sca;

%% Save the raw data

saveData(raw_data_filename,participant,object_number,adapter_view,…
timestamp,test_view_data,response_data,reaction_time_data);
fprintf(‘Filename: %s\n’,raw_data_filename);
clear;

%% Handle errors

catch the_error
save([‘ERROR_’ raw_data_filename])
sca;
sca;
rethrow(the_error);
end
end

Hi,

the main reason for this failure you’ve got already covered with closing textures, so that’s not it - good job with that! Skimming the code does not show obvious red flags.

Apart from that your approach to timing is really broken, assuming you care about timing as some of the code comments suggest. Use WaitSecs and GetSecs for general timing, tic and toc should never show up in a well written script. More importantly use the timestamps and timing mechanisms of Screen(‘Flip’), not WaitSecs etc. like you do now! Our intro pdf, tutorials and demos show how to do timing competently.

Wrt. to the actual cause of freezes, any further advice from my side will require buying priority support help PsychPaidSupportAndServices. Ofc. if the problem should be caused by graphics driver or operating system bugs, the by far best course of action is upgrading to Linux, as wrt. OS bugs usually no fix is possible and only some shady workarounds if you are lucky. If you go the route of priority support, provide details about your OS version, graphics card model etc, hardware configuration etc. to make sure paid time is spent efficiently.

-mario