Lightweight experiment framework for PsychoPy
Project description
PsychoPy-Scene
English | 简体中文
This project is a lightweight experiment framework for PsychoPy, source code only 200 lines.
[!NOTE] this project aim to provide a new way to build PsychoPy experiments, only provide the basic API and encourage developers to develop on top of this project.
Features
- Lightweight: Only 1 file, no extra dependencies
- Type-safe: All parameters are type annotated
- Newcomer-friendly: Only the concepts of
ContextandSceneare required to get started.
Install
pip install psychopy-scene
or copy the psychopy_scene folder directly to the root directory of your project.
Get Started
Context
Experiment context Context means this experiment's global settings,
including environment parameters and task parameters.
The first step to writing an experiment is to create an experiment context.
from psychopy_scene import Context
from psychopy.visual import Window
from psychopy.monitors import Monitor
from psychopy.data import ExperimentHandler
# create monitor
monitor = Monitor(
name="testMonitor",
width=52.65,
distance=57,
)
monitor.setSizePix((1920, 1080))
# create window
win = Window(
monitor=monitor,
units="deg",
fullscr=False,
size=(800, 600),
)
# create experiment context
ctx = Context(
win,
exp=ExperimentHandler(extraInfo={"subject": "test"}),
)
Scene
The experiment can be seen as a composition of a series of scenes, only 2 steps are required to write an experiment program:
- create scene
- write scene presentation logic
scene provides some configuration parameters:
duration:seconds unitclose_on:the event to close scene, such askey_fmeans pressing thefkey to close the sceneon_key_[name]:when the keyboard key is pressed, execute the functionon_mouse_[name]:when the mouse button is clicked, execute the functionon_scene_[name]:when the scene reaches a specific stage, execute the function
Creating a scene only requires a function that accepts stimulus parameters and returns the stimulus:
from psychopy.visual import TextStim
# create stimulus
stim_1 = TextStim(ctx.win, text="Hello")
stim_2 = TextStim(ctx.win, text="World")
# create scene
@ctx.scene(
duration=1,
close_on=["key_f", "mouse_right"],
on_key_escape=lambda: print("escape key was pressed"),
on_mouse_left=lambda: print("left mouse button was pressed"),
on_scene_drawn=lambda: print("it will be called after first drawing"),
on_scene_frame=lambda: print("it will be called each frame"),
)
def demo(color: str, ori: float): # it will be used as on_scene_setup
print('it will be called before first drawing')
stim_1.color = color
stim_2.ori = ori
return stim_1, stim_2
# show scene
demo.show(color="red", ori=45)
scene can also be configured dynamically, it is useful in some cases, such as presentations with variable duration:
@ctx.scene()
def demo():
return stim
demo.config(duration=0.5).show()
this ctx.scene method is a shortcut for demo.config, so they are equivalent.
Event
An event represents a specific moment in the program's running, such as pressing a key or clicking the mouse. To execute some operation when an event occurs, we need to add a callback function for it:
demo = ctx.scene(close_on="key_f") # or
demo = ctx.scene(on_key_f=lambda: demo.close()) # or
demo = ctx.scene().on("key_f", lambda: demo.close())
[!NOTE] each event can only have one callback function, adding repeatedly will raise an error.
each callback parameter name should follow the format on_[type]_[name].
Now we support the following events:
| type | name |
|---|---|
| scene | setup、drawn、frame |
| key | any、other keys is same as returned by Keyboard.getKeys |
| mouse | left、right、middle |
these events will be triggered in the following order after the show method is executed:
graph TD
initialize --> on_scene_setup --> first-draw --> on_scene_drawn --> c{whether to draw}
c -->|no| stop-draw
c -->|yes| on_scene_frame --> re-draw --> _["on_key_[name]<br>on_mouse_[name]"] --> c
Data
scene will collect data automatically in showing:
| name | description |
|---|---|
| show_time | first drawing timestamp |
| events | interaction events list: keyboard events and mouse events |
we can access these data by scene.get:
@ctx.scene(close_on=["key_f", "key_j"])
def demo():
return stim
demo.show()
close_event = demo.get("events")[-1]
close_key = close_event.key.value
close_time = close_event.timestamp - demo.get('show_time')
we can also collect data manually:
@ctx.scene(
on_key_f=lambda: demo.set('rt', core.getTime() - demo.get('show_time')),
)
def demo():
return stim
demo.show()
rt = demo.get('rt')
Shortcut
Context also provides some shortcut methods:
ctx.text('Welcome to the experiment!', pos=(0, 0)).show() # show static text
ctx.fixation(1).show()
ctx.blank(1).show()
ctx.addRow(a='', b=1, c=True) # collect data to ExperimentHandler
Best Practices
Separation of context and task
It is recommended to write the task as a function, pass the experimental context as the first parameter, the task-specific parameters as the rest of the parameters, and return the experimental data.
from psychopy_scene import Context
def task(ctx: Context, duration: float):
from psychopy.visual import TextStim
stim = TextStim(ctx.win, text="")
scene = ctx.scene(duration, on_scene_setup=lambda: stim)
scene.show()
ctx.addRow(time=scene.get('show_time'))
Focus only on task-related logic
Task functions should not contain any logic that is not related to the task itself, for example:
- Introductory and closing statements
- Number of blocks
- Data processing, analysis, presentation of results
If there are no data dependencies between blocks, it is recommended to write the task function as a single block. For experiments that require the presentation of multiple blocks, consider the following example.
from psychopy_scene import Context
from psychopy.visual import Window
def task(ctx: Context):
from psychopy.visual import TextStim
stim = TextStim(ctx.win, text="")
scene = ctx.scene(1, on_scene_setup=lambda: stim)
scene.show()
ctx.addRow(time=scene.get('show_time'))
win = Window()
data = []
for block_index in range(10):
ctx = Context(win)
ctx.exp.extraInfo['block_index'] = block_index
task(ctx)
block_data = ctx.exp.getAllEntries()
data.extends(block_data)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file psychopy_scene-0.1.1.tar.gz.
File metadata
- Download URL: psychopy_scene-0.1.1.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
286f4fa3b927de548197f84c9efc44dac99a6f80a6e6f2364711da8b3a8458f0
|
|
| MD5 |
3bb285dac30c7ddf2f90c392f523665f
|
|
| BLAKE2b-256 |
0e24301a770f72270b9c01a8260944f8fb75cefa9b24c88e04fb721c48036121
|
Provenance
The following attestation bundles were made for psychopy_scene-0.1.1.tar.gz:
Publisher:
pypi.yaml on bluebones-team/psychopy-scene
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psychopy_scene-0.1.1.tar.gz -
Subject digest:
286f4fa3b927de548197f84c9efc44dac99a6f80a6e6f2364711da8b3a8458f0 - Sigstore transparency entry: 202153914
- Sigstore integration time:
-
Permalink:
bluebones-team/psychopy-scene@a0c37408faf3d8096abd0d74d6fd22567fa7b39d -
Branch / Tag:
refs/tags/0.1.1 - Owner: https://github.com/bluebones-team
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@a0c37408faf3d8096abd0d74d6fd22567fa7b39d -
Trigger Event:
push
-
Statement type:
File details
Details for the file psychopy_scene-0.1.1-py3-none-any.whl.
File metadata
- Download URL: psychopy_scene-0.1.1-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66f561b2ca3bbc94c2f10cc2a3320c98f77381f4a48fba97e6211dff8ac8a7af
|
|
| MD5 |
1c6b006cf0853bee6e21d560ba8142f3
|
|
| BLAKE2b-256 |
7bb3d53ae9cd4cd6958842e509d6dbabc171155d3de73ff696d56bb6a817098e
|
Provenance
The following attestation bundles were made for psychopy_scene-0.1.1-py3-none-any.whl:
Publisher:
pypi.yaml on bluebones-team/psychopy-scene
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
psychopy_scene-0.1.1-py3-none-any.whl -
Subject digest:
66f561b2ca3bbc94c2f10cc2a3320c98f77381f4a48fba97e6211dff8ac8a7af - Sigstore transparency entry: 202153917
- Sigstore integration time:
-
Permalink:
bluebones-team/psychopy-scene@a0c37408faf3d8096abd0d74d6fd22567fa7b39d -
Branch / Tag:
refs/tags/0.1.1 - Owner: https://github.com/bluebones-team
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yaml@a0c37408faf3d8096abd0d74d6fd22567fa7b39d -
Trigger Event:
push
-
Statement type: