psychopy.org | Reference | Downloads | Github

Sphere consisting of dots

Hello this is what I’m currently using to generate a rotating sphere of dots in Psychopy. Is there any better way of doing this?

warper = Warper(self.experiment_window, warpfile="", eyepoint=eyepoint,
flipHorizontal=False, flipVertical=False, warpGridsize=warp_grid_size)
warper.dist_cm = distance_from_monitor
#warper.changeProjection(“spherical”)
field_size = (field_size[1:-1].split(’,’)[0], field_size[1:-1].split(’,’)[1])
right = dot.DotStim(win=self.experiment_window, nDots=number_of_dots, units=‘pix’,
fieldShape=field_shape, fieldSize=field_size,
dir=direction, speed=speed, dotLife=dot_life, coherence=coherence)

Hello,

Is this a sphere that the observer is within and the dots are moving around? Or are you trying to generate a sphere of dots away from the viewer?

-mdc

The latter.It is the perceptual rivalry task in this paper.

Hello,

This example should help you get the result you want. Note that we’re rendering the sphere with perspective here.

from psychopy import event, core
import numpy as np
import pyglet.gl as GL
import psychopy.visual as visual
import psychopy.tools.mathtools as mt
import psychopy.tools.gltools as gltools

win = visual.Window(monitor='testMonitor', size=(800, 800),
                    color='Black', allowGUI=True, fullscr=False)

# configure how the spehre is drawn
NDOTS = 500
SPHERE_SCALE = 0.25
SPHERE_POS = (0, 0, -1.5)  # 1.5 meters away from screen, -Z is forward in OpenGL

# note that the distance from the viewer to SPHERE_POS is abs(viewdist + SPHERE_POS[2])
length = mt.normalize(np.random.uniform(-1, 1, size=(NDOTS, 3))) * SPHERE_SCALE

# Create a buffer in GPU memory with vertex data, it must be set as
# GL_DYNAMIC_DRAW since we are updating it's contents often.
vertBuffer = gltools.createVBO(length, usage=GL.GL_DYNAMIC_DRAW)

# create a vertex array object
vertVAO = gltools.createVAO({0: vertBuffer})

# Here is how you can update dots. The buffer is in GPU memory, but needs to be
# accessed by the CPU. Use `mapBuffer` to get a pointer to the buffer. You can
# then treat the buffer like a regular numpy array. Be super careful though,
# you need to unmap the buffer right after writing new values to it. The data
# has to have the same size as the original data (same number of dots).
#    ptrVerts = gltools.mapBuffer(vertBuffer)
#    ptrVerts[:, :] = mt.normalize(np.random.uniform(-1, 1, size=(NDOTS, 3))) * SPHERE_SCALE
#    gltools.unmapBuffer(vertBuffer)  # ABSOLUTELY CALL THIS AFTER SETTING VALUES!

# store rotation angle
rotationDir = 1  # make 1 or -1 to switch directions
rotationAngle = 0.0

while 1:
    # set a perspective view, needs a monitor configuration with viewing
    # distance defined
    win.setPerspectiveView()

    # enable 3D drawing
    win.draw3d = True
    GL.glPushMatrix()
    GL.glColor3f(1, 1, 1)  # set dot color
    GL.glPointSize(6.0)  # size in pixels
    GL.glTranslatef(*SPHERE_POS)
    GL.glRotatef(rotationAngle * rotationDir, 0, 1, 0)  # angle, ax, ay, az
    gltools.drawVAO(vertVAO, GL.GL_POINTS)  # draw the VAO as points
    GL.glPopMatrix()
    win.draw3d = False

    rotationAngle += 0.1   # rotation in degrees per frame

    # reset if angle greater than 1 revolution
    if rotationAngle > 360.0 or rotationAngle < -360.0:
        rotationAngle = 0.0

    win.flip()

    if event.getKeys():
        break

core.quit()

If you want to draw with no perspective, do the following. Note the the scale of the stimulus will be in normalized screen units (eg. 0.5 will make the sphere have a radius of which is equal to half the width of the screen). There is no translation anymore.

from psychopy import event, core
import numpy as np
import pyglet.gl as GL
import psychopy.visual as visual
import psychopy.tools.mathtools as mt
import psychopy.tools.gltools as gltools

win = visual.Window(monitor='testMonitor', size=(800, 600),
                    color='Black', allowGUI=True, fullscr=False)

# configure how the spehre is drawn
NDOTS = 1000
SPHERE_SCALE = 0.25  # in screen units

length = mt.normalize(np.random.uniform(-1, 1, size=(NDOTS, 3))) * SPHERE_SCALE

# Create a buffer in GPU memory with vertex data, it must be set as
# GL_DYNAMIC_DRAW since we are updating it's contents often.
vertBuffer = gltools.createVBO(length, usage=GL.GL_DYNAMIC_DRAW)

# create a vertex array object
vertVAO = gltools.createVAO({0: vertBuffer})

# Here is how you can update dots. The buffer is in GPU memory, but needs to be
# accessed by the CPU. Use `mapBuffer` to get a pointer to the buffer. You can
# then treat the buffer like an regular numpy array. Be super careful though,
# you need to unmap the buffer right after writing new values to it. The data
# has to have the same size as the original data (same number of dots).
#    ptrVerts = gltools.mapBuffer(vertBuffer)
#    ptrVerts[:, :] = mt.normalize(np.random.uniform(-1, 1, size=(NDOTS, 3))) * SPHERE_SCALE
#    gltools.unmapBuffer(vertBuffer)  # ABSOLUTELY CALL THIS AFTER SETTING VALUES!

# store rotation angle
rotationDir = 1  # make 1 or -1 to switch directions
rotationAngle = 0.0

while 1:
    # set a perspective view, needs a monitor configuration with viewing
    # distance defined
    GL.glMatrixMode(GL.GL_PROJECTION)
    GL.glLoadIdentity()
    aspect = win.size[0] / win.size[1]
    GL.glOrtho(-1 * aspect, 1 * aspect, -1, 1, -1, 1)

    # clear the depth buffer
    win.depthMask = True
    GL.glClear(GL.GL_DEPTH_BUFFER_BIT)

    GL.glMatrixMode(GL.GL_MODELVIEW)
    GL.glLoadIdentity()
    # enable 3D drawing
    win.draw3d = True
    GL.glPushMatrix()
    GL.glColor3f(1, 1, 1)  # set dot color
    GL.glPointSize(6.0)  # size in pixels
    GL.glRotatef(rotationAngle * rotationDir, 0, 1, 0)  # angle, ax, ay, az
    gltools.drawVAO(vertVAO, GL.GL_POINTS)  # draw the VAO as points
    GL.glPopMatrix()
    win.draw3d = False

    rotationAngle += 0.1   # rotation in degrees per frame

    # reset if angle greater than 1 revolution
    if rotationAngle > 360.0 or rotationAngle < -360.0:
        rotationAngle = 0.0

    win.flip()

    if event.getKeys():
        break

core.quit()

Thanks. I’m currently unable to run the code since I don’t have a GPU. I will get back to you incase I need anything.

I tried running the code on collab but I’m unable to do it because of some openGl errors. Have you tried rendering images using a remote server on local display ?

No I haven’t. I’m not sure how to configure things to do that. Can you render other things in PsychoPy remotely (like basic stimuli)?

I wasn’t able to get it to work. Is there anyway to do this without using a GPU?

Hello,

PsychoPy requires hardware acceleration for rendering stimuli. You’ll need to have it installed on a system with a supported graphics processor to render anything.

-mdc

Oh okay. Thanks!

This link has code which has a rotating cylinder. I want to generate a sphere in the same manner.

I tried but I am unable to do say. Here are the changes that I have made. My code generates a top of rotating but I want a side view. How do I achieve this?

gui = psychopy.gui.Dlg()

    gui.addField("Subject ID:")
    gui.addField("Cond. num.:")
    gui.addField("Repeat num:")

    gui.show()

    subj_id = gui.data[0]
    cond_num = gui.data[1]
    rep_num = gui.data[2]

    data_path = subj_id + "_cond_" + cond_num + "_rep_" + rep_num + ".tsv"

    if os.path.exists(data_path):
        sys.exit("Data path " + data_path + " already exists!")

    responses = []

    r = 200
    h = 200
    sfm_dot_size_pix = 5
    sfm_n_dots = 1000
    sfm_dot_shape = "gauss"

    if cond_num == "1":
        sfm_speed_rev_per_s = 0.2
    elif cond_num == "2":
        sfm_speed_rev_per_s = 0.1
    else:
        sys.exit("Unknown condition number")

    z = np.random.uniform(0, r * np.pi/2, sfm_n_dots)

    theta = np.random.uniform(0, np.pi/2, sfm_n_dots)

    win = psychopy.visual.Window(
        size=[400, 400],
        units="pix",
        fullscr=False
    )

    sfm_stim = psychopy.visual.ElementArrayStim(
        win=win,
        units="pix",
        nElements=sfm_n_dots,
        elementTex=None,
        elementMask=sfm_dot_shape,
        sizes=sfm_dot_size_pix
    )

    instructions = psychopy.visual.TextStim(
        win=win,
        wrapWidth=350,
    )

    instructions.text = """
    Press the left arrow key when you see the front surface rotating to the left.\n
    Press the right arrow key when you see the front surface rotating to the right.\n
    \n
    Press any key to begin.
    """

    instructions.draw()
    win.flip()

    psychopy.event.waitKeys()

    duration_s = 10.0

    clock = psychopy.core.Clock()

    while clock.getTime() < duration_s:

        #controls the speed
        phase_offset = (clock.getTime() * sfm_speed_rev_per_s) * (2 * np.pi)

        sfm_xys = []

        for i_dot in range(sfm_n_dots):

            #dot_x_pos = np.sin(theta[i_dot]) * np.cos(z[i_dot] + phase_offset) * r / 2.0
            #dot_y_pos = np.sin(theta[i_dot]) * np.sin(z[i_dot] + phase_offset) * r / 2.0
            dot_x_pos = np.sin(theta[i_dot]) * np.cos(z[i_dot] + phase_offset) * r / 2.0
            dot_y_pos = np.sin(theta[i_dot]) * np.sin(z[i_dot] + phase_offset) * r / 2.0
            sfm_xys.append([dot_x_pos, dot_y_pos])

        sfm_stim.xys = sfm_xys

        sfm_stim.draw()

        win.flip()

        keys = psychopy.event.getKeys(
            keyList=["left", "right"],
            timeStamped=clock
        )

        for key in keys:

            if key[0] == "left":
                key_num = 1
            else:
                key_num = 2

            responses.append([key_num, key[1]])

    np.savetxt(data_path, responses, delimiter="\t")

    win.close()