Loading... <div class="tip share">请注意,本文编写于 432 天前,最后修改于 431 天前,其中某些信息可能已经过时。</div> <div class='handsome_aplayer player-content' data-preload="auto" data-autoplay="false" data-listMaxHeight="340px" data-order="list"> <div class="handsomePlayer-tip-loading"><span></span> <span></span> <span></span> <span></span><span></span></div><div class="handsome_aplayer_music" data-id="29774186" data-server="netease" data-type="song" data-auth="6a2c64b37f52e862a51d1180ab59e672"></div> </div> ## 开始之前 <div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://bcmonomial.xyz/shelf/PsychoPy_Tutorial_Intro.html" target="_blank" class="post_inser_a no-external-link no-underline-link"> <div class="inner-image bg" style="background-image: url(https://www.bcmonomial.xyz/usr/uploads/2022/07/3915263065.png);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">PsychoPy 简明扼要上手指南——入门篇</p> <div class="inster-summary text-muted"> 前言容我想想要从哪开始写啊……前一阵子因为各种机缘巧合,接触到了 PsychoPy 这个工具,而且真用它完整做完了... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div> 建议在基本掌握 PsychoPy 操作后阅读本文。可参考前述的入门篇指南。 ## 前言 前略,这一篇主要是作为 PsychoPy 入门后的一些小技巧讲解,大多以我自己的经验出发,篇幅应该会短很多,也不会像[入门篇](https://www.bcmonomial.xyz/shelf/PsychoPy_Tutorial_Intro.html)那样讲解得非常细致,整体结构也会比较松散一些。 也因为是从自己的角度出发,所以我自己还没涉及过的东西(脑电啊、眼动啊)我肯定也不会做,还是那句话: <div class="tip inlineBlock warning"> 本文无意成为一篇全面详尽的 PsychoPy 指南,本文作者也并不具有这样的实力与能力。 本文希望做到的是能让阅读者快速了解这一工具,可以在较短时间内掌握其使用方法。 如在开发、使用过程中遇到问题,官方文档与搜索引擎是更好的资料来源。 </div> ## 0. 关于颜色的那些事 PsychoPy 的默认背景颜色是非常中性的一种灰`[128, 128, 128]`,默认文字颜色是纯白色`[255, 255, 255]`。 有的时候这种灰色可能不太好,我们希望使用黑色作为底色,这个可以在实验设置中修改。点击顶上的 ⚙️ Edit experiment settings 按钮,选择 Screen 选项卡:  在 Color 一栏填入颜色代码或颜色字符串即可,具体可用的颜色可点击右边的按钮查看:  <div class="tip inlineBlock info"> 版本较老的 PsychoPy(如`2021.2.3`,脑岛的推荐版本)在将 RGB 三色代码转换至 Javascript 时存在 bug,无法将其正确映射为数组形式,因此转换出来的程序无法运行。如果有相应的线上实验需要,请直接使用颜色词汇。 </div> ## 1. 时间随机化 很多实验会要求对注视点或者空屏时间进行随机化,防止被试进行预测或形成习惯化,影响实验结果。 较新版的 PsychoPy 里预设了随机注视点的 Routine,可以参照学习一下:  如果使用的是旧版 PsychoPy 也没关系,我们可以仿照它的模式自己写一个:  这里使用了一个`random()`函数,可以生成 0 到 1 范围内的随机数,因此这里的持续时间便是 1 到 2 秒内随机。 如法炮制,如果我们想要实现 400-600 毫秒内随机呢?  <div class="tip inlineBlock info"> 可能有会 Python 的同学这里就要问了,Python 里还有别的随机函数,而且写起来更简单,更简短。 但是我的实验结果是,不行。 因为不知道 PsychoPy 引入了一些什么库,把什么样的指令引入到了什么样的程度,我尝试的各种更加简单的随机化代码都运行不起来,反而是这种写法最稳定。 当然你也可以在头部 `import` 一点别的什么东西,但是这些东西只会适用于 Python 环境,所以如果想要做线上实验的话还是……就这样吧。 </div> ## 2. 提供反馈 大多数简单的实验应该是不需要这个操作的,但是这个操作的知识(比如变量赋值、变量传递等)可以为后面的东西打下基础。 假设在 Stroop 任务中,我们需要在被试的练习阶段为被试提供正确与错误的反馈,被试做出正确反应则显示绿色的反馈词,错误反应则为红色, 到这里,纯 GUI 的编辑功能已经满足不了我们了,我们需要用到 PsychoPy 里的代码组件了。 <div class="tip inlineBlock warning"> 我的 Python 水平也非常有限,这些代码通常都是现学现卖现抄的,所以如果有其他需求的话,可以考虑先自学一下 Python。 </div> 先假设我们已经设置好了各种指导语和实验用的 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:在实验完全结束时,生成数据文件前运行 <div class="tip inlineBlock info"> 如要进行线上实验需要注意,脑岛要求研究者不要在 Before Experiment 阶段引入任何代码。 </div> 我们可以先初始化几个变量,用来承载我们的反馈消息。在 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`应该长这样:  <div class="tip inlineBlock info"> 在引入代码组件时,注意程序的初始化顺序。 PsychoPy 的初始化顺序是从上到下,也就是靠近顶端的代码最先运行,靠近底端的代码最后运行。 在刚刚的例子,如果把`code`放到了`text`底下,那么就无法正常显示刚刚设置好的消息——程序会先加载文本,再给文本赋值,而这个时候文本已经显示到屏幕上了(或者程序已经报错闪退了)。 </div> 事实上其实这一节讲的内容大多数人在大多数实验中都用不上,包括我自己。讲这个主要是为了把代码组件介绍一下,并且给下面的内容开个头—— ## 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 提供支持,而我第一次上传实验时用的是更新的版本,因此——脑岛的系统不认识我的实验。我只能降版本重编译。 这里就引出了第一个血泪史: <div class="tip inlineBlock error"> 跨版本重新编译程序时,请一定完整试运行一次,以免遇到奇奇怪怪的 bug。 </div> 我就遇到了两个非常要命的 bug: * 一个是旧版本的 PsychoPy 转译为 JS 后,把本应是数组(`[0, 0, 0]`)的颜色代码转换成了字符串(`'0, 0, 0'`),然后我程序就炸了。解决方法是我把所有涉及到颜色代码的部分全部改成了颜色代词。 * 第二个是,旧版本的 PsychoJS 脚本,处理旋转的方向和 Python 是反的。我头疼了一个小时,最后用了很魔法的方法,直接修改原始代码解决了这个问题。 所以,一定一定记得 debug,不要直接上传了事。 第二个血泪史就和脑岛处理实验的流程有关了: <div class="tip inlineBlock error"> 请一定注意,在脑岛上运行实验时,一个网页只能承载一个被试。如果在同一个网页内多次打开实验,靠后的数据会覆盖靠前的数据。 </div> 脑岛的实验页面是长这样的:  请注意,在这里点击“开始”后,会进入正式的实验程序,而从这里的开始到整体实验结束,都只能是同一个被试,不能通过反复开始实验对多个被试施测,否则会丢失数据。 一定要在实验完全结束后,再重新开启一个实验页面。 因为我们是远程实验,与实际施测的主试朋友沟通不善,出现了重复使用同一个页面的情况,导致丢失了一部分实验数据,非常悲惨。 ## 后记 我能说出来的东西估计其实也就差不多了,暂时是没有别的能提到的内容了。 说实话,我也并没有开发出 PsychoPy 的全部能力,笔下所能表达出的东西不及其潜能的十分之一。工具是为人所用的,而使用它的人则决定了工具能发挥多大的作用。 “学海无涯,忌自满。” 加油,祝你好运! 最后修改:2022 年 07 月 28 日 © 允许规范转载 赞 2 虽然点赞什么的确实没什么意义但是也可以点一个再走呗?(