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.
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).
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.
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'
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.
@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.