Consecutive stimulus presentations

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

‘’’

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
                stimulus = new_tuple[2]
        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()

To make the code readable on the forum, the marks around it should be backticks (`), not apostrophes/single quotes (').

On a standard US keyboard, you can type this using the key above the tab key, to the left of the 1. If you have a different keyboard, this may help:

Sorry! I changed it!