|
| 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` 以了解如何管理整个实验的设置。 |
0 commit comments