ElementArray of gratings with non-zero pedestal value

Hi all,

I would like to draw a number of gratings on a background that is darker than 0 (in Psychopy’s [-1, 1] convention) luminance. I need to be able to manipulate the contrast of these gratings from trial to trial. I want the mean luminance to be constant across the window, so the contrast needs to change the span of the grating luminance around the background value (pedestal) My understanding is that this is not currently possible out of the box in Psychopy.

I’ve found some discussion of a similar problem using GratingStim directly on the old mailing list. This led me to this very nice piece of code that adds a pedestal value to the Psychopy GratingStim.

I’m willing to do some hacking to get the same functionality in ElementArrayStim, but I’m not exactly sure where to start. The PedestalGratingStim seems to work mainly by overriding the _updateListShaders method, but this method does not exist for ElementArrayStim.

Can you please give me some hints for how to apply the solution in PedestalGratingStim to make a PedestalElementArrayStim?

Thanks!

Replying to myself:

I determined that the relevant code that was in GratingStim._updateListShaders appears in the ElementArrayStim.draw method. Therefore, I applied the solution from PedestalGratingStim (see link above) rather directly. I now have an object that, in minimal testing, behaves the way I want it to. I have put my code into a gist.

It would still be helpful to know if I am missing anything obvious about the difference between GratingStim and ElementArrayStim that is going to cause me problems.

Actually I suppose this is not fully fixed. Things work fine if I import my module after opening a PsychoPy window. However, if I import my module before doing that, it fails with

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-111c4cce99b8> in <module>()
----> 1 import pedestalelementarray

/Users/mwaskom/Google Drive/for__Michael/Projects/scdp_python/pedestalelementarray.py in <module>()
     36     '''
     37
---> 38 class PedestalElementArrayStim(ElementArrayStim, MinimalStim, TextureMixin):
     39     """
     40     This stimulus class defines a field of elements whose behaviour can be independently

TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

I’m not fully sure I understand this error or why it seems to be dependent on whether a Window has been opened. Any thoughts?

OK, I suppose that’s this issue.

Easily solved by importing ElementArrayStim directly from psychopy.visual.elementarray.

I might not be completely understanding, but I think this should be able to be done with a regular GratingStim or ElementArrayStim by setting (before drawing) win.blendMode = "add" and adjusting the contrast?

To create the pedestal that @mwaskom needs he’d need to draw once with sf=0 and color set to be his desired pedestal, then with blendMode = ‘add’ draw again (now with his actual desired element SFs) but that all feels like a bit of a hack.

Jonas’ solution definitely won’t work because, as pointed out, it changes the contrast not the luminance.

The gist code that is linked by @mwaskom is the right thing to do (which we should add to these stimuli again). It doesn’t need to be done in the update list section though, it can be done in the regular draw() code instead for stimuli that don’t use lists. The key pieces are the creation of the fragment shader code at the top (to replace the standard shader for the stimulus) and then the step whereby the value of the pedestal is passed to the fragment shader which is this in the gist linked. That’s the part that could happily go in the list creation or in the draw function:

        GL.glUseProgram(self._progSignedTexMask)
        GL.glUniform1i(GL.glGetUniformLocation(self._progSignedTexMask, "texture"), 0) #set the texture to be texture unit 0
        GL.glUniform1i(GL.glGetUniformLocation(self._progSignedTexMask, "mask"), 1)  # mask is texture unit 1
        GL.glUniform1f(GL.glGetUniformLocation(self._progSignedTexMask, "pedestal"), self.pedestal)  # mask is texture unit 1

I can’t think of anything else you need to worry about :slight_smile:

Thanks Jon. I’ll mark this as solved as everything seems to be working smoothly for now, but I am hopeful that this feature will make its way (back) into PsychoPy core at some.point.