psychopy.org | Reference | Downloads | Github

Task-Switching in Builder view

First off, thanks for your ongoing patience!

The code you suggested returns a “list index out of range” error. Removing the [0] resolves this error, but as you would probably expect, no key presses do anything.

I fixed this by having Letter height set to “every repeat”. I had opacity set to that as well, but letter height was set to constant. It seems to work fine now.

I am attaching a screenshot of my view. I have my routines ordered as you describe above, but because I keep getting an error, I have not been able to debug just yet.

I edited the code above to fix this, it would be safer to use that approach, just in case someone mashes a couple of keys.

Not sure why that is being used. but glad to hear that the visibility is working at least.

What is the function of the loop around the second_task routine?

We need to see the full text of this error.

I have edited my code to match this, but there is no response to pressing c or i. I checked the output file, and it is recording responses properly. I am attaching my full code (in the Each Frame tab) here. I tried to stay as close to the code you shared so as not to cause any more errors.

# get just a single key from the keyboard queue:
response = event.getKeys()

if response:
    response = response[0]
    # check if subject wants to do secondary task,
    # but only on relevant trials:
    if response == 'y' and popup_prompt_rng == 1:
        run_secondary_task = True
        # store the answer in the data:
        thisExp.addData('response', response)
        continueRoutine = False # quit this task immediately
    # otherwise, check if this is a response to the task:
    elif response == 'c' or response == 'i': 
        run_secondary_task = False
        # store the answer in the data:
        thisExp.addData('response', response)
        if response == correct: #correct refers to the correct answer to the math problem (either c or i)
            gainsThisBlock=gainsThisBlock+3 #3 points for a correct answer
        else:
            gainsThisBlock=gainsThisBlock-3 # minus 3 points for an incorrect answer 

That loop is for the conditions file tied to that task. There are several word stimuli that can be chosen from.

I was able to get the secondary task working after going back and messing with some of the settings. I’m just unsure about the first task now (as above).

That was very useful to put everything together like that. So it seems that the logic of the code is basically running OK, given that the responses are being recorded properly. I guess the only thing that is missing is that you also need to end the routine in response to the c or i keys, so that the next maths problem can be presented .

ie add continueRoutine = False as the last line within the elif clause above.

For convenience, have also edited below to store whether or not the response was correct, and have also added some defensive coding in case the subject accidentally engages the caps lock key (i.e. we force the response to be lower case):

# check the keyboard queue for events:
response = event.getKeys()

if response: # there was at least one, so
    # get just the last if there were several pushed, 
    # and enforce lower case:
    response = response[0].lower() 

    # check if subject wants to do secondary task,
    # but only on relevant trials:
    if response == 'y' and popup_prompt_rng == 1:
        run_secondary_task = True
        # store the answer in the data:
        thisExp.addData('response', response)
        thisExp.addData('response_correct', 'N/A')
        continueRoutine = False # quit this task immediately

    # otherwise, check if this is a response to the task:
    elif response == 'c' or response == 'i': 
        run_secondary_task = False
        thisExp.addData('response', response)

        # amend the score accordingly:
        if response == correct: # correct refers to the correct answer to the math problem (either c or i)
            gainsThisBlock = gainsThisBlock + 3 # 3 points for a correct answer
            thisExp.addData('response_correct', 'True')
        else:
            gainsThisBlock = gainsThisBlock - 3 # minus 3 points for an incorrect answer
            thisExp.addData('response_correct', 'False')
        
        # go on to the next problem:
        continueRoutine = False 

This worked perfectly for the primary task, thank you!

Because our secondary task consists of multiple key presses (i.e. a word completion task in which two letters are missing from a word), I am assuming that this would be executed much more easily by ditching the keyboard component as was done for the primary task.
The code below (in the Each Frame tab of the secondary task) works for stimuli with only one key press:

response2 = event.getKeys()

if response2: # must allow for multiple key presses, currently only works for 1
    response2 = response2[0].lower() 

# amend the score accordingly:
    if response2 == corrAns: # corrAns refers to the correct answer to the missing letters
        gainsThisBlock = gainsThisBlock + 5 # 5 points for a correct answer
        thisExp.addData('response2_correct', 'True')
        continueRoutine = False
    else:
        gainsThisBlock = gainsThisBlock - 5 # minus 5 points for an incorrect answer
        thisExp.addData('response2_correct', 'False')
        
         #go on to the next problem: 
        continueRoutine = False 

I suspect I would have to further modify

if response2: # must allow for multiple key presses, currently only works for 1
    response2 = response2[0].lower()

but removing the [0] returns an error. Is it still possible to allow for more than one key press, as well as to check for a correct answer (from a conditions file) involving multiple key presses?

The way that keyboard checking happens is that we generally check faster than people can type, so (usually) we would detect each keypress on separate runs through the code (i.e. during separate screen refreshes). This means that we need to maintain a counter across screen refreshes, so we know if we have detected the first key press (in which case, we want to keep on checking), or the second (in which case we see if the response was correct, and then end the trial).

What does your corrAns variable contain? i.e. is it a string of two letters (like 'ab') or a list of two separate letters (like ['a', 'b'])? And are there any other constraints?

And how do you score correctness? Do both letters have to be identified, or is one letter sufficient for half a score? Does the order of responses matter? i.e. would 'a' then 'b' be equivalent to 'b' then 'a', or not?

It has a string of letters, no other constraints. Would it also be possible to have some strings with more or less than two letters?

All letters must be identified for full points.

Yes, if the answer is ab, then ba would be incorrect.

Yes, we can check the length of your corrAns variable on each trial and then use that to determine how many responses we need to count for the trial to be completed. e.g.

In the Begin routine tab, define some variables that will be needed in the Each frame tab:

num_required = len(corrAns)
num_received = 0
answer = ''

In the Each frame code, each time a key press is detected, you increment num_received and add the received letter to answer. If num_received == num_required, then you check if answer is equal to corrAns, and take action as appropriate.

Again, sprinkle in lots of (temporary) print() statements, so you can see if the logic is flowing as required, and check on what the variables look like.

I’m working on a funding application tonight, so you won’t get much more response from me for a while. Give me a prod in a day or so (if you haven’t figured it out independently).

Sorry to bother you again, but I’m still having trouble implementing this last part:

I’ve tried reviewing other forum posts similar to this (Record string response from keyboard and use Enter key to continue loop), but I have been unsuccessful so far.

I have this in my Each Frame tab in the secondary task routine:

# check the keyboard queue for events:
wp_response = event.getKeys()

if wp_response: # must allow for multiple key presses
    wp_response = wp_response.lower() 
    for key in keys:
        num_received = []
        num_received += answer
        if len(num_received) == num_required:

# amend the score accordingly:
            if answer == corrAns: # corrAns refers to the correct answer to the word problem, can be any key
                gainsThisBlock = gainsThisBlock + 5 # 5 points for a correct answer
                thisExp.addData('wordproblem_correct', 'True')
                continueRoutine = False
            else:
                gainsThisBlock = gainsThisBlock - 5 # minus 5 points for an incorrect answer
                thisExp.addData('wordproblem_correct', 'False')
        
         #go on to the next problem: 
                continueRoutine = False 

When I run this, I get the following error:
wp_response = wp_response.lower() AttributeError: 'list' object has no attribute 'lower'
I’m assuming this is due to my logic being off (or just completely wrong), but I’m not sure where it’s going wrong.

As an aside, would it be possible to record the time it takes for each key press (and the total time) for this task? I know this

thisExp.addData('response_rt', t)

works for my primary task, but I’m not sure if this is possible for trials with multiple key presses.

As always, thanks for your help!

# check the keyboard queue for events:
wp_response = event.getKeys()

if wp_response: # must allow for multiple key presses
    for key in wp_response:
        key = key.lower()
        num_received = num_received + 1
        answer = answer + key

    if num_received == num_required:
        # amend the score accordingly:
        if answer == corrAns: # corrAns refers to the correct answer to the word problem, can be any key
            gainsThisBlock = gainsThisBlock + 5 # 5 points for a correct answer
            thisExp.addData('wordproblem_correct', 'True')
        else:
            gainsThisBlock = gainsThisBlock - 5 # minus 5 points for an incorrect answer
            thisExp.addData('wordproblem_correct', 'False')
        
        #go on to the next problem: 
        continueRoutine = False 

No, this is just because the .lower() function can only operate on strings of characters (like 'Hello', not a list object, even if that list contains strings (like ['Hello', 'bye'] ). A lot of Python is about getting used to thinking about what sort of object a variable name refers to, and hence what you can do with it. e.g. 2 + 2 == 4 while '2' + '2' == '22', because you can add integers and you can add strings, but what addition means is different for each of these sorts of objects. Meanwhile, '2'.lower() is valid (as '2' is a string, even though it isn’t a letter, while 2.lower() is not a valid operation.

When in doubt, try print(some_variable) and print(type(some_variable)) to see if your variable represents what you think it does.

So anyway, have amended the code above to (hopefully) take care of some of those issues (being more careful to distinguish between numbers, responses, and so on). The basic structure was correct, just some of the variables weren’t being handled correctly (although the second if statement did need to be de-indented so that it didn’t run in response to every letter if more than one was received). Try this and see if it works. Then we can look at the time recording once this is working.

I see, thank you for taking the time to break it down like that.

The code you posted works perfectly! Just a quick question; Would you recommend inserting event.clearEvents() in the Begin Routine tab of the secondary task to make sure the program isn’t counting previous key presses as responses to the task? I did so for the primary task (the math problems) since pressing c, ,i, or ‘y’ even when there was no stimuli present resulted in each key press being counted, but I just want to be sure this won’t have any other consequences on how they are recorded. I checked my output files and they look fine.

Anyway, this is what I have in my End Routine tab for the secondary task:

if wp_response:
    thisExp.addData('wordproblem_rt', t) #rt for entire trial
    
if not wp_response : #if no response
    gainsThisBlock = gainsThisBlock - 5 # minus 5 points for no response
    thisExp.addData('wordproblem_correct', 'N/A')
    run_secondary_task = False
    
#update total points
gainsTotal=gainsThisBlock + gainsTotal
thisExp.addData('TotalPoints', gainsTotal)

This is similar to what I have for time recording in the primary task. Is it possible to isolate the rt by each key press?

In general yes, that is a good idea (this is effectively why the Builder keyboard component does when you select “Discard previous”), and something that we should handle explicitly when dealing with responses in code. I’ve lost a little touch with what is happening in this thread, but sometimes you might only want to do this on the first iteration of a loop that occurs within a trial.

You should record the rt for each response when it occurs, and append it to a list, something like:

Begin routine:

rt_list = [] # initialise empty list

Each frame:

for key in wp_response:
        rt_list.append(t)
        key = key.lower()
        num_received = num_received + 1
        answer = answer + key

“End routine” either:

if wp_response:
    thisExp.addData('wordproblem_rts', rt_list)

or split them out into separate columns:

if wp_response:
    # write a separate column variable for each reaction time 
    # (wordproblem_rt_ 0, wordproblem_rt_ 1, etc):
    for column_num in len(rt_list):
        thisExp.addData('wordproblem_rt_' + str(column_num), rt_list[column_num])

I have updated the code to your previous suggestion from 4 days ago, but I have been getting the following error

    if wp_response:
NameError: name 'wp_response' is not defined

This is in regards to the End Routine tab in my secondary task routine:

if wp_response:
    thisExp.addData('wordproblem_rts', rt_list)
    
if not wp_response : #if no response
    gainsThisBlock = gainsThisBlock - 5 # minus 5 points for no response
    thisExp.addData('wordproblem_correct', 'N/A')


    
#update total points
gainsTotal=gainsThisBlock + gainsTotal
thisExp.addData('TotalPoints', gainsTotal)

I have tried troubleshooting this all weekend, and it seems to happen every time unless the very first trial of the experiment contains the popup and it is responded to (y press to switch tasks instead of c or i for the primary task.)

Sorry, this thread has been going for too long for me to know what code you currently have and where. That makes it impossible to diagnose a bug like this. Can you once again show all of the code, with what tab/routine it comes from??

Of course.

Begin Routine:

if not run_secondary_task:     
    continueRoutine = False

gainsThisBlock = 0


num_required = len(corrAns)
num_received = 0 
answer = ''

rt_list = [] # initialise empty list 

Each Frame:

# check the keyboard queue for events:
wp_response = event.getKeys()

if wp_response: # must allow for multiple key presses
    for key in wp_response:
        rt_list.append(t)
        key = key.lower()
        num_received = num_received + 1
        answer = answer + key

    if num_received == num_required:
        # amend the score accordingly:
        if answer == corrAns: # corrAns refers to the correct answer to the word problem, can be any key
            gainsThisBlock = gainsThisBlock + 5 # 5 points for a correct answer
            thisExp.addData('wordproblem_correct', 'True')
        else:
            gainsThisBlock = gainsThisBlock - 5 # minus 5 points for an incorrect answer
            thisExp.addData('wordproblem_correct', 'False')
        
        #go on to the next problem: 
        continueRoutine = False 

End Routine:

if wp_response:
    thisExp.addData('wordproblem_rts', rt_list)
    
if not wp_response : #if no response
    gainsThisBlock = gainsThisBlock - 5 # minus 5 points for no response
    thisExp.addData('wordproblem_correct', 'N/A')

#update total points
gainsTotal=gainsThisBlock + gainsTotal
thisExp.addData('TotalPoints', gainsTotal)

Oops, OK, that helped make a possible issue obvious. I think the issue is that if the secondary task doesn’t run (i.e. the routine is skipped) the “end routine” code will still execute. This will be an issue because in that case, as the routine itself didn’t run, neither did the “every frame” code, and so the variable wp_response never got created. So we should make the code in that “end routine” tab conditional too.

So we should tidy up that “end routine” code, something like this:

if run_secondary_task: # now only execute the following code conditionally
    if wp_response:
        thisExp.addData('wordproblem_rts', rt_list)    
    else: # there was no response
        gainsThisBlock = gainsThisBlock - 5 # minus 5 points for no response
        thisExp.addData('wordproblem_correct', 'N/A')

    # update total points
    gainsTotal = gainsThisBlock + gainsTotal
    thisExp.addData('TotalPoints', gainsTotal)

This was actually a useful bug to arise, because it also highlights that if the routine was skipped, the reaction times for that trial might still be saved, simply because the wp_response variable still exists from a previous run of the routine.
So we should also reset the wp_response variable at the start of the routine, as you already do for rt_list. i.e. add this to the at the end of the “begin routine” code:

wp_response = []

Yes, this must’ve been the case. I assumed that the ContinueRoutine = False clause in the Begin Routine tab would stop everything, including the End Routine tab.

I tried using the code you suggested earlier to split up key press rt into different columns:

but it returns the following error:

 for column_num in len(rt_list):
TypeError: 'int' object is not iterable

That is the last problem I have with the code. I do have another question concerning PsychoPy however. Every time I open my experiment file, one tab of code is compressed into one line for some reason, so I have to go in and fix all the indents. Is this a known issue?

Sorry, try range(len(rt_list))

The task works perfectly now, thank you so much!
I do still have the issue below however.

I am including a screenshot to better illustrate my issue. Is there a way to prevent this from happening every time the psyexp file is opened?

See: