Randomization of paired stimuli

so I am new to coding and psychopy in general. I am currently working on this experiment with dual tasks, task 1 is determining whether the image stimulus is horizontal or vertical(a or s key), task 2 is go/nogo (press k or no)

anyway we want to have 3 unique blocks that are repeated 3 times in a random order,
each block will have 24 trials – 12 paired with go 12 paired with no go,
in each of these 12, there are 4 unique stimuli of both horizontal and vertical pictures that are repeated 3 times (2 horizontal x go, 2 vertical x go, 2 horizontal x nogo, 2 vertical x nogo) – this means each block has 8 unique stimuli (4 horizontal and 4 vertical)
we want the assigned stimuli to be random for each participant but still maintain the ratio of 1:1 go/no-go and 1:1 horizontal and vertical

in total we have 24 unique stimuli which will be shown 9 times throughout the experiment and 3 unique blocks (with 8 unique stimuli each) which will be repeated 3 times in a random order (9 blocks in total)

the question is how do I best go about it? I know that this is a bit too complex to just use builder alone. Could someone start me up with a rough idea of a code? Thank you very much in advance.

I need a little clarification

When you say “unique stimuli” do you mean the same image files but rotated? Or are the horizontal and vertical items actually different images? Are you randomizing which images are horizontal or vertical?

Is go/no-go determined by the image or some other feature of the display?

Do you want the stimuli to be consistent throughout one participant’s run? That is, for each of the 8 unique stimuli in a given block, should those same 8 stimuli be part of that block every time the block is shown? Or do you want the assignment of stimuli to blocks to also change between repetitions?

Depending on exactly what you’re looking to randomize this might be easier or harder, but my intuitive read is that this can mostly be done in the builder with a little extra ‘before experiment’ code that assigns stimuli to blocks in advance.

Hi Jonathan,
the horizontal and vertical stimuli are all different stimuli. h1-12.PNG and v1-12.PNG
the go/no-go should be randomly assigned regardless of the feature/category, however it should still be at 1:1 ratio

the pairing of the stimuli per block should stay the same per block throughout the experiment, however the order in which it was presented should be different

I have tried writing some codes but I’m not sure where to put it in the builder and if it’s going to work
I have some ideas about the shuffling and pairing, but I’m not sure how to separate it to 3 different blocks

import random

random.seed()

# Example stimuli
vertical_stimuli = ["v1.png", "v2.png", "v3.png", "v4.png", "v5.png", "v6.png", "v7.png", "v8.png", "v9.png", "v10.png", "v11.png", "v12.png"]
horizontal_stimuli = ["h1.png", "h2.png", "h3.png", "h4.png", "h5.png", "h6.png", "h7.png", "h8.png", "h9.png", "h10.png", "h11.png", "h12.png"]

# Shuffle the stimuli
random.shuffle(vertical_stimuli)
random.shuffle(horizontal_stimuli)

# Determine the number of stimuli to pair with each condition
num_stimuli = len(vertical_stimuli) // 2

# Create pairings
vgo = []
vnogo = []
hgo = []
vnogo = []

# Pair half of the vertical stimuli with "go"
for stimulus in vertical_stimuli[:num_stimuli]:
    vgo.append((stimulus, "go"))

# Pair the remaining half of the vertical stimuli with "nogo"
for stimulus in vertical_stimuli[num_stimuli:]:
    vnogo.append((stimulus, "nogo"))

# Pair half of the horizontal stimuli with "go"
for stimulus in horizontal_stimuli[:num_stimuli]:
    hgo.append((stimulus, "go"))

# Pair the remaining half of the horizontal stimuli with "nogo"
for stimulus in horizontal_stimuli[num_stimuli:]:
    hnogo.append((stimulus, "nogo"))

# Shuffle the pairings
random.shuffle(vgo)
random.shuffle(vnogo)
random.shuffle(hgo)
random.shuffle(hnogo)

I want each block to have 2vgo, 2hgo, 2hnogo, and 2vnogo randomly picked from the pairings
and this to be repeated 3 times within the block

That at least is easy: Create a code component in one of your early trials and put this code in the “Before experiment” tab. That way the lists you create will be global variables that you can refer to anywhere in the experiment.

Repeating within a block is easy, you can do it in a loop in the builder with nreps. Randomly shuffling the paired stimuli into blocks is not too bad either since your code has already made the pairs and shuffled them. They just need to be assigned.

blocks = {'blockA':[], 'blockB':[], 'blockC':[]}
blockNames = list(blocks.keys())

for i in range(0, len(blockNames)):
     for j in range(0, 2):
         blocks[blockNames[i]].append(vgo.pop(0)) # using "pop" removes the entry from the list  guaranteeing no repeats
         blocks[blockNames[i]].append(vnogo.pop(0)) 
         blocks[blockNames[i]].append(hgo.pop(0)) 
         blocks[blockNames[i]].append(hnogo.pop(0)) 

Something like that will let you split it up. Being able to read that into the experiment is a little trickier, but one option is to just write the whole thing into an excel file and use it as a condition file.

I am not sure where I should put the code I wrote?
So do I put both yours and mine in “Before Experiment”?
Are you doing this all in one loop?

I have another problem in which I try to refer to each element separately but I couldn’t

so I have since added to my code

import random

random.seed()

# Stimuli
vertical_stimuli = ["v1.png", "v2.png", "v3.png", "v4.png", "v5.png", "v6.png", "v7.png", "v8.png", "v9.png", "v10.png", "v11.png", "v12.png"]
horizontal_stimuli = ["h1.png", "h2.png", "h3.png", "h4.png", "h5.png", "h6.png", "h7.png", "h8.png", "h9.png", "h10.png", "h11.png", "h12.png"]

# Shuffle the stimuli
shuffle(vertical_stimuli)
shuffle(horizontal_stimuli)

# Create pairings
vgo = []
vnogo = []
hgo = []
hnogo = []

# Pair half of the vertical stimuli with "go"
for stimulus in vertical_stimuli[0:6]:
    vgo.append((stimulus, "go"))

# Pair the remaining half of the vertical stimuli with "nogo"
for stimulus in vertical_stimuli[6:12]:
    vnogo.append((stimulus, "nogo"))

# Pair half of the horizontal stimuli with "go"
for stimulus in horizontal_stimuli[0:6]:
    hgo.append((stimulus, "go"))

# Pair the remaining half of the horizontal stimuli with "nogo"
for stimulus in horizontal_stimuli[6:12]:
    hnogo.append((stimulus, "nogo"))

B1vgo = vgo[0:2]
B1vnogo = vnogo[0:2]
B1hgo = hgo[0:2]
B1hnogo = hnogo[0:2]
B1stim = B1vgo + B1vnogo + B1hgo + B1hnogo

B2vgo = vgo[2:4]
B2vnogo = vnogo[2:4]
B2hgo = hgo[2:4]
B2hnogo = hnogo[2:4]
B2stim = B2vgo + B2vnogo + B2hgo + B2hnogo

B3vgo = vgo[4:6]
B3vnogo = vnogo[4:6]
B3hgo = hgo[4:6]
B3hnogo = hnogo[4:6]
B3stim = B3vgo + B3vnogo + B3hgo + B3hnogo

# Access the components of the pair separately in B1stim
B1stim_1 = [pair[0] for pair in B1stim]
B1stim_2 = [pair[1] for pair in B1stim]

# Access the components of the pair separately in B2stim
B2stim_1 = [pair[0] for pair in B2stim]
B2stim_2 = [pair[1] for pair in B2stim]

# Access the components of the pair separately in B3stim
B3stim_1 = [pair[0] for pair in B3stim]
B3stim_2 = [pair[1] for pair in B3stim]

my idea is to put $B1stim_1 as image of Task 1, but psychopy does not recognize that
and $t2 (either go or nogo) to be replace with a number depending on which counterbalanced version

if B1stim_2 == 'Go':
    t2 = '6'
else:
    t2 = '8'

thisExp.addData("t2", t2)

I currently have 3 loops for each block and 1 giant loop for the block structure without condition files

another problem I have is for the response mapping, can I do this exclusively with codes?

so if I put

# Response mapping for t1_resp
t1_respcorr = {}
for i in range(1, 13):
    t1_resp_mapping[f'h{i}.png'] = 's'  # Response for horizontal stimuli
    t1_resp_mapping[f'v{i}.png'] = 'a'  # Response for vertical stimuli

# Response mapping for t2_resp
t2_respcorr = {'k': 'go', None: 'nogo'}

before my routine start, will this work?

I have a separate code I use for the feedback routine following the trial

if t1_resp.corr and t2_resp.corr:  
    msg = "Richtig"
elif ((t1_resp.keys == None) and (t2_resp.keys != None)):   # if there is no answer for task1, but for task2 (never possible in nogo trial)
    msg = "Falsche Reihenfolge"   
elif ((t1_resp.keys == None) and (t2_resp.keys == None)):   # if there is no answer at all
    msg = "Zu langsam"   
else:
    msg = "Falsch"

I am just not sure where to put them all so they could work or if there is any simpler approach to this

I am sorry for the long reply, I feel like I have an idea but I’m just not there yet.

I am trying to do something similar with repetitions of stimuli category 3x in a row. For my experiment I am presenting paired stimuli that belong to one of three categories. The goal is that if category A is randomly selected an image pairing from this category will be shown and the next two consecutive images should also show paired stimuli from category A. And once an image is shown, it is not repeated. Did any of the code you tried solve your question?

not really, sorry
something is still wrong. I still can’t get response mapping correctly, for example
and can’t get it to repeat past the length of the list

still waiting for anyone who might have an answer :frowning:

OK, let’s break this down a bit.

First, the first block of code in your post looks generally good. All of that should go in the “Before Experiment” tab because it’s setting up stuff you will be referring to later.

For the image component you would need to put something like B1stim_1[0] or some other way or referring to items within the list. The reason is that B1stim_1 is a list. If you run the code you put and then tell it to print B1stim_1, this is what you get:

['v5.png', 'v9.png', 'v8.png', 'v12.png', 'h10.png', 'h5.png', 'h11.png', 'h6.png']

The trick is that you need to reference each of these individually on every loop, and you want them (presumably) in a different order every time. That’s a little harder.

This is where it becomes important whether you are planning to only run this in the lab or also run it online. If you’re only planning to run it in the lab, one way forward is to actually create a conditions file in the “before experiment” code that you can just refer to normally in your loops. In other words, save three condition files, Block1.xlsx, Block2.xlsx, Block3.xlsx. It’s easier to do this if you make a dict like so:

import random
import pandas as pd

random.seed()

# Stimuli
vertical_stimuli = ["v1.png", "v2.png", "v3.png", "v4.png", "v5.png", "v6.png", "v7.png", "v8.png", "v9.png", "v10.png", "v11.png", "v12.png"]
horizontal_stimuli = ["h1.png", "h2.png", "h3.png", "h4.png", "h5.png", "h6.png", "h7.png", "h8.png", "h9.png", "h10.png", "h11.png", "h12.png"]

# Shuffle the stimuli
random.shuffle(vertical_stimuli)
random.shuffle(horizontal_stimuli)

# Pre-fill "go" and "nogo"
vgo={'img':[], 'gonogo':['go']*6}
vnogo={'img':[], 'gonogo':['nogo']*6}
hgo={'img':[], 'gonogo':['go']*6}
hnogo={'img':[], 'gonogo':['nogo']*6}

# Pair half of the vertical stimuli with "go"
for stimulus in vertical_stimuli[0:6]:
    vgo['img'].append(stimulus)

# Pair the remaining half of the vertical stimuli with "nogo"
for stimulus in vertical_stimuli[6:12]:
    vnogo['img'].append(stimulus)

# Pair half of the horizontal stimuli with "go"
for stimulus in horizontal_stimuli[0:6]:
    hgo['img'].append(stimulus)

# Pair the remaining half of the horizontal stimuli with "nogo"
for stimulus in horizontal_stimuli[6:12]:
    hnogo['img'].append(stimulus)

B1stim = {'img':[],'gonogo':[]}

for i in range(0,2):
    B1stim['img'].append(vgo['img'][i])
    B1stim['gonogo'].append(vgo['gonogo'][i])
    B1stim['img'].append(vnogo['img'][i])
    B1stim['gonogo'].append(vnogo['gonogo'][i])
    B1stim['img'].append(hgo['img'][i])
    B1stim['gonogo'].append(hgo['gonogo'][i])
    B1stim['img'].append(hnogo['img'][i])
    B1stim['gonogo'].append(hnogo['gonogo'][i])

B1stim = pd.DataFrame(data=B1stim)
B1stim.to_excel('Block1.xlsx')

# Etc. for B2 and B3stim.

This will save an excel file with two colums, ‘img’ and ‘gonogo’. It will be different on every run of the experiment.

Then in the builder you can make something like a nested loop that would look a bit like this (I didn’t fill in nreps correctly, but in your case I think it would be 3 and 3).


where Blocks.xlsx just has one column, ‘block’, with three entries, ‘Block1.xlsx’, ‘Block2.xlsx’, ‘Block3.xlsx’, and then the inner loop refers to that field to actually populate the trial.

Then you just need to refer to $img and $gonogo in the trial itself.

Now as for the response mapping, what you want to do is actually add an additional column to the dictionaries (and ultimately the block excel file) above that’s something like ‘t1_corrkey’ and is set by horizontal vs. vertical. For example:

vgo={'img':[], 'gonogo':['go']*6, 't1_corrkey':['a']*6}
# and etc. for vnogo and 's' for the horizontal ones
# and make sure to add it to B1stim etc. as well!

That will take care of t1_resp.corr automatically. For t2, rather than correct/incorrect, you just care if they hit the key at all, right? So you create a feedback trial, and you actually (counter-intuitively) want to put this in the “Begin routine” code of your feedback trial (which will remember the last responses from the previous trial)

t2_pass = False
if thisTrial['gonogo'] == 'go':
    if 'k' in t2_resp.keys:
        t2_pass = True
else:
    if t2_resp.keys == None:
        t2_pass = True

if t1_resp.corr and t2_pass:  
    msg = "Richtig"
elif ((t1_resp.keys == None) and (t2_resp.keys != None)):   # if there is no answer for task1, but for task2 (never possible in nogo trial)
    msg = "Falsche Reihenfolge"   
elif ((t1_resp.keys == None) and (t2_resp.keys == None)):   # if there is no answer at all
    msg = "Zu langsam"   
else:
    msg = "Falsch"

Then your feedback trial text component just displays $msg. Something along these lines should work.

Dear Jonathan,
thank you for taking the time to reply to me.
However, unfortunately this experiment will be online and the stimuli list is ideally different for everyone (to make sure it’s not item effect because of the order and particular pairings but the actual effect we’re looking for)
and also the condition that horizontal images and vertical ones are equally paired with go/no-go
i.e. each block has 24 trials (12 go and 12 nogo) – each 12 has 6 horizontal and 6 vertical that are 3x repetition of 2 unique horizontal and vertical stimuli (8 unique stimuli: 4 horizontal and 4 vertical per block in total)

This is where it’s tricky for me and using condition file becomes more difficult.

for the t2, it is also important to notice if it’s correct or not correct, as my feedback code wrote, I’m just not sure how to write it correctly

if t1_resp.corr and t2_resp.corr:  
    msg = "Richtig" #richtig = correct

I tried using a condition file for the response mapping but it seems like it’s only following the rows in the condition file and did not actually check for response

Do you have any idea how to do this if it was online?

As for response mapping, using the code that you provided me can I edit it to this:

vgo={'img':[], 'gonogo':['go']*6, 't1_corrkey':['a']*6, 't2_corrkey':['k']*6}

Also I’m not sure what you mean by adding it to B1stim? I need to append it to B1stim? But it is a stimuli pair list.
Moreover, what should I put in t1_resp and t2_resp as a reference of the correct answer? it can’t be $B1stim, right?

About what you said here

For the image component you would need to put something like `B1stim_1[0]` or some other way or referring to items within the list.

I figured that I should make something like cur_t1 = 0
and in the image element for task 1 I put something like $B1stim_1[cur_t1=0]
and on the End routine I put cur_t1 = cur_t1 +1 so that it moves (did the same for t2)
the problem is that Psychopy told me I ran out of index after going through the 8 random selected stimuli and I can’t repeat it 3x in a block like I wanted to, the NReps in the inner loops seem to correspond to how many trials I want to do. Is there anyway to fix this?

Thank you in advance.

If this is going to be online things get more difficult, mostly because you can’t load fresh condition files on the fly as easily (I know it’s possible in the most recent versions but I’m not very experienced with it). All the code will need to be in JavaScript as well, though the automatic code conversion should help with that, but ‘shuffle’ won’t work the same way (see the Python to Javascript cheat sheet).

In the code I wrote, rather than a list of stimulus pairs, I created a dictionary with separate lists for stimulus and go/no-go, and those lists were aligned. So a ‘pair’ would just be the same list index in the ‘img’ and ‘gonogo’ fields of the dictionary. It’s just a different way of achieving the same thing, but it had the primary advantage of being something you could turn into an Excel file so it could be used as a condition file and fed into the builder. So in the loop I used to add ‘img’ and ‘gonogo’ to B1Stim above, you would just have to add ‘t1_corrkey’ and ‘t2_corrkey’ as well.

Doing this in JS is going to be trickier. For the feedback part, you can look at some past entries on the forum, like this one: Display contingent feedback - #16 by emregurbuz

The randomization is trickier. You could try starting here: Setting order of stimuli conditionally - link generated order to condition file

Alternately, you could construct the orders in advance the same way as you do now, and then set the parameters for all the stimuli in some “Begin Routine” code, with extra code on a trial that appears between blocks to organize the order of blocks and shuffle the order within each block. Basically replace the functionality of the condition file with code. That’s a little involved and unfortunately not something I have time to put together a demonstration of right now.

hey Jonathan,
I appreciate all the time you took to help me, but I think I found some break through yesterday.
Whether or not it works online I still don’t know but I will look into the links you provided me, thank you.

The only problem I have now is the data saving in one cell instead of different rows for each trial.
I had to save these datas manually because I’m not using a condition file. Would you know anything about that?

It’s always going to save different rows for each trial by default, but if you need to add columns, you can always use this in you end routine code:

loopname_loop.addData("column_name", data)

Where “loopname” is replaced by the name of the loop the routine is part of, “column_name” is what you want the column in the data file to be called, and data is the variable with whatever information you want to store.

If the issue is you have one line per trial but want to summarize or make the data more compact, it’s better to do that in Excel or R rather than trying to mess with the output directly. PsychoPy generally defaults to saving as much data as possible so you can sort through it afterward if needed.