Examples

You can find all examples on this page in the doc/examples folder.

Inter-temporal Choice Task

This is the exact same example as on the front page.

 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import pygame
from pyparadigm.surface_composition import *
from pyparadigm.misc import empty_surface, display, init
from pyparadigm.eventlistener import EventListener

import json
import time

# Scroll to the bottom, and start reading in the main() ;)

def offer_box(title, amount):
    # Creates a border around a vertical layout containing 2 cells, where the
    # lower one has twice the size of the upper one (layout children are
    # automatically wrapped in LLItems with relative_size=1). Both Boxes are
    # filled with text, wich is centered in its parent area.
    return Border()(
            LinLayout("v")(
                Text(title, Font(size=50)),
                LLItem(2)(Text(f"{amount}€", Font(size=50, bold=True)))
            )
        )


def make_offer(now, later, delay):
    # Create pygame.Surface with a white background.
    # The LinLayout splits the available space into (in this case)
    # equally sized horizontally aligned parts. 80% of the available
    # space of each part is used to display a offer box.
    return compose(empty_surface(0xFFFFFF), LinLayout("h"))(
        Padding.from_scale(0.8)(offer_box("Now", now)),
        Padding.from_scale(0.8)(offer_box(f"In {delay} days", later)),
    )


def make_feedback(amount, delay):
    # creates a pygame.Surface which only contains the text message
    msg = f"{amount}€ " + ("now" if delay == 0 else f"in {delay} days")
    return compose(empty_surface(0xFFFFFF))(Text(msg, Font(size=50)))


def main():
    # initiate a window with a resolution of 800 x 600 pixels
    init((800, 600))
    # alternatively, to create a full screen, hardware accelrated window, you
    # could use:
    # init((1920, 1080), pygame.FULLSCREEN | pygame.HWSURFACE | pygame.DOUBLEBUF)

    # Create an Eventlistener object
    event_listener = EventListener()

    # Initiate the data for the paradigm, and create 2 lists to store
    # the results
    immediate_offers = ([10] * 3) + ([20] * 3) + ([30] * 3)
    delays = [10, 20, 30] * 3
    delayed_offers = [delay + im_offer
        for delay, im_offer in zip(delays, immediate_offers)]
    chosen_amounts = []
    chosen_delays = []
    reaction_times = []

    # Execute the paradigm
    for im_offer, del_offer, delay in zip(immediate_offers, delayed_offers, delays):
        # display the offer
        display(make_offer(im_offer, del_offer, delay))
        offer_onset = time.time()

        # wait for a decision in form of the left or right arrow-key
        key = event_listener.wait_for_keys([pygame.K_LEFT, pygame.K_RIGHT])
        # calculate reaction time and save it
        reaction_times.append(time.time() - offer_onset)

        # store results according to decision
        if key == pygame.K_LEFT:
            chosen_amounts.append(im_offer)
            chosen_delays.append(0)
        else:
            chosen_amounts.append(del_offer)
            chosen_delays.append(delay)

        # display a feedback for 2 seconds
        display(make_feedback(chosen_amounts[-1], chosen_delays[-1]))
        event_listener.wait_for_seconds(2)

    # save results to a json File
    with open("results.json", "w") as file:
        json.dump({"amount": chosen_amounts, "delay": chosen_delays,
                   "reaction_times": reaction_times}, file)


if __name__ == '__main__':
    main()

Flashing Checkerboard

This example just alternates the two stimuli with frequency of 2Hz. To make sure, that the interpreter finds the stimuli cd into the examples folder, and execute it from there.

 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
import pygame
from pyparadigm.misc import init, display, empty_surface
from pyparadigm.surface_composition import compose, Surface, Text, Font
from pyparadigm.eventlistener import EventListener
from functools import lru_cache
from itertools import cycle

def render_frame(screen, frame):
    # This time we dont use :py:func:`misc.display` and instead draw directly
    # onto the screen, and call flip() then to display it. Usually we would want
    # to generate a screen with a function (with lru_cache), and then use
    # :py:func:`misc.display` to blit the different screens. This way every
    # screen is only computed once. This time though, no screens are computed,
    # it is simply displaying an existing image, and no screens are reused.
    compose(screen)(Surface(scale=1)(frame))
    pygame.display.flip()


def main():
    # we want to display the two states with 2Hz, therefore the timeout is 1/2s
    timeout = 0.5
    # initialize a window, and get a reference to the pygame.Surface
    # representing the screen.
    screen = init((1024, 800))
    # Load the frames. When loading a pygame.Surface from a file, you should
    # always call convert() on it, this will change the image format to optimize
    # performance. If you have an image that uses transparent pixels, use
    # convert_alpha() instead.
    # We use itertools.cycle to get an iterator that will alternate between the
    # images, see the python-doc (https://docs.python.org/3/library/itertools.html)
    frames = cycle([pygame.image.load(f"checkerboard_{i}.png").convert() 
                        for i in range(2)])
    # Create an EventListener object. No additional handlers needed here.
    event_listener = EventListener()
    # Display an initial text
    display(compose(empty_surface(0xFFFFFF))(Text(
        """Press Return to start.

        Press again to end.""", Font(size=60))))
    # Wait for the return key
    event_listener.wait_for_n_keypresses(pygame.K_RETURN)
    key = None
    # Repeat until return is pressed again
    while key == None:
        # display one of the two checkerboard images
        render_frame(screen, next(frames))
        # wait for timeout seconds for RETURN to be pressed. If RETURN is
        # pressed :py:meth:`EventListener.wait_for_keys` will return
        # pygame.K_RETURN otherwise it will return NONE
        key = event_listener.wait_for_keys([pygame.K_RETURN], timeout)
    


if __name__ == '__main__':
    main()

Stroop Task

This example is more serious, and implements a stroop task with a two-stage training procedure.

  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
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
""" This script contains an example implementation of the stroop task. 

The easiest way to understand this code is to start reading in the main
function, and then read every function when it's called first
"""

import random
import time
import csv
from collections import namedtuple
from enum import Enum
from functools import lru_cache
from itertools import islice, repeat

import pygame

from pyparadigm import (EventListener, Fill, Font, LinLayout, LLItem, Padding,
                        RectangleShaper, Surface, Text, compose, display,
                        empty_surface, init, slide_show)

# ================================================================================
# Just Configuration
# ================================================================================
n_train_1_trials = 30
n_train_2_trials = 10
trials_per_block = 10
intro_text = ["""
Welcome to the Stroop-demo.
In this task you will be presented with words naming colors
which are written in a colored font.
You will either have to indicate the name of the color, or the
color of the font, using the arrow keys.

Press Return to continue.
""",
"""
To indicate the color you will have to use the number keys,

1 for red
2 for green
3 for blue

press Return to continue
""",
"""
First you will learn the mappings by heart.
To do so you will be displayed with the mappings, after %d trials
the mappings won't be shown any more. Then after %d
correct trials, the main task will start.
""" % (n_train_1_trials, n_train_2_trials)]


pre_hidden_training_text = """
Now we will hide the mapping, and the task will continue until you answered
correctly %d times.

Press Return to continue""" % n_train_2_trials


post_hidden_training_text = """
The training was succesful.

Press Return to continue"""


pre_text_block_text = """
Now, we begin with the test. Please indicate the color 
that is named by the letters

Press Return to continue
"""


post_test_block_text = """
Now, please indicate the color in which 
the word is written

Press Return to continue
"""


end_text = """
The task is complete, thank you for your participation
Press Return to escape
"""

# ================================================================================
# some utility functions
# ================================================================================

@lru_cache()
def text(s: str, color=0):
    # This is our configuration of how to display text, with the arial font, and
    # a pointsize of 30.
    # Due to the way text is plotted it needs the information of an alphachannel
    # therefore it is not possible to simply pass the hex-code of the color, but it 
    # is necessary to create a pygame.Color object. For which, again, it is necessary
    # to multiply the hex code with 0x100 to respect the alphachannel
    return Text(s, Font("Arial", size=30), color=pygame.Color(color * 0x100))


def _bg():
    # short for background
    return empty_surface(0xFFFFFF)


def display_text(s:str):
    display(compose(_bg())(text(s)))


def display_text_and_wait(s: str, el: EventListener, key: int = pygame.K_RETURN):
    display_text(s)
    el.wait_for_keys(key)


# ================================================================================
# Main Program
# ================================================================================
class Color(Enum):
    red = 0xFF0000
    green = 0x00FF00
    blue = 0x0000FF


colors = list(Color)


key_color_mapping = {
    eval("pygame.K_%d" % (i + 1)): color
    for i, color in enumerate(Color)
}


def display_intro(el: EventListener):
    # the first argument for slide_show must be an iterable of pygame.Surface
    # to create it we render the text onto an empty surface with the map()
    # function. slide_show displays one slide, and then calls the function that
    # is passed to it as second argument. When this function returns, the next
    # slide is displayed and the function is called again. The function that is 
    # passed simply waits for the return key
    slides = map(lambda s: compose(_bg())(text(s)), intro_text)
    slide_show(slides, lambda: el.wait_for_keys(pygame.K_RETURN))


@lru_cache()
def make_train_stim(type: str, color: Color):
    # encapsulates the creation of a training stim. Either a square of the given
    # or the name of the color as text, they are wrapped by a RectangleShaper, 
    # by default, will create a square
    assert type in ["color", "text"]
    return RectangleShaper()(
        text(color.name) if type == "text" else Fill(color.value))


def make_color_mapping():
    # The mapping is a horizontal layout consisting of groups of
    # a text element describing the key, and a square containing the color
    # we use make_train_stim() to create the square, and add a LLItem(1) in
    # the back and the front to get visible gaps between the groups.
    # The * in from of the list is used to expand the list to the arguments
    # for the LinLayouts inner function.
    return LinLayout("h")(*[LinLayout("h")(LLItem(1), text(str(key + 1)),
                                           make_train_stim("color", color), LLItem(1))
                            for key, color in enumerate(Color)])


@lru_cache()
def render_train_screen(show_mapping, stim_type, target_color):
    # the contents are aranged in a vertical layout, topmost is the
    # title "target color", followed by the stim for training (either a
    # square containing the color, or the word naming the color)
    # in the Bottom there is the information which key is mapped to which
    # color. But its only displayed optionally

    return compose(_bg(), LinLayout("v"))(
        # Create the Text
        text("target color:"),

        # Create the stimulus, and scale it down a little.
        Padding.from_scale(0.3)(make_train_stim(stim_type, target_color)),

        # Up till here the content is static, but displaying the mapping is optionally
        # and depends on the parameter, therefore we either add the mapping, or
        # an LLItem(1) as placeholder
        make_color_mapping() if show_mapping else LLItem(1)
    )


def do_train_trial(event_listener: EventListener, show_mapping: bool, stim_type:
                   str, target_color: Color):
    # displays a training_screen
    display(render_train_screen(show_mapping, stim_type, target_color))

    # waits for a response
    response_key = event_listener.wait_for_keys(key_color_mapping)

    # returns whether the response was correct
    return key_color_mapping[response_key] == target_color


def rand_elem(seq, n=None):
    """returns a random element from seq n times. If n is None, it continues indefinitly"""
    return map(random.choice, repeat(seq, n) if n is not None else repeat(seq))


def until_n_correct(n, func):
    n_correct = 0
    while n_correct < n:
        if func():
            n_correct += 1
        else:
            n_correct = 0


def do_training(el: EventListener):
    arguments = zip(rand_elem(["text", "color"]), rand_elem(colors))
    for stim_type, color in islice(arguments, n_train_1_trials):
        do_train_trial(el, True, stim_type, color)

    display_text_and_wait(pre_hidden_training_text, el)
    until_n_correct(n_train_2_trials, lambda: do_train_trial(el, False, *next(arguments)))    
    display_text_and_wait(post_hidden_training_text, el)


@lru_cache()
def render_trial_screen(word, font_color, target):
    return compose(_bg(), LinLayout("v"))(
        # Create the Text
        text("Which color is named by the letters?" if target == "text"
             else "What's the color of the word?"),

        # Create the stimulus, and scale it down a little.
        Padding.from_scale(0.3)(text(word, font_color)),
        LLItem(1)
    )


BlockResult = namedtuple("BlockResult", "RT word font_color response was_correct")


def run_block(event_listener: EventListener, by: str, n_trials: int)-> BlockResult:
    assert by in ["text", "color"]
    RTs = []; words = []; fonts = []; responses = []; was_correct = []
    for word, font in zip(rand_elem(colors, n_trials), rand_elem(colors)):
        words.append(word)
        fonts.append(font)
        display(render_trial_screen(word.name, font.value, by))

        # We use this to record reaction times
        start = time.time()
        response_key = event_listener.wait_for_keys(key_color_mapping)

        # Now the reaction time is just now - then
        RTs.append(time.time() - start)
        response = key_color_mapping[response_key] 
        responses.append(response)
        was_correct.append(response == (word if by == "text" else font))
    
    return BlockResult(RTs, words, fonts, responses, was_correct)


def save_results(text_res: BlockResult, font_res: BlockResult):
    with open("results.tsv", "w") as f:
        writer = csv.writer(f, delimiter="\t")
        writer.writerow(("by",) + BlockResult._fields)
        for line in zip(*text_res):
            writer.writerow(("text",) + line)
        for line in zip(*font_res):
            writer.writerow(("font",) + line)


def main():
    # create the pygame window. It has a resolution of 1024 x 800 pixels
    init((1024, 800))

    # create an event listener that will be used through the whole program
    event_listener = EventListener()
    display_intro(event_listener)
    do_training(event_listener)
    
    display_text_and_wait(pre_text_block_text, event_listener)
    text_block_results = run_block(event_listener, by="text",
                                   n_trials=trials_per_block)
    display_text_and_wait(post_test_block_text, event_listener)
    color_block_results = run_block(event_listener, by="color",
                                    n_trials=trials_per_block)
    display_text_and_wait(end_text, event_listener)

    save_results(text_block_results, color_block_results)


if __name__ == "__main__":
    main()