A little new to python and psychopy. I’m trying to build a function that would sort of functionally replicate a roulette wheel and I’ve essentially done so. This function will revolve a small circle around a large circle and pressing 'space' while it is revolving will initiate an exponential deceleration in the revolution velocity of the smaller circle. In order to model the roulette wheel (the larger circle), I used the visual.RadialStim
component from PsychoPy
. It really was designed, as I understand it, to conveniently build the spinning checkerboard visuals often used as controls in fMRI experiments. However, that means that it evenly divides the wheel into alternating sections of color which is really helpful for my purposes. Here's a visualization of the task in action:
I'd like to be able to place more than two colors on the wheel, but I'm not sure that it's possible with visual.RadialStim
. Looking through the documentation, I can't see anything that helps, though, I did come across this old thread where Jon seems to suggest that it is possible, but I frankly can't make heads or tails of it and I think the OP left it unresolved as well. Does anyone know if my suspicions about RadialStim
are correct (i.e., can't use more than two colors)? Alternatively, does anyone have another recommended solution to replacing it so that I could maybe get 3 or 4 colors modeled on this larger circle?
PLEASE NOTE - for those unfamiliar with PsychoPy, it is a collection functions specially built for creating reseach studies and requires any code that uses it to be run in a PsychoPy terminal (rather than just any old Python terminal). PsychoPy can run any Python package, but a Python terminal cannot run PsychoPy code, so if you tried to run this on your own without PsychoPy, it likely will not work.
# Psychopy modules
from psychopy import core, event, visual, gui
# Needed to calculate the trajectory of the revolving ball
import math
# Needed to calculcate the deceleration of the revolving ball
import random
# Specifying which monitor to use
monitor=0
# The speed with which the ball revolves around the wheel
speed = 0.125
# The radius of the wheel around which the ball is revolving
wheel_radius=0.45
# How many frames per second the animation should use
fps = 30
# Specifying Window & Screen Information -----
win = visual.Window(size=(1024, 768),
fullscr=True,
screen= monitor,
winType='pyglet',
allowGUI=True,
allowStencil=False,
monitor='testMonitor',
color=[0,0,0],
colorSpace='rgb',
blendMode='avg',
useFBO=True,
units='height')
# Noting the starting position of the revolving ball
position = 0.0
# Noting whether the ball is decelerating
decelerate = False
# Creating the ball
ball = visual.Circle(win, edges=100,radius=0.02, fillColor='white', lineColor=None, pos=[position,position])
# Creating the wheel
wheel = visual.RadialStim(win, pos=(0,0), size=((wheel_radius * 2), (wheel_radius * 2)),
color =('red', 'blue', 'white'), angularRes=300,
angularCycles=6, radialCycles = 0, opacity= 0.8, autoLog=False)
# While speed is greater than 0:
while speed > 0:
# Change the position of the ball according to the current value of position
ball.pos = [((math.sin(position)/10) * (wheel_radius * 10)),
((math.cos(position)/10) * (wheel_radius * 10))]
# Produce the visualization of the wheel
wheel.draw()
# Produce the visualization of the ball
ball.draw()
# If the participant hasn't asked to stop the spinner yet
if decelerate == False:
# Continue spinning the ball around the wheel according to the specified speed
position += speed
# If the participant has asked to stop the spinner
if decelerate == True:
# Randomly select a value between 0.005 and 0.035
rand_dec = random.uniform(0.005,0.035)
# Reduce speed to be a percentage (99.5% - 96.5%) of its last value
# Randomizing the the value of the deceleration will hopefully prevent
# participants from being able to predict where the ball will stop. Also
# making speed a fraction or what it once was, rather than using a linear value
# will better model friction and exponential decay in the real world
speed *= 1 - rand_dec
# Continue spinning the ball around the wheel according to the new speed
position += speed
# If speed drops below 0.001
if speed < 0.001:
# Round speed down to 0
speed = 0
# If escape is pressed, end the task
if event.getKeys('escape'):
break
# If space is pressed, begin slowing the ball
if event.getKeys('space'):
decelerate = True
# Refresh the screen according to the core.wait rate allowing for objects and visualizations
# to change position
win.flip()
# How long psychopy should wait before updating the screen
core.wait(1/fps)
# close the window
win.close()
Try as I could, I could not get the texture approach to work, but I settled on a far less eloquent solution. By reducing the opacity of the RadialStim and overlaying another RadialStim of a complementary color at half opacity, and situated at a 30-degree angle, I was able to more or less create the appearance of four colors. Not thrilled, but it'll do for now. Looking forward to someone else showing me up.
# Psychopy modules
from psychopy import core, event, visual, gui
# Needed to calculate the trajectory of the revolving ball
import math
# Needed to calculcate the deceleration of the revolving ball
import random
# Needed to create colors for the roulette wheel
import numpy as np
# Specifying which monitor to use
monitor=0
# The speed with which the ball revolves around the wheel
speed = 0.125
# The radius of the wheel around which the ball is revolving
wheel_radius=0.45
# How many frames per second the animation should use
fps = 30
# Specifying Window & Screen Information -----
win = visual.Window(size=(1024, 768),
fullscr=True,
screen= monitor,
winType='pyglet',
allowGUI=True,
allowStencil=False,
monitor='testMonitor',
color=[0,0,0],
colorSpace='rgb',
blendMode='avg',
useFBO=True,
units='height')
# Noting the starting position of the revolving ball
position = 0.0
# Noting whether the ball is decelerating
decelerate = False
# Creating the ball
ball = visual.Circle(win, edges=100,radius=0.02, fillColor='white', lineColor=None, pos=[position,position])
# Creating the wheel
wheel_base = visual.RadialStim(win, pos=(0,0), size=((wheel_radius * 2), (wheel_radius * 2)),
color ='yellow', angularRes=300,
angularCycles=3, radialCycles = 0, opacity= 0.9, autoLog=False)
wheel_layer = visual.RadialStim(win, pos=(0,0), size=((wheel_radius * 2), (wheel_radius * 2)),
color ='red', angularRes=300, ori=30,
angularCycles=3, radialCycles = 0, opacity= 0.5, autoLog=False)
# While speed is greater than 0:
while speed > 0:
# Change the position of the ball according to the current value of position
ball.pos = [((math.sin(position)/10) * (wheel_radius * 10)),
((math.cos(position)/10) * (wheel_radius * 10))]
# Produce the visualization of the wheel
wheel_base.draw()
wheel_layer.draw()
# Produce the visualization of the ball
ball.draw()
# If the participant hasn't asked to stop the spinner yet
if decelerate == False:
# Continue spinning the ball around the wheel according to the specified speed
position += speed
# If the participant has asked to stop the spinner
if decelerate == True:
# Reduce speed to be a percentage (99.5% - 96.5%) of its last value
# Randomizing the the value of the deceleration will hopefully prevent
# participants from being able to predict where the ball will stop. Also
# making speed a fraction or what it once was, rather than using a linear value
# will better model friction and exponential decay in the real world
speed *= 1 - rand_dec
# Continue spinning the ball around the wheel according to the new speed
position += speed
# If speed drops below 0.001
if speed < 0.001:
# Round speed down to 0
speed = 0
# If escape is pressed, end the task
if event.getKeys('escape'):
break
# If space is pressed, begin slowing the ball
if event.getKeys('space'):
decelerate = True
# Randomly select a value between 0.005 and 0.035
rand_dec = random.uniform(0.005,0.035)
# Refresh the screen according to the core.wait rate allowing for objects and visualizations
# to change position
win.flip()
# How long psychopy should wait before updating the screen
core.wait(1/fps)
# close the window
win.close()