PsychoPy 中的可变试验条件

Variable Trial Conditions in PsychoPy

我在构建器视图中使用的是 PsychoPy 版本 1.82.01。 我对 Python 的了解仍然很有限。我 运行 遇到了任务障碍,非常感谢社区的任何建议!

任务是这样的:

首先向对象呈现两个目标三角形:一个指向左侧,一个指向右侧。指示受试者在发现这些目标之一时按相应的向左或向右箭头键。

Excel 条件文件的示例如下所示:

angle_01    angle_02    angle_03    angle_04    corrAns
   0           45          135        180        none
  315          45          135        180        none
  315         225          135        180        none
  270         225          135        180        left
   0          225          135        180        none

** 然后向受试者展示一组 4 个三角形。我在 Builder 中创建了这 4 个三角形。每个三角形设置为具有 1 秒的持续时间。每个三角形的方向由条件文件确定——三角形 1 方向字段设置为 "angle_01," 三角形 2 方向设置为 "angle_02" 等等。所以,如果你能在上面的代码片段中看到,我在条件文件中创建了每一行,这样只有一个三角形每秒改变它的方向。当其中一个三角形像这样变化时,它被称为"distractor shift."

经过多次这样的干扰转移后,其中一个三角形转移到 "target" 位置(指向左或右,90 或 270 度),此时该人将以按键。这是 "target shift." 我在 Excel 条件文件中指定了所有三角形的方向。现在,为了简单起见,我将此设置为顺序循环,具有固定数量的干扰转移。

然而,我的最终目标是在每次试验的目标转移之前进行可变数量的干扰物转移,并且每次试验都以注视十字结束,然后再进行下一次试验。我很难弄清楚要使用什么代码来实现这一点。我开始让循环在条件文件中随机进行,但结果是每个三角形在每次重复时都会改变其方向(而不是一次只改变一个)。我是否以错误的方式解决了这个问题?下面粘贴了完整的代码。

谢谢!

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This experiment was created using PsychoPy2 Experiment Builder (v1.82.01), Thu Aug 27 10:56:53 2015
If you publish work using this script please cite the relevant PsychoPy publications
  Peirce, JW (2007) PsychoPy - Psychophysics software in Python. Journal of Neuroscience Methods, 162(1-2), 8-13.
  Peirce, JW (2009) Generating stimuli for neuroscience using PsychoPy. Frontiers in Neuroinformatics, 2:10. doi: 10.3389/neuro.11.010.2008
"""

from __future__ import division  # so that 1/3=0.333 instead of 1/3=0
from psychopy import visual, core, data, event, logging, sound, gui
from psychopy.constants import *  # things like STARTED, FINISHED
import numpy as np  # whole numpy lib is available, prepend 'np.'
from numpy import sin, cos, tan, log, log10, pi, average, sqrt, std, deg2rad, rad2deg, linspace, asarray
from numpy.random import random, randint, normal, shuffle
import os  # handy system and path functions

# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)

# Store info about the experiment session
expName = 'simultaneous_04'  # from the Builder filename that created this script
expInfo = {u'session': u'001', u'participant': u''}
dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)
if dlg.OK == False: core.quit()  # user pressed cancel
expInfo['date'] = data.getDateStr()  # add a simple timestamp
expInfo['expName'] = expName

# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = _thisDir + os.sep + 'data/%s_%s_%s' %(expInfo['participant'], expName, expInfo['date'])

# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
    extraInfo=expInfo, runtimeInfo=None,
    originPath=None,
    savePickle=True, saveWideText=True,
    dataFileName=filename)
#save a log file for detail verbose info
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING)  # this outputs to the screen, not a file

endExpNow = False  # flag for 'escape' or other condition => quit the exp

# Start Code - component code to be run before the window creation

# Setup the Window
win = visual.Window(size=(1366, 768), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
    monitor='testMonitor', color=[-1,-1,-1], colorSpace='rgb',
    blendMode='avg', useFBO=True,
    )
# store frame rate of monitor if we can measure it successfully
expInfo['frameRate']=win.getActualFrameRate()
if expInfo['frameRate']!=None:
    frameDur = 1.0/round(expInfo['frameRate'])
else:
    frameDur = 1.0/60.0 # couldn't get a reliable measure so guess

# Initialize components for Routine "Target_Presentation"
Target_PresentationClock = core.Clock()
target_triangle = visual.ShapeStim(win=win, name='target_triangle',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=1.0, pos=[-7, 3],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=0.0, 
interpolate=True)
text_4 = visual.TextStim(win=win, ori=0, name='text_4',
    text='Press the LEFT ARROW key when you spot the triangle pointing to the left.\n\nPress the RIGHT ARROW key when you spot the triangle pointing to the right.\n\nPress the spacebar when you are ready to begin.',    font='Arial',
    pos=[0,-0.5], height=0.08, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=-1.0)
target_triangle_02 = visual.ShapeStim(win=win, name='target_triangle_02',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=90, pos=[7,3],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=-2.0, 
interpolate=True)
text_5 = visual.TextStim(win=win, ori=0, name='text_5',
    text='or',    font='Arial',
    pos=[0, 0.25], height=0.1, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=-3.0)

# Initialize components for Routine "Start_Fixation_Cross"
Start_Fixation_CrossClock = core.Clock()
text_2 = visual.TextStim(win=win, ori=0, name='text_2',
    text='+',    font='Arial',
    pos=[0, 0], height=0.1, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=0.0)

# Initialize components for Routine "Trial"
TrialClock = core.Clock()


triangle_01 = visual.ShapeStim(win=win, name='triangle_01',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=1.0, pos=[-8.2,6.2],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=-1.0, 
interpolate=True)
triangle_02 = visual.ShapeStim(win=win, name='triangle_02',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=1.0, pos=[8.2,6.2],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=-2.0, 
interpolate=True)
triangle_03 = visual.ShapeStim(win=win, name='triangle_03',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=1.0, pos=[8.2,-6.2],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=-3.0, 
interpolate=True)
triangle_04 = visual.ShapeStim(win=win, name='triangle_04',units='cm', 
    vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
    ori=1.0, pos=[-8.2,-6.2],
    lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
    fillColor=[1,1,1], fillColorSpace='rgb',
    opacity=1,depth=-4.0, 
interpolate=True)

# Initialize components for Routine "Ending"
EndingClock = core.Clock()
text_3 = visual.TextStim(win=win, ori=0, name='text_3',
    text='This concludes the experiment. \n\nThank you for participating!',    font='Arial',
    pos=[0, 0], height=0.1, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=0.0)

# Create some handy timers
globalClock = core.Clock()  # to track the time since experiment started
routineTimer = core.CountdownTimer()  # to track time remaining of each (non-slip) routine 

#------Prepare to start Routine "Target_Presentation"-------
t = 0
Target_PresentationClock.reset()  # clock 
frameN = -1
# update component parameters for each repeat
target_triangle.setOri(270)
key_resp_3 = event.BuilderKeyResponse()  # create an object of type KeyResponse
key_resp_3.status = NOT_STARTED
# keep track of which components have finished
Target_PresentationComponents = []
Target_PresentationComponents.append(target_triangle)
Target_PresentationComponents.append(text_4)
Target_PresentationComponents.append(target_triangle_02)
Target_PresentationComponents.append(text_5)
Target_PresentationComponents.append(key_resp_3)
for thisComponent in Target_PresentationComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

#-------Start Routine "Target_Presentation"-------
continueRoutine = True
while continueRoutine:
    # get current time
    t = Target_PresentationClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame

    # *target_triangle* updates
    if t >= 0.0 and target_triangle.status == NOT_STARTED:
        # keep track of start time/frame for later
        target_triangle.tStart = t  # underestimates by a little under one frame
        target_triangle.frameNStart = frameN  # exact frame index
        target_triangle.setAutoDraw(True)

    # *text_4* updates
    if t >= 0.0 and text_4.status == NOT_STARTED:
        # keep track of start time/frame for later
        text_4.tStart = t  # underestimates by a little under one frame
        text_4.frameNStart = frameN  # exact frame index
        text_4.setAutoDraw(True)

    # *target_triangle_02* updates
    if t >= 0.0 and target_triangle_02.status == NOT_STARTED:
        # keep track of start time/frame for later
        target_triangle_02.tStart = t  # underestimates by a little under one frame
        target_triangle_02.frameNStart = frameN  # exact frame index
        target_triangle_02.setAutoDraw(True)

    # *text_5* updates
    if t >= 0.0 and text_5.status == NOT_STARTED:
        # keep track of start time/frame for later
        text_5.tStart = t  # underestimates by a little under one frame
        text_5.frameNStart = frameN  # exact frame index
        text_5.setAutoDraw(True)

    # *key_resp_3* updates
    if t >= 0.0 and key_resp_3.status == NOT_STARTED:
        # keep track of start time/frame for later
        key_resp_3.tStart = t  # underestimates by a little under one frame
        key_resp_3.frameNStart = frameN  # exact frame index
        key_resp_3.status = STARTED
        # keyboard checking is just starting
        key_resp_3.clock.reset()  # now t=0
        event.clearEvents(eventType='keyboard')
    if key_resp_3.status == STARTED:
        theseKeys = event.getKeys(keyList=['space'])

        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            key_resp_3.keys = theseKeys[-1]  # just the last key pressed
            key_resp_3.rt = key_resp_3.clock.getTime()
            # a response ends the routine
            continueRoutine = False

    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in Target_PresentationComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished

    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()

    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

#-------Ending Routine "Target_Presentation"-------
for thisComponent in Target_PresentationComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if key_resp_3.keys in ['', [], None]:  # No response was made
   key_resp_3.keys=None
# store data for thisExp (ExperimentHandler)
thisExp.addData('key_resp_3.keys',key_resp_3.keys)
if key_resp_3.keys != None:  # we had a response
    thisExp.addData('key_resp_3.rt', key_resp_3.rt)
thisExp.nextEntry()
# the Routine "Target_Presentation" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()

#------Prepare to start Routine "Start_Fixation_Cross"-------
t = 0
Start_Fixation_CrossClock.reset()  # clock 
frameN = -1
routineTimer.add(1.000000)
# update component parameters for each repeat
# keep track of which components have finished
Start_Fixation_CrossComponents = []
Start_Fixation_CrossComponents.append(text_2)
for thisComponent in Start_Fixation_CrossComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

#-------Start Routine "Start_Fixation_Cross"-------
continueRoutine = True
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = Start_Fixation_CrossClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame

    # *text_2* updates
    if t >= 0.0 and text_2.status == NOT_STARTED:
        # keep track of start time/frame for later
        text_2.tStart = t  # underestimates by a little under one frame
        text_2.frameNStart = frameN  # exact frame index
        text_2.setAutoDraw(True)
    if text_2.status == STARTED and t >= (0.0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
        text_2.setAutoDraw(False)

    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in Start_Fixation_CrossComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished

    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()

    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

#-------Ending Routine "Start_Fixation_Cross"-------
for thisComponent in Start_Fixation_CrossComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)

# set up handler to look after randomisation of conditions etc
TrialLoop = data.TrialHandler(nReps=1, method='sequential', 
    extraInfo=expInfo, originPath=None,
    trialList=data.importConditions('distractor_conditions.xlsx'),
    seed=None, name='TrialLoop')
thisExp.addLoop(TrialLoop)  # add the loop to the experiment
thisTrialLoop = TrialLoop.trialList[0]  # so we can initialise stimuli with some values
# abbreviate parameter names if possible (e.g. rgb=thisTrialLoop.rgb)
if thisTrialLoop != None:
    for paramName in thisTrialLoop.keys():
        exec(paramName + '= thisTrialLoop.' + paramName)

for thisTrialLoop in TrialLoop:
    currentLoop = TrialLoop
    # abbreviate parameter names if possible (e.g. rgb = thisTrialLoop.rgb)
    if thisTrialLoop != None:
        for paramName in thisTrialLoop.keys():
            exec(paramName + '= thisTrialLoop.' + paramName)

    #------Prepare to start Routine "Trial"-------
    t = 0
    TrialClock.reset()  # clock 
    frameN = -1
    routineTimer.add(1.000000)
    # update component parameters for each repeat



    triangle_01.setOri(distractor_angle_01)
    triangle_02.setOri(distractor_angle_02)
    triangle_03.setOri(distractor_angle_03)
    triangle_04.setOri(distractor_angle_04)
    trial_resp = event.BuilderKeyResponse()  # create an object of type KeyResponse
    trial_resp.status = NOT_STARTED
    # keep track of which components have finished
    TrialComponents = []
    TrialComponents.append(triangle_01)
    TrialComponents.append(triangle_02)
    TrialComponents.append(triangle_03)
    TrialComponents.append(triangle_04)
    TrialComponents.append(trial_resp)
    for thisComponent in TrialComponents:
        if hasattr(thisComponent, 'status'):
            thisComponent.status = NOT_STARTED

    #-------Start Routine "Trial"-------
    continueRoutine = True
    while continueRoutine and routineTimer.getTime() > 0:
        # get current time
        t = TrialClock.getTime()
        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
        # update/draw components on each frame




        # *triangle_01* updates
        if t >= 0 and triangle_01.status == NOT_STARTED:
            # keep track of start time/frame for later
            triangle_01.tStart = t  # underestimates by a little under one frame
            triangle_01.frameNStart = frameN  # exact frame index
            triangle_01.setAutoDraw(True)
        if triangle_01.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
            triangle_01.setAutoDraw(False)

        # *triangle_02* updates
        if t >= 0 and triangle_02.status == NOT_STARTED:
            # keep track of start time/frame for later
            triangle_02.tStart = t  # underestimates by a little under one frame
            triangle_02.frameNStart = frameN  # exact frame index
            triangle_02.setAutoDraw(True)
        if triangle_02.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
            triangle_02.setAutoDraw(False)

        # *triangle_03* updates
        if t >= 0 and triangle_03.status == NOT_STARTED:
            # keep track of start time/frame for later
            triangle_03.tStart = t  # underestimates by a little under one frame
            triangle_03.frameNStart = frameN  # exact frame index
            triangle_03.setAutoDraw(True)
        if triangle_03.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
            triangle_03.setAutoDraw(False)

        # *triangle_04* updates
        if t >= 0 and triangle_04.status == NOT_STARTED:
            # keep track of start time/frame for later
            triangle_04.tStart = t  # underestimates by a little under one frame
            triangle_04.frameNStart = frameN  # exact frame index
            triangle_04.setAutoDraw(True)
        if triangle_04.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
            triangle_04.setAutoDraw(False)

        # *trial_resp* updates
        if t >= 0.0 and trial_resp.status == NOT_STARTED:
            # keep track of start time/frame for later
            trial_resp.tStart = t  # underestimates by a little under one frame
            trial_resp.frameNStart = frameN  # exact frame index
            trial_resp.status = STARTED
            # keyboard checking is just starting
            trial_resp.clock.reset()  # now t=0
            event.clearEvents(eventType='keyboard')
        if trial_resp.status == STARTED and t >= (0.0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
            trial_resp.status = STOPPED
        if trial_resp.status == STARTED:
            theseKeys = event.getKeys(keyList=['left', 'right'])

            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                trial_resp.keys = theseKeys[-1]  # just the last key pressed
                trial_resp.rt = trial_resp.clock.getTime()
                # was this 'correct'?
                if (trial_resp.keys == str(corrAns)) or (trial_resp.keys == corrAns):
                    trial_resp.corr = 1
                else:
                    trial_resp.corr = 0

        # check if all components have finished
        if not continueRoutine:  # a component has requested a forced-end of Routine
            break
        continueRoutine = False  # will revert to True if at least one component still running
        for thisComponent in TrialComponents:
            if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
                continueRoutine = True
                break  # at least one component has not yet finished

        # check for quit (the Esc key)
        if endExpNow or event.getKeys(keyList=["escape"]):
            core.quit()

        # refresh the screen
        if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
            win.flip()

    #-------Ending Routine "Trial"-------
    for thisComponent in TrialComponents:
        if hasattr(thisComponent, "setAutoDraw"):
            thisComponent.setAutoDraw(False)

    # check responses
    if trial_resp.keys in ['', [], None]:  # No response was made
       trial_resp.keys=None
       # was no response the correct answer?!
       if str(corrAns).lower() == 'none': trial_resp.corr = 1  # correct non-response
       else: trial_resp.corr = 0  # failed to respond (incorrectly)
    # store data for TrialLoop (TrialHandler)
    TrialLoop.addData('trial_resp.keys',trial_resp.keys)
    TrialLoop.addData('trial_resp.corr', trial_resp.corr)
    if trial_resp.keys != None:  # we had a response
        TrialLoop.addData('trial_resp.rt', trial_resp.rt)
    thisExp.nextEntry()

# completed 1 repeats of 'TrialLoop'


#------Prepare to start Routine "Ending"-------
t = 0
EndingClock.reset()  # clock 
frameN = -1
routineTimer.add(3.000000)
# update component parameters for each repeat
# keep track of which components have finished
EndingComponents = []
EndingComponents.append(text_3)
for thisComponent in EndingComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

#-------Start Routine "Ending"-------
continueRoutine = True
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = EndingClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame

    # *text_3* updates
    if t >= 0.0 and text_3.status == NOT_STARTED:
        # keep track of start time/frame for later
        text_3.tStart = t  # underestimates by a little under one frame
        text_3.frameNStart = frameN  # exact frame index
        text_3.setAutoDraw(True)
    if text_3.status == STARTED and t >= (0.0 + (3.0-win.monitorFramePeriod*0.75)): #most of one frame period left
        text_3.setAutoDraw(False)

    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in EndingComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished

    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()

    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

#-------Ending Routine "Ending"-------
for thisComponent in EndingComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)

win.close()
core.quit()

您的 Excel 条件文件的布局可能需要稍作更改。 PsychoPy 的结构围绕着将该文件中的每一行视为对应于一个试验,而您目前正在将一个试验分布在多行中。这将使存储和收集相应刺激的响应变得更加困难。建议你这样布局:

numShifts     angle_01         angle_02
4             [0,315,315,270]  [45,45,225,225]
3             [0,45,315]        [45,45,90]
etc

然后在 Builder 中,在现有循环中插入一个新循环。

重要的是:取消select它的"is trials"复选框。这意味着此循环将在一次试验中多次 运行,从而改变刺激,但此特定试验需要多次。

我们称内部循环stimulusLoop和外部试验级循环trialLoop)。即 trialLoop 将 运行 每次试验仅一次,处理条件文件的一行。 stimulusLoop 将在试验中提取每个刺激的角度列表的每个条目。为此,将 numShift 放在内部循环的 nReps 字段中。例如给定上面的条件文件,它会在第一次试验中 运行 4 次,在第二次试验中会 运行 3 次。

然后在每个刺激的方向字段中,放这样的东西(未测试):

eval(angle_01)[stimulusLoop.thisN]

它所做的是评估角度列表(当它被读入时,PsychoPy 只是认为它是字符列表 "[0,315,315,270]"eval() 函数告诉它评估它作为一个Python 表达式。在这种情况下,它会意识到它是一个数字列表,然后我们可以对其进行索引以获取当前值,在这种情况下使用内循环的当前迭代次数 (stimulusLoop.thisN ).

希望这能帮助您入门。