Have my stimuli appear across the entirety of the screen, not just a smaller location

Hello everyone,

I am making good progress with my visual search task and I have managed to create two sets of random distractors of Red and Green Letter T’s, however due to the code I have implemented, rather than use the entire screen, the distractors only appear in a small subset in the center of the screen

As you can see from the image the items are located relatively closely together and they can overlap at times.

This is my code (see below) I used to create my stimuli. I know why it’s happening, because I use Norm units (I find norm units the easiest to understand) So the random function is picking a number between 0-1 and then subtracting 0.5 in order to balance the display, I have tried messing around with other values but then my stimuli are no longer centered. Would it be more optimal to switch to pixels, and if so, what would my random function become?

Also would it be more efficient to use an element arraystim to create a screen of various targets and a singular distractor?

T_textlargeRed =
T_textlargeGreen=

targetPos=(random()-0.5, random()-0.5)

for x in range(10):
T_textR = visual.TextStim(win=win, name=‘T_textR’,
text=‘T’,
font=‘Open Sans’,
pos=(random()-0.5, random()-0.5), height=0.1, wrapWidth=None, ori=0.0,
color=‘red’, colorSpace=‘rgb’, opacity=None,
units = ‘norm’,
languageStyle=‘LTR’,
depth=0.0)

    T_textlargeRed.append(T_textR)

print(T_textlargeRed)

for letter in T_textlargeRed:
letter.setAutoDraw(True)

Did you know that norm units go from -1 to +1 in each dimension?

Hi, thanks for the assistance again! Yes, I read about the types spacial units yesterday to refresh my memory. random() is between 0 and 1, with -0.5 added to x and y to create a spread that is not skewed towards left/right of the monitor. I just realised I never tested just random() without any minus-05

Maybe I have misunderstood, but if random() selects a random number 0-1, then I need to increase the number I minus from this to create a better spread, say -1 in order to account for norm ranging from between -1 to 1

In my readings yesterday I actually wondered whether random()-0.is the optimal way to randomize my stimuli, rather randomrange() maybe better, as I no longer need to perform a calculation on the outcome of random(), but rather set the range between -1 and 1 then have some step size that would create enough random permutations, do you think this would make more sense?

Kind regards,

Rob

Hi, I encontered the same problem while programming a visual search task. In order to avoid overlaps, I checked the positions with np.allclose() (also tried np.isclose() and math.isclose()):

for i in range(1,n_items):
    x_pos = np.random.uniform(-0.3, 0.3)
    y_pos = np.random.uniform(-0.3, 0.3)
    x_comp = np.allclose(x_pos, x_locations[i-1], rtol = 0.01)
    y_comp = np.allclose(y_pos, y_locations[i-1], rtol = 0.01)
    while x_comp == TRUE:
        x_pos = np.random.uniform(-0.3, 0.3)
    while y_comp == TRUE:
        y_pos = np.random.uniform(-0.3, 0.3)
    x_locations.append(x_pos)
    y_locations.append(y_pos)

Still, I get overlapped items sometimes and after 5-15 repetitions, the experiment crashes.

So I was wondering if you found a solution for your problem about the overlapped items in visual search that may work better?

Hey Mel,

The best solution I found was adding an offset to either the X or Y co-ordinates. Are you running your experiment online?

Still, I get overlapped items sometimes and after 5-15 repetitions, the experiment crashes. - as in stimuli being created?

Kind regards,

Rob

Hi @Mel499,

I’m not a Python pro, but looking at your code I don’t think it does what you want it to do. Firstly, you only compare item i and item i-1 in terms of being close. So there is nothing in the code preventing, for example, item i and item i-2 from overlapping. Second, you only compare x and y coordinates separately. Third, once x_comp or y_comp are True (you write TRUE so this may also be a problem) you go into an infinite while loop, because inside the loop those values can’t change and the loop will therefore never break. I suspect this is why the experiment crashes at some point.

Maybe, this is more suitable for you:

for i in range(1,n_items):
    x_pos = np.random.uniform(-0.3, 0.3)
    y_pos = np.random.uniform(-0.3, 0.3)

    x_close = np.isclose(x_pos, x_locations, rtol = 0.01)
    y_close = np.isclose(y_pos, y_locations, rtol = 0.01)

    overlap = np.any(np.logical_and(x_close, y_close))
    
    while overlap == True:
        x_pos = np.random.uniform(-0.3, 0.3)
        y_pos = np.random.uniform(-0.3, 0.3)

        x_close = np.isclose(x_pos, x_locations, rtol = 0.01)
        y_close = np.isclose(y_pos, y_locations, rtol = 0.01)

        overlap = np.any(np.logical_and(x_close, y_close))
    
    x_locations.append(x_pos)
    y_locations.append(y_pos)

If you still get to much overlap with this code, you can increase rtol to make the definition of “close” more liberal or specify an absolute tolerance (may be easier to control) by including the argument atol instead of rtol. Now that I think of it rtol = .01 is probably way too low to prevent overlap.

1 Like

Hi @ajus ,

thank you so much for your suggestion! This was indeed the problem, with your help, the code works just fine right now. In the end, I used atol instead of rtol, because the items were still overlapping.

2 Likes

OS (e.g. Win10):
macOS Ventura13.0

PsychoPy version (e.g. 1.84.x):
PsychoPy v2022.2.4

Hello everyone,

I am trying to do a very similar thing as the one being discussed in this thread.
I am very new to psychopy and was hoping that someone in this forum can detect where I go wrong.

I am making a visual search task that consist of a target and three different distractors. Psychopy draw these via my input file. On top of that I have added a code component so that the distractors get drawn 16 times each for a total of 48 distractors. This works fine. But of course overlapping is an issue.
I have attempted to implement the code presented by ajus in this thread.

My code is as follows:

n_distractors=16

distractors=

for i in range(n_distractors):
distractor = visual.TextStim(win=win, name=‘distractor’,
text=(text),
font=‘Open Sans’,
pos=(random()-0.5,random()-0.5), height=0.1, wrapWidth=None, ori=0.0,
color=(text_color), colorSpace=‘rgb’, opacity=None,
languageStyle=‘LTR’,
depth=0.0);

distractors.append(distractor)    

for i in range(n_distractors):
distractor = visual.TextStim(win=win, name=‘distractor’,
text=(text_two),
font=‘Open Sans’,
pos=(random()-0.5,random()-0.5), height=0.1, wrapWidth=None, ori=0.0,
color=(text_two_color), colorSpace=‘rgb’, opacity=None,
languageStyle=‘LTR’,
depth=0.0);

distractors.append(distractor)    

for i in range(n_distractors):
distractor = visual.TextStim(win=win, name=‘distractor’,
text=(text_three),
font=‘Open Sans’,
pos=(random()-0.5,random()-0.5), height=0.1, wrapWidth=None, ori=0.0,
color=(text_three_color), colorSpace=‘rgb’, opacity=None,
languageStyle=‘LTR’,
depth=0.0);

distractors.append(distractor)

for distractor in distractors:
distractor.setAutoDraw(True)

for i in range(1,n_distractors):
x_pos=np.random.uniform(-0.3,0.3)
y_pos=np.random.uniform(-0.3,0.3)

x_close=np.isclose(x_pos, x_locations, atol=0.01)
y_close=np.isclose(y_pos, y_locations, atol=0.01)
    
overlap=np.any(np.logical_and(x_close, y_close))
    
while overlap==True:
    x_pos=np.random.uniform(-0.3,0.3)
    y_pos=np.random.uniform(-0.3,0.3)
        
    x_close=np.isclose(x_pos, x_locations, atol=0.01)
    y_close=np.isclose(y_pos, y_locations, atol=0.01)
        
    overlap=np.any(np.logical_and(x_close, y_close))
        
x_locations.append(x_pos)
y_locations.append(y_pos)

When I try to run, the experiment close with this message:

x_close=np.isclose(x_pos, x_locations, atol=0.01)

NameError: name ‘x_locations’ is not defined
################# Experiment ended with exit code 1 [pid:1195] #################

If anyone could help me with how I might go about fixing this, it would be much, much appreciated.

Best regards, Mathilde

Hey Mathilde

I think the issue is ‘x_locations in not defined, is that it isn’t defined before you need it, or Psychopy can’t understand what this is referring too. Are x_locations defined prior to your while loop?

Kind regards,

Rob

Hey Rob,
Thanks for your reply!
It’s very likely the issue.
Would you happen to know how I define x_locations?
In the part of the code that is related to the text stimuli, under position maybe or something completely different?

Hey no worries!

I would create an empty array at the beginning of the routine.

x_locations = [  ]

See if that helps?

Kind regards,

Rob

Yea it did! In the sense that the experiment is now running and not closing with an error.
However, all the distractors are now in the same position, looking like a big clump.
Do you have an idea what could be causing that?

Best regards, Mathilde

Amazing! hmm, This sounds similar to an issue I had ages ago.

When you say in the same position, I assume it’s plotting them all on the same position, so you have the target, then 48 Distractors on top of each other?

I have a feeling what it’s doing now, is making some random positions, then applying it to each distractor, which is not what we want, we want random positions per distractor.

Yes that was exactly what it was doing.
And yes, I just realized that I had removed ‘pos=(random()-0.5,random()-0.5)’ at some point and then forgotten about it… I have put it back, and it seems that the distractors are being distributed better than before I used the code, with less overlap than before.
I still get some overlap though between distractors.
I Wonder if distances can be made bigger still using this code? Thanks again!

1 Like

What I do is create a grid of possible points, shuffle them and then add a little jitter to create random locations without overlap.

e.g.

Begin Experiment

maxX = .4
maxY = .35
nX = 10
nY = 8
positions = []
for Idx in range(nX):
    for Jdx in range(nY):
        positions.append([Idx/(nX-1)*maxX*2-maxX,Jdx/(nY-1)*maxY*2-maxY])

jitter = .05
maxElements = 24
elements = []


for Idx in range(maxElements):
    elements.append(visual.TextStim(win=win, 
    name='element'+str(Idx),
    text=' ',
    font='Arial',
    pos=(0,0),
    height=0.06, wrapWidth=None, ori=0, 
    color="white"))

Begin Routine

shuffle(positions)
elements[0].text = Target
elements[0].setPos([positions[0][0]+random()*jitter-random()*jitter, positions[0][1]+random()*jitter-random()*jitter])
elements[0].setAutoDraw(True)
for Idx in range(1,nDistractors):
    elements[Idx].text = Distractors
    elements[Idx].setPos([positions[Idx][0]+random()*jitter-random()*jitter, positions[Idx][1]+random()*jitter-random()*jitter])
    elements[Idx].setAutoDraw(True)

This comes from a visual search demo that it appears I haven’t yet uploaded to my Online Demos. tTraceTest8 uses something similar but (I think) avoids any points being directly in line horizontally or vertically.

In the end I did something quite simple - I set the y-coordinate to always be the same and the x-axis to be random, which was a good enough solution to the problem in this case.
Thank you for taking the time and replying to my question, much appreciated.