Request for Priority Support: Psychtoolbox Crashing Unpredictably

FX2MYQ66-202221011134:f1f8bebaf73ec8b7e071812a60bfd2461604a2b8e900cf85b7430f714ba5d65f

PsychtoolboxVersion Output:
‘3.0.18 – Flavor: Manual Install, 26-Oct-2021 18:56:38.

The desktop computer that I am using has a NVIDIA GeForce GT 710 GPU, an Intel® Core™ i7-8700 CPU @ 3.20 GHz, and 16 GB of memory. Its operating system is Windows 10 Enterprise, Version 20H2, OS build 19042.1415. It just has a single monitor.

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, and the probability that this error will happen seems to be around 50%.

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 still 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.

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

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

Ok, so there’s a couple of things to try:

Before running your script, type in Matlab:
diary on; echo on;

This will create log file “diary.txt” in the current working directory which will record all Matlab command window output (diary command) and the echo command will print all statements in your script as it executes, so you should find out what the last command was when things went sideways.

You can also execute PsychDebugWindowConfiguration to make the stimulus window half-transparent, so you can see and interact via mouse with Matlab and other windows in the background to see what’s going on. Maybe launch the Windows task manager, so you can observe the graph of memory consumption (i think in the “Performance” tab?) and cpu load to see if memory consumption or processor load is raising drastically during script execution. I kind of doubt it, at least your script has nothing wrong in it.

The most suspicious thing i see in your code is the way to select test_index without repetition. The way it is done, its runtime would increase with every trial as it has a harder time each trial to find a test_index that meets the conditions. So i wonder if around 90 trials it just takes very long and you think your script hangs?

-mario

[So far 27 Minutes of work time used up].

And to answer my suspicion: See
https://gist.github.com/kleinerm/084de715c21c1154258a800484522a51

for my take on your sampling problem. The if 1 selects my approach, changing to if 0 selects your method.

Indeed, my method completes reliably, whereas yours sometimes completes, sometimes gets stuck in an infinite loop around trial 87 - 89.

A few other words of advice:

Use GetSecs, not tic and toc for timing!

If stimulus presentation timing matters, e.g., you really want to present for 0.2 seconds and not 0.2 +/- quite a bit, then look at how to use the returned timestamps and target times of tvbl = Screen('Flip', window, tWhen); - Various timing demos and our intro PDF in PsychDocumentation explain how to do proper visual timing.

Also: To make keyboard input more portable across operating systems:

RestrictKeysForKbCheck([65 186]);RestrictKeysForKbCheck([KbName('a'), KbName(';:')]);

And before your while response_made loop could start_time should come from Screen('Flip') not GetSecs.

[So far 81 minutes used up – Priority support contingent of 60 minutes completely spent.]

-mario

The function carryoverCounterbalance.m available here: G-SOC
might help you. This function allows you to create trial sequences where each condition is preceded by every other condition an equal number of times. You can specify whether or not conditions should be preceded by themselves or not.

Thank you so much for your help! I really appreciate it, and I’ll definitely try what you suggested.