Consecutive presentation problem

Hello everyone!

I’m reaching out to ask for your help, if you have some time. I started learning about psychopy this year.

I’ve developed a code in Psychopy Coder to run a task presenting two stimuli,each associated with a slider, text. One of the two stimulus is associated with a sound in 50% of the presentations.

We’re trying to make sure that the same stimulus isn’t presented more than three times in a row, both within a single block and across different blocks.

Despite my attempts to fix it, I’m still having trouble with this issue, and sometimes four presentations occur consecutively.

You might be able to quickly point out the problem (unlike me :/)
I’d really appreciate your help!
Thank you very much!

Best regards :slight_smile:

``

Initialize Routine “rec”

Initialize Routine “instruct_us_exp”

instruct_us_exp = visual.TextStim(win=win, name=‘instruct_us_exp’,
text=‘Rate your expectancy of the sound.’, font=‘Arial’, pos=(0, 0), height=0.07, color=‘white’)
instruct_frame.draw()
instruct_us_exp.draw()
win.flip()
instruct_us_exp_duration = 2
core.wait(instruct_us_exp_duration)
check_exit_key()
#US Expectancy rating
US_exp = visual.Slider(win=win, name=‘US_exp’,
startValue=None, size=(0.8, 0.13), pos=(0.0, -0.6), units=win.units,
labels=(‘Not at All’, ‘Very Much’), ticks=(0, 100), granularity=0.0,
style=‘rating’, opacity=1,
labelColor=‘White’, markerColor=None, lineColor=‘White’, colorSpace=‘rgb’,
font=‘Arial’, labelHeight=0.06,
flip=False, ori=0.0, depth=0, readOnly=False)
US_exp_text = visual.TextStim(win=win, name=‘US_exp_text’,
text=‘To what extent do you expect the sound?’,
font=‘Arial’,
pos=(0, -0.4), height=0.07, wrapWidth=None, ori=0.0,
color=‘white’, colorSpace=‘rgb’, opacity=None,
languageStyle=‘LTR’,
depth=-1.0);
def draw_ticks(slider, tick_positions, tick_labels):
for tick_position, tick_label in zip(tick_positions, tick_labels):
tick_text = visual.TextStim(win=win, text=str(tick_label), font=‘Arial’, height=0.07,
pos=(slider.pos[0] + slider.size[0] * ((tick_position - slider.ticks[0]) / (slider.ticks[-1] - slider.ticks[0])), slider.pos[1] + 0.13),
color=‘white’)
tick_text.draw()
tick_positions = [-50, 50]
tick_labels = [0, 100]
check_exit_key()

Define stimuli

cs_plus = visual.ImageStim(win, image=‘C:/Users/marie/Documents/UM/Master 2/Internship/Psychopy/round.png’, pos=(0, 170), units=‘pix’, size=(600, 600))
cs_minus = visual.ImageStim(win, image=‘C:/Users/marie/Documents/UM/Master 2/Internship/Psychopy/triangle.png’, pos=(0, 170), units=‘pix’, size=(600, 600))
us_image = visual.ImageStim(win, image=‘C:/Users/marie/Documents/UM/Master 2/Internship/Psychopy/sound.png’, pos=(0, 170), units=‘pix’, size=(600, 600))
us_sound = sound.Sound(‘C:/Users/marie/Documents/UM/Master 2/Internship/Psychopy/us.wav’, stereo=True)
#Routine “complete”
sliders_and_texts_and_stimuli_and_sounds = [
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, us_sound),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_plus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
(US_exp, US_exp_text, cs_minus, None),
]
random.shuffle(sliders_and_texts_and_stimuli_and_sounds)
sound_component = None
response_data_list =
reaction_time = None
previous_stimulus = None
all_stimuli =

Define some initial variables

for i in range(4): # Loop for 4 blocks
presentations_per_block = {
(US_exp, US_exp_text, cs_plus, us_sound): 8 // 4,
(US_exp, US_exp_text, cs_plus, None): 8 // 4,
(US_exp, US_exp_text, cs_minus, None): 16 // 4
}
block_stimuli =

for slider, text, stimulus, sound in presentations_per_block:
    presentations = presentations_per_block[(slider, text, stimulus, sound)]
    block_stimuli.extend([(slider, text, stimulus, sound)] * presentations_per_block[(slider, text, stimulus, sound)])

consecutive_count = 0
all_stimuli.extend(sliders_and_texts_and_stimuli_and_sounds)
np.random.shuffle(all_stimuli)
for slider, text, stimulus, sound in sliders_and_texts_and_stimuli_and_sounds:
    if stimulus == previous_stimulus:
        # Increment the consecutive count
        consecutive_count += 1
    else:
        # If the current stimulus is different, reset the consecutive count
        consecutive_count = 0
        
    if consecutive_count >= 2:
        # If the limit is reached, choose a random tuple with a different stimulus
        other_stimulus = cs_plus if stimulus == cs_minus else cs_minus
        # Filter out tuples with the different stimulus
        valid_tuples = [(s, t, st, so) for s, t, st, so in block_stimuli if st == other_stimulus]
        # Choose a random tuple from the valid ones
        if valid_tuples:
            new_tuple = random.choice(valid_tuples)
        # Swap the current tuple with the new one
            all_stimuli[all_stimuli.index((slider, text, stimulus, sound))] = new_tuple
    previous_stimulus = stimulus

``
# Mélanger la liste des stimuli pour chaque bloc
np.random.shuffle(block_stimuli)
for item in block_stimuli:
slider, text, stimulus, sound = item[:4]
current_stimulus = (slider, text, stimulus, sound)
marqueur = visual.Rect(win=win, width=0.03, height=0.05, fillColor=‘red’, lineColor=None)
stimulus_type = ‘cs_plus’ if stimulus == cs_plus and sound is None else ‘cs_us’ if stimulus == cs_plus and sound == us_sound else ‘cs_minus’
event.Mouse().setPos((0, -0.2))
non_marker = (0, -0.2)
onset_time = experiment_clock.getTime()
sound_onset = None
response_value = None
# Onset timing of slider, text, stimulus and sound
while experiment_clock.getTime() - onset_time < 10.5:
loop_time = experiment_clock.getTime() - onset_time
# Draw stimulus, text, and slider
if loop_time < 2.5:
stimulus.draw()
sound_playing = False
if stimulus == cs_plus and sound is not None:
if loop_time >= 1 and sound_onset is None:
sound_onset = onset_time + 1
if sound_component is not None:
sound_component.stop()
sound.play(when=sound_onset)
sound_component = sound
sound_playing = True
check_exit_key()
if loop_time <= 1 and response_value is None:
slider.draw()
text.draw()
draw_ticks(slider, tick_positions, tick_labels)
mouse_pos = event.Mouse().getPos()
if mouse_pos[0] != non_marker[0] or mouse_pos[1] != non_marker[1]:
marqueur_pos = max(min(mouse_pos[0] - slider.pos[0], slider.size[0] / 2), -slider.size[0] / 2)
marqueur.pos = (slider.pos[0] + marqueur_pos, slider.pos[1])
marqueur.draw()
# Restrict the mouse click to the slider region
if event.Mouse().getPressed()[0]:
mouse_pos = event.Mouse().getPos()
scale_pos = slider.pos
scale_size = slider.size[0]
if (
scale_pos[0] - scale_size / 2 < mouse_pos[0] < scale_pos[0] + scale_size / 2 and
scale_pos[1] - slider.size[1] / 2 < mouse_pos[1] < scale_pos[1] + slider.size[1] / 2
):
response_value = (mouse_pos[0] - scale_pos[0] + scale_size / 2) / scale_size * 100
response_value_onset = experiment_clock.getTime()
reaction_time = response_value_onset - onset_time
check_exit_key()
win.flip()
#Draw intertrial interval
else:
us_image.draw()
win.flip()
check_exit_key()
# Record response data
response_data_list.append({
“Subject ID”: participant_info[‘Subject ID’],
f"{slider.name}_onset_time": onset_time,
f"{slider.name}_reaction_time": reaction_time,
f"{slider.name}_response": response_value,
“Stimulus Type”: stimulus_type,
“US onset”: sound_onset,
})
check_exit_key()
check_exit_key()

Create a DataFrame from the list of responses

response_df = pd.DataFrame(response_data_list)
response_file_path = os.path.join(output_dir, f’{participant_info[“Subject ID”]}_rating_responses_day1.xlsx’)
if os.path.isfile(response_file_path):
existing_df = pd.read_excel(response_file_path)
updated_df = pd.concat([existing_df, response_df], ignore_index=True)
updated_df.to_excel(response_file_path, index=False)
else:
response_df.to_excel(response_file_path, index=False)
print(f"{slider.name} response has been written to {response_file_path}")

Close the window at the end

previous_stimulus = stimulus
win.flip()

Could you re-post the relevant part of the code as a code block so we can see the indentation?

To post a code block you need to enclose it in ` symbols above and below. This is not a single quote but rather the character to the left of the “1” key on a US keyboard layout (I don’t know where it is on a French keyboard layout).

The relevant part of the code is this loop here:

but without the indentation I can’t tell why it might not be working.

Hello Jonathan,

Thank you for your help!
You should be able to see the indentation level now :slight_smile:

Best regards,
Mama

That is helpful. The code also changed slightly, and if this version is accurate I see a couple potential problems.

  1. previous_stimulus is always going to be the original stimulus, not what it is changed to. So if you trip the condition that you have more than 2 consecutive instances of the same stimulus in a row (e.g., 3x cs_minus), while this will change the last one to a cs_plus stimulus, previous_stimulus for the next trial will still be cs_minus.
  2. You don’t reset consecutive_count when you change a stimulus, so even if previous_stimulus updates correctly, it’ll have the consecutive_count wrong and maybe change the next stimulus when it’s not supposed to.

You can fix these problems like so:

for slider, text, stimulus, sound in sliders_and_texts_and_stimuli_and_sounds:
    if stimulus == previous_stimulus:
        # Increment the consecutive count
        consecutive_count += 1
    else:
        # If the current stimulus is different, reset the consecutive count
        consecutive_count = 0
        
    if consecutive_count >= 2:
        # If the limit is reached, choose a random tuple with a different stimulus
        other_stimulus = cs_plus if stimulus == cs_minus else cs_minus
        # Filter out tuples with the different stimulus
        valid_tuples = [(s, t, st, so) for s, t, st, so in block_stimuli if st == other_stimulus]
        # Choose a random tuple from the valid ones
        if valid_tuples:
            new_tuple = random.choice(valid_tuples)
        # Swap the current tuple with the new one
            all_stimuli[all_stimuli.index((slider, text, stimulus, sound))] = new_tuple
            previous_stimulus = other_stimulus
        else: # Your failsafe if you are out of valid tuples.
             previous_stimulus = stimulus
        consecutive_count = 0
    else:
        previous_stimulus = stimulus

Hi,

Thank you a lot for trying to help me!

I ran the experiment twice, I had no more than 3 presentations of the same stimulus the first time but i had four cs_minus in a row in the thrid block during the second run :frowning: