How to control signal-to-noise contrast ratio for a Gabor+noise patch?

Hi,

I would like to precisely control the signal-to-noise contrast ratio for a Gabor grating in noise. According to this link here OpenGL and Rendering — PsychoPy v2023.2.3, for blendMode = ‘avg’ in visual.Window:

Mathematically, each pixel colour is constructed from opacity*stimRGB + (1-opacity)*backgroundRGB.

My understanding is that, if I draw the noise patch first, it’ll be in the background. If I then draw the Gabor patch on top of it, every overlapping pixels should be computed based on the opacity of the Gabor. If I set Gabor’s opacity to 1.0, then the noise pixels should be given a zero weight, and they should not be visible at all.

I tried doing this using the code below:

from psychopy import visual, core, event
from numpy.random import random

# open window
win = visual.Window([1024,768],units='pix',monitor='testMonitor',blendMode='avg')

X = 128; # width of gabor patch in pixels
sf = .05; # cycles per pixel
noiseTexture = random([X,X])*2.-1. # a X-by-X array of random numbers in [-1,1]

# signal grating patch
s = visual.GratingStim(
    win = win, mask='gauss', sf = sf, 
    size = X, contrast = 1.0, opacity = 1.0,
)

# noise patch
n = visual.GratingStim(
    win = win, mask='gauss', tex = noiseTexture,
    size = X, contrast = 1.0, opacity = 1.0,
)

while not event.getKeys():
    n.draw() # draw noise in the background
    s.draw() # draw gabor on top of noise
    win.flip()
    event.clearEvents('mouse')  # for pygame only

win.close() 
core.quit()

but I still see the noise pixels:
stimuli

I thought someone might have asked this but I couldn’t find a relevant post… maybe I’m missing something here. Ultimately, I just want to have a way to precisely control the signal-to-noise contrast ratio between a Gabor patch (as signal) relative to the noise pixels. If there’s a better way to do it, I’d love to know!

I’m running PsychoPy 1.8x on Spyder 3 (Python 2.7) in Anaconda 2, in lubuntu 18.04 (I don’t remember the exact version of PsychoPy installed…).

Cheers,
Alan

I think the Gaussian part of the Gabor is implemented by modulating the opacity. You could instead pre-compute the Gabor and use that as the texture. Something like:

gabor_tex = (
    visual.filters.makeGrating(res=X, cycles=X * sf) *
    visual.filters.makeMask(matrixSize=X, shape="gauss", range=[0, 1])
)

which you could then use as:

# signal grating patch
s = visual.GratingStim(
    win = win, tex = gabor_tex, mask = None,
    size = X, contrast = 1.0, opacity = 1.0,
)

Hi Damien,

Thanks for the quick response! and it works perfectly!

Just one more question: my stimulus actually contains multiple Gabor elements with noise, and I originally planned to use visual.ElementArrayStim. If I have to draw the whole pixel array like you suggested, does it mean that I cannot use visual.ElementArrayStim to control the signal-to-noise contrast ratio for a multiple-Gabor pattern?

Thanks,
Alan

It might not be a workable strategy for ElementArrayStim - it probably depends on whether you need the elements to be close to one another or overlapping. How is the complete stimulus intended to look?

For this experiment, I plan to show around 20 Gabor patches, with a few (randomly selected across trials) to be superimposed on noisy pixels (regenerated for every trial for every Gabor patch). I’d like to individually control the orientations for all Gabors and the signal-to-noise ratios for those with noise patches. Because they’re all static, I can still make do with for-loops within each trial for all the Gabors and noise patches, and it’s going fine without much lag. So what you suggested is a good workaround, and, for this experiment, the problem is solved.

However, I do have another type of motion stimulus in mind and I thought of using ElementArrayStim to do it. It’s a multiple-Gabor pattern, with around 200 non-overlapping Gabor elements (imagine they are fitted into a grid, with every cell having the same size). In every trial, I want to control orientation and drift speed for each Gabor, so that the whole pattern could signal a coherent global motion direction. I haven’t successfully implemented it with PsychoPy, but I suppose ElementArrayStim should work. But recently, I’ve tried adding noisy patches on all Gabors in this pattern to measure the effect of contrast noise in motion perception. The noise patches need to be resampled every frame (or at least frequently enough to make them noisy in time as well). In MATLAB with psychtoolbox, I did it in the old “point matrix” way and it was OK. But I still haven’t successfully migrated it to psychopy…