pythonpsychopy

How can I make a circular visual object with its area evenly divided among three or more colors?


Background:

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:

enter image description here

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 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?

Code:

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

Solution

  • 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.

    enter image description here

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