Ugly gratings: harware issue? software?

I’m not really sure what the problem is. First I confirmed that the gratings look good on my MacBook Pro (OS 10.12.6)-driven CRT (gamma = 2.24). They do (see exhibit 1). Then I ran exactly the same code (exhibit 2, adapted from AdditiveBlendingForLinearSuperpositionTutorial) on a new PC with built-in display “HP 800 G3 AiO” (gamma = 2.27). To see the artifacts, you’ll need to zoom into exhibit 3. Is this an inescapable hardware issue? Or is it a problem with PTB?

function debugPseudoGray

KbName('UnifyKeyNames');
UpArrow = KbName('UpArrow');
DownArrow = KbName('DownArrow');
LeftArrow = KbName('LeftArrow');
RightArrow = KbName('RightArrow');
esc = KbName('ESCAPE');
space = KbName('space');
GammaIncrease = KbName('i');
GammaDecrease = KbName('d');

try
    % This script calls Psychtoolbox commands available only in OpenGL-based 
    % versions of the 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.  
    screenNumber = max(Screen('Screens'));

    % Open a double-buffered fullscreen window with a gray (intensity =
    % 0.5) background and support for 16- or 32 bpc floating point framebuffers.
    PsychImaging('PrepareConfiguration');

    lrect = [];

    % This will try to get 32 bpc float precision if the hardware supports
    % simultaneous use of 32 bpc float and alpha-blending. Otherwise it
    % will use a 16 bpc floating point framebuffer for drawing and
    % alpha-blending, but a 32 bpc buffer for gamma correction and final
    % display. The effective stimulus precision is reduced from 23 bits to
    % about 11 bits when a 16 bpc float buffer must be used instead of a 32
    % bpc float buffer:
    PsychImaging('AddTask', 'General', 'FloatingPoint32BitIfPossible');

    PsychImaging('AddTask', 'General', 'EnablePseudoGrayOutput');

    PsychImaging('AddTask', 'FinalFormatting', 'DisplayColorCorrection', 'SimpleGamma');
    doTheGamma =1;

    %PsychImaging('AddTask', 'General', 'InterleavedLineStereo', 0);

    % Finally open a window according to the specs given with above
    % PsychImaging calls, clear it to a background color of 0.5 aka 50%
    % luminance:
    [w, wRect]=PsychImaging('OpenWindow', screenNumber, 0.5, lrect);

    gamma = 1 / 2.0;
    PsychColorCorrection('SetEncodingGamma', w, gamma);

    % From here on, all color values should be specified in the range 0.0
    % to 1.0 for displayable luminance values. Values outside that range
    % are allowed as intermediate results, but the final stimulus image
    % should be in range 0-1, otherwise result will be undefined.
    [width, height]=Screen('WindowSize', w);

    % Enable alpha blending. We switch it into additive mode which takes
    % source alpha into account:
    Screen('BlendFunction', w, GL_SRC_ALPHA, GL_ONE);

    inc=0.25;

    s=200;
    [x,y]=meshgrid(-s:s-1, -s:s-1);
    angle=0*pi/180; % 30 deg orientation.
    f=2*p/16; % cycles/pixel
    a=cos(angle)*f;
    b=sin(angle)*f;

    % Build grating texture:
    m=sin(a*x+b*y);
    tex=Screen('MakeTexture', w, m,[],[], 2);

    % Show the gray background:
    Screen('Flip', w);

    i=0;
    rotate = 0;
    yd =0;
    show2nd = 1;

        Screen('TextSize', w, 18);

    % Center mouse on stimulus display, then make mouse cursor invisible:
    [cx, cy] = RectCenter(wRect);
    SetMouse(cx, cy, w);
    HideCursor(screenNumber);
    framecount = 0;

    tstart = GetSecs;

    % Animation loop:
    while 1
        Screen('DrawTexture', w, tex, [], [], [], [], 0.25);
        i=i+rotate;
        [x,y,buttons]=GetMouse(w);
        [x,y] = RemapMouse(w, 'AllViews', x, y);
        if any(buttons)
            if buttons(1)
                i=i+0.1;
            end
            if buttons(2)
                i=i-0.1;
            end
        end

        if show2nd
           dstRect=CenterRectOnPoint(Screen('Rect', tex), x, y+yd);
           Screen('DrawTexture', w, tex, [], dstRect, i, [], inc);
        end

        [d1, d2, keycode]=KbCheck; %#ok<*ASGLU>
        if d1
            if keycode(UpArrow)
                yd=yd-0.1;
            end

            if keycode(DownArrow)
                yd=yd+0.1;
            end

            if keycode(LeftArrow) && inc >= 0.001
                inc=inc-0.001;
            end

            if keycode(RightArrow) && inc <= 0.999
                inc=inc+0.001;
            end

            % Change of encoding gamma?
            if keycode(GammaIncrease) && doTheGamma
                gamma = min(gamma+0.001, 1.0);
                PsychColorCorrection('SetEncodingGamma', w, gamma);
            end

            if keycode(GammaDecrease) && doTheGamma
                gamma = max(gamma-0.001, 0.0);
                PsychColorCorrection('SetEncodingGamma', w, gamma);
            end

            if keycode(space)
                show2nd = 1-show2nd;
                KbReleaseWait;
                if show2nd
                    HideCursor(screenNumber);
                else
                    ShowCursor(screenNumber);
                end
            end

            if keycode(esc)
                break;
            end
        end

        txt0= 'At startup:\ngrating = sin(f*cos(angle)*x + f*sin(angle)*y);         % Compute luminance grating matrix in Matlab.\n';
        txt1= 'tex = Screen(''MakeTexture'', win, grating, [], [], 2); % Convert it into a 32bpc floating point texture.\n';
        txt2= 'Screen(''BlendFunction'', win, GL_SRC_ALPHA, GL_ONE);   % Enable additive alpha-blending.\n\nIn Display loop:\n\n';
        txt3= 'Screen(''DrawTexture'', win, tex, [], [], [], [], 0.25); % Draw static grating at center of screen.\n';
        txt4 = sprintf('Screen(''DrawTexture'', win, tex, [], [%i %i %i %i], %f, [], %f);\n', dstRect(1), dstRect(2), dstRect(3), dstRect(4), i, inc);
        txt5 = sprintf('\nEncoding Gamma is %f --> Correction for a %f gamma display.', gamma, 1/gamma);

        DrawFormattedText(w, [txt0 txt1 txt2 txt3 txt4 txt5], 0, 20, 1.0);

        framecount = framecount + 1;

        % For the fun of it: Set a specific scanline to send a trigger
        % signal for the VideoSwitcher. This does nothing if the driver for
        % VideoSwitcher is not selected. We send out a trigger for 1 redraw
        % cycle every 30 redraw cycles. The triggerline is placed at
        % scanline 10 (for no special reason):
        if mod(framecount, 30) == 0
            PsychVideoSwitcher('SetTrigger', w, 10, 1);
        end

        % Show stimulus at next display retrace:
        Screen('Flip', w);
    end

    % Done.
    avgfps = framecount / (GetSecs - tstart);
    fprintf('Average redraw rate in demo was %f Hz.\n', avgfps);

    % Again, just to test conversion speed: A fast benchmark with sync of
    % buffer swaps to retrace disabled -- Go as fast as you can!
    nmaxbench = 300;
    tstart = Screen('Flip', w);
    for i=1:nmaxbench
        Screen('Flip', w, 0, 2, 2);
    end
    tend = Screen('Flip', w);
    fprintf('Average update rate in pipeline was %f Hz.\n', nmaxbench / (tend - tstart));

    Screen('Preference','TextAntiAliasing', 1);

    % We're done: Close all windows and textures:
    sca;

catch %#ok<*CTCH>
    %this "catch" section executes in case of an error in the "try" section
    %above.  Importantly, it closes the onscreen window if its open.
    ShowCursor(screenNumber);
    sca;
    Screen('Preference','TextAntiAliasing', 1);
    psychrethrow(psychlasterror);
end %try..catch..

% Restore gfx gammatables if needed:
RestoreCluts;

return;

There is some junk in that code, but that should cause an error so the code wouldn’t run…

Which OS?

Try to remove the Pseudogray mode and / or the gamma correction, is it resolved? Also have you tried the Procedural grating demos, ProceduralGarboriumDemo or others?

thanks, ian
i fixed the code.
will try those other things.

PTB bug is extremely unlikely, user error or OS/driver bug much more likely. As usual, for any advice from myself, help PsychPaidSupportAndServices → Authentication token etc. Once you present a valid authentication token, and your license key has been enabled - which takes anywhere between 1 - 10 business days, i can have a look. Unless we are then already in christmas vacation, in which case nothing will happen until at least the 2nd week of january. Or you have already a still valid license, ie.not used up and no older than 12 months since purchase, as any able lab should have since a year, then things will go faster ofc.

There is some junk in that code

Removed. Sorry about that.

Which OS?

Windows 10 Enterprise 21H1

Try to remove the Pseudogray mode and / or the gamma correction, is it resolved?

Commenting out line 42 turns the background black, but the gratings look OK.
Changing do the gamma = 0 (line 43) does not resolve the issue.

Also have you tried the Procedural grating demos, ProceduralGarboriumDemo or others?

I hadn’t but now I have. ProceduralGarboriumDemo produces nice-looking gratings. So do we think PseudoGray is incompatible with this PC?

js

Well the usual advice will be that PTB is optimised to work best on Linux and AMD / Intel so it will be hard to know if your error is a Windows driver bug, or a genuine issue with PseudoGrey and your hardware (you didn’t mention what GPU you have?)… I’ve tested PseudoGrey on Linux workstations with AMD graphics without issue previously (although we ended up using a 10bit display and the native 10bit output from PTB, pseudogrey is after all a creative hack)…

HP 800 G3 AiO Connected to Intel HD Graphics 630.
Thanks again, Ian.

js

cheers, mate

3HHW9DNL-20211216215132:6d95c8a66eefbd64f304770dfe548de9010bcc8a365bda5b6fa5e5c18afdda7f

Thanks, license key is now activated and validated, just before the christmas vacation.

“HP 800 G3 AiO” may have multiple variants, with different display types and resolutions, according to my Google search.

But the most important info is that this is Windows-10 and Intel HD Graphics 630.

As a side note a bit unrelated to your request: In general my experience with Intel graphics on Windows-10 is that the Intel graphics drivers are very buggy, at least wrt. visual stimulation timing. On my own (former Windows-10, now Windows-11) Windows test machine with Intel UHD 620 - which is basically the same chip as yours, just a few percent slower - i never managed to get reliable visual timing, PTB sync tests fail about 9 out of 10 times, so far unfixable even on my own setup. On Linux otoh. the Intel drivers are essentially perfect wrt. quality and performance. So if you’d require not failing sync tests, you might have to upgrade or downgrade through various versions of the Intel drivers. Or upgrade to Linux if possible, for a much happier life with that graphics chip.

Back to your topic: I ran your test script on both Windows-10 and Ubuntu 20.04.3-LTS Linux, on a Microsoft Surface Pro 6 tablet with native 2736 x 1824 and also scaled 1920 x 1080 pixels resolution. On Linux, visually to my “naive observer” eye, the results looked like perfect gratings. On Windows 11 they looked a tiny bit weird on the same hardware on some stripes of the grating, but only very subtle, so i wasn’t sure if the weirdness was real or just a placebo or bias on my side, neither was my girlfriend as a naive observer, when looking at it.

Note that the “PseudoGray” method you tried to use to get up to 10.7 bits of grayscale precision, is identical to BitStealing, from what people told me. It does require a monitor that is well linearized and it was also designed for use on 8 bpc analog VGA input CRT monitors. It is entirely possible that results on a LCD monitor may be less good, given that those LCD panels only simulate a “CRT monitor like” gamma response curve, and given that subtle details matter here, it could be that it just doesn’t work well on your computers LCD display, or needs more stringent calibration.

Another issue could be that our software gpu accelerated gamma correction via the PsychImaging DisplayColorCorrection task applies before the Pseudogray precision boosting method, not after it. I think you should drop that method of gamma correction from the script and instead use Screen('LoadNormalizedGammaTable', w, lut); with a suitably computed linearization lut for gamma 2.2 instead for possibly better results.

Another thing to try would be connecting your analog VGA CRT monitor to the new machine via an active DisplayPort-to-VGA or HDMI-to-VGA adapter, to see if the different properties of the LCD panel versus the CRT monitor are the reason for the problem, given the method is originally meant for CRT displays.

The question is also what grayscale precision do you need? Only grayscale or also color precision?

If you can use a CRT monitor, assuming this is not an Intel graphics driver bug, the PseudoGray method is the cheapest way to get ~10.7 bits on a well calibrated setup.
For a few hundred dollars, there’s also Xiangrui Li et al. “Video Switcher” from https://lobes.osu.edu/videoSwitcher/. It only works with analog VGA CRT monitors, but has high quality builtin Psychtoolbox support via the ‘Videoswitcher’ tasks in AdditiveBlending… tutorial. It supposedly can provide close to 15 bits of grayscale resolution according to the authors (“precision is increased by up to 7 bits” says the manual).

With a modern AMD graphics card, if adding one is an option for that machine, you may fix the problem if it were an Intel graphics driver bug instead of a display problem. And with AMD graphics - on Linux only! - you can get 10 bpc or even up to 12 bpc (if the panel is high quality enough) grayscale or color precision via spatial dithering on a 8 bit panel. 10 bpc native or 12 bpc dithered on a 10 bit panel, or 12 bpc native on a true 12 bpc panel.

The panel of your machine is most likely only 8 bit native, so 10 bpc or 12 bpc would be only achievable via spatial dithering.

On Windows 10 bpc can be achieved by use of spatial dithering on certain models of more high priced AMD or NVidia cards.

So things to try cheaply:

  • Does it work better with a connected CRT monitor?
  • Does a Intel graphics driver update/downgrade on Windows improve anything?

Otherwise, is upgrading to Linux an option?

More expensive:

  • Videoswitcher? If a CRT monitor can be used.

If no CRT monitor can be used:

  • Intel chip on Linux to avoid graphics driver bugs for maybe better results with your panel and high quality timing.

  • AMD graphics card on Linux for 10 (maybe even 12) bpc dithered precision with your display and high quality timing.

  • Some higher end / more expensive NVidia Quadro or AMD Fire/Pro graphics cards on Windows for maybe 10 bpc dithered precision on Windows and maybe ok timing, depending on luck with your Windows setup.

These are roughly your theoretical options in the fast to test/free/cheap/low price range.
So what your practical options or constraints?

-mario
[Priority support nominally used up at 1 hour so far]

On Linux, visually to my “naive observer” eye, the results looked like perfect gratings. On Windows 11 they looked a tiny bit weird on the same hardware on some stripes of the grating,

Right! Just like in my ‘Exhibit 3’, or even more subtle? NB: Ian’s idea of using ‘Procedural’ gratings was a good one. Those look just fine on my PC. (Therefore, if and when this bug gets fixed, it doesn’t really matter to me.)

those LCD panels only simulate a “CRT monitor like” gamma response curve

I haven’t measured the resolution of gray levels, but I did confirm that patches of uniform texture do indeed have the correct Weber contrast on a mean-gray background, following PsychColorCorrection.

, and given that subtle details matter here, it could be that it just doesn’t work well on your computers LCD display, or needs more stringent calibration.

Another issue could be that our software gpu accelerated gamma correction via the PsychImaging DisplayColorCorrection task applies before the Pseudogray precision boosting method, not after it. I think you should drop that method of gamma correction from the script and instead use Screen('LoadNormalizedGammaTable', w, lut); with a suitably computed linearization lut for gamma 2.2 instead for possibly better results.

I’ll try that when I get back to the university. Maybe not until January.

Another thing to try would be connecting your analog VGA CRT monitor to the new machine via an active DisplayPort-to-VGA or HDMI-to-VGA adapter, to see if the different properties of the LCD panel versus the CRT monitor are the reason for the problem, given the method is originally meant for CRT displays.

Happy to do that, too.

The question is also what grayscale precision do you need? Only grayscale or also color precision?

For my current purposes, only grayscale.

If you can use a CRT monitor, assuming this is not an Intel graphics driver bug, the PseudoGray method is the cheapest way to get ~10.7 bits on a well calibrated setup.
For a few hundred dollars, there’s also Xiangrui Li et al. “Video Switcher” from https://lobes.osu.edu/videoSwitcher/. It only works with analog VGA CRT monitors, but has high quality builtin Psychtoolbox support via the ‘Videoswitcher’ tasks in AdditiveBlending… tutorial. It supposedly can provide close to 15 bits of grayscale resolution according to the authors (“precision is increased by up to 7 bits” says the manual).

Wow.

Otherwise, is upgrading to Linux an option?

Expressedly forbidden for all networked machines at my university.

I don’t know. I already found it difficult to see which of your exhibits shows the weirdness, or what the weirdness was. It wasn’t clear which weird looking things were on your screen and which were encoding artifacts from your camera or jpeg image encoding artifacts. With higher frequency gratings you can also get sampling/aliasing artifacts from the grating sampled by the monitor sampled by the cameras ccd sensor, iow. screenshots with camera can be difficult. And ofc. adaptation. At least i see ghost-images in the environment if i stare too long at a grating, so after a bit of looking i don’t know which is the image in the world and which is something in my brain adapting…

Towards that, the following link to the following gist…

… has a pimped version of your debugPseudogray.m script:
debugPseudogray(0); - Display stim as before, but in a window, and disable mouse movement of the 2nd grating.

debugPseudogray(1); - Same, but it takes a Screen('GetImage') screenshot of the drawn stimulus image, as created by your drawing commands, in floating point precision, and then plots a horizontal line from that screenshot. This way you can check in Matlab’s figure window if the sine grating actually shows up as the expected sine grating. If yes, then the problem is with the ‘PseudoGray’ encoding method for 10.8 bits precision, or the display calibration, or your display. If no, then something goes wrong with drawing and you can see if the procedural gabor method works better. Wrt. procedural gabors, you can also run ProceduralGaborDemo(2); that runs a precision test on gabors, comparing gabor patches drawn by the gpu with ones computed by Matlab (ESCape key for a while to stop the precision test).

Needless to say, both methods, the one implemented via MakeTexture in debugPseudoGray / AdditiveBlendingForSuper… and the procedural methods work perfectly on your type of graphics chip under Linux, and on other machines/gpu’s/OS’es. So if this gives wrong results then it is not a Psychtoolbox bug, but a Intel on MS-Windows bug, and nothing we could do about it. You could only try to use the other proceduralgabor method if that works better, or upgrade/downgrade to different versions of Intel graphics drivers for Windows and hope that one of those works better. Or upgrade to Linux, which is sadly not an option for you.

debugPseudogray(2); also takes a screenshot, but this time of the same pixel line in 8 bit color, plotting a red, green and blue intensity curve between 0 - 255. This is the image after gamma/pseudogray processing, as stored in the framebuffer for presentation. The red/gree/blue plots should also roughly show expected sine waves, at least if you set gamma = 1 in the code, instead of the 0.5 you’ve coded into the script, because a gamma correction for a gamma other than 1 will obviously make the sine waves non-linearly distorted/gamma corrected. You will see slight differences of up to 1 unit between the red/green/blue channels in some parts, as that is part of the working principle of the bitstealing/pseudogray precision boosting method. If you get something that looks fishy there, then maybe that’s a problem. However, again, this works perfect on Linux, so whatever would cause trouble would be an Intel graphics driver bug on Windows, nothing we can do, see above…

Running this on Linux gives nice results, i haven’t retested on Windows, because that would just be a too cruel thing to do at christmas.

Another thought i had: Do you have trouble if you no longer allow moving the 2nd grating with the mouse, as done in the gist above? Because if you accidentally moved the mouse on your desk, the gratings won’t align perfectly anymore, so the superposition pattern may be not what you expected. Or even more fun, what if the computed dstRect in your code does not end up exactly on top of the fixed grating and creates a fishy superposition pattern? Depending on the screen resolution, mouse query precision on your operating system etc., i think fractional pixel positions could happen. Iow. your test code is not the best one to avoid measuring problems that aren’t there.

Another problem you can get with the LCD panels is that the Intel display driver on Windows may decide to enable digital spatial dithering on the framebuffer image as it is sent out to the display panel or any external digital (DVI/HDMI/DisplayPort) display, which will totally screw up the working principle of pseudogray for sure. On Linux it won’t do that on a 8 bit panel, on Windows or macOS i don’t know what it will do. Another reason why it may only work well on a analog VGA driven CRT that is well calibrated. For PseudoGray it is important to understand the method used, cfe. “help CreatePseudoGrayLUT” for some explanation and pointers to more detailed explanations. It is really a poor man’s solution for more than 8 bit luminance precision, for quick and dirty testing under the right conditions.

I’m afraid your university may have the most stunningly incompetent IT department in the history of neuroscience, if they forbid the probably in practice most secure OS (both by design, and also statistically because it isn’t a major desktop OS target for attackers or viruses - desktop market share too low to be an attractive target) while allowing the two biggest threats to security happily. Otoh. given the level of incompetence, they might not notice if somebody just sneaks a dual-booted Ubuntu desktop machine onto the network? Or one could not network it, by installing and setting up the machine outside the lab network, then only connect to the network for software updates, or via some cell phone data connection. USB drives still exist to get data and scripts onto and off the machine. And a non-networked machine won’t even need security updates… Just saying, sometimes one has to be sneaky in the interest of science and the efficient and effective use of tax payer money… Maybe we should start to sell Linux distros on a stick or SSD drive, so people with dumb constraints have an easier way to bypassing them without installing Linux on networked machines…

Apart from that, i think your options for more than 8 bit / 256 levels of grayscale precision are probably more limited than i thought, if upgrading to Linux is not an option. That all in one PC probably can not be upgraded with other graphics cards? And the internal display can only work with the Intel graphics anyway.

So essentially if MS-Windows with Intel graphics is your only option, i would try:

  1. Run those tests to figure out if it is an unsolvable Intel problem, apart from trying random Intel display driver upgrades or downdgrades, or not. Other important tests to run wrt. precision before conducting experiments are DriftTexturePrecisionTest(2) and HighColorPrecisionDrawingTest.

  2. If the tests give good results, try with your external CRT monitor. That one has the best chance of working if properly adjusted. If that works and your built-in display doesn’t, maybe then the panel is just not suitable for the method. In any case you need to measure the whole intensity range you’re gonna use in the end with a photometer to see
    if the method works well enough and is linear enough.

  3. If you wanted to spend some money on a Video switcher, which only works with VGA CRT monitors in any case, there are the test HighPrecisionLuminanceOutputDriversImagingPipelineTest(method) allows to test if your Intel graphics is bug-free and accurate enough to drive such a device properly. E.g., with the method ‘VideoSwitcherSimple’ or ‘VideoSwitcherCalibrated’. If those tests fail, then it is game over → Intel graphics driver bug on Windows.

Good luck and nice remaining christmas. Btw. if you are in a chistmas mood, we wouldn’t mind getting a few more licenses bought, given that this is so far an over 200 Euros financial loss for us wrt. work time spent vs. paid.

-mario

Hi Ian (and Mario, if you’re getting this). For a while I thought these ‘procedural’ Gabors were good. However, there is something about their behaviour I don’t understand. At the monitor’s Nyquist frequency (line 14 in the attached code), the Gabors have a central white stripe (i.e., the gratings appear in cosine phase with respect to their Gaussian windows) when phase = 180 (line 17). On the other hand, with much smaller spatial frequencies (e.g., freq = .05), the Gabors have a central white stripe when phase = 270 (line 18). Bug? Feature? If it’s a feature, how does the Gabor’s true phase depend on the variables phase and freq?
js

function procedural

% JAS modified ProceduralGarboriumDemo. 
% (Is that a spelling error?)

measuredGamma=2.27;

% Size of support in pixels. 
% NB: ProceduralGarboriumDemo has odd integers here.
tw = 513; th = 513;

% Frequency of sine grating.
% (Seems to have units of cycles per pixel.)
freq = .5;

% Phase of underlying sine grating in degrees:
% phase = 180; % cosine phase when freq = .5 (top and bottom rasters are black.)
phase = 270; % invisible when freq = .5; cosine phase when freq is small.

% Spatial constant of the exponential "hull" (pixels)
sc = 9964.0;
% Peak Contrast of Gabor:
contrast = .95;
% Aspect ratio width vs. height:
aspectratio = 1.0;

PsychDefaultSetup(2);

% From ProceduralGarboriumDemo:
screenid = max(Screen('Screens'));
PsychImaging('PrepareConfiguration');
PsychImaging('AddTask', 'General', 'FloatingPoint32BitIfPossible');
PsychImaging('AddTask', 'FinalFormatting', 'DisplayColorCorrection', 'SimpleGamma');

% EnablePseudoGrayOutput:
PsychImaging('AddTask', 'General', 'EnablePseudoGrayOutput');
[win, winRect] = PsychImaging('OpenWindow', screenid, 0.5);
PsychColorCorrection('SetEncodingGamma', win, 1/measuredGamma);

% From ProceduralGarboriumDemo:
[w, h] = RectSize(winRect);
ifi = Screen('GetFlipInterval', win);
Screen('BlendFunction', win, GL_ONE, GL_ONE);

% All we need is one Gabor:
mypars = repmat([phase+180, freq, sc, contrast, aspectratio, 0, 0, 0]', 1, 1);
gabortex = CreateProceduralGabor(win, tw, th, 0,[0,0,0,0],1);
texrect = Screen('Rect', gabortex);
dstRect = CenterRectOnPoint(texrect, w/2, h/2)';
rotAngle=90;

% Remainder of code from ProceduralGarboriumDemo
Screen('DrawTexture', win, gabortex, [], [], [], [], [], [], [], kPsychDontDoRotation, [phase, freq, sc, contrast, aspectratio, 0, 0, 0]);
vbl = Screen('Flip', win);
count = 0;

% Animation loop: Run until any keypress:
while ~KbCheck
    Screen('DrawTextures', win, gabortex, [], dstRect, rotAngle, [], [], [], [], kPsychDontDoRotation, mypars);
    Screen('DrawingFinished', win);
    vbl = Screen('Flip', win, vbl + 0.5 * ifi);
    count = count + 1;
end

% Done. Last flip to take end timestamp and for stimulus offset:
Screen('Flip', win);

% Close onscreen window, release all ressources:
sca;

% Done.
return;

A very quick reply: from my distant memory the phase is determined from the edge of the grating virtual texture, therefore changes to both SF and/or to the texture size will shift the phase at the centre point. For this reason, for my gratingStimulus class (which wraps the procedural grating commands), offers a CorrectPhase property:

Which calls this method:

This allows me to align two gratings overlaid with a different size (classic centre-surround stimulus pattern) while equalising the phase offsets. My code is focused on size changes, rather than SF changes, but it should give you an idea of the issues…


5° and 4° gratings overlaid; left (without correction, note phase is matched at the left of the grating), right (correctPhase applied, phase is matched at the centre)

I just tested doubling SF and the phase remains matched (the yellow grid dots are 1° apart, SFs are 1c/d and 2c/d), so this method works for size and SF changes:

Egad, Ian, that’s pretty opaque.
Line 5 suggests that sf = spatial period (not spatial frequency) in degrees?
Let’s see.
Assume (line 359) ppd = pixels per degree
(line 360) size has units degrees (half the grating length)
(line 361) sfTmp has units pixels (spatial period)
(line 362) md has units degrees squared. That’s no good.

Hi Mario.
Interesting suggestion you made. I connected an external (flat screen) monitor to the new machine via HDMI. (I don’t have an HDMI to VGA adaptor handy, so I can’t try my CRT just yet). In mirror mode, gratings on the new machine look ugly but they look…well, now I’m not sure. Check out these graphs from your ‘pimped’ version of debugPseudoGray:

My stimulus must handle dynamic changes during the experiment, so while sf / tf and size are always set in degree-based values, internally they are auto-transformed to values that PTB uses. This is done using temporary dynamic properties; size > sizeOut, sf > sfOut etc. and set and get methods do the conversion in the background. Note that scale is also a required parameter. So they should all be in pixel-based co-ordinates at this point…

Thanks for getting right back to me, Ian, but it still isn’t clear; sorry. If size and sfTmp both have units of pixels, then md must have units of pixels^2 (see line 362), which doesn’t make sense to me, does it make sense to you?
js

I’m travelling on a mobile so I can’t really have a deep dive, all I can tell you, and you can visualise from the screenshots I sent, is that this correctly offsets the phase for changes of stimulus size and sf (i.e. it works). Looking through off the top of my head, sfTmp is assigned back as frequency and not period so if sfTmp = 1 and size is 4° @ 32 ppd = 128px, then md = 128 / (32 / 1) = 4, i.e. 4 cycles in a 4° stimulus, so 4 - floor(4) = 0 and phase is (360 * 0) = 0°. If size = 4.5°, md = 176 / (32 / 1) = 4.5; 4.5 - floor(4.5) = 0.5 and 0.5 * 360 = 180° as we would expect from a 0.5° size shift @ 1c/d (i.e. we shunt the 1c/d grating by 180° so the center stays the same). By changing sf we change the denominator for md, e.g sfOut = 1.2 size = 4° then md = 128 / (32/1.2) = 4.8 and 4.8-floor(4.8)=0.8 and (360 * 0.8) = 288° offset. As I say you can test this by downloading the code and running it yourself.

>> s=screenManager('windowed',[800 800]);
>> open(s)
>> g1=gratingStimulus('size',10,'mask',true,'correctPhase',true);
>> g2=gratingStimulus('size',4,'sf',1.6,'mask',true,'correctPhase',true);
>> setup(g1, s); setup(g2, s);
>> draw(g1); draw(g2); drawGrid(s); flip(s);
>> close(s); reset(g1); reset(g2)

I can reproduce those plots of broken sine grating rendering when booting into Windows-11 + Intel, instead of Ubuntu 20.04.3-LTS + Intel. This confirms it is a Intel OpenGL graphics driver bug on Windows, as expected.

It triggers in all high precision display modes involving floating point framebuffers, e.g., when using PseudoGray or video switcher modes or anything demonstrated in our high precision display demos. The bug seems to cause reduced sampling accuracy when using shaders for texture filtering. Not fixable by us.

So the proper and safe solution is to upgrade to Linux. The best workaround on Windows would be either…

Screen('Preference', 'ConserveVRAM', 2^28)

… at the beginning of your script, to disable use of filter shaders and rely on the fixed function texture filtering in the hope that works better, or setting the filterMode flag to zero in Screen('DrawTexture',...) to avoid any texture filtering and only use nearest-neighbour sampling, or to not use ‘MakeTexture’ + ‘DrawTexture’ with conventional textures or offscreen windows. Procedural textures may work. I say “may”, because if the driver is buggy enough to impair basic shader based texture filtering creating such artifacts, then i don’t know if i would trust the accuracy of computed procedural textures either. The bug is “interesting”/insidious, because it doesn’t trigger test failure in tests i would have expected to fail, like HighColorPrecisionDrawingTest. It does fail for HighPrecisionLuminanceOutputDriversImagingPipelineTest('VideoSwitcherCalibrated') though, whereas HighPrecisionLuminanceOutputDriversImagingPipelineTest('VideoSwitcherSimple') passes. So a video switcher on a CRT monitor may not be usable in calibrated mode for highest possible precision either, and the achievable precision boost would be probably suboptimal, ymmv…

Intel graphics is not recommended on MS-Windows, too buggy, and upgrading to Linux for any serious work is everybody’s best option.

This concludes all i have to say to the topic, at a financial loss of over 900 Euros for us, unless you feel like contributing some more licenses + authentication tokens to reduce our losses.

-mario