Issues with ending the routine after a specified number of mouse clicks when using "mouse.isPressedIn"

OS (e.g. Win10): Windows10
PsychoPy version (e.g. 1.84.x): Standalone 2020.1.3
What are you trying to achieve?:

I’m new to Psychopy/Python and am primarily using Builder with added Code Components.

I am designing a task where 60 circles appear on the screen and on each trial, between 1 and 10 circles will turn black (one at a time), indicating that these are the target circles. After the last target circle is shown, participants will have a five second delay and will then be asked to click on the target circles. After they click on a circle, it will turn green if it was a target circle, and red it was not a target circle. Importantly, if there were 5 target circles, for example, they are only allowed 5 guesses/clicks when responding.

I’m having trouble ending the response routine after the allocated amount of clicks.

What did you try to make it work?:
I have tried the following code (sorry that it is so lengthy - I’m sure there’s an easier way to do this but it technically works!). My logic was to use a Click Count, and end the trial once a certain number of clicks were reached. ClickCount1 is set to 0 (in the begin experiment section).

Here, the target circles are circle7_3, circle16_3, circle29_3, circle35_3 and circle48_3. These will turn green if clicked, and the others will turn red if clicked.

if unaidedtrials.thisN == 0:
    if unaidedresponses.isPressedIn(circle1_3):
        circle1_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle2_3):
        circle2_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle3_3):
        circle3_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle4_3):
        circle4_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle5_3):
        circle5_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle6_3):
        circle6_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle7_3):
        circle7_3.fillColor = (0,128,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle8_3):
        circle8_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle9_3):
        circle9_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle10_3):
        circle10_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle11_3):
        circle11_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle12_3):
        circle12_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle13_3):
        circle13_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle14_3):
        circle14_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle15_3):
        circle15_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle16_3):
        circle16_3.fillColor = (0,128,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle17_3):
        circle17_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle18_3):
        circle18_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle19_3):
        circle19_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle20_3):
        circle20_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle21_3):
        circle21_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle22_3):
        circle22_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle23_3):
        circle23_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle24_3):
        circle24_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle25_3):
        circle25_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle26_3):
        circle26_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle27_3):
        circle27_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle28_3):
        circle28_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle29_3):
        circle29_3.fillColor = (0,128,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle30_3):
        circle30_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle31_3):
        circle31_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle32_3):
        circle32_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle33_3):
        circle33_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle34_3):
        circle34_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle35_3):
        circle35_3.fillColor = (0,128,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle36_3):
        circle36_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle37_3):
        circle37_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle38_3):
        circle38_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle39_3):
        circle39_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle40_3):
        circle40_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle41_3):
        circle41_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle42_3):
        circle42_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle43_3):
        circle43_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle44_3):
        circle44_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle45_3):
        circle45_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle46_3):
        circle46_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle47_3):
        circle47_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle48_3):
        circle48_3.fillColor = (0,128,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle49_3):
        circle49_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle50_3):
        circle50_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle51_3):
        circle51_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle52_3):
        circle52_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle53_3):
        circle53_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle54_3):
        circle54_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle55_3):
        circle55_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle56_3):
        circle56_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle57_3):
        circle57_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle58_3):
        circle58_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle59_3):
        circle59_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if unaidedresponses.isPressedIn(circle60_3):
        circle60_3.fillColor = (255,0,0)
        ClickCount1 = ClickCount1 + 1
    if ClickCount1 >= 5:
        continueRoutine = False

What specifically went wrong when you tried that?:

This trial that was meant to end after five clicks instead ended after one or sometimes two.

I read lots of other questions on this forum and realised that “mouse.isPressedIn” in the Every Frame section of the Code Component will keep reporting ‘True’ on every frame until the mouse is released. This means that my ClickCount1 variable obviously didn’t work as I intended it to.

Here are my questions:

  1. Can someone please advise me on how I can end this routine after five clicks (and whether the same principle can be applied to subsequent trials that need to end after a different number of clicks)?

  2. I assume that if I’m successful in ending this trial after five clicks, participants won’t actually be able to see their feedback for the fifth click (whether the circle that they clicked on was/was not a target). Is it possible to end the trial one second after the final click so they have time to see their feedback?

Thanks heaps!

Yes, the mouse will be reported as being pressed on every click, so one needs to keep track of when it was first clicked only, and then reset to allow subsequent clicks only once the button has been released. But this will be immensely inefficient to implement with your code as it currently is. Ideally we would loop through all of the stimuli and check them in turn.

The questions then become:

  • How are you determining which stimuli are the targets on each trial? It looks here like they are hard-wired by their name, but presumably you would want them to vary from one trial to the next, as you describe having varying numbers of targets per trial.
  • What does your conditions file look like?
  • Can you show us a screenshot of your flow and routine panels?

Hi Michael, thanks for your quick response.

  1. Currently, the task has been counterbalanced as it would be if we were to do it in person (with an apparatus rather than a computer task). This was originally chosen because the intention is to do the same task with both children and adults, and we have had some issues with children’s engagement in computer tasks before. We therefore wanted the option to present participants with identical conditions regardless of whether they were doing it via an apparatus or on a computer task. Therefore, the targets are currently hardwired.

In saying that, given all the roadblocks I have hit so far, it may be easier to randomise the targets for the computer task and re-evaluate the solution if we have to revert to doing the task in person. I’m assuming this would make the building/coding process considerably easier?

  1. Given that we are currently manually setting the targets, my conditions file simply has a ‘task difficulty’ variable (levels: easy, medium, hard) and a ‘number of targets’ variable (levels: 1, 5, 10)

  2. This is what the relevant part of the flow and routine looks like. The issue from above has come from the ‘Response’ routine.

You can have fixed targets, if that is what your task requires. But it is best not to do this in code in the way you have above. e.g. it would be easier to maintain if you just specify in a conditions file which stimuli are targets - that way the behaviour can be altered without have to do any programming.

But I’m still confused, as your description says that the number of targets can vary from 1 to 5 to 10 and yet you have fixed code to deal with this. I have the horrible suspicion that you have conditional code depending on the trial number, and the code above is only a small selection, corresponding to trial 0 only. Is that the case?

Also, what does the task difficulty variable represent, and how does it influence the stimuli?

Great! I can do that. I did try a conditions file to deal with all this at one point, but I was unsure about the best way to structure it and it just lost me a little, but happy to have another shot at it. Would a conditions file with ‘Target1’, ‘Target2’, ‘Target3’ etc. as variables work even though some trials only have one target and others have five or 10?

I have asked myself the same thing about the usefulness of the task difficulty variable. The important variable is the ‘number of targets’ one, and I think I just left ‘Task Difficulty’ variable
there because it wasn’t harming anything.

Sorry - I did warn you I was being inefficient! There’s no one I can really ask around here, so I was running with “if it works, we move on”. Thanks for helping me clean it up!

OK, so add a column to your conditions file called targets, which will contain the list of target stimuli for that trial. It can vary in length as required from trial to trial:

targets
"[circle1_3]"
"[circle7_3, circle16_3, circle29_3, circle35_3, circle48_3]"
# etc

Then in the “begin routine” tab of your code component, put something like this:

ClickCount1 = 0
target_list = eval(targets)

because the target variable will just be read in as a string of characters, and we need to tell Python to evaluate it into an actual list of names that we can work with.

Then your “each frame” code can be much shorter (except that you’ll need to type in a list that contains all 60 of your stimulus names: sorry I’m just working with what you have set up so far):

for stimulus in [circle1_3, circle2_3, circle3_3, …]: # type in all of them

    if unaidedresponses.isPressedIn(stimulus):
        # only respond once:    
        if stimulus.fillColor == 'black':
            ClickCount1 = ClickCount1 + 1
            if stimulus in target_list:
                stimulus.fillColor = (0,128,0)
            else:
                stimulus.fillColor == 'red'

        break # can stop checking

if ClickCount1 == len(target_list):
    continueRoutine = False

Untested of course, but hopefully that gives you something to work with.

It’s not quite running yet, but I just wanted to say the biggest thanks for helping me out - you’ve really simplified my overly complicated attempt at this!

Two more quick questions:

  1. The above code is giving me the following error message:

if ClickCount == len(target_list):
TypeError: object of type ‘Polygon’ has no len()

This appears to be a pretty common error (based on my scroll through this forum) but I’m yet to come across a solution that will work. Any ideas?

  1. Just following up on the second question I included in my original post. Once this code does run, I assume each trial will end as soon as the number of clicks = number of targets. Is it possible to end the trial one second after the final click so they have enough time to see their feedback (whether their final click was/was not a target circle)?

Thank you again! I have been stuck on this for a few days so your help is very much appreciated!

That means that object (target_list) has somehow become a single polygon object, rather than a list of polygons. Where you have this line:

target_list = eval(targets)

could you also insert:

print(target_list)
print(type(target_list))

But as a first check, in your conditions file, in your targets column, are you surrounding the values with square brackets as suggested above?

In the “begin routine” tab, add:

trial_ending = False

and replace the final bit of code in the “each frame” tab with:

if ClickCount1 == len(target_list) and not trial_ending:
    trial_end_time = t + 1.0
    trial_ending = True

if trial_ending and t >= trial_end_time:
    continueRoutine = False

Thanks Michael, that’s super helpful.

One (hopefully) small issue - I can’t get the new conditions file to read. Every time I open ‘Loop Properties’ and select a file, it will just say “No parameters set”.

I’ve tried a number of solutions I found in other forums - deleting the loop and starting again, saving the file as a CSV, checking for any accidental spaces/characters, making the file again from scratch on the same computer, and making the file again from scratch on another computer. No luck with any of them. I also tried to test the file along the way to see at what point it can no longer be read - it was fine if it just contained the column headings and values for the ‘numberoftargets’ variable, but as soon as I introduced the square brackets in the ‘targets’ variable, it would no longer read. The only way I can get it to read at this point is if I take away the square brackets (which I know isn’t an option).

Sorry about all the issues!

Ahh, it will likely be the commas inside the brackets (which mucks up the CSV “comma separated values” format). Try surrounding those cell contents with quote marks, so the commas are interpreted as belonging within a cell, rather than as markers that separate cells.

Ah brilliant, no more issues with reading the file - it feels like we’re getting close!

This is what my code currently looks like (note that I’ve changed the circle names, and ClickCount1 has become ClickCount):

In the ‘Begin Routine’ section:

target_list = eval(targetcircles)

ClickCount = 0

win.mouseVisible = True 
unaidedresponses.setPos(newPos=(0,0))

trial_ending = False 

In the ‘Each Frame’ section:

for stimulus in [circle1, circle2, circle3, circle4, circle5, circle6, circle7, circle8, circle9, circle10, circle11, circle12, circle13, circle14, circle15, circle16, circle17, circle18, circle19, circle20, circle21, circle22, circle23, circle24, circle25, circle26, circle27, circle28, circle29, circle30, circle31, circle32, circle33, circle34, circle35, circle36, circle37, circle38, circle39, circle40, circle41, circle42, circle43, circle44, circle45, circle46, circle47, circle48, circle49, circle50, circle51, circle52, circle53, circle54, circle55, circle56, circle57, circle58, circle59, circle60]:
    if unaidedresponses.isPressedIn(stimulus):
        if stimulus.fillColor == ('white'):
            ClickCount = ClickCount + 1
            if stimulus in target_list:
                stimulus.fillColor == (0,128,0)
            else:
                stimulus.fillColor == ('red')

        break 

if ClickCount == len(target_list) and not trial_ending:
    trial_end_time = t + 1
    trial_ending = True 

if trial_ending and t >= trial_end_time:
    continueRoutine = False 

With this code, none of the clicks are registering (so I can click as many times as I like and the trial will not end) and the circles do not change colour after being clicked. I’ve played around with the code a bit but still can’t get it to behave as I need.

I’m not getting any error messages, but I do get a Future Warning that looks like this:

FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
if stimulus.fillColor == (‘white’):

Not sure what I’m missing, but I’m hoping you might have some insight as to why this is happening. Sorry again for all the questions!

This is the difficulty of working on code remotely. The thing to do is try to figure out where the logic might be tripping up. So try inserting some (TEMPORARY) print() statements for debugging purposes. e.g. these will tell you if clicks are actually being detected and if the check of the colour works:

for stimulus in [circle1_3, circle2_3, circle3_3, …]: # type in all of them

    if unaidedresponses.isPressedIn(stimulus):
        print(stimulus.name + ' clicked')
        # only respond once:    
        if stimulus.fillColor == 'black':
            ClickCount1 = ClickCount1 + 1
            print('  Clicks: ' + str(ClickCount1))
            if stimulus in target_list:
                stimulus.fillColor = (0,128,0)
            else:
                stimulus.fillColor == 'red'

        break # can stop checking

if ClickCount1 == len(target_list):
    continueRoutine = False

What output do you see?

Yeah I completely understand it’d be so difficult to answer these questions without seeing it, so I really appreciate you helping me out.

I clicked on circle1, circle2, circle3, circle4, and circle5 (one time each) and this is all the output:

circle1clicked
circle1clicked
circle1clicked
circle1clicked
circle1clicked
circle1clicked
circle1clicked
circle1clicked
circle2clicked
circle2clicked
circle2clicked
circle2clicked
circle2clicked
circle2clicked
circle3clicked
circle3clicked
circle3clicked
circle3clicked
circle4clicked
circle4clicked
circle5clicked
circle5clicked
circle5clicked

And I also still get FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
if stimulus.fillColor == ‘white’:

OK, that’s great, the clicks are being detected (it happens multiple times, as the mouse button is being held down for more than 16.66 ms). So let’s change the debugging like this:

for stimulus in [circle1_3, circle2_3, circle3_3, …]: # type in all of them

    if unaidedresponses.isPressedIn(stimulus):
        print(stimulus.fillColor) # XXX Need to check if this is what we expect.
        # only respond once:    
        if stimulus.fillColor == 'black':
            ClickCount1 = ClickCount1 + 1
            if stimulus in target_list:
                stimulus.fillColor = (0,128,0)
            else:
                stimulus.fillColor == 'red'

        break # can stop checking

if ClickCount1 == len(target_list):
    continueRoutine = False

Clicked on the same five circles and this is the output:

[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]

I believe this is correct, because all circles should be white at the beginning of the trial (as participants are trying to recall which ones previously flashed black). I did change this in my code (as pasted above), where instead of if stimulus.fillColor==‘black’, I have if stimulus.fillColor==‘white’.

OK, those colour specifications should be equivalent, and although I’m unkeen to compare floating point numbers, as that can easily fail, what about changing the line to be:

if stimulus.fillColor == [1. 1. 1.]:

Yeah I tried that this morning, and ended up with this error message:

if stimulus.fillColor == [1,1,1]:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Experiment ended.

It seems that with every problem we fix, another appears. Sorry that this is taking so long!

OK, I just looked into this. It seems that the colour value is being returned as a numpy array, rather than a standard Python list object. That means we can’t compare it against something else in its entirety, but has to be done element-by-element (I hadn’t paid attention to the warning about that you had provided much earlier).

So the solution here is to either define the colour initially as a single string value (like 'white') or to force the numpy array of values back into a Python list, like this:

if list(stimulus.fillColor) == [1., 1., 1.]:

@jon: Is this behaviour (of colour triplets being returned as numpy arrays rather than Python lists or tuples) new, or something intentional that I just haven’t noticed before?

Okay, thanks for looking into that. I tried forcing them into a list using your code above. My code now looks like this:

for stimulus in [circle1, circle2, circle3, circle4, circle5, circle6, circle7, circle8, circle9, circle10, circle11, circle12, circle13, circle14, circle15, circle16, circle17, circle18, circle19, circle20, circle21, circle22, circle23, circle24, circle25, circle26, circle27, circle28, circle29, circle30, circle31, circle32, circle33, circle34, circle35, circle36, circle37, circle38, circle39, circle40, circle41, circle42, circle43, circle44, circle45, circle46, circle47, circle48, circle49, circle50, circle51, circle52, circle53, circle54, circle55, circle56, circle57, circle58, circle59, circle60]:
    if unaidedresponses.isPressedIn(stimulus):
        if list(stimulus.fillColor) == (1, 1, 1):
            ClickCount = ClickCount + 1
            if stimulus in target_list:
                stimulus.fillColor == (0,128,0)
                print ("in list")
            else:
                stimulus.fillColor == (255,0,0)
                print("not in list")

        break 

if ClickCount == len(target_list) and not trial_ending:
    trial_end_time = t + 1
    trial_ending = True 

if trial_ending and t >= trial_end_time:
    continueRoutine = False 

This produced the following error:
if stimulus in target_list:
TypeError: 'in ’ requires string as left operand, not Polygon

I wasn’t exactly sure how to define the colour initially as a single string value as you suggested, so I tried to fix this error instead. I had a quick read through some forums for some solutions, and tried simply changing the line “if stimulus in target_list” to “if ‘stimulus’ in target_list”. Making this change got rid of the error message (although I have no idea if it was the right thing to do) but when running the experiment, the circles still do not change colour after being clicked.

I also wanted to see whether it was able to discern between circles that were in the target list and circles that were not (see above in the code for the two ‘print’ lines). Even when I click on a circle from the list, it will produce “not in list” as the output. This leads me to think that there’s another issue happening as well (aside from just the colour problem).

This looks like target_list is still a string (as it would be when it is originally read in from your conditions file). Do you still have the line:

target_list = eval(targetcircles) # or whatever the column name is

that evaluates the string variable in to a list of polygons?

If needed, just insert code like this to check:

print(target_list)
print(type(target_list))

Python is more relaxed than some languages, but will still be fussy when required that objects are of the right type for certain purposes.