Creating movie (screen capture) on a "retina" display

Hi,

Apologies if I post twice, my first post does not seem to have gotten through.

I am wanting to capture a movie of a stimulus that I will be presenting in an experiment. This is not “live” during the experiment, rather just to be able to show the stimuli in a movie without Matlab and PTB.

I have successfully used a very slow way via Matlab for many years, but would like to update my code to use the functionality via PTB and gstreamer.

I have successfully got things working fine with

Screen(‘CreateMovie’,…)

and related functions, but for one issue.

The movie I get is only the lower left hand portion of the screen. I am pretty sure that the likely culprit is the scaling that goes on from “retina” displays. As such I tried adding

PsychImaging(‘AddTask’, ‘General’, ‘UseRetinaResolution’);

This was honoured in that the windows on the screen before and after PTB displays stimulus scale appropriately (visually at least). However the recorded movie I get out at the end still is the same as before i.e. the capture movie is not what I see on the screen. I see the full stimulus on the screen.

Is there a known simple workaround for this?

I think I am probably being dumb and have missed something in the documentation.

Many thanks

Peter

My existing code is very complex, but if needed I could write some demo code. But I think the description above possible might result in an answer as I am pretty sure I am missing something known and simple.

Hi Peter,

hmm. Dealing with Apples Retina display implementation required a lot of awful hacks and workarounds, compared to Linux or Windows, and not everything there is perfect, even after dozens of hours of tinkering over the years - It is a gift that keeps giving. But the PsychImaging(‘AddTask’, ‘General’, ‘UseRetinaResolution’); should actually lead to the easy cases with less potential for trouble and bugs.

How do the Screen CreateMovie and AddMovieToFrame lines look like?
Especially, what width and height if any did you specify in CreateMovie, if any?

-mario

Hi Mario,

I have made a short demo script which draws dots in the corners and middle sides and centre of the screen to try and get a handle on what’s going on. I cannot upload movies so have uploaded the script and screenshots of the resulting movies when opened in VLC.

There is a variable (mType) to change which gives two different results, even though I assume that it should be equivalent. I attach two screenshots of the resulting movie.

mType == 1: Gives the bottom left corner/part of the screen (here I use the default for the “rect” for adding a frame)
mType == 2: Gives the top left corner/part of the screen (here I pass a rect equal to what opening the screen gives me).

The Script

% Clear the workspace
clear all;
close all;

% Cue to do movie or not
doMovie = 1;

% This changes either default for rect for add frame or specified
% (1) Gives a captured movie showing only the bottom left
% (2) Gives a captured movie showing only the top left
mType = 2;

% For dmeo only
Screen(‘Preference’, ‘SkipSyncTests’, 2);

% Setup and open screen
PsychImaging(‘PrepareConfiguration’);
PsychImaging(‘AddTask’, ‘General’, ‘UseRetinaResolution’);
PsychImaging(‘FinalizeConfiguration’);

screenid = max(Screen(‘Screens’));
[window, windowRect] = PsychImaging(‘OpenWindow’, screenid);

% Start the movie now if requested
if doMovie == 1
moviePtr = Screen(‘CreateMovie’, window, [‘exampleMovieType’ num2str(mType) ‘.mp4’]);
end

% Draw dots in the corners of the screen
bottom = windowRect(4);
top = 0;
left = 0;
right = windowRect(3);
middleX = windowRect(3) / 2;
middleY = windowRect(4) / 2;

xyPos = [left top; right top; left bottom; right bottom;…
middleX top; middleX bottom; left middleY; right middleY;…
windowRect(3) / 2 windowRect(4) / 2]‘;
colors = [255 0 0; 0 255 0; 0 0 255; 0 0 0;…
128 128 128; 128 128 128; 128 128 128; 128 128 128; 255 255 0]’;

% Drawing loop
while ~KbCheck
Screen(‘DrawDots’, window, xyPos, 15, colors, , 2)
Screen(‘Flip’, window)
if doMovie == 1

    if mType == 1
        Screen('AddFrameToMovie', window, [], [], moviePtr);
    elseif mType == 2
        Screen('AddFrameToMovie', window, windowRect, [], moviePtr);
    end

end

end

% Clean up and leave the building
Screen(‘FinalizeMovie’, moviePtr);
sca

So, this seems to work mostly as intended, although the default behavior on macOS with a Retina panel can be confusing.

  1. Remove the call to PsychImaging(‘FinalizeConfiguration’); in your script, as that is wrong usage and also essentially never needed. It is one of these “use only if you really know what you’re doing and have very special needs” functions. If you remove that call then the warning about “unfinished configuration phases” will go away which PTB prints each time your original script runs. More importantly, now the ‘UseRetinaResolution’ request gets honored, the rendering pipeline gets configured for the full native Retina panel resolution, and proper high resolution movies get written out.

  2. If you don’t request ‘‘UseRetinaResolution’’, or have that bug in your script that causes ignoring all PsychImaging() requests, then the panelfitter will be enabled to give you a quarter size virtual framebuffer for drawing, and it upscales that content during ‘Flip’ to the full native panel resolution. This is the backwards compatible behavior to non-Retina Macs, as it emulates what earlier macOS would do for non-Retina-enabled applications. In practice you’ll get a 1440x900 pixels virtual drawbuffer size for a physical 2880x1800 pixels Retina panel, ie. half the horizontal and vertical size of the real display.

Here’s where the fun starts. You don’t specify the desired width x height of your movie frames in the call to ‘CreateMovie’ in your script, so the function has to guess a reasonable size and it chooses the “size” of the onscreen windows drawing buffer, ie. the same size as reported in the ‘windowRect’, or by Screen(‘Rect’, window) etc., because that’s what one would usually expect. In my example that would be 1440x900 pixels. This will define the size of the rectangle that gets screen-captured and written out into the movie. The ‘AddFrameToMovie’ function can take screenshots from different buffers, and if one doesn’t specify which buffer to screenshot from, it will default to the OpenGL frontbuffer, ie. what gets physically shown on the display right now. On Retina panels on macOS in this compatibility mode, in my example the back-/and frontbuffers are full size 2880 x 1800 and so the chosen size for screen shots for your movie is only 1440x900 vs. 2880x1800 and so you only capture one quadrant of the image.

The way around this is to specify ‘drawBuffer’ as the buffer to capture, e.g.,

        if mType == 1
            Screen('AddFrameToMovie', window, [], ['drawBuffer'], moviePtr);
        elseif mType == 2
            Screen('AddFrameToMovie', window, windowRect, ['drawBuffer'], moviePtr);
        end

Now the drawbuffer with size 1440x900 gets captured and all is good - but lower than Retina resolution. This captures what your script has drawn, with the catch that you’d have to put the AddFrameToMovie call before the ‘Flip’, because otherwise that drawbuffer will have been cleared to background color after ‘Flip’. The default of capturing the ‘frontbuffer’ will give full resolution currently displayed image, a setting of ‘backBuffer’ would give the full resolution image that will be shown after next ‘Flip’, with all the image processing operationg of the imaging pipeline applied, e.g., geometric distortion correction, color conversions, stereo modes etc.

Or you specify the wanted size of the movie frames in the ‘CreateMovie’ call, so no guesswork by the software wrt. to wanted movie sizes etc. is needed.

But the most simple solution for bog standard use cases is just to remove that wrong ‘FinalizeConfiguration’ call and get movies in full Retina resolution.

There seems to be one bug though specific to macOS Retina handling, if one leaves everything at default settings and doesn’t use ‘UseRetinaResolution’, like in your original script: That is that both your mType 1 and 2 should both capture the top-left quadrant, iow. mtype 1 should behave like mtype 2, which it doesn’t. I guess i’ll see if that can be fixed in some future release, once somebody pays for the bug fix.

Btw. if you look at ImagingStereoDemo.m’s CreateMovie call, you’ll see those optional ‘CodecSettings…’ parameter. That changes the encoding parameters to something that not only higher quality movie playback software like Psychtoolbox/GStreamer or VLC can play back, but also the more primitive/limited QuickTime player of macOS or whatever MS-Windows calls its media player. Worth a try if you don’t need highest quality and want to simplify your life.

-mario

[Work time for this investigation on a MBP 2017 with macOS Catalina so far: 3.5 hours. For other readers of this response: The only reason this was investigated for free by myself is as a thank you to Peter for making all those nice tutorials. For a regular user, this investigation would have been a paid support request, costing them 1050 Euros + sales tax, unless discounted for labs who already have a long running paid support membership, as any lab should have.]

Many thanks Mario. I had some slight issue on Monterey with the solution. But with a bit of tinkering of your solution I got it to work.

The

PsychImaging(‘FinalizeConfiguration’)

Was indeed just shear desperation! (having read the docs I knew that it was not needed, but had hit a wall).

I will ping you about the demos via email. There are a few things I would like to chat about.