psychopy.org | Reference | Downloads | Github

shapeStim.overlaps method with multiple shapes

I’m building an experiment (version of the corsi block), which draws n squares on the screen. This is accomplished by looping the visual.shapeStim into a list of items called my_box so that I can access its properties later (e.g. my_box[0].lineColor = ...). An important feature of course is that the boxes do not overlap but since they are plotted on the screen randomly for each iteration, I need to check the coordinates all the time.

I was hoping to use the “overlaps” method however it only takes a single input.

(my_box[block].overlaps(my_box[block-1]))

I would need to test whether any of the objects overlap with any of the others; not just the previous one. I could make another long loop with each box against the others. But is there a way to use this method or any other one you can think of which would accomplish this?

Maybe also with an additional buffer zone so that blocks aren’t too close to each other? Or is there a way to generate random coordinates in a way that they are not too close to each other and so handle the problem before it even gets to the object stage?

Thanks for the help and advice.

I think doing it your way would indeed require a loop that so that you check against all the other blocks each time.

But another way might be to create a regular grid (how many blocks will be in your task?) and then allow jitter within that, whereby you know the limits of the jitter that will prevent overlaps.

give me a moment to knock up a demo…

from psychopy import visual, core
import numpy as np

nBlocks = 10
blockSize = 50
jitterX = 50
jitterY = 50
grid = []
for x in range (-200,201,100):
    for y in range(-200,201,100):
        grid.append( [x,y] )
nElements = len(grid)
grid = np.array(grid)
print grid.shape

win = visual.Window([1024,768], units='pix')

blocks = []

#create a list of blocks (can be shuffled)
for ii in range(nElements):
    thisBox = visual.Rect(win, size=blockSize) # we'll set pos each frame
    blocks.append(thisBox)

for trialN in range(5):
    #create new XY positions
    jitter = np.random.random(grid.shape)
    jitter[:,0] *= jitterX
    jitter[:,1] *= jitterY
    xys = grid+jitter
    for boxN, thisBox in enumerate(blocks):
        thisBox.pos = xys[boxN]
    
    np.random.shuffle(blocks) # so that the ones drawn get randomised
    
    # on each frame
    for frameN in range(120):
        if frameN == 20:
            blocks[0].fillColor = 'black'
        elif frameN == 40:
            blocks[0].fillColor = None
            blocks[1].fillColor = 'black'
        elif frameN == 60:
            blocks[1].fillColor = None
            blocks[2].fillColor = 'black'
        elif frameN == 80:
            blocks[2].fillColor = None
        
        #then draw them all
        for thisBox in blocks[:nBlocks]: 
            # draw the first N blocks (rest are skipped)
            thisBox.draw()
        
        win.flip()
    #end of trial
1 Like

Thanks a lot for looking into such a detailed solution!

I ended up using a different approach to the problem using the box centers as coordinates and then using scipy’s pdist function to compute the distance matrix between all blocks and then check if the distances were at least the distance apart of the blocks edges plus some buffer (ended up with a large buffer to encourage lots of spacing)… then just convert the box centers into the vertices with another little function

def boxCoordinates(centre, size):

    size = size / 2    
    
    box_vertices = [ \
    [centre[0] - size, centre[1] - size], \
    [centre[0] - size, centre[1] + size], \
    [centre[0] + size, centre[1] + size], \
    [centre[0] + size, centre[1] - size] ]
    
    return box_vertices


def getBoxPositions(num_box, box_size):

    #calculate minimum safe distance
    min_dist = 1.0 / num_box + box_size
       
    # check minimum distance for all element pair distances
    while True:
        # generate random positions
        centers = np.random.uniform(-0.8, 0.8, [num_box, 2])   
        
        dist = scipy.spatial.distance.pdist(centers)
        
        if dist.min() > min_dist:
            break

    # allocate vertices array
    box_vertices=num_box*[None]    

    for block in range(0, num_box):
        box_vertices[block] = boxCoordinates(centers[block, :], box_size) 
    
    return box_vertices

This works well for anything I’ve tried with less than 10 boxes, which is probably more than the effective maximum for the corsi block test anyways.

Cool. Many ways to skin a cat (a saying I’ve never understood)