Python SDK (psycloudpy)
psycloudpy lets you describe a behavioral experiment in Python and save it as
the same PsyCloud bundle that Studio uses. Use it when your study starts from a
stimulus table, a factorial design, or a notebook where you are already preparing
materials and checking trial counts.
The main idea is simple: build the trial table, describe the screen shown for each row, and name the columns you want in the data file.
The SDK ships inside the PsyCloud monorepo and is under active development
(psycloudpy is not yet on PyPI). From a clone of the repo:
python -m pip install -e packages/psycloudpyAfter that, import psycloudpy should work from Python, IPython, or a
notebook.
Start With The Design
Before writing code, write the columns you expect to see in your final data:
| Column | What it means |
|---|---|
condition | congruent or incongruent |
color | the ink color on this trial |
word | the text printed on the screen |
correct_key | the key that should count as correct |
| response fields | response key, RT, correctness, timeout |
The SDK code below builds exactly that table, then uses the table to draw each
trial. Notice that correctness is declared inside keypress(...); you do not
need a post-trial callback.
from psycloudpy import (
experiment,
fixation_cross,
keypress,
seed_per_participant,
text,
trial,
write_bundle,
)
from psycloudpy.expr import case_when, expr_if, row, uppercase, when
instructions = """
# Color Stroop
Name the INK COLOR, not the word.
Keys:
- r = red
- g = green
- b = blue
Press any key to begin.
""".strip()
bundle = (
experiment(id="demo.stroop", name="Color Stroop")
.instructions_page(instructions)
.phase("trials", label="Stroop trials")
.factors(
condition=["congruent", "incongruent"],
color=["red", "green", "blue"],
)
.cross()
.derive(
"word",
expr_if(
row.condition == "congruent",
uppercase(row.color),
case_when(
when(row.color == "red", "GREEN"),
when(row.color == "green", "BLUE"),
default="RED",
),
),
)
.derive(
"correct_key",
case_when(
when(row.color == "red", "r"),
when(row.color == "green", "g"),
default="b",
),
)
.repeat(2)
.shuffle(seed=seed_per_participant("stroop-demo"))
.screen("trial")
.present(fixation_cross(x="center", y="center", size=36), 500)
.ask(
text(trial.word, fill=trial.color, font_size=72, x="center", y="center"),
keypress(["r", "g", "b"], correct=trial.correct_key, timeout_ms=2000),
)
.record("condition", "color", "word", "correct_key")
.end_page("# Done\n\nThanks for participating.")
.bundle(with_auto_ids=True)
)
write_bundle(bundle, dir="stroop_bundle", overwrite=True)Run it:
python stroop.pyThe result is a stroop_bundle/ directory. You can import that bundle into
Studio, validate it with the CLI, or keep it as a reproducible study artifact.
Read The Chain
The fluent chain is meant to read like a methods section:
- Start the study
experiment(id=..., name=...)gives the study a stable identity.instructions_page(...)adds a one-screen introduction that waits for a key. - Build the trial table
.phase("trials")opens a block of trials..factors(...)names the independent variables,.cross()makes every combination,.derive(...)computes useful columns,.repeat(...)adds repetitions, and.shuffle(...)randomizes the rows. - Draw one row
.screen("trial")describes what a single row looks like.trial.wordandtrial.colormean "use the value from the current row." - Collect the response
keypress([...], correct=trial.correct_key, timeout_ms=2000)stores the response and lets PsyCloud score correctness at runtime. - Choose output columns
.record(...)names the condition columns you want copied into the output data, alongside the response fields that PsyCloud records automatically.
The Two Kinds Of Trial Values
You will see two pronouns in examples:
| Use this | Where | Meaning |
|---|---|---|
row.color | inside .derive(...), .filter(...), constraints | a row while the trial table is being built |
trial.color | inside .screen(...) stimuli and responses | the participant's current trial at runtime |
In the Stroop example, row.color is used to compute word and correct_key
before the study runs. Later, trial.word, trial.color, and
trial.correct_key are filled in separately on every participant trial.
If You Have A Fixed Item List
Not every study is a full factorial crossing. For a lexical decision, recognition
memory, or sentence task, you may already know the exact items. Define those rows
directly in code and pass them to .trials(...). Each row becomes one trial.
from psycloudpy import experiment, keypress, text, trial, write_bundle
stimuli = [
{"item_id": "cat_01", "letter_string": "CAT", "condition": "word", "correct_key": "f"},
{"item_id": "blit_01", "letter_string": "BLIT", "condition": "nonword", "correct_key": "j"},
]
bundle = (
experiment(id="demo.lexical", name="Lexical Decision")
.instructions_page("Press F for WORD and J for NONWORD.")
.phase("main", label="Trials")
.trials(stimuli)
.screen("trial")
.ask(
text(trial.letter_string, font_size=64, x="center", y="center"),
keypress(["f", "j"], correct=trial.correct_key, timeout_ms=2500),
)
.record("item_id", "condition", "correct_key")
.end_page("# Done\n\nThank you.")
.bundle(with_auto_ids=True)
)
write_bundle(bundle, dir="lexical_bundle", overwrite=True)That is often the most ergonomic pattern for fixed-item experiments: keep the item list in the same script as the task, so the design is visible, versioned, and reproducible. If you already have a CSV from another source, import it only after checking the rows and column names explicitly.
Check Before You Run Participants
Use the workbench helpers before launching a study. check(...) and
simulate(...) call the local psycloud CLI, while simulate_tables(...)
turns a simulation report into tables you can inspect in Python.
from psycloudpy import check, simulate, simulate_tables, write_bundle
write_bundle(bundle, dir="stroop_bundle", overwrite=True)
# Compile and lint the bundle.
report = check("stroop_bundle")
# Dry-run fake sessions to catch design mistakes early.
simulation = simulate("stroop_bundle", participants=10)
tables = simulate_tables(simulation["report"])For a quick manual check, import the bundle into Studio and preview it like any other study. The bundle files are ordinary JSON plus assets, so they can be kept with the scripts that generated them.
Before recruiting anyone, inspect the materialized trial table. Confirm the number of rows, the balance of conditions, the correct keys, and the columns you plan to analyze later.
Common Recipes
| Study need | Use |
|---|---|
| factorial design | .factors(...).cross() |
| fixed item list | .trials(rows) |
| fixed delay | .present(stimulus, duration_ms) |
| response-gated screen | .ask(stimulus, response) |
| keyboard response | keypress(["f", "j"], correct=..., timeout_ms=...) |
| "press any key" page | instructions_page(...), end_page(...), or anykey() |
| survey page | survey_page(...), form_response() |
| output condition columns | .record("condition", "item_id", ...) |
For survey-style work, the ergonomic path is one survey_page(...) with an item
list, rather than one trial per questionnaire item:
from psycloudpy import experiment, form_response, survey_page, write_bundle
items = [
{"name": "calm", "label": "Calm"},
{"name": "alert", "label": "Alert"},
{"name": "nervous", "label": "Nervous"},
]
bundle = (
experiment(id="demo.mood", name="Mood Survey")
.instructions_page("Please rate how you feel right now.")
.phase("survey", label="Mood ratings")
.factors({})
.screen("survey")
.ask(
survey_page(
items,
response_type="likert",
labels=["Not at all", "A little", "Moderately", "Very much"],
required=True,
submit_label="Continue",
),
form_response(),
)
.bind(role="trial")
.end_page("# Thanks")
.bundle(with_auto_ids=True)
)
write_bundle(bundle, dir="mood_bundle", overwrite=True)What To Learn First
Focus on these five verbs first: experiment, phase, factors or trials,
screen, and record. Once those make sense, add derived columns, feedback,
counterbalancing, and adaptive flow.
The lower-level pc_* constructors are still available for package authors and
advanced migrations, but most study authors should start with the fluent helpers
shown here.
Worked Examples
Runnable examples live under
packages/psycloudpy/src/psycloudpy/examples/high_level/, including Stroop,
simple reaction time, recognition memory, continuous recognition, PANAS,
matrix reasoning, adaptive practice, Luck and Vogel change detection, and a
design-first memory task.