Setting more than one color for visual.RadialStim

Background:

I’m trying to build a task that would basically replicate a roulette wheel and I’ve essentially done so. This task will revolve a small circle around a large circle and pressing space 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, knowing full-well that it’s probably better used for spinning checkboard type stuff. However, it evenly divides the wheel into alternating sections of color which is really helpful for my task.

PsychoPy_2022-04-04_16-27-33_AdobeCreativeCloudExpress

Problem:

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 for sure? If not, does anyone have another recommended solution to replacing it so that I could maybe get 3 or 4 colors modeled on this larger circle?

Code:

# 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

# Variables
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

# Draw a circle
ball = visual.Circle(win, edges=100,radius=0.02, fillColor='white', lineColor=None, pos=[position,position])

# Draw a circle
# wheel = visual.Circle(win, edges=300, radius=wheel_radius, fillColor='black', lineColor=None, pos=[0,0])

wheel = visual.RadialStim(win, pos=(0,0), size=((wheel_radius * 2), (wheel_radius * 2)),
                          color = 'red', 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_2022-04-06_17-58-30_AdobeCreativeCloudExpress (2)

# 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()