Note
Go to the end to download the full example code.
Run a simple experiment#
This example demonstrates much of the basic functionality built into the ExperimentController class.
=======================
Generate sample stimuli
=======================
This shows how to generate some simple stimuli. It will also play a couple
of sample stimuli.
exp_name: testExp
date: 2024-11-19 15_32_13.366314
file: /home/circleci/project/examples/simple_experiment.py
participant: foo
session: 001
2024-11-19 15:32:13,366 - INFO - Expyfun: Using version 2.0.0.dev0 (requested dev)
2024-11-19 15:32:13,367 - INFO - Expyfun: Setting up sound card using pyglet backend with 2 playback channels
2024-11-19 15:32:13,552 - WARNING - Expyfun: Mismatch between reported stim sample rate (24414.0625) and device sample rate (44100.0). Experiment Controller will resample for you, but this takes a non-trivial amount of processing time and may compromise your experimental timing and/or cause artifacts.
2024-11-19 15:32:13,552 - INFO - Expyfun: Setting up screen
2024-11-19 15:32:13,583 - EXP - Expyfun: Set screen visibility True
2024-11-19 15:32:13,592 - INFO - Initialized [800 600] window on screen XlibScreen(display=<pyglet.canvas.xlib.XlibDisplay object at 0x7f25553adba0>, x=0, y=0, width=1400, height=900, xinerama=0) with DPI 69.73
2024-11-19 15:32:13,593 - INFO - Expyfun: Initializing dummy triggering mode
2024-11-19 15:32:13,594 - INFO - Expyfun: Initialization complete
2024-11-19 15:32:13,594 - EXP - Expyfun: Participant: foo
2024-11-19 15:32:13,594 - EXP - Expyfun: Session: 001
2024-11-19 15:32:13,595 - EXP - Expyfun: Set screen visibility False
2024-11-19 15:32:13,595 - EXP - Expyfun: Set screen visibility True
2024-11-19 15:32:14,083 - WARNING - Expyfun: Resampling 0.5 seconds of audio
2024-11-19 15:32:14,091 - EXP - Expyfun: Loading 44100 samples to buffer
2024-11-19 15:32:14,091 - EXP - Expyfun: Stamp trial ID to ec_id : 2
2024-11-19 15:32:14,092 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 0]
2024-11-19 15:32:14,092 - EXP - Stamping TTL triggers: [4 4]
2024-11-19 15:32:14,133 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:14,135 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:14,145 - EXP - Expyfun: Audio stopped and reset.
2024-11-19 15:32:14,145 - EXP - Expyfun: Trial OK
2024-11-19 15:32:14,349 - WARNING - Expyfun: Resampling 0.5 seconds of audio
2024-11-19 15:32:14,356 - EXP - Expyfun: Loading 44100 samples to buffer
2024-11-19 15:32:14,357 - EXP - Expyfun: Stamp trial ID to ec_id : 0
2024-11-19 15:32:14,357 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 0]
2024-11-19 15:32:14,357 - EXP - Stamping TTL triggers: [4 4]
2024-11-19 15:32:14,398 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:14,399 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:14,409 - EXP - Expyfun: Audio stopped and reset.
2024-11-19 15:32:14,409 - EXP - Expyfun: Trial OK
2024-11-19 15:32:14,613 - WARNING - Expyfun: Resampling 0.5 seconds of audio
2024-11-19 15:32:14,620 - EXP - Expyfun: Loading 44100 samples to buffer
2024-11-19 15:32:14,621 - EXP - Expyfun: Stamp trial ID to ec_id : 0
2024-11-19 15:32:14,621 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 0]
2024-11-19 15:32:14,621 - EXP - Stamping TTL triggers: [4 4]
2024-11-19 15:32:14,662 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:14,663 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:14,674 - EXP - Expyfun: Audio stopped and reset.
2024-11-19 15:32:14,674 - EXP - Expyfun: Trial OK
2024-11-19 15:32:14,878 - WARNING - Expyfun: Resampling 0.5 seconds of audio
2024-11-19 15:32:14,885 - EXP - Expyfun: Loading 44100 samples to buffer
2024-11-19 15:32:14,885 - EXP - Expyfun: Stamp trial ID to ec_id : 1
2024-11-19 15:32:14,885 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 0]
2024-11-19 15:32:14,885 - EXP - Stamping TTL triggers: [4 4]
2024-11-19 15:32:14,926 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:14,927 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:14,937 - EXP - Expyfun: Audio stopped and reset.
2024-11-19 15:32:14,938 - EXP - Expyfun: Trial OK
2024-11-19 15:32:15,144 - WARNING - Expyfun: Resampling 0.5 seconds of audio
2024-11-19 15:32:15,154 - EXP - Expyfun: Loading 44100 samples to buffer
2024-11-19 15:32:15,155 - EXP - Expyfun: Stamp trial ID to ec_id : 1
2024-11-19 15:32:15,155 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 0]
2024-11-19 15:32:15,155 - EXP - Stamping TTL triggers: [4 4]
2024-11-19 15:32:15,196 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:15,197 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:15,208 - EXP - Expyfun: Audio stopped and reset.
2024-11-19 15:32:15,208 - EXP - Expyfun: Trial OK
2024-11-19 15:32:15,423 - WARNING - Expyfun: Resampling 3.22 seconds of audio
2024-11-19 15:32:15,455 - EXP - Expyfun: Loading 284226 samples to buffer
2024-11-19 15:32:15,457 - EXP - Expyfun: Stamp trial ID to ec_id : multi-tone
2024-11-19 15:32:15,457 - EXP - Expyfun: Stamp trial ID to ttl_id : [0, 1]
2024-11-19 15:32:15,457 - EXP - Stamping TTL triggers: [4 8]
2024-11-19 15:32:15,498 - EXP - Expyfun: Starting stimuli: flipping screen and playing audio
2024-11-19 15:32:15,499 - EXP - Stamping TTL triggers: [1]
2024-11-19 15:32:15,516 - WARNING - ec.trial_ok called before stimulus had stopped
2024-11-19 15:32:15,517 - EXP - Expyfun: Trial OK
2024-11-19 15:32:15,521 - INFO - Expyfun: Exiting
2024-11-19 15:32:15,523 - EXP - Expyfun: Audio stopped and reset.
# Author: Dan McCloy <drmccloy@uw.edu>
#
# License: BSD (3-clause)
import os
import sys
from os import path as op
import numpy as np
import expyfun.analyze as ea
from expyfun import (
ExperimentController,
building_doc,
get_keyboard_input,
set_log_level,
)
from expyfun.io import read_hdf5
print(__doc__)
set_log_level("INFO")
# set configuration
noise_db = 45 # dB for background noise
stim_db = 65 # dB for stimuli
min_resp_time = 0.1
max_resp_time = 2.0
max_wait = np.inf
feedback_dur = 2.0
isi = 0.2
running_total = 0
# make the stimuli if necessary and then load them
fname = "equally_spaced_sinewaves.hdf5"
if not op.isfile(fname):
# This sys.path wrangling is only necessary for Sphinx automatic
# documentation building
sys.path.insert(0, os.getcwd())
from generate_simple_stimuli import generate_stimuli
generate_stimuli()
stims = read_hdf5(fname)
orig_rms = stims["rms"]
freqs = stims["freqs"]
fs = stims["fs"]
trial_order = stims["trial_order"]
num_trials = len(trial_order)
num_freqs = len(freqs)
if num_freqs > 8:
raise RuntimeError("Too many frequencies, not enough buttons.")
# keep only sinusoids, order low-high, convert to list of arrays
wavs = [stims[k] for k in sorted(stims.keys()) if k.startswith("stim_")]
# instructions
instructions = (
f"You will hear tones at {num_freqs} different frequencies. Your job is"
" to press the button corresponding to that frequency. Please "
f"press buttons 1-{num_freqs} now to hear each tone."
)
instr_finished = (
"Okay, now press any of those buttons to start the real "
"thing. There will be background noise."
)
with ExperimentController(
"testExp",
verbose=True,
screen_num=0,
window_size=[800, 600],
full_screen=False,
stim_db=stim_db,
noise_db=noise_db,
stim_fs=fs,
participant="foo",
session="001",
version="dev",
output_dir=None,
) as ec:
# define usable buttons / keys
live_keys = [x + 1 for x in range(num_freqs)]
# do training, or not
ec.set_visible(False)
long_resp_time = max_resp_time + 1
if building_doc:
max_wait = max_resp_time = min_resp_time = train = feedback_dur = 0
long_resp_time = 0
else:
train = get_keyboard_input("Run training (0=no, 1=yes [default]): ", 1, int)
ec.set_visible(True)
if train:
not_yet_pressed = live_keys[:]
# show instructions until all buttons have been pressed at least once
ec.screen_text(instructions)
ec.flip()
while len(not_yet_pressed) > 0:
pressed, timestamp = ec.wait_one_press(live_keys=live_keys)
for p in pressed:
p = int(p)
ec.load_buffer(wavs[p - 1])
ec.play()
ec.wait_secs(len(wavs[p - 1]) / float(ec.fs))
ec.stop()
if p in not_yet_pressed:
not_yet_pressed.pop(not_yet_pressed.index(p))
ec.flip() # clears the screen
ec.wait_secs(isi)
# show instructions finished screen
ec.screen_prompt(instr_finished, live_keys=live_keys, max_wait=max_wait)
ec.wait_secs(isi)
ec.call_on_next_flip(ec.start_noise())
ec.screen_text("OK, here we go!", wrap=False)
screenshot = ec.screenshot()
ec.wait_one_press(max_wait=feedback_dur, live_keys=None)
ec.wait_secs(isi)
single_trial_order = trial_order[range(len(trial_order) // 2)]
mass_trial_order = trial_order[len(trial_order) // 2 :]
# run the single-tone trials
for stim_num in single_trial_order:
ec.load_buffer(wavs[stim_num])
ec.identify_trial(ec_id=stim_num, ttl_id=[0, 0])
ec.write_data_line("one-tone trial", stim_num + 1)
ec.start_stimulus()
pressed, timestamp = ec.wait_one_press(max_resp_time, min_resp_time, live_keys)
ec.stop() # will stop stim playback as soon as response logged
ec.trial_ok()
# some feedback
if pressed is None:
message = "Too slow!"
elif int(pressed) == stim_num + 1:
running_total += 1
message = "Correct! Your reaction time was " f"{round(timestamp, 3)}"
else:
message = (
f"You pressed {pressed}, the correct answer was " f"{stim_num + 1}."
)
ec.screen_prompt(message, max_wait=feedback_dur)
ec.wait_secs(isi)
# create 100 ms pause to play between stims and concatenate
pause = np.zeros(int(ec.fs / 10))
concat_wavs = wavs[mass_trial_order[0]]
for num in mass_trial_order[1 : len(mass_trial_order)]:
concat_wavs = np.r_[concat_wavs, pause, wavs[num]]
concat_dur = len(concat_wavs) / float(ec.fs)
# run mass trial
ec.screen_prompt(
f"Now you will hear {len(mass_trial_order)} tones in a row. After they stop, "
f'wait for the "Go!" prompt, then you will have {max_resp_time} '
"seconds to push the buttons in the order that the tones "
"played in. Press one of the buttons to begin."
"",
live_keys=live_keys,
max_wait=max_wait,
)
ec.load_buffer(concat_wavs)
ec.identify_trial(ec_id="multi-tone", ttl_id=[0, 1])
ec.write_data_line("multi-tone trial", [x + 1 for x in mass_trial_order])
ec.start_stimulus()
ec.wait_secs(len(concat_wavs) / float(ec.stim_fs) if not building_doc else 0)
ec.screen_text("Go!", wrap=False)
ec.flip()
pressed = ec.wait_for_presses(long_resp_time, min_resp_time, live_keys, False)
answers = [str(x + 1) for x in mass_trial_order]
correct = [press == ans for press, ans in zip(pressed, answers)]
running_total += sum(correct)
ec.call_on_next_flip(ec.stop_noise())
ec.screen_prompt(
f"You got {sum(correct)} out of {len(answers)} correct." "",
max_wait=feedback_dur,
)
ec.trial_ok()
# end experiment
ec.screen_prompt(
f"All done! You got {running_total} correct out of {num_trials} tones. Press "
"any key to close.",
max_wait=max_wait,
)
ea.plot_screen(screenshot)
Total running time of the script: (0 minutes 2.244 seconds)