Sternberg Task

../../_images/stern.png

This is the Sternberg task. Developed by Saul Sternberg in the 1960’s, this task is designed to test a participants working memory by asking them to view a list of several stimuli, usually words, numbers, or letters, and then showing them a stimuli that may or may not have been in that list. They are then required to make a judgement on whether or not that word was in the list. Below is the SMILE version of that classic task. We use Action states like KeyPress and Label in this experiment, as well as Flow states like UntilDone and Loop.

Each participant of this experiment will have a different log that will contain all of the information about each block, as well as all of the information that would be needed to run analysis of this experiment, i.e. reaction times.

The Experiment

First, let’s do the imports of the experiment. Below is the start of stern.py. We will also execute the configuration file and the stimulus generation file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# global imports
import random
import string
# load all the states
from smile.common import *

#execute both the configuration file and the
#stimulus generation file
from config import *
from gen_stim import *

Easy! Now, let’s also set up all the experiment variables. These are all the variables that are needed for generating stimuli, durations of states, and little things like instructions and the keys for KeyPress states. The following is config.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# config vars
NUM_TRIALS = 2
#The trials, shuffled, for the stimulus generation.
NUM_ITEMS = [2,2,2,2,2,2,2,3,3,3,3,3,3,3,4,4,4,4,4,4,4]
random.shuffle(NUM_ITEMS)
ITEMS = string.ascii_lowercase
#instructions written in another document
instruct_text = open('stern_instructions.rst', 'r').read()
RSTFONTSIZE = 30
RSTWIDTH = 900
STUDY_DURATION = 1.2
STUDY_ISI = .4
RETENTION_INTERVAL = 1.0
#KeyPress stuff
RESP_KEYS = ['J','K']
RESP_DELAY = .2
ORIENT_DURATION = 1.0
ORIENT_ISI = .5
ITI = 1.0
FONTSIZE = 30

Next is the generation of our stimuli. In SMILE, the best practice is to generate lists of dictionaries to loop over, that way you don’t have to do any calculations during the actual experiments. Next is the definition of a function that was written to generate a stern trial called stern_trial(), as well as where we call it to generate our stimulus. The following is gen_stim.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# generate sternberg trial
def stern_trial(nitems=2, lure_trial=False,):
    if lure_trial:
        condition = 'lure'
        items = random.sample(ITEMS,nitems+1)
    else:
        condition = 'target'
        items = random.sample(ITEMS,nitems)
        # append a test item
        items.append(random.sample(items,1)[0])
    trial = {'nitems':nitems,
             'study_items':items[:-1],
             'test_item':items[-1],
             'condition':condition,}
    return trial

trials = []
for i in NUM_ITEMS:
    # add target trials
    trials.extend([stern_trial(i,lure_trial=False) for t in range(NUM_TRIALS)])
    # add lure trials
    trials.extend([stern_trial(i,lure_trial=True) for t in range(NUM_TRIALS)])

# shuffle and number
random.shuffle(trials)
for t in range(len(trials)):
    trials[t]['trial_num'] = t

After we generate our stimulus we need to set up our experiment. The comments in the following code explain what every few lines do.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#Define the experiment
exp = Experiment()
#Present the instructions to the participant
init_text = RstDocument(text=instruct_text, width=RSTWIDTH, font_size=RSTFONTSIZE, top=exp.screen.top, height=exp.screen.height)
with UntilDone():
    #Once the KeyPress is detected, the UntilDone
    #cancels the RstDocument
    keypress = KeyPress()
# loop over study block
with Loop(trials) as trial:
    #Setup the list of study times.
    exp.study_times = []
    # orient stim
    orient = Label(text='+',duration=ORIENT_DURATION, font_size=FONTSIZE)
    Wait(ORIENT_ISI)
    # loop over study items
    with Loop(trial.current['study_items']) as item:
        # present the letter
        ss = Label(text=item.current, duration=STUDY_DURATION, font_size=FONTSIZE)
        # wait some jittered amount
        Wait(STUDY_ISI)
        # append the time
        exp.study_times+=[ss.appear_time['time']]
    # Retention interval
    Wait(RETENTION_INTERVAL - STUDY_ISI)
    # present the letter
    test_stim = Label(text=trial.current['test_item'], bold=True, font_size=FONTSIZE)
    with UntilDone():
        # wait some before accepting input
        Wait(RESP_DELAY)
        #After the KeyPress is detected, the UntilDone
        #cancels the Label test_stim and allows the
        #experiment to continue.
        ks = KeyPress(keys=RESP_KEYS,
                      base_time=test_stim.appear_time['time'])
    # Log the trial
    Log(trial.current,
        name="Stern",
        resp=ks.pressed,
        rt=ks.rt,
        orient_time=orient.appear_time['time'],
        study_times=exp.study_times,
        test_time=test_stim.appear_time['time'],
        correct=(((trial.current['condition']=='target')&
                 (ks.pressed==RESP_KEYS[0])) |
                 ((trial.current['condition']=='lure')&
                 (ks.pressed==RESP_KEYS[1]))))
    Wait(ITI)
# run that exp!
exp.run()

Analysis

When coding your experiment, you don’t have to worry about losing any data because all of it is saved out into .slog files anyway. The thing you do have to worry about is whether or not you want that data to be easily available or if you want to spend hours slogging through your data. We made it easy for you to pick which data you want saved out during the running of your experiment with use of the Log state.

The relevant data that we need from a Sternberg task would be the reaction times for every test event, all of the presented letters from the study and test portion of the experiment, and whether they answered correctly or not. In the Log that we defined in our experiment above, we saved a little more than that out, because it is better to save out data and not need it, then to not save it and need it later.

If you would like to grab your data from the .slog files to analyze your data in python, you need to use the log2dl(). This function will read in all of the .slog files with the same base name, and convert them into one long list of dictionaries. Below is a the few lines of code you would use to get at all of the data from three imaginary participants, named as s000, s001, and s002.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from smile.log as lg
#define subject pool
subjects = ["s000/","s001/","s002/"]
dic_list = []
for sbj in subjects:
    #get at all the different subjects
    dic_list.append(lg.log2dl(log_filename="data/" + sbj + "Log_Stern"))
#print out all of the study times in the first study block for
#participant one, block one
print dic_list[0]['study_times']

You can also translate all of the .slog files into .csv files easily by running the command log2csv() for each participant. An example of this is located below.

1
2
3
4
5
6
from smile.log as lg
#define subject pool
subjects = ["s000/","s001/","s002/"]
for sbj in subjects:
    #Get at all the subjects data, naming the csv appropriately.
    lg.log2csv(log_filename="data/" + sbj + "Log_Stern", csv_filename=sbj + "_Stern")

stern.py in Full

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# global imports
import random
import string
# load all the states
from smile.common import *

#execute both the configuration file and the
#stimulus generation file
from config import *
from gen_stim import *

#Define the experiment
exp = Experiment()
#Present the instructions to the participant
init_text = RstDocument(text=instruct_text, width=RSTWIDTH, font_size=RSTFONTSIZE top=exp.screen.top, height=exp.screen.height)
with UntilDone():
    #Once the KeyPress is detected, the UntilDone
    #cancels the RstDocument
    keypress = KeyPress()
# loop over study block
with Loop(trials) as trial:
    #Setup the list of study times.
    exp.study_times = []
    # orient stim
    orient = Label(text='+',duration=ORIENT_DURATION, font_size=FONTSIZE)
    Wait(ORIENT_ISI)
    # loop over study items
    with Loop(trial.current['study_items']) as item:
        # present the letter
        ss = Label(text=item.current, duration=STUDY_DURATION, font_size=FONTSIZE)
        # wait some jittered amount
        Wait(STUDY_ISI)
        # append the time
        exp.study_times+=[ss.appear_time['time']]
    # Retention interval
    Wait(RETENTION_INTERVAL - STUDY_ISI)
    # present the letter
    test_stim = Label(text=trial.current['test_item'], bold=True, font_size=FONTSIZE)
    with UntilDone():
        # wait some before accepting input
        Wait(RESP_DELAY)
        #After the KeyPress is detected, the UntilDone
        #cancels the Label test_stim and allows the
        #experiment to continue.
        ks = KeyPress(keys=RESP_KEYS,
                      base_time=test_stim.appear_time['time'])
    # Log the trial
    Log(trial.current,
        name="Stern",
        resp=ks.pressed,
        rt=ks.rt,
        orient_time=orient.appear_time['time'],
        study_times=exp.study_times,
        test_time=test_stim.appear_time['time'],
        correct=(((trial.current['condition']=='target')&
                 (ks.pressed==RESP_KEYS[0])) |
                 ((trial.current['condition']=='lure')&
                 (ks.pressed==RESP_KEYS[1]))))
    Wait(ITI)
# run that exp!
exp.run()

config.py in Full

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# config vars
NUM_TRIALS = 2
NUM_ITEMS = [2,3,4]
ITEMS = string.ascii_lowercase
instruct_text = open('stern_instructions.rst', 'r').read()
RSTFONTSIZE = 30
RSTWIDTH = 900
STUDY_DURATION = 1.2
STUDY_ISI = .4
RETENTION_INTERVAL = 1.0
RESP_KEYS = ['J','K']
RESP_DELAY = .2
ORIENT_DURATION = 1.0
ORIENT_ISI = .5
ITI = 1.0
FONTSIZE = 30

gen_stim.py in Full

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# generate Sternberg trial
def stern_trial(nitems=2, lure_trial=False,):
    if lure_trial:
        condition = 'lure'
        items = random.sample(ITEMS,nitems+1)
    else:
        condition = 'target'
        items = random.sample(ITEMS,nitems)
        # append a test item
        items.append(random.sample(items,1)[0])
    trial = {'nitems':nitems,
             'study_items':items[:-1],
             'test_item':items[-1],
             'condition':condition,}
    return trial

trials = []
for i in NUM_ITEMS:
    # add target trials
    trials.extend([stern_trial(i,lure_trial=False) for t in range(NUM_TRIALS)])
    # add lure trials
    trials.extend([stern_trial(i,lure_trial=True) for t in range(NUM_TRIALS)])

# shuffle and number
random.shuffle(trials)
for t in range(len(trials)):
    trials[t]['trial_num'] = t

CITATION

Sternberg, S. (1966), "High-speed scanning in human memory", Science 153 (3736), 652-654