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()
|