Skip to content

Commit fa262e3

Browse files
committed
update md docs
1 parent 1efcdb2 commit fa262e3

File tree

6 files changed

+1458
-1
lines changed

6 files changed

+1458
-1
lines changed

docs/tutorials/build_blocks_cn.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# BlockUnit: 组织和运行试验
2+
3+
## 概述
4+
5+
`BlockUnit``psyflow` 中用于将单个试验(trials)组织成块(blocks)并以可重复、可随机化的方式运行它们的控制器。它简化了常见的实验设计模式,如试验循环、条件随机化和在块之间传递数据。
6+
7+
使用 `BlockUnit`,您可以:
8+
9+
- 从试验规格列表(例如,从 CSV 文件或 `pandas.DataFrame` 加载)创建一个块。
10+
- 在将试验传递给您的运行函数之前,自动对它们进行随机化(加洗牌)。
11+
- 在块的开始和结束时显示指令或休息屏幕。
12+
- 在块内的所有试验中轻松传递常量参数。
13+
- 将每个试验的数据聚合到一个单一的、有组织的列表中,以便于保存。
14+
15+
## 核心概念
16+
17+
`BlockUnit` 的工作流程围绕以下三个步骤:
18+
19+
1. **定义试验**: 您创建一个字典列表,其中每个字典代表一个试验并包含该试验所需的所有信息(例如,条件、刺激名称、正确答案)。这通常是通过加载一个 CSV 文件并将其转换为 `pandas.DataFrame` 来完成的。
20+
2. **初始化 `BlockUnit`**: 您使用您的试验列表、一个运行单个试验的函数以及任何固定的参数来创建一个 `BlockUnit` 实例。
21+
3. **运行块**: 您调用 `.run()` 方法,该方法会迭代您的试验,根据需要对它们进行随机化,并为每个试验执行您的运行函数。
22+
23+
## 详细使用指南
24+
25+
### 1. 定义您的试验
26+
27+
一个块是由一个试验列表定义的。在 `psyflow` 中,这通常是一个 `pandas.DataFrame`,其中每一行代表一个试验,每一列代表一个参数。
28+
29+
假设您有一个名为 `trials.csv` 的文件,内容如下:
30+
31+
```csv
32+
condition,target_stim,correct_key
33+
win,win_stim_A,f
34+
win,win_stim_B,j
35+
loss,loss_stim_A,f
36+
loss,loss_stim_B,j
37+
```
38+
39+
您可以使用 `pandas` 来加载这个文件:
40+
41+
```python
42+
import pandas as pd
43+
44+
trial_df = pd.read_csv('trials.csv')
45+
```
46+
47+
`BlockUnit` 期望这个 DataFrame 被转换成一个字典列表,其中每个字典代表一行。您可以使用 `.to_dict('records')` 来轻松完成这个转换:
48+
49+
```python
50+
trial_list = trial_df.to_dict('records')
51+
# trial_list 现在是:
52+
# [
53+
# {'condition': 'win', 'target_stim': 'win_stim_A', 'correct_key': 'f'},
54+
# {'condition': 'win', 'target_stim': 'win_stim_B', 'correct_key': 'j'},
55+
# ...
56+
# ]
57+
```
58+
59+
### 2. 创建一个试验运行函数
60+
61+
接下来,您需要一个 Python 函数,它知道如何运行 **单个** 试验。这个函数将接收 `BlockUnit` 中的信息作为参数。
62+
63+
- 它必须接受一个名为 `trial_info` 的参数,这是一个来自您列表的字典(即,一行来自您的 CSV)。
64+
- 它还可以接受您在初始化 `BlockUnit` 时定义的任何其他关键字参数(“固定参数”)。
65+
66+
这是一个典型的试验运行函数的骨架,它位于 `src/run_trial.py` 中:
67+
68+
```python
69+
# In src/run_trial.py
70+
71+
def run_trial(trial_info, win, kb, stim_bank, settings):
72+
"""
73+
运行一个单独的试验。
74+
75+
参数:
76+
- trial_info (dict): 来自试验列表的当前试验的规格。
77+
- win, kb, stim_bank, settings: 传递给 BlockUnit 的固定参数。
78+
"""
79+
# 1. 从 trial_info 中解包信息
80+
condition = trial_info['condition']
81+
target_name = trial_info['target_stim']
82+
correct_key = trial_info['correct_key']
83+
84+
# 2. 使用 stim_bank 检索刺激
85+
target_stim = stim_bank.get(target_name)
86+
87+
# 3. 使用 StimUnit 来呈现刺激并捕获反应
88+
# (这是一个简化的例子)
89+
unit = StimUnit(f"trial_{trial_info['trial_num']}", win, kb)
90+
unit.add_stim(target_stim)
91+
unit.capture_response(keys=['f', 'j'], correct_keys=[correct_key], duration=2.0)
92+
93+
# 4. 返回试验数据
94+
return unit.to_dict()
95+
```
96+
97+
### 3. 初始化 `BlockUnit`
98+
99+
现在您已经有了您的试验列表和您的 `run_trial` 函数,您可以初始化 `BlockUnit`
100+
101+
```python
102+
from psyflow.block_unit import BlockUnit
103+
from src.run_trial import run_trial # 导入您的函数
104+
105+
# 试验列表 (来自上面的 .csv)
106+
trial_list = pd.read_csv('trials.csv').to_dict('records')
107+
108+
# 固定的参数,将传递给每个 run_trial 调用
109+
fixed_params = {
110+
'win': my_psychopy_window,
111+
'kb': my_keyboard,
112+
'stim_bank': my_stim_bank,
113+
'settings': my_experiment_settings
114+
}
115+
116+
# 创建 BlockUnit 实例
117+
block = BlockUnit(
118+
trial_list=trial_list,
119+
run_func=run_trial,
120+
fixed_params=fixed_params,
121+
shuffle=True # 在运行前对试验列表进行随机化
122+
)
123+
```
124+
125+
- `trial_list`: 您的试验规格列表。
126+
- `run_func`: 您为运行单个试验而创建的函数。
127+
- `fixed_params`: 一个字典,包含将作为关键字参数传递给您的 `run_func` 的对象。这对于传递像 `win``kb``stim_bank` 这样的全局对象非常有用。
128+
- `shuffle`: 一个布尔值,指示是否在运行前对 `trial_list` 进行随机化。默认为 `False`
129+
130+
### 4. 运行块
131+
132+
最后,调用 `.run()` 方法来执行块。这将迭代(可能是随机化后的)`trial_list`,并为每个条目调用您的 `run_trial` 函数。
133+
134+
```python
135+
# 在块开始前显示指令
136+
show_instructions(win, "准备好开始第一个块!")
137+
138+
# 运行块并收集数据
139+
block_data = block.run()
140+
141+
# 在块结束后显示休息屏幕
142+
show_break_screen(win, "休息一下!")
143+
144+
# block_data 现在是一个列表,其中包含了每个试验返回的字典
145+
# [{'condition': 'win', 'response': 'f', ...}, {'condition': 'loss', ...}]
146+
```
147+
148+
`.run()` 方法会自动将 `trial_info``fixed_params` 传递给您的 `run_trial` 函数。它收集从每个 `run_trial` 调用返回的字典,并将它们聚合到一个列表中。
149+
150+
### 5. 添加开始和结束屏幕
151+
152+
`BlockUnit` 使得在块的开始和结束时显示屏幕变得容易,使用 `.on_start()``.on_end()` 方法。这些方法接受一个函数,该函数将被调用。
153+
154+
```python
155+
def show_start_screen(block_info):
156+
"""在块开始时显示。"""
157+
# block_info 是一个包含块信息的字典
158+
msg = f"即将开始块 {block_info['block_num']}"
159+
instruction_unit = StimUnit('instructions', win, kb)
160+
instruction_unit.add_stim(visual.TextStim(win, text=msg))
161+
instruction_unit.wait_and_continue()
162+
163+
def show_end_screen(block_info, block_data):
164+
"""在块结束时显示。"""
165+
# block_data 是从块收集的数据
166+
accuracy = calculate_accuracy(block_data)
167+
msg = f"块完成!您的准确率是: {accuracy:.2f}%"
168+
feedback_unit = StimUnit('feedback', win, kb)
169+
feedback_unit.add_stim(visual.TextStim(win, text=msg))
170+
feedback_unit.wait_and_continue()
171+
172+
# 将钩子附加到您的块
173+
block.on_start(show_start_screen)
174+
block.on_end(show_end_screen)
175+
176+
# 现在,当您调用 .run() 时,这些函数将被自动执行
177+
block_data = block.run()
178+
```
179+
180+
- 传递给 `.on_start()` 的函数接收一个包含关于块的信息的字典(例如,`block_num`)。
181+
- 传递给 `.on_end()` 的函数接收该信息字典 **** 从块中收集的 `block_data`
182+
183+
## 后续步骤
184+
185+
通过 `BlockUnit`,您现在拥有了在 `psyflow` 中构建功能齐全的实验的所有构建块。您可以:
186+
187+
- 回顾 `StimUnit` 教程以获取有关构建单个试验的更多详细信息。
188+
- 学习 `TaskSettings` 以了解如何管理整个实验的设置。
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# StimBank:灵活的刺激管理
2+
3+
## 概述
4+
5+
`StimBank` (刺激银行) 是 `psyflow` 中用于管理、创建和重用 PsychoPy 刺激的中央枢纽。它将您的刺激定义从实验逻辑中分离出来,使您的代码更清晰、更易于维护。
6+
7+
通过 `StimBank`,您可以:
8+
9+
-`config.yaml` 中以声明方式定义所有刺激。
10+
- 按名称动态创建和检索刺激。
11+
- 在运行时格式化刺激文本(例如,插入分数或条件名称)。
12+
- 预加载刺激以确保精确的呈现时间。
13+
- 轻松地在不同的试次(trials)和单元(units)中重用相同的刺激。
14+
15+
## 核心概念
16+
17+
`StimBank` 的工作流程很简单:
18+
19+
1. **定义**: 您在 `config.yaml` 文件中的 `stimuli` 部分定义所有刺激及其属性。
20+
2. **初始化**: 您在实验开始时创建一个 `StimBank` 的单一实例。
21+
3. **检索**: 在您的试次逻辑中,您通过名称从 `StimBank``.get()` 刺激。
22+
23+
这种方法遵循“关注点分离”的原则,将您的刺激内容(在 YAML 中)与您的实验逻辑(在 Python 中)分开。
24+
25+
## 详细使用指南
26+
27+
### 1. 在 `config.yaml` 中定义刺激
28+
29+
您的所有刺激都定义在 `config/config.yaml` 文件中的 `stimuli` 键下。每个刺激都有一个唯一的名称(例如,`fixation``win_feedback`)和一组属性。
30+
31+
- `type`: 指定要创建的 PsychoPy 对象的类型(例如,`text``circle``sound`)。
32+
- 其他键: 任何其他键都直接作为关键字参数传递给该 PsychoPy 对象的构造函数。
33+
34+
这是一个示例 `stimuli` 配置:
35+
36+
```yaml
37+
stimuli:
38+
fixation:
39+
type: text
40+
text: '+'
41+
color: white
42+
height: 0.8
43+
44+
win_target:
45+
type: circle
46+
radius: 1.5
47+
fillColor: gold
48+
lineColor: white
49+
50+
loss_feedback:
51+
type: text
52+
text: "您输了 {last_loss} 分"
53+
color: red
54+
pos: [0, 2]
55+
56+
pop_sound:
57+
type: sound
58+
value: 'assets/sounds/pop.wav'
59+
```
60+
61+
在这个例子中:
62+
- `fixation` 是一个白色的文本刺激。
63+
- `win_target` 是一个金色的圆形。
64+
- `loss_feedback` 是一个红色的文本刺激,其中包含一个 f-string 风格的占位符 `{last_loss}`。
65+
- `pop_sound` 是一个声音刺激。
66+
67+
### 2. 初始化 StimBank
68+
69+
在您的主实验脚本中,在设置好您的 PsychoPy 窗口之后,创建 `StimBank` 的一个实例。
70+
71+
```python
72+
from psychopy import visual
73+
from psyflow.stim_bank import StimBank
74+
75+
# 1. 创建您的 PsychoPy 窗口
76+
win = visual.Window(size=[800, 600], color='black', units='deg')
77+
78+
# 2. 从您的配置文件中加载刺激定义
79+
# (假设 stim_config 是从 config.yaml 加载的字典)
80+
stim_config = {
81+
'fixation': {'type': 'text', 'text': '+', 'color': 'white'},
82+
'target': {'type': 'circle', 'radius': 1.0, 'fillColor': 'blue'}
83+
}
84+
85+
# 3. 创建 StimBank 实例
86+
stim_bank = StimBank(win, stim_config)
87+
```
88+
89+
`StimBank` 现在已经准备好按需创建刺激了。
90+
91+
### 3. 检索刺激
92+
93+
使用 `.get()` 方法按名称从 `StimBank` 中检索刺激。
94+
95+
```python
96+
# 检索一个简单的注视十字
97+
fixation_cross = stim_bank.get('fixation')
98+
99+
# 在一个 StimUnit 中使用它
100+
unit = StimUnit('fixation_trial', win, kb)
101+
unit.add_stim(fixation_cross)
102+
unit.show(duration=1.0)
103+
```
104+
105+
`StimBank` 会缓存刺激,所以如果您多次调用 `.get('fixation')`,您将收到对完全相同的 PsychoPy 对象的引用。这对于高效地重用刺激非常有用。
106+
107+
### 4. 动态格式化文本
108+
109+
`StimBank` 最强大的功能之一是能够在检索时动态格式化文本刺激。如果在您的 `config.yaml` 中定义的 `text` 字段包含 f-string 风格的占位符(例如,`{score}`),您可以在调用 `.get()` 时提供值来填充它们。
110+
111+
假设您的 `config.yaml` 中有这个:
112+
113+
```yaml
114+
stimuli:
115+
score_display:
116+
type: text
117+
text: "分数: {current_score}"
118+
color: white
119+
```
120+
121+
您可以像这样格式化和检索它:
122+
123+
```python
124+
# 在运行时提供 current_score 的值
125+
score_text = stim_bank.get('score_display', current_score=95)
126+
127+
# score_text.text 现在是 "分数: 95"
128+
```
129+
130+
这对于显示动态反馈、分数更新或特定于试次的指令非常有用。
131+
132+
### 5. 预加载刺激
133+
134+
为了确保精确的计时并避免在第一次呈现刺激时出现延迟,您可以预加载部分或全部刺激。这会在它们被需要之前在内存中创建 PsychoPy 对象。
135+
136+
```python
137+
# 预加载所有在 config.yaml 中定义的刺激
138+
stim_bank.preload_all()
139+
140+
# 只预加载特定的刺激
141+
stim_bank.preload(['fixation', 'win_target', 'loss_target'])
142+
```
143+
144+
一个好的做法是在实验开始时,在任何试次开始之前,调用 `stim_bank.preload_all()`。
145+
146+
## 完整的工作流程示例
147+
148+
下面是一个将所有部分组合在一起的简短示例:
149+
150+
```python
151+
from psychopy import visual
152+
from psyflow.stim_bank import StimBank
153+
from psyflow.stim_unit import StimUnit
154+
from psychopy.hardware.keyboard import Keyboard
155+
156+
# 1. 设置
157+
win = visual.Window(size=[800, 600], color='black')
158+
kb = Keyboard()
159+
160+
# 2. 从 YAML 加载的刺激定义
161+
stim_config = {
162+
'fixation': {'type': 'text', 'text': '+'},
163+
'feedback': {'type': 'text', 'text': '你得了 {points} 分!'}
164+
}
165+
166+
# 3. 初始化 StimBank 并预加载
167+
stim_bank = StimBank(win, stim_config)
168+
stim_bank.preload_all()
169+
170+
# --- 试次循环 ---
171+
for trial in range(5):
172+
# 4. 呈现一个注视十字
173+
fixation_unit = StimUnit('fix', win, kb)
174+
fixation_unit.add_stim(stim_bank.get('fixation'))
175+
fixation_unit.show(duration=0.5)
176+
177+
# (这里是您的核心试次逻辑...)
178+
points_this_trial = 10 # 假设
179+
180+
# 5. 呈现动态反馈
181+
feedback_unit = StimUnit('feedback', win, kb)
182+
feedback_stim = stim_bank.get('feedback', points=points_this_trial)
183+
feedback_unit.add_stim(feedback_stim)
184+
feedback_unit.show(duration=1.0)
185+
186+
win.close()
187+
```
188+
189+
## 后续步骤
190+
191+
现在您已经掌握了 `StimBank`,请继续学习 `StimUnit` 教程,了解如何使用您创建的刺激来构建和运行单个试次。

docs/tutorials/build_stimunit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ By using `StimUnit`, your trial logic (typically defined in `src/run_trial.py`)
3434
| Response hook | `.on_response(keys, fn)` | `@unit.on_response(['left','right'])` |
3535
| Timeout hook | `.on_timeout(sec, fn)` | `@unit.on_timeout(2.0)` |
3636
| End hook | `.on_end(fn)` | `@unit.on_end()` |
37-
| | | |
37+
3838
| Simple display | `.show(duration)` | `unit.show(1.0)` |
3939
| Response capture | `.capture_response(keys, duration)` | `unit.capture_response(['left','right'], 2.0)` |
4040
| Full trial control | `.run()` | `unit.run()` |

0 commit comments

Comments
 (0)