开始之前
建议在基本掌握 PsychoPy 操作后阅读本文。可参考前述的入门篇指南。
前言
前略,这一篇主要是作为 PsychoPy 入门后的一些小技巧讲解,大多以我自己的经验出发,篇幅应该会短很多,也不会像入门篇那样讲解得非常细致,整体结构也会比较松散一些。
也因为是从自己的角度出发,所以我自己还没涉及过的东西(脑电啊、眼动啊)我肯定也不会做,还是那句话:
本文希望做到的是能让阅读者快速了解这一工具,可以在较短时间内掌握其使用方法。
如在开发、使用过程中遇到问题,官方文档与搜索引擎是更好的资料来源。
0. 关于颜色的那些事
PsychoPy 的默认背景颜色是非常中性的一种灰[128, 128, 128]
,默认文字颜色是纯白色[255, 255, 255]
。
有的时候这种灰色可能不太好,我们希望使用黑色作为底色,这个可以在实验设置中修改。点击顶上的 ⚙️ Edit experiment settings 按钮,选择 Screen 选项卡:
在 Color 一栏填入颜色代码或颜色字符串即可,具体可用的颜色可点击右边的按钮查看:
2021.2.3
,脑岛的推荐版本)在将 RGB 三色代码转换至 Javascript 时存在 bug,无法将其正确映射为数组形式,因此转换出来的程序无法运行。如果有相应的线上实验需要,请直接使用颜色词汇。
1. 时间随机化
很多实验会要求对注视点或者空屏时间进行随机化,防止被试进行预测或形成习惯化,影响实验结果。
较新版的 PsychoPy 里预设了随机注视点的 Routine,可以参照学习一下:
如果使用的是旧版 PsychoPy 也没关系,我们可以仿照它的模式自己写一个:
这里使用了一个random()
函数,可以生成 0 到 1 范围内的随机数,因此这里的持续时间便是 1 到 2 秒内随机。
如法炮制,如果我们想要实现 400-600 毫秒内随机呢?
但是我的实验结果是,不行。
因为不知道 PsychoPy 引入了一些什么库,把什么样的指令引入到了什么样的程度,我尝试的各种更加简单的随机化代码都运行不起来,反而是这种写法最稳定。
当然你也可以在头部 import
一点别的什么东西,但是这些东西只会适用于 Python 环境,所以如果想要做线上实验的话还是……就这样吧。
2. 提供反馈
大多数简单的实验应该是不需要这个操作的,但是这个操作的知识(比如变量赋值、变量传递等)可以为后面的东西打下基础。
假设在 Stroop 任务中,我们需要在被试的练习阶段为被试提供正确与错误的反馈,被试做出正确反应则显示绿色的反馈词,错误反应则为红色,
到这里,纯 GUI 的编辑功能已经满足不了我们了,我们需要用到 PsychoPy 里的代码组件了。
先假设我们已经设置好了各种指导语和实验用的 routine,分别是Instruction
practice
trial
endings
(实际情况要比这个复杂,会有多个指导语,这里仅为举例进行简化),在trial
中有一个叫做trial_key_resp
的键盘组件作为反应接收组件,并且已经载入了正确的条件文件,可以判断被试输入的正确与错误。大致应该和下面的图一样:
我们梳理一下流程:我们需要从practice
中获取几个变量(在这里,我们需要被试的反应时、被试的反应是否正确),将其显示在反馈中。
我们在practice
后面插入一个新的 Routine,命名为feedback
,作为我们提供反馈的部分。随后,我们在feedback
里添加一个代码组件(Code):
可以看到,这里有六个选项卡,分别对应六种运行的时机:
- Before Experiment:在实验文件加载前就加载,即放在所有代码的最开头运行
- Begin Experiment:在实验文件初始化时加载,即和其他初始化代码同步运行
- Begin Routine:在当前 Routine 开始时(即程序实际执行到该 Routine 时)运行
- Each Frame:实验程序运行时的每一帧都运行一次
- End Routine:在当前 Routine 结束时运行
- End Experiment:在实验完全结束时,生成数据文件前运行
我们可以先初始化几个变量,用来承载我们的反馈消息。在 Begin Experiment 阶段引入这样的代码:
右边的代码区是什么?注意到顶上的 Code Type 了吗?Auto -> JS
的模式下,左边填写的 Python 代码可以自动转换为 Javascript 代码,如果需要把程序转换为 JS 运行,操作得当的情况下可以省去重写代码的时间。
在这里,我们在实验开始时初始化了两个变量:message
和message_color
,用以更新后面的文本。
接下来,我们希望每一次运行时,这里的数据都会相应更新一遍。因此我们切换到 Begin Routine,依葫芦画瓢填写这样的代码:
if trial_key_resp.corr:
message = 'Correct! RT=%.3f' %(trial_key_resp.rt)
message_color = 'green'
else:
message = 'Wrong! RT=%.3f' %(trial_key_resp.rt)
message_color = 'red'
别急,我知道你很懵,看这玩意跟看魔法似的,听我解释.jpg
我们从上一个 Routine 的键盘组件trial_key_resp
中获取了它的一个属性:corr
,即正确与否。如果被试输入正确,则会返回 1,反之返回 0。
如果 trial_key_resp.corr
不为 0,则会执行第一段 Correct 的代码。我们在这里把另外一个属性:rt
,即反应时载入到了 message
变量中,然后将消息的颜色设置为绿色。同理,在被试反应错误的情况下,返回红色的消息。
那么到这里你大概会有一个问题,一个我当时也有的问题:我从哪里知道的corr
和rt
呢?我怎么知道它有多少个属性,有哪些可以用的变量呢?
说实话我翻官方教程也没翻到这个,我用了一个比较野的路子——还记得数据文件吗?
每一个程序当中会用到的变量,都会在这里创建一个对应的列,而每列第一格就是它的名字。
所以,通过阅读数据文件,我可以知道有哪些变量在实验过程中被创建,可以在代码组件中使用。
接下来,保存代码组件,在面板中添加一个新的文本组件:
这样,我们就有了一个给被试提供反馈的组件。完成之后,你的feedback
应该长这样:
PsychoPy 的初始化顺序是从上到下,也就是靠近顶端的代码最先运行,靠近底端的代码最后运行。
在刚刚的例子,如果把code
放到了text
底下,那么就无法正常显示刚刚设置好的消息——程序会先加载文本,再给文本赋值,而这个时候文本已经显示到屏幕上了(或者程序已经报错闪退了)。
事实上其实这一节讲的内容大多数人在大多数实验中都用不上,包括我自己。讲这个主要是为了把代码组件介绍一下,并且给下面的内容开个头——
3. 休息设置
很多实验动不动就会有成百上千个试次,中间不插休息不行。
当然比较简单粗暴的一个方法是做成这种样子:
当然这样做也不是不行,只是……PsychoPy 会卡。
亲身经历,我的同一个实验里有大概二十来个 Routine 一字排开,随后我想要编辑任何一个东西都会卡上半分钟才能打开窗口。
我怀疑和 PsychoPy 的缓存管理有关系,可能做得不是很好。
PsychoPy 论坛和一些教程里推荐了这样的一种做法:
当然,会用到代码组件,不可能做一个 trial 就休息一次。
打开rest
,在里面添加一个代码组件:
if (trials.thisN+1) % 24 != 0:
continueRoutine = False
意思是,如果当前trial
的计数对 24 取模,余数不为 0 时,则不执行这个 Routine。
continueRoutine
是一个内建的值,指示程序是否运行这个 Routine。所以上面这段代码的效果就是,每 24 个trial
过去,就会执行一次rest
。
注意,thisN
的计数从 0 开始,所以在这里计算时加一处理。
4. 数据分析
这里就没有太多实例了,主要是我把自己的经验打个包讲一讲。
拿到一个真实的数据文件,你会看到它大概是长这样的:
很乱,一瞬间是反应不过来的。
之前在入门篇里提到了,数据文件的每一行表示一个 Routine,每一列表示一个变量。在同一个 Routine 里发生的事件,就会在同一行里出现。所以整体上,按照各个变量初始化的顺序,数据文件呈现从左上到右下的一个阶梯排列状。
事实上,我们不会关心各个指导语、休息期间发生的事件。这个时候之前提到的,规范化命名就起作用了。比如说,我们在设计时就只给正式的试次命名为*_trial
,在数据分析时我们就完全可以直接搜索trial
的字段。
同时,如果涉及到多个条件文件,请尽量为不同的条件文件设计不同的变量名。因为可以注意到一个问题:PsychoPy 里的同名变量视作同一个变量,被放到同一列。假设我们有一个实验,分为两个阶段,每个阶段都会用到数字作为刺激。如果我们在设置条件文件时都命名为numbers
,那么最后两个实验阶段的numbers
就会合并到同一列,在数据文件中就会出现数据与条件相隔甚远的情况,不利于数据分析。
我在分析 PsychoPy 的数据时,会采用一个思路:先选择有效的列,再选择有效的行。大家也可以这样参考。
5. 线上实验的那些事
终于到这个阶段了,就是为了这碗醋包的这么多饺子(
这里也是,不会涉及实例,主要是大致说一下我遇到的各种情况。
线上实验部分,之前提到了,是把实验程序编译成 Javascript 脚本运行。
随后,你可以在本地 debug 你的实验。进入 Runner 界面,选择 Run → Run JS for local debug:
PsychoPy 会在本地创建完整的实验程序和网页文件,随后自动在浏览器里打开实验程序,供 debug 使用。
如果要进行线上实验,那么有两个方法:
- 第一,自建网站进行实验,所需技术过于繁杂,大多数人也用不上,所以这里就不讲了
- 第二,依靠现有平台(如 Pavlovia.org,脑岛等)进行在线实验
我的经验主要针对脑岛展开。
首先是版本问题。前面提到了,脑岛只对几个特定的 PsychoPy 提供支持,而我第一次上传实验时用的是更新的版本,因此——脑岛的系统不认识我的实验。我只能降版本重编译。
这里就引出了第一个血泪史:
我就遇到了两个非常要命的 bug:
- 一个是旧版本的 PsychoPy 转译为 JS 后,把本应是数组(
[0, 0, 0]
)的颜色代码转换成了字符串('0, 0, 0'
),然后我程序就炸了。解决方法是我把所有涉及到颜色代码的部分全部改成了颜色代词。 - 第二个是,旧版本的 PsychoJS 脚本,处理旋转的方向和 Python 是反的。我头疼了一个小时,最后用了很魔法的方法,直接修改原始代码解决了这个问题。
所以,一定一定记得 debug,不要直接上传了事。
第二个血泪史就和脑岛处理实验的流程有关了:
脑岛的实验页面是长这样的:
请注意,在这里点击“开始”后,会进入正式的实验程序,而从这里的开始到整体实验结束,都只能是同一个被试,不能通过反复开始实验对多个被试施测,否则会丢失数据。
一定要在实验完全结束后,再重新开启一个实验页面。
因为我们是远程实验,与实际施测的主试朋友沟通不善,出现了重复使用同一个页面的情况,导致丢失了一部分实验数据,非常悲惨。
后记
我能说出来的东西估计其实也就差不多了,暂时是没有别的能提到的内容了。
说实话,我也并没有开发出 PsychoPy 的全部能力,笔下所能表达出的东西不及其潜能的十分之一。工具是为人所用的,而使用它的人则决定了工具能发挥多大的作用。
“学海无涯,忌自满。”
加油,祝你好运!