Optimizing for rapid presentation of several stimuli at high refresh rates

Hello all! First time writing on the forum. I have been using Psychopy recently to build SSVEP experiments at high refresh rates (stimuli between 50 and 70 Hz). So far we are doing great when presenting a single rectangle object that changes colors based on a sine function.

However, we tried to present a checkerboard-like stimulus, and since Psychopy does not include that as a built-in object (that I could find, at least), I created a class that manages an array of rectangle objects and updates them in bundle to alternate between black and white using the same sine function. This has caused us to have problems with the duration of the stimulus, since they are presented in a loop that uses the flip method and runs over frames (see below).

Since we do not have this problem with only one rectangle and we tested that it gets increasingly worse the more rectangles we have, we suspect that the problem is that brute-forcing the experiment to draw, say, 9 rectangles (3x3 checkerboard) results in the process being performed in more than one frame, so the loop ends up running for longer than expected.

I would like to know if there is a way of optimizing the process, if there are alternatives to be used that are built in Psychopy, or whether the process of drawing N objects can be optimized in the experimental code. Find here the main loop of the experiment:

# For each block... 
for trial_list in block_list:
    # For each trial...
    for trial in trial_list:
        marker_info = [f"{trial.freq}Hz, phase {phase}"]
        outlet.push_sample(marker_info)
        t0 = trialClock.getTime() # Retrieve time at trial onset
        # For each frame in the trial...
        for n in range(trial_duration):
            # Draw the stim at the corresponding intensity at each frame
            t1 = trialClock.getTime() # Time at this particular frame
            time = t0 - t1 + phase # Center first frame at 0, then shift by phase parameter

            trial.draw(time=time)  # Draw the board with the correct contrast
            window.flip()

        # Preset fix cross for the same duration
        for n in range(trial_duration):
            fixation.draw()
            window.flip()

Some notes:

  • The variable ‘trial_duration’ is an int resulting from multiplying the desired duration of the stim in seconds by the refresh rate of the monitor. In the case of a 60Hz monitor and a 5s desired duration, this variable would have a value of 300

  • The trial in the loop is the CheckerBoard object, that contains an array of Psychopy Rect objects stored

  • The draw method consisits of a nested for loop iterating over the array of Rect objects to set their contrast to the corresponding value of the sin function at time ‘time’

Of course, I realize that this brute-force approach of just iterating over several Rect objects is far from ideal in terms of performance. I would really appreciate any tips or pointers towards making this more efficient, in case there are no other alternatives that I have missed. We would really like to present more than one rectangle for the experiments, and this becomes incressible more demanding the higher the refresh rate is, since the time between flips becomes shorter.

Thank you very much.

Hi There,

Firstly, you are correct that PsychoPy does not have a checkerboard class (although the github is always open to contributors ! https://github.com/psychopy/psychopy). PsychoPy does have a noise class that can make low res noise that appears ‘checkerboard like’ - but I am not sure this is exactly what you want (I recommend having a play with that in builder first to see if it ‘looks’ like what you want).

Becca

Hello,

Thank you for your response! I will have a look at the class you mentioned. Regarding what I did, I’d be happy to contribute the code (it will need some tweaks, of course), but I think that the performance issues that led me to write the post make it unusable at the moment. Maybe opening a PR with the class on it could be a more practical way of inspecting and potentially fixing the issue, since then the reviewers would have access to the code. I just fear that Psychopy (or the libraries it depends on for drawing shapes) does not provide a solution for this kind of complex stim.

I know this might seem super simplistic, but could you make the checkerboards just a single image?

1 Like

Would the ElementArrayStim class help with this?

Coder view: Demos menu -> stimuli -> elementArrays.py

Ok, the ElementArrayStim class looks just like what I was looking for. I have several questions about it after checking the example and toying a bit with the parameters:

  • How to make the elements be solid-colored rectangles? The ‘mask’ parameter accepts some strings but I could not find a list of accepted parameters

  • Is there a particular reason while the class does not accept strings as color parameters? I get the following error when I try:

AttributeError: 'ElementArrayStim' object has no attribute '_texID'

Thank you very much!

Please show us your code.

board = visual.ElementArrayStim(window, 
                                fieldPos=(0.0, 0.0), fieldSize=(300, 300), 
                                fieldShape='square', nElements=9, sizes=100,
                                colors='black', 
                                colorSpace='rgb',
                                elementMask='gaussian')

This will give the error mentioned before:

AttributeError: 'ElementArrayStim' object has no attribute '_texID'

But, more important than that, I am concerned with whether this class is capable of having squares or rectangles as elements. If dot-like stimuli is the only thing that can be presented, I cannot use it to make a checkerboard.

Hello! I have problem with new versio too.

 @attributeSetter
    def mask(self, value):
        """The alpha mask (forming the shape of the image)

        This can be one of various options:
            + 'circle', 'gauss', 'raisedCos', 'cross'
            + **None** (resets to default)
            + the name of an image file (most formats supported)
            + a numpy array (1xN or NxN) ranging -1:1
        """
        self.__dict__['mask'] = value
        if self.__class__.__name__ == 'ImageStim':
            dataType = GL.GL_UNSIGNED_BYTE
        else:
            dataType = None
        self._createTexture(
            value, id=self._maskID, pixFormat=GL.GL_ALPHA, dataType=dataType,
            stim=self, res=self.texRes, maskParams=self.maskParams,
            wrapping=False)

    def setMask(self, value, log=None):
        """Usually you can use 'stim.attribute = value' syntax instead,
        but use this method if you need to suppress the log message.
        """
        setAttribute(self, 'mask', value, log)

    @attributeSetter
    def texRes(self, value):
        """Power-of-two int. Sets the resolution of the mask and texture.
        texRes is overridden if an array or image is provided as mask.

        :ref:`Operations <attrib-operations>` supported.
        """
        self.__dict__['texRes'] = value

        # ... now rebuild textures (call attributeSetters without logging).
        if hasattr(self, 'tex'):
            setAttribute(self, 'tex', self.tex, log=False)
        if hasattr(self, 'mask'):
            setAttribute(self, 'mask', self.mask, log=False)

I did resart treal, drivers for videoCard, but Slider doesn’t work.

Thank for all in advance!