psychopy.org | Reference | Downloads | Github

mouse.isPressedIn() which of several shapes - for loop is slow

Hello,

I have nine shapes on the screen, and I want to detect which one is clicked. I have implemented a working solution by putting the shapes in a list and looping through to check whether each one has been clicked with mouse.isPressedIn(shape[n]). However, it is slow and sometimes you have to click a few times before it is registered. Is there a faster way to code this that doesn’t use a loop?

Here’s some example code:

shapes = [button1, button2, button3, button4, button5, button6, button7, button8, button9]

clicked = False
while not clicked:
    for n in range(len(shapes)):
        if mouse.isPressedIn(shape[n]):
            clicked = True
            response = n

print response

Any help would be much appreciated!

Sarah

Hi Sarah,

There is a tiny optimisation you could apply here, in that in for loops, we should (usually) avoid recalculating the upper bound on every iteration (which you are doing by calling range(len()) each time. But with a small list like this, the delay will be negligible. But for reference, if you want to cycle through the elements of a list while also getting an index number, you can do this in Python:

for n, shape in enumerate(shapes):
    if mouse.isPressedIn(shape):
        clicked = True
        response = n

But the real reason for the slowdown may be because your loop is running too fast. The way it is currently structured is that the code will repetitively check through the list of shapes hundreds, thousands, or more likely, millions of times before detecting a mouse button press. This may be starving the computer of the opportunity to do anything else. At some point, the operating system will jump in and seize control from your code so that the computer can do some urgent housekeeping. This may mean that that computer is actually caught up in some other task when the mouse press occurs and so you don’t detect it until control returns to you.

The way to avoid this is by being a good programming citizen, and hand back control to other processes frequently but briefly, so that no bottleneck builds up. There are two ways we tend to do this in PsychoPy code (i.e. throttling down our code). One is to call Python’s time.sleep() function on each iteration (even just for a millisecond). This pauses your code briefly and allows other processes to grab the CPU. e.g:

while not clicked:
    # check the list of shapes
    for n, shape in enumerate(shapes):
        if mouse.isPressedIn(shape):
            clicked = True
            response = n  
            break # exit this loop
    else: # this runs once at the completion of the for loop
        # breathe for 1 ms
        time.sleep(0.001) 

i.e. this should pause for 1 ms after each time you cycle through the list. The checking itself will be very fast, so you’ll still be checking at something just a little under 1000 Hz, which is still probably faster than the temporal resolution of the hardware itself.

The alternative is to embed your checking within a drawing loop (this is what Builder does, and how many of the Coder demos are structured). i.e. on every screen refresh, re-draw all of your stimuli, call win.flip() to send them to the screen, and then loop through all of the stimuli, checking for presses.

This will dramatically slow your checking: a check will happen only 60 times per second if you have a 60 Hz display, for example. But when you call win.flip(), your code halts until the screen refresh occurs, generally giving a lot of time for the rest of the computer to do its stuff. e.g. if all of your drawing and checking takes, say 4 ms, then the remaining 12.666 ms of the screen refresh period is available for the operating system to distribute to other processes, making it unlikely you’ll lose control at key points.

I’m not sure that this is the cause of your issue, but it is a good direction to start testing with.

Try googling for how to time parts of your code. It may surprise you just how fast, as well as how slow, various parts run.

2 Likes

Hi Michael,

Thank you so much for your fast and detailed response.

Using win.flip() didn’t help with the problem I was having, but time.sleep() did, so thank you!

I also appreciate the tip about enumerate(), I’m going to use that all the time now.

Sarah