psychopy.org | Reference | Downloads | Github

Oddball paradigm - How to set probabilities of oddball or common stimulus

I am creating an oddball paradigm for an infant study. The experiment shows a 7 minute clip of fantasia. During the video every 6-10 seconds a pair of vibrations are sent out (triggered by an auditory beep sound) with an ISI of 700ms between them and a duration of 100ms for each vibration. For the oddball experiment we’d like to set an expectation of paired stimulation by having x amount of pairs appear first (e.g., 5) and then onwards have paired stimulation occur x% of time (70% chance) whilst 30% only a single vibration occurs.

Below is the code I have related to vibration. Keep in mind I’ve deleted the EEG trigger and tactors (vibration machine) triggers so essentially this looks like an auditory oddball paradigm. My question is how do i set a probability measure? Any help would be greatly appreciated!


# we want to present the tone every
# 6 - 10 seconds for the duration of the trial
# if the sound is not currently playing
if not tactors1.playing and not tactors1.waiting:
    # pick how long we will wait for
    tactors1ISI = randint(6, 10)
    print('tactorISI', tactors1ISI)
    tactors1ISIs.append(tactors1ISI)
    tactors1Onset = t +tactors1ISI
    #we are waiting for the sound to play
    tactors1.waiting = True
elif not tactors1.playing and tactors1.waiting:
    if t >= tactors1Onset:
        print('playing 1st tactor')
        first_tactor_played = False
        tactors1.play()
        tactors1Onsets.append(t)
        tactors1.playing = True
        tactors1.waiting = False
        tactor1_on_times +1
        first_tactor_start_time = core.getTime() # Gets a timestamp of the start of the first vibration       
        first_tactor_played = True
elif tactors1.playing:
    if t >= tactors1Onset + tactors1.secs:
        tactors1.stop()
        tactors1.playing = False
        first_tactor_played = True
        first_tactor_stop_time = core.getTime()

#2nd vibration

if not tactors2.playing and not tactors2.waiting:
    if first_tactor_played == True:
        # pick how long we will wait for
        tactors2ISI = 0.7
        print('tactor2ISI', tactors2ISI)
        tactors2ISIs.append(tactors2ISI)
        tactors2Onset = t +tactors2ISI
        #we are waiting for the sound to play
        tactors2.waiting = True
elif not tactors2.playing and tactors2.waiting:
    if first_tactor_played == True:
        if t >= tactors2Onset:
            print('playing 2nd tactor')
            tactors2.play()
            tactors2Onsets.append(t)
            tactors2.playing = True
            tactors2.waiting = False
            tactor2_on_times +1
        #first_tactor_start_time = core.getTime() # Gets a timestamp of the start of the first vibration
                
elif tactors2.playing:
    if t >= tactors2Onset + tactors2.secs:
        if first_tactor_played == True:
            tactors2.stop()
            tactors2.playing = False
            first_tactor_played = False

Bumpity bump!

So below I created a function that each time stimulus is presented a random roll between 0-100 happens and for the oddball stimulus to happen the number has to be > 75. This sort of works but it is not optimal because by chance i might get less or more than 25% oddball appearances within the 7 minute trial.

See code below:

percentage_chance_pair = 0.75
dice_roll = random.randint(0,100)

we want to present the tone every

6 - 10 seconds for the duration of the trial

if the sound is not currently playing

if not tactors1.playing and not tactors1.waiting:
# pick how long we will wait for
tactors1ISI = randint(6, 10)
print(‘tactorISI’, tactors1ISI)
tactors1ISIs.append(tactors1ISI)
tactors1Onset = t +tactors1ISI
#we are waiting for the sound to play
tactors1.waiting = True
pulse_started_tactors1 = False
port_tactors.write([0x1b, 0x54, 0x31, 0x4c, 0x44])
port_tactors.write([0x1b, 0x54, 0x32, 0x52, 0x44])
port_trigger_eeg.write([0x00])
dice_roll = random.randint(0,100)

#elif
if not tactors1.playing and tactors1.waiting:
if t >= tactors1Onset:
print(‘playing 1st tactor’)

    first_tactor_played = False
    tactors1.play()
    tactors1_on_times +=1
    tactors1Onsets.append(t)
    tactors1.playing = True
    tactors1.waiting = False
    first_tactor_start_time = core.getTime() # Gets a timestamp of the start of the first vibration       
    port_tactors.write([0x1b, 0x54, 0x31,0x45])
    port_tactors.write([0x1b, 0x54, 0x32,0x45])
    first_tactor_played = True
    print("the dice roll was", dice_roll)

#elif
if tactors1.playing:
if t >= tactors1Onset + tactors1.secs:
tactors1.stop()
port_tactors.write([0x1b, 0x54, 0x31,0x44])
port_tactors.write([0x1b, 0x54, 0x32,0x44])
tactors1.playing = False
first_tactor_played = True
first_tactor_stop_time = core.getTime()

if tactors1.playing == True and not pulse_started_tactors1:
port_trigger_eeg.write([0x01])
pulse_start_time_tactors1 = globalClock.getTime()
pulse_started_tactors1 = True

print("tactor1 has played",tactors1_on_times,"times.")

if pulse_started_tactors1 and not pulse_ended_tactors1:
if globalClock.getTime() - pulse_start_time_tactors1 >= 0.1:
port_trigger_eeg.write([0x00])
port_tactors.write([0x1b, 0x54, 0x31,0x44])
port_tactors.write([0x1b, 0x54, 0x32,0x44])
pulse_ended_tactors1 = True

#2nd vibration

if not tactors2.playing and not tactors2.waiting:
if first_tactor_played == True:
# pick how long we will wait for
tactors2ISI = 0.7
print(‘tactor2ISI’, tactors2ISI)
tactors2ISIs.append(tactors2ISI)
tactors2Onset = t +tactors2ISI
#we are waiting for the sound to play
tactors2.waiting = True
pulse_started_tactors1 = False
port_tactors.write([0x1b, 0x54, 0x31, 0x4c, 0x44])
port_tactors.write([0x1b, 0x54, 0x32, 0x52, 0x44])
#elif
if not tactors2.playing and tactors2.waiting:
if first_tactor_played == True and tactors1_on_times <=5:
if t >= tactors2Onset:
print(‘playing 2nd tactor’)
tactors2.play()
tactors2Onsets.append(t)
tactors2.playing = True
tactors2.waiting = False
port_tactors.write([0x1b, 0x54, 0x31,0x45])
port_tactors.write([0x1b, 0x54, 0x32,0x45])
print("tactor2 has played ",tactors2_on_times,“times.”)

if not tactors2.playing and tactors2.waiting:
if first_tactor_played == True and tactors1_on_times >5:
#if dice_roll <= percentage_chance_pair and t >= tactors2Onset:
if dice_roll <= 75 and t >= tactors2Onset:
print(‘playing 2nd tactor’)
tactors2.play()
tactors2_on_times +=1
tactors2Onsets.append(t)
tactors2.playing = True
tactors2.waiting = False
port_tactors.write([0x1b, 0x54, 0x31,0x45])
port_tactors.write([0x1b, 0x54, 0x32,0x45])
first_tactor_played = True

#elif
if tactors2.playing:
if t >= tactors2Onset + tactors2.secs:
if first_tactor_played == True:
tactors2.stop()
tactors2.playing = False
first_tactor_played = False
port_tactors.write([0x1b, 0x54, 0x31, 0x4c, 0x44])
port_tactors.write([0x1b, 0x54, 0x32, 0x52, 0x44])

if tactors2.playing == True and not pulse_started_tactors2:
port_trigger_eeg.write([0x02])
pulse_start_time_tactors2 = globalClock.getTime()
pulse_started_tactors2 = True
print(“tactor2 has played”,tactors2_on_times,“times.”)

if pulse_started_tactors2 and not pulse_ended_tactors2:
if globalClock.getTime() - pulse_start_time_tactors2 >= 0.1:
port_trigger_eeg.write([0x00])
port_tactors.write([0x1b, 0x54, 0x31,0x44])
port_tactors.write([0x1b, 0x54, 0x32,0x44])
pulse_ended_tactors2 = True

The way to fix this I think is to create upon start of the experiment a randomised array of tactors1ISIs. Following this, another array based on the array above should ensure that exactly 75% of stimuli presented will be pairs and that the remaining 25% are oddball stimuli… Way more complicated than I can do… Any ideas/help on how to do this would be super appreciated

Hi Achilles,

The TrialHandlerExt class can be used to create oddball experiments. it takes a parameter called weight, which can be used to set the probabilities. You can then use the random or fullRandom method for shuffling.

The setting of expectation part is not randomized, and so could be hard-coded.

Best
Suddha

1 Like

Hello Suddha,

I didn’t know about this in-built method. I’ve now created my oddball paradigm in a different way. For anyone interested in doing it my way. This code randomises single (oddball) and pair stimulation but ensures that there can never be two continuous single stimulations in a row:

#begin experiment
import random
import serial
from psychopy import sound
import serial
import psychtoolbox as ptb
from psychopy.core import StaticPeriod

#loads the sound file and plays sound for 0.2 seconds
tactors1 = sound.Sound('experiment_audio.wav', name='$tactors1', stereo = True, secs = 0.2)
tactors1.setVolume(1)
tactors1.playing = False
tactors1.waiting = False


tactors2 = sound.Sound('experiment_audio.wav', name='$tactors2', stereo = True, secs = 0.2)
tactors2.setVolume(1)
tactors2.playing = False
tactors2.waiting = False
first_tactor_start_time = core.getTime()
first_tactor_stop_time = core.getTime()

first_tactor_played = False


#this is serial port of the tactors. Change COM8 to another number if 'USB Serial Port' in Ports in Device manager has changed COM number
##port_tactors = serial.Serial('COM8',57600)
##port_tactors.write([0x1b, 0x54, 0x31, 0x4c, 0x44])
##port_tactors.write([0x1b, 0x54, 0x32, 0x52, 0x44])

##port_trigger_eeg = serial.Serial('COM5')
##port_trigger_eeg.write([0x00])


tactors1_on_times = 0
tactors2_on_times = 0

#array approach

import random

stimulation_array = []
one_indices = set()

while len(one_indices) != 15:
    # set 2nd integer to however many total stimulations you'd like to deliver in total -1
    idx = random.randint(0, 39)
    if not {idx, idx+1, idx-1} & one_indices:
        one_indices.add(idx)
# range should be total number of stimulations but not total -1
for idx in range(40):
    if idx in one_indices:
        stimulation_array.append(1)
    else:
        stimulation_array.append(2)

print(stimulation_array)
length = len(stimulation_array)
array_position_number = 0
value = stimulation_array[array_position_number]


#begin routine

tactors1Onsets=[]
tactors1ISIs=[]

tactors2Onsets=[]
tactors2ISIs=[]


pulse_started_tactors1 = False
pulse_ended_tactors1 = False

pulse_started_tactors2 = False
pulse_ended_tactors2 = False

pulse_started_trigger = False
pulse_ended_trigger = False


tactors1.playing = False
tactors2.playing = False

trial_ended = True

#each frame

# we want to present the tone every
# 5 - 9 seconds for the duration of the trial
# if the sound is not currently playing
if not tactors1.playing and not tactors1.waiting:
    if trial_ended == True:
        # pick how long we will wait for
        tactors1ISI = randint(5, 9)
        print('tactorISI', tactors1ISI)
        tactors1ISIs.append(tactors1ISI)
        tactors1Onset = t +tactors1ISI
        #we are waiting for the sound to play
        tactors1.waiting = True
        tactors2.waiting = False

        # If Loop to iterate through list:
        if array_position_number < length and tactors1_on_times >5:
                value = stimulation_array[array_position_number]
                array_position_number += 1
                print('array position number is ',array_position_number)
        
        #reset trial variables
        pulse_started_tactors1 = False
        pulse_ended_tactors1 = False
        pulse_started_tactors2 = False
        pulse_ended_tactors2 = False
        trial_ended = False
elif not tactors1.playing and tactors1.waiting:
    if t >= tactors1Onset:
        print('playing 1st tactor')
        tactors1.play()
        tactors1_on_times +=1
        tactors1Onsets.append(t)
        tactors1.playing = True
        tactors1.waiting = False
        first_tactor_start_time = core.getTime() # Gets a timestamp of the start of the first vibration       
        ##port_tactors.write([0x1b, 0x54, 0x31,0x45])
        ##port_tactors.write([0x1b, 0x54, 0x32,0x45])
        ##port_trigger_eeg.write([0x01])
        

if tactors1.playing:
    if t >= tactors1Onset + tactors1.secs:
            tactors1.stop()
            ##port_tactors.write([0x1b, 0x54, 0x31,0x44])
            ##port_tactors.write([0x1b, 0x54, 0x32,0x44])
            tactors1.playing = False
            first_tactor_played = True
            first_tactor_stop_time = core.getTime()


if tactors1.playing == True and not pulse_started_tactors1:
    pulse_start_time_tactors1 = globalClock.getTime()
    pulse_started_tactors1 = True
    print("tactor1 has played",tactors1_on_times,"times.")


if pulse_started_tactors1 and not pulse_ended_tactors1:
    if globalClock.getTime() - pulse_start_time_tactors1 >= 0.01:
        ##port_trigger_eeg.write([0x00])
        pulse_ended_tactors1 = True

#2nd vibration

if not tactors2.playing and not tactors2.waiting:
    if first_tactor_played == True:
            # pick how long we will wait for
            tactors2ISI = 0.7
            print('tactor2ISI', tactors2ISI)
            tactors2ISIs.append(tactors2ISI)
            tactors2Onset = t +tactors2ISI
            #we are waiting for the sound to play
            tactors2.waiting = True

if not tactors2.playing and tactors2.waiting:
    if first_tactor_played == True and tactors1_on_times <=5:
        if t >= tactors2Onset:
            print('playing 2nd tactor')
            tactors2.play()
            tactors2_on_times +=1
            tactors2Onsets.append(t)
            print("tactor2 has played ",tactors2_on_times,"times.")
            ##port_tactors.write([0x1b, 0x54, 0x31,0x45])
            ##port_tactors.write([0x1b, 0x54, 0x32,0x45])
            ##port_trigger_eeg.write([0x02])
            tactors2.playing = True
            tactors2.waiting = False
            
    elif first_tactor_played == True and tactors1_on_times >5:
        if value>1 and t >= tactors2Onset:
            print('playing 2nd tactor')
            tactors2.play()
            tactors2_on_times +=1
            tactors2Onsets.append(t)
            print("tactor2 has played ",tactors2_on_times,"times.")
            ##port_tactors.write([0x1b, 0x54, 0x31,0x45])
            ##port_tactors.write([0x1b, 0x54, 0x32,0x45])
            ##port_trigger_eeg.write([0x02])
            tactors2.playing = True
            tactors2.waiting = False
   
        elif value <= 1:
            trial_ended = True
            first_tactor_played == False


if tactors2.playing:
    if t >= tactors2Onset + tactors2.secs:
            tactors2.stop()
            tactors2.playing = False
            ##port_tactors.write([0x1b, 0x54, 0x31,0x44])
            ##port_tactors.write([0x1b, 0x54, 0x32,0x44])
            first_tactor_played = False
            
if tactors2.playing == True and not pulse_started_tactors2:
    pulse_start_time_tactors2 = globalClock.getTime()
    pulse_started_tactors2 = True
    pulse_ended_tactors2 = False
    print("tactor2 has played",tactors2_on_times,"times.")


if pulse_started_tactors2 and not pulse_ended_tactors2:
    if globalClock.getTime() - pulse_start_time_tactors2 >= 0.01:
        ##port_trigger_eeg.write([0x00])
        pulse_ended_tactors2 = True
        trial_ended = True


Hopefully others trying to create oddball experiments will find this code useful :slight_smile: