psychopy.org | Reference | Downloads | Github

Can the dots of a visual.DotStim be made to wrap around?

I need to generate dots that move at a constant speed with 100% coherence (i.e., no noise dots) within a circular aperture. The dots should move until they hit the boundary of the circle and then wrap around to the other side. This can almost be accomplished with the visual.DotStim class, except for the wraparound. Once the dots reach they boundary, they are initialized at a random location within the field. I think this random initialization (following a dot reaching the boundary) is why the dot density ends up non-uniform, as in the attached figure (e.g., the dots are moving upwards, so there are more ways that a dot could end up near the top of the field relative to the bottom of the field).

Is there a way to use the visual.DotStim class so that the dots wrap? Here’s a snippet with how I am generating the dots. Let me know if an executable demo would help.

dots = visual.DotStim(
            win=win,
            units='deg',
            fieldShape="circle",
            dotSize=deg2pix(0.08, mon),
            dotLife=-1,
            coherence=1,
            nDots=1000,
            fieldSize=10,
            speed=0.03)

This may be possible if you specify the ‘xys’ of the dots yourself. You’ll need to come up with your own method of generating positions of points over time and handling the wraparound condition.

This wraparound feature seems like something that should be included with default DotStim, I’ll see to it this functionality is made available in a future release.

1 Like

Sounds good. Here’s a gist for how I’m wrapping around with a circular field (defined as part of the _update_dotsXY method for a class copied from DotStim)

        norms = np.linalg.norm(self._verticesBase, axis=1)
        dist_beyond = np.clip(norms - self.fieldSize[0]/2, 0, None)
        # reflect those vertices which are beyond the boundary
        self._verticesBase[dist_beyond > 0] *= -1 
        # push the dot back to the boundary (dist_beyond once), then through the boundary 
        # based on how far it overshot (dist_beyond twice)
        self._verticesBase[:, 0] += 2*dist_beyond * np.cos(self._dotsDir)
        self._verticesBase[:, 1] += 2*dist_beyond * np.sin(self._dotsDir)