Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Supported by

Speeding up (vectorising?) a for loop

edited May 2016 in OpenSesame

We are developing a dynamic visual search experiment, where a number of dots move randomly around the screen. The code involves a series of for loops (an extract from the code is pasted in below). Our issue is that as NStim increases, the time taken to execute the for loops increases, meaning that the dots are moving slower when there are more of them. Is it possible to vectorise the code in order to avoid this problem? (sorry, we have some experience with Matlab but not with Python). We'd really like the dots to move at the same speed!

Thanks!

# Loop through all frames
for j in range(NFrames):

        # For each frame, clear the canvas and then loop through all stimuli
        myCanvas.clear()
        for i in range(NStim):
                # Get the stimulus properties
                x, y, a, v, r = stimList[i]
                # Update the position of the stimulus based on the angle and the speed
                x += v * math.cos(a)
                y += v * math.sin(a)   
                # If the stimulus leaves the screen, reverse direction by 180 deg (= 1pi radial)
                if x <= 0 or x >= self.get('width') or y <= 0 or y >= self.get('height'):
                        a = a + math.pi
                else:
                        # else randomly rotate the stimulus a bit
                        a += (random.random()-.5) * maxRotSpeed                
                # Highlight the targets on the first frame
                if i < NTarget and j == 0:
                        color = targetColor
                else:
                        color = normalColor                    
                # Draw the stimulus
                myCanvas.circle(x, y, r, fill=True, color=color)
                # Store the new stimulus properties back in the stimulus list
                stimList[i] = x, y, a, v, r
        # Show the canvas      
        myCanvas.show()
        # Sleep after the first frame so that the participant can identify the targets
        if j == 0:
                self.sleep(firstFrameDur)
# Store the coordinates of the stimuli in the last frame, in order to compare
# the mouse click responses with the actual positions

Comments

  • edited 1:17PM

    Hi,

    Rendering the stimuli like this is not optimal, but in most cases it should be fast enough. What I think might be going on is that you're using the xpyriment back-end, and have not set the auto_prepare keyword to False when creating canvas objects (it's True by default). When auto_prepare is True the canvas will be prepared for being flipped to the display after every drawing operation, which takes a bit of time. If you set it to False, drawing will go much faster, but you have to explicitly call canvas.prepare(). This is illustrated in the example:

    This, by the way, is currently specific to the xpyriment backend.

    Does this help? If not, there are plenty of other ways to speed up rendering.

    Cheers,
    Sebastiaan

    There's much bigger issues in the world, I know. But I first have to take care of the world I know.
    cogsci.nl/smathot

  • edited 1:17PM

    Hi Sebastiaan,

    thanks for your reply! We're actually not using xpyriment backend (I just ran it using xpyriment and it was REALLY slow). It is much faster/smoother in pyscho & legacy, but there is still a slight speed difference between the Nstim sizes (i.e. 5 dots move quicker than >5 dots), which isn't ideal.

    Thanks,
    Khia

  • edited May 2016

    In that case, it's probably best to really implement this properly, using PsychoPy, which allows you to update stimuli on the fly. So you don't need to clear the canvas and redraw everything from scratch, but rather simply change the position of stimuli. This is much faster, so you should be able to draw as many stimuli as you want.

    Below you see an example script that does more or less what you need. You can just copy-paste it into an inline_script. I also wrapped the moving dot into a nice class, so that the code remains clean. But I don't think you need any real knowledge of object-oriented programming to use this.

    Does this get you started?

    from psychopy import visual
    import random
    import math
    
    nstim = 10
    nframes = 500
    
    class MovingDot:
    
        """
        A single moving dot.
        """
    
        def __init__(self, x, y, v, d, maxrot):
    
            """
            Create a new dot.
    
            x -- starting x coordinate
            y -- starting y coordinate
            v -- starting velocity in px per frame
            d -- starting direction in radial angle
            maxrot -- the maximum random rotation of the angle.
            """
    
            self._v = v
            self._d = d
            self._maxrot = maxrot
            self._circle = visual.Circle(win, radius=20, pos=(x,y))
    
        def draw(self):
    
            """
            Draw the dot, but don't update the display.
            """
    
            self._circle.draw()
    
        def move(self):
    
            """
            Move the dot and handle direction reversals etc.
            """
    
            x, y = self._circle.pos
            dx = math.cos(self._d) * self._v
            dy = math.sin(self._d) * self._v
            # Reverse direction if the dot leaves the screen
            if abs(x+dx) > var.width/2 or abs(y+dy) > var.height/2:
                self._d += math.pi
                self.move()
                return
            self._circle.pos = x+dx, y+dy
            self._d += (random.random()-.5) * self._maxrot
    
    # Create a list of moving dots
    mdlist = []
    for i in range(nstim):
        md = MovingDot(x=0, y=0, v=10, d=random.random()*2*math.pi, maxrot=.5)
        mdlist.append(md)
    
    # Draw all moving dots
    for i in range(nframes):
        for md in mdlist:
            md.move()
            md.draw()
        win.flip()
    

    There's much bigger issues in the world, I know. But I first have to take care of the world I know.
    cogsci.nl/smathot

  • edited 1:17PM

    That's great thank you Sebastiaan!

    Seeing as you have been so helpful, do you happen to know if/how I can interrupt the trial before nframe runs to its end? So nframe would be the maximum run length of a trial before time-out but the trial could be halted at any point before that by moving the mouse and having the cursor appear on the screen.

    I would like to be able to move the cursor in the trial window before the max nframe so I can select a target (that is then recorded with the data logger before a new trial begins).

    Many thanks,

    Khia

Sign In or Register to comment.