开始之前

建议在基本掌握 PsychoPy 操作后阅读本文。可参考前述的入门篇指南。

前言

前略,这一篇主要是作为 PsychoPy 入门后的一些小技巧讲解,大多以我自己的经验出发,篇幅应该会短很多,也不会像入门篇那样讲解得非常细致,整体结构也会比较松散一些。

也因为是从自己的角度出发,所以我自己还没涉及过的东西(脑电啊、眼动啊)我肯定也不会做,还是那句话:

本文无意成为一篇全面详尽的 PsychoPy 指南,本文作者也并不具有这样的实力与能力。

本文希望做到的是能让阅读者快速了解这一工具,可以在较短时间内掌握其使用方法。

如在开发、使用过程中遇到问题,官方文档与搜索引擎是更好的资料来源。

0. 关于颜色的那些事

PsychoPy 的默认背景颜色是非常中性的一种灰[128, 128, 128],默认文字颜色是纯白色[255, 255, 255]

有的时候这种灰色可能不太好,我们希望使用黑色作为底色,这个可以在实验设置中修改。点击顶上的 ⚙️ Edit experiment settings 按钮,选择 Screen 选项卡:

在 Color 一栏

在 Color 一栏填入颜色代码或颜色字符串即可,具体可用的颜色可点击右边的按钮查看:

颜色选择器

版本较老的 PsychoPy(如2021.2.3,脑岛的推荐版本)在将 RGB 三色代码转换至 Javascript 时存在 bug,无法将其正确映射为数组形式,因此转换出来的程序无法运行。如果有相应的线上实验需要,请直接使用颜色词汇。

1. 时间随机化

很多实验会要求对注视点或者空屏时间进行随机化,防止被试进行预测或形成习惯化,影响实验结果。

较新版的 PsychoPy 里预设了随机注视点的 Routine,可以参照学习一下:

新建 Routine 时,选择 random_interval 选项

如果使用的是旧版 PsychoPy 也没关系,我们可以仿照它的模式自己写一个:

原来在这

这里使用了一个random()函数,可以生成 0 到 1 范围内的随机数,因此这里的持续时间便是 1 到 2 秒内随机。

如法炮制,如果我们想要实现 400-600 毫秒内随机呢?

我总之是这么写的

可能有会 Python 的同学这里就要问了,Python 里还有别的随机函数,而且写起来更简单,更简短。

但是我的实验结果是,不行。

因为不知道 PsychoPy 引入了一些什么库,把什么样的指令引入到了什么样的程度,我尝试的各种更加简单的随机化代码都运行不起来,反而是这种写法最稳定。

当然你也可以在头部 import 一点别的什么东西,但是这些东西只会适用于 Python 环境,所以如果想要做线上实验的话还是……就这样吧。

2. 提供反馈

大多数简单的实验应该是不需要这个操作的,但是这个操作的知识(比如变量赋值、变量传递等)可以为后面的东西打下基础。

假设在 Stroop 任务中,我们需要在被试的练习阶段为被试提供正确与错误的反馈,被试做出正确反应则显示绿色的反馈词,错误反应则为红色,

到这里,纯 GUI 的编辑功能已经满足不了我们了,我们需要用到 PsychoPy 里的代码组件了。

我的 Python 水平也非常有限,这些代码通常都是现学现卖现抄的,所以如果有其他需求的话,可以考虑先自学一下 Python。

先假设我们已经设置好了各种指导语和实验用的 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:在实验完全结束时,生成数据文件前运行

如要进行线上实验需要注意,脑岛要求研究者不要在 Before Experiment 阶段引入任何代码。

我们可以先初始化几个变量,用来承载我们的反馈消息。在 Begin Experiment 阶段引入这样的代码:

注意到右边的代码区了吗?

右边的代码区是什么?注意到顶上的 Code Type 了吗?Auto -> JS的模式下,左边填写的 Python 代码可以自动转换为 Javascript 代码,如果需要把程序转换为 JS 运行,操作得当的情况下可以省去重写代码的时间。

在这里,我们在实验开始时初始化了两个变量:messagemessage_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变量中,然后将消息的颜色设置为绿色。同理,在被试反应错误的情况下,返回红色的消息。

那么到这里你大概会有一个问题,一个我当时也有的问题:我从哪里知道的corrrt呢?我怎么知道它有多少个属性,有哪些可以用的变量呢?

说实话我翻官方教程也没翻到这个,我用了一个比较野的路子——还记得数据文件吗?

老图新用

每一个程序当中会用到的变量,都会在这里创建一个对应的列,而每列第一格就是它的名字。

所以,通过阅读数据文件,我可以知道有哪些变量在实验过程中被创建,可以在代码组件中使用。

接下来,保存代码组件,在面板中添加一个新的文本组件:

文本组件的设置,记得修改后面的显示方式——每次重复时设置

这样,我们就有了一个给被试提供反馈的组件。完成之后,你的feedback应该长这样:

feedback 内部

在引入代码组件时,注意程序的初始化顺序。

PsychoPy 的初始化顺序是从上到下,也就是靠近顶端的代码最先运行,靠近底端的代码最后运行。

在刚刚的例子,如果把code放到了text底下,那么就无法正常显示刚刚设置好的消息——程序会先加载文本,再给文本赋值,而这个时候文本已经显示到屏幕上了(或者程序已经报错闪退了)。

事实上其实这一节讲的内容大多数人在大多数实验中都用不上,包括我自己。讲这个主要是为了把代码组件介绍一下,并且给下面的内容开个头——

3. 休息设置

很多实验动不动就会有成百上千个试次,中间不插休息不行。

当然比较简单粗暴的一个方法是做成这种样子:

羊肉串.jpg

当然这样做也不是不行,只是……PsychoPy 会卡。

亲身经历,我的同一个实验里有大概二十来个 Routine 一字排开,随后我想要编辑任何一个东西都会卡上半分钟才能打开窗口。

我怀疑和 PsychoPy 的缓存管理有关系,可能做得不是很好。

PsychoPy 论坛和一些教程里推荐了这样的一种做法:

烤肠.jpg

当然,会用到代码组件,不可能做一个 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 脚本运行。

点击这个按钮,就可以编译实验的 JS 脚本了

随后,你可以在本地 debug 你的实验。进入 Runner 界面,选择 Run → Run JS for local debug:

也就是这里

PsychoPy 会在本地创建完整的实验程序和网页文件,随后自动在浏览器里打开实验程序,供 debug 使用。

如果要进行线上实验,那么有两个方法:

  • 第一,自建网站进行实验,所需技术过于繁杂,大多数人也用不上,所以这里就不讲了
  • 第二,依靠现有平台(如 Pavlovia.org,脑岛等)进行在线实验

我的经验主要针对脑岛展开。

首先是版本问题。前面提到了,脑岛只对几个特定的 PsychoPy 提供支持,而我第一次上传实验时用的是更新的版本,因此——脑岛的系统不认识我的实验。我只能降版本重编译。

这里就引出了第一个血泪史:

跨版本重新编译程序时,请一定完整试运行一次,以免遇到奇奇怪怪的 bug。

我就遇到了两个非常要命的 bug:

  • 一个是旧版本的 PsychoPy 转译为 JS 后,把本应是数组([0, 0, 0])的颜色代码转换成了字符串('0, 0, 0'),然后我程序就炸了。解决方法是我把所有涉及到颜色代码的部分全部改成了颜色代词。
  • 第二个是,旧版本的 PsychoJS 脚本,处理旋转的方向和 Python 是反的。我头疼了一个小时,最后用了很魔法的方法,直接修改原始代码解决了这个问题。

所以,一定一定记得 debug,不要直接上传了事。

第二个血泪史就和脑岛处理实验的流程有关了:

请一定注意,在脑岛上运行实验时,一个网页只能承载一个被试。如果在同一个网页内多次打开实验,靠后的数据会覆盖靠前的数据。

脑岛的实验页面是长这样的:

脑岛的实验开始页面

请注意,在这里点击“开始”后,会进入正式的实验程序,而从这里的开始到整体实验结束,都只能是同一个被试,不能通过反复开始实验对多个被试施测,否则会丢失数据。

一定要在实验完全结束后,再重新开启一个实验页面。

因为我们是远程实验,与实际施测的主试朋友沟通不善,出现了重复使用同一个页面的情况,导致丢失了一部分实验数据,非常悲惨。

后记

我能说出来的东西估计其实也就差不多了,暂时是没有别的能提到的内容了。

说实话,我也并没有开发出 PsychoPy 的全部能力,笔下所能表达出的东西不及其潜能的十分之一。工具是为人所用的,而使用它的人则决定了工具能发挥多大的作用。

“学海无涯,忌自满。”

加油,祝你好运!

最后修改:2022 年 07 月 28 日
虽然点赞什么的确实没什么意义但是也可以点一个再走呗?(