I checked out the source code for dotStim and it looks pretty basic in its generation of the location of dots
def _newDotsXY(self, nDots):
"""Returns a uniform spread of dots, according to the fieldShape and fieldSize
usage::
dots = self._newDots(nDots)
"""
if self.fieldShape=='circle':#make more dots than we need and only use those that are within circle
while True:#repeat until we have enough
new=numpy.random.uniform(-1, 1, [nDots*2,2])#fetch twice as many as needed
inCircle= (numpy.hypot(new[:,0],new[:,1])<1)
if sum(inCircle)>=nDots:
return new[inCircle,:][:nDots,:]*0.5
else:
return numpy.random.uniform(-0.5, 0.5, [nDots,2])
It doesn’t seem to handle instances where in the event if the spawn location of a dot occurs within the perimeter of another (when two dots overlap), regenerate another random location. I can’t quite figure out how to do this at the moment. Anyone able to lend a helping hand with that? On a side note, I believe it would be a pretty useful added function in future psychopy updates for users who require such a feature for their experiments. In cases where nDot is say set at 10, nobody would really hope for a physical presentation of nDot = 9.5 in cases of a partial overlap or nDot = 9 in a full overlap (or an even smaller value if two or more dots overlap).
There are scenarios where overlapping elements are desirable, and I think it would be difficult to code a generic solution that avoids overlap when it isn’t desired. It is probably best left to the user to control the generation of the coordinates in such a situation.
Here’s some (untested) code that might help get you started:
import functools
import numpy as np
def generate(
n_dots,
gen_func=None,
test_func=None,
max_iter=10000
):
if gen_func is None:
# default is to return a random location between -1 and 1 for both
# dimensions
gen_func = functools.partial(
np.random.uniform,
low=-1.0,
high=1.0,
size=2
)
if test_func is None:
# default is to return True regardless of the established dots and the
# current candidate
test_func = lambda _, __: True
dots = []
iteration = 0
while iteration < max_iter:
candidate = gen_func()
candidate_ok = test_func(candidate, dots)
if candidate_ok:
dots.append(candidate)
if len(dots) == n_dots:
break
iteration += 1
else:
raise ValueError("Iteration limit reached")
dots = np.array(dots)
return dots
You need to provide it with a ‘test’ that returns True if the proposed dot is OK. For example, you could use something like the following if you wanted to make it such that the minimum dot distance was 0.5:
I ran the codes and it generates the new list nicely. My only problem now is not being able to get the dotStim to update the new positions subsequently. Below is a snippet of the crucial elements of the script that I am currently using:
So basically there is a visible change between the lists of position from the 1st and 2nd print. However the updated list wasn’t translated during the second draw (ie. the dots were still in the same position as before the test_func was initiated). What went wrong?
I think it is actually using _verticesBase internally rather than _dotsXY. You could change the targetStim._dotsXY = line to be targetStim._verticesBase =, but you might be better off overriding the new dot creation method:
You would need to do this before you create your targetStim.
Note however that the position constraints will currently only operate for the initially generated distribution; any replacements for ‘dead’ dots and some forms of dot motion will not have the same constraints.
Say if I have two different dot stimuli present simultaneously, which vary between 312 and 382 dots each. Would randomly generating a new dot if two overlap slow my program down too much? is there a workaround that would work?
Also, would doing so hinder my ability to upload my experiment online, as I would be moving away from using the builder view?