Last active
March 5, 2018 03:47
-
-
Save cosacog/3293f721bc7832b8f6b66599ed167d10 to your computer and use it in GitHub Desktop.
line bisection task using psychopy. refer to Fierro et al. (2000) PMID: 10841369、ダウンロードする時は右上のダウンロードボタンからお願いします。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# -*- coding: utf-8 -*- | |
""" | |
This experiment was created using PsychoPy2 Experiment Builder (v1.85.2), | |
on 2017_11_15_2037 | |
If you publish work using this script please cite the 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 absolute_import, division | |
from psychopy import locale_setup, sound, gui, visual, core, data, event, logging | |
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED, | |
STOPPED, FINISHED, PRESSED, RELEASED, FOREVER) | |
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 | |
import sys # to get file system encoding | |
from datetime import datetime | |
#-------------- settings ------------------------- | |
# line info: see also "stim info" | |
pixelN_of_shortestLine = 200 # pixel num of shortest line (in this case 70 mm) | |
pixelN_of_lineWidth = 5 # pixel num of line width | |
# window info | |
pixelN_of_window_width = 1024 # pixel num of window resolution (horizontal) | |
pixelN_of_window_height = 768 # pixel num of window resolution (vertical) | |
show_full_screen = False # True or False | |
screenN = 2 # screen number, starts from 1 (main screen), 2(sub screen).... | |
# time info | |
maxSec_for_responseWait = 5.0 # seconds to wait response | |
ITI_sec = 2.0 # seconds from 1 trial end to next stim | |
interval_sec_from_stim_to_response = 1.0 # second of blank after showing stim | |
durationSec_to_show_selected_response = 0.5 # seconds to show selected response button | |
durationSec_to_show_arrow = 1.0 # seconds to show arrow | |
frameN_to_show_line = 3 # frame number to show line stim (16.7ms/frame@ 60 Hz monitor) | |
blankSec_from_arrow_to_lineStim = 0.05 # sec of blank from arrow disappear to show line stim | |
# stim info: list of stim patterns | |
scores_original = {'rt_is_longer':1, 'lt_is_longer':-1, 'same_length':0} | |
answer_which_of_lt_rt_line_is_longer = True # False-answer the position of vertical line | |
scores = {} | |
scores['rt'] = scores_original['rt_is_longer'] | |
scores['lt'] = scores_original['lt_is_longer'] | |
scores['mid'] = scores_original['same_length'] | |
msgTask = u"線分のどちらが長いか\n答えてください" | |
if not answer_which_of_lt_rt_line_is_longer: | |
scores['rt'] = scores_original['rt_is_longer']*-1 | |
scores['lt'] = scores_original['lt_is_longer']*-1 | |
scores['mid'] = scores_original['same_length']*-1 | |
msgTask = u"真ん中の縦線が左右どちらに寄っているか\n答えてください" | |
# stim patterns | |
list_dictStimConditions = [ | |
{'name':'line1', 'rt':75, 'lt':75, 'trialN':2}, | |
{'name':'line2_lt_elongated', 'rt':70, 'lt':75, 'trialN':1}, | |
{'name':'line3_lt_elongated', 'rt':75, 'lt':80, 'trialN':1}, | |
{'name':'line4_rt_elongated', 'rt':75, 'lt':70, 'trialN':1}, | |
{'name':'line5_rt_elongated', 'rt':80, 'lt':75, 'trialN':1}, | |
] | |
# stim info-continued | |
len_vertical_line = 10 # mm of length of vertical line | |
color_vertical_line = [1,-1,-1] # color of vertical line. default:red | |
# button info | |
# lt/rt button | |
posLtBtn = (100, -250) # lt button position (pixel). default (-300, -200) | |
posRtBtn = (400, -250) # rt button position (pixel). default (300, -200) | |
sizeBtnLtRt = (150, 100) # lt/rt button size (pixel). default (300, 200) | |
# middle button | |
posMidBtn = (250, -250) # mid button position (pixel). default (0, -200) | |
sizeMidBtn = (100, 100) # mid button size (pixel). default (200, 200) | |
#-------------- functions --------------------- | |
def getBtnBorder(posBtn, sizeBtn): | |
''' | |
get border of lt, rt, up, down of button | |
''' | |
btnBorder = { | |
'lt': posBtn[0]-0.5*sizeBtn[0], | |
'rt': posBtn[0]+0.5*sizeBtn[0], | |
'up': posBtn[1]+0.5*sizeBtn[1], | |
'down':posBtn[1]-0.5*sizeBtn[1] | |
} | |
return btnBorder | |
def isInsideBtn(x,y,border): | |
''' | |
judge mouse pointer is inside buttton area or not | |
''' | |
isInside = \ | |
(border['lt'] < x) and \ | |
(x < border['rt']) and \ | |
(border['down'] < y) and \ | |
(y < border['up']) | |
return isInside | |
def getStimSide(lt, rt, answer_which_of_lt_rt_line_is_longer): | |
''' | |
get stim side (lt, rt, mid) | |
params: | |
answer_which_of_lt_rt_line_is_longer:bool | |
return: | |
'lt','rt' or 'mid' | |
''' | |
import numpy as np | |
strOut = "" | |
if answer_which_of_lt_rt_line_is_longer: | |
if lt > rt: | |
strOut = "lt" | |
elif lt < rt: | |
strOut = "rt" | |
else: | |
strOut = "mid" | |
else: | |
if lt > rt: | |
strOut = "rt" | |
elif lt < rt: | |
strOut = "lt" | |
else: | |
strOut = "mid" | |
return strOut | |
def lineupStim(list_dictStimConditions, minPixelLtOrRt, randomise=True): | |
''' | |
line up each stim according to stim conditions | |
''' | |
import numpy as np | |
# get minimum length => minLen | |
minLen = _getMinLen(list_dictStimConditions) | |
baseMinLen = 1.0/float(minLen) | |
# lineup each stimuli: convert length -> pixels | |
listStim = list() | |
for idx, item in enumerate(list_dictStimConditions): | |
trialNum = item['trialN'] | |
pixLt = np.round(item['lt']*baseMinLen * minPixelLtOrRt) | |
pixRt = np.round(item['rt']*baseMinLen * minPixelLtOrRt) | |
# dictStim = {'name':item['name'],'lt_pix':pixLt, 'rt_pix':pixRt, 'score':item['score']} | |
dictStim = {'name':item['name'],'lt_pix':pixLt, 'rt_pix':pixRt} | |
for i in np.arange(trialNum): | |
listStim.append(dictStim) | |
if randomise: | |
listStim = np.random.permutation(listStim) | |
return listStim | |
def calcPixelNforLineLength(lenLine, list_dictStimConditions, minPixelLtOrRt): | |
''' | |
calculate pixel num for vertical line | |
''' | |
import numpy as np | |
minLen = _getMinLen(list_dictStimConditions) | |
return np.round(lenLine/float(minLen)*minPixelLtOrRt) | |
def _getMinLen(list_dictStimConditions): | |
''' | |
get minimum length in conditions | |
''' | |
for idx, item in enumerate(list_dictStimConditions): | |
minLenInItem = min([item['rt'], item['lt']]) | |
if idx==0: | |
minLen = minLenInItem | |
else: | |
minLen = min([minLen, minLenInItem]) | |
return minLen | |
# ---------- initialise stimuli --------------------- | |
# Setup the Window | |
win = visual.Window( | |
size=(pixelN_of_window_width, pixelN_of_window_height), | |
fullscr=show_full_screen, | |
screen=screenN, | |
allowGUI=False, allowStencil=False, units='pix', | |
monitor='testMonitor', color=[0,0,0], colorSpace='rgb', | |
blendMode='avg', useFBO=True) | |
frameRate = win.getActualFrameRate | |
# Initialize components for Routine "trial" | |
# cross line | |
pixelN_of_vertical_line = calcPixelNforLineLength(len_vertical_line, | |
list_dictStimConditions, pixelN_of_shortestLine) # pixelN of vertical line | |
colorLine = [-1,-1,-1] # black | |
horz_line = visual.Line( | |
win=win, name='horz_line',units='pix', | |
start=(-200, 0), end=(200, 0), | |
ori=0, pos=(0, 0), | |
lineWidth=pixelN_of_lineWidth, lineColor=colorLine, lineColorSpace='rgb', | |
opacity=1, depth=0.0, interpolate=True) | |
vert_line = visual.Line( | |
win=win, name='vert_line', | |
start=(0, -pixelN_of_vertical_line), | |
end=(0, pixelN_of_vertical_line), | |
ori=0, pos=(0, 0), units='pix', | |
lineWidth=pixelN_of_lineWidth, lineColor=color_vertical_line, lineColorSpace='rgb', | |
opacity=1, depth=-1.0, interpolate=True) | |
# arrow: refer to goo.gl/SJAoog | |
widthArrow = 0.02 | |
arrowVert = [(-0.4,widthArrow),(-0.4,-widthArrow),(-.2,-widthArrow),(-.2,-0.1),(0,0),(-.2,0.1),(-.2,widthArrow)] | |
arrowVert = [(n*1000,m*1000) for (n,m) in arrowVert] | |
arrow = visual.ShapeStim(win, vertices=arrowVert, fillColor='black', size=.5, lineColor='black') | |
arrow.ori = 90.0 | |
# button:Lt | |
imageLtBtn = visual.ImageStim( | |
win=win, name='imageRest',units='pix', | |
image=u'btn_lt.png', mask=None, | |
ori=0, pos=posLtBtn, size=sizeBtnLtRt, | |
color=[1,1,1], colorSpace='rgb', opacity=1, | |
flipHoriz=False, flipVert=False, | |
texRes=128, interpolate=True, depth=0.0) | |
btnBorderLt = getBtnBorder(posLtBtn, sizeBtnLtRt) | |
# button:Rt | |
imageRtBtn = visual.ImageStim( | |
win=win, name='imageRest',units='pix', | |
image=u'btn_rt.png', mask=None, | |
ori=0, pos=posRtBtn, size=sizeBtnLtRt, | |
color=[1,1,1], colorSpace='rgb', opacity=1, | |
flipHoriz=False, flipVert=False, | |
texRes=128, interpolate=True, depth=0.0) | |
btnBorderRt = getBtnBorder(posRtBtn, sizeBtnLtRt) | |
# button: mid | |
imageMidBtn = visual.ImageStim( | |
win=win, name='imageMid',units='pix', | |
image=u'btn_mid.png', mask=None, | |
ori=0, pos=posMidBtn, size=sizeMidBtn, | |
color=[1,1,1], colorSpace='rgb', opacity=1, | |
flipHoriz=False, flipVert=False, | |
texRes=128, interpolate=True, depth=0.0) | |
btnBorderMid = getBtnBorder(posMidBtn, sizeMidBtn) | |
# text | |
textMsg = visual.TextStim(win=win, name='text', | |
text=u'temporary text', | |
font=u'Meiryo', units='pix', | |
pos=(0, 0), height=50, wrapWidth=None, ori=0, | |
color=u'black', colorSpace='rgb', opacity=1, | |
depth=0.0) | |
def showMessage(msg, t_dur): | |
''' | |
show message | |
''' | |
textMsg.text = msg | |
textMsg.setAutoDraw(True) | |
win.flip() | |
core.wait(t_dur) | |
textMsg.setAutoDraw(False) | |
win.flip() | |
# mouse | |
mouse = event.Mouse(win=win) | |
x, y = [None, None] | |
# ------ line up stimulus conditions ------- | |
list_dictStim = lineupStim(list_dictStimConditions, pixelN_of_shortestLine, randomise=True) | |
# ------Prepare to start Routine "trial"------- | |
trialClock = core.Clock() | |
# create file to save results | |
fnameSave = "result{0}.csv".format(datetime.now().strftime("%y%m%d%H%M")) | |
with open(fnameSave, "w") as f: | |
f.write("no,stim side,lt length,rt length,response side,score\n") | |
# -------Start Routine "trial"------- | |
frameRate = win.getActualFrameRate() | |
msgInit = u"テストを開始します\n画面の中心を注視してください\n\nrefresh rate:\n{0:0.1f} Hz".format(frameRate) | |
showMessage(msgTask, 3.0) | |
showMessage(msgInit, 2.0) | |
# ----settings ---------- | |
trialN = len(list_dictStim) | |
# -------------------- | |
# for idxTrial in np.arange(trialN): | |
for idxTrial, dictStim in enumerate(list_dictStim): | |
print(dictStim['name']) | |
print(dictStim['lt_pix']) | |
print(dictStim['rt_pix']) | |
# ------- start Routine "arrow" -------------- | |
continueRoutine = True | |
arrow.setAutoDraw(True) | |
win.flip() | |
t=0 | |
trialClock.reset() | |
while continueRoutine: | |
t=trialClock.getTime() | |
if t > durationSec_to_show_arrow: | |
continueRoutine = False | |
arrow.setAutoDraw(False) | |
win.flip() | |
core.wait(blankSec_from_arrow_to_lineStim) # sec | |
# ------- start Routine "flash line"------------ | |
frameN = -1 | |
continueRoutine = True | |
horz_line.status = NOT_STARTED | |
horz_line.start = (-1*dictStim['lt_pix'], 0) | |
horz_line.end = (dictStim['rt_pix'], 0) | |
# stimSide = getLongerSide(horz_line.start, horz_line.end) | |
stimSide = getStimSide(dictStim['lt_pix'], dictStim['rt_pix'], | |
answer_which_of_lt_rt_line_is_longer) | |
while continueRoutine: | |
# get current time | |
frameN = frameN + 1 | |
# horz_line, vert_line updates | |
if frameN >= 0 and horz_line.status == NOT_STARTED: | |
# keep track of start time/frame for later | |
horz_line.frameNStart = frameN # exact frame index | |
horz_line.setAutoDraw(True) | |
vert_line.setAutoDraw(True) | |
horz_line.status = STARTED | |
if horz_line.status == STARTED and frameN >= frameN_to_show_line: | |
horz_line.setAutoDraw(False) | |
vert_line.setAutoDraw(False) | |
continueRoutine = False | |
# refresh the screen | |
win.flip() | |
# ------ Start Routine "wait button response" | |
core.wait(interval_sec_from_stim_to_response) | |
# initial settings | |
trialClock.reset() | |
continueRoutine = True | |
t = 0 | |
event.mouseButtons = [0, 0, 0] # mouse button status | |
wasLtMouseBtnDown = False | |
# show images | |
imageLtBtn.setAutoDraw(True) | |
imageRtBtn.setAutoDraw(True) | |
imageMidBtn.setAutoDraw(True) | |
responseSide = "" # "lt", "rt" or "mid" | |
while continueRoutine: | |
t = trialClock.getTime() | |
x, y = mouse.getPos() | |
# judge pointer position | |
isInsideLtBtn = isInsideBtn(x, y, btnBorderLt) | |
isInsideRtBtn = isInsideBtn(x, y, btnBorderRt) | |
isInsideMidBtn = isInsideBtn(x, y, btnBorderMid) | |
# judge mouse button status: pressed, released | |
isLtMouseBtnPressed,_,_ = mouse.getPressed() | |
isLtMouseBtnReleased = (wasLtMouseBtnDown) & (not isLtMouseBtnPressed) | |
wasLtMouseBtnDown = isLtMouseBtnPressed | |
# determine lt button image according to button status | |
if isInsideLtBtn and not isLtMouseBtnPressed: | |
imageLtBtn.image = 'btn_lt_mouseover.png' | |
elif isInsideLtBtn and isLtMouseBtnPressed: | |
# push | |
imageLtBtn.image= 'btn_lt_push.png' | |
responseSide = "lt" | |
# erase other buttons | |
imageRtBtn.setAutoDraw(False) | |
imageMidBtn.setAutoDraw(False) | |
win.flip() | |
core.wait(durationSec_to_show_selected_response) | |
continueRoutine = False | |
else: | |
imageLtBtn.image= 'btn_lt.png' | |
# determine rt button image according to button status | |
if isInsideRtBtn and not isLtMouseBtnPressed: | |
imageRtBtn.image = 'btn_rt_mouseover.png' | |
elif isInsideRtBtn and isLtMouseBtnPressed: | |
# push | |
imageRtBtn.image= 'btn_rt_push.png' | |
responseSide = "rt" | |
# erase other buttons | |
imageLtBtn.setAutoDraw(False) | |
imageMidBtn.setAutoDraw(False) | |
win.flip() | |
core.wait(durationSec_to_show_selected_response) | |
continueRoutine = False | |
else: | |
imageRtBtn.image= 'btn_rt.png' | |
# determine mid button image according to button status | |
if isInsideMidBtn and not isLtMouseBtnPressed: | |
imageMidBtn.image = 'btn_mid_mouseover.png' | |
elif isInsideMidBtn and isLtMouseBtnPressed: | |
# push | |
imageMidBtn.image= 'btn_mid_push.png' | |
responseSide = "mid" | |
# erase other buttons | |
imageLtBtn.setAutoDraw(False) | |
imageRtBtn.setAutoDraw(False) | |
win.flip() | |
core.wait(durationSec_to_show_selected_response) | |
continueRoutine = False | |
else: | |
imageMidBtn.image= 'btn_mid.png' | |
if t >= maxSec_for_responseWait: | |
continueRoutine = False | |
# check for quit (the Esc key) | |
if event.getKeys(keyList=["escape"]): | |
core.quit() | |
# refresh the screen | |
win.flip() | |
# delete images | |
imageLtBtn.setAutoDraw(False) | |
imageRtBtn.setAutoDraw(False) | |
imageMidBtn.setAutoDraw(False) | |
win.flip() | |
# calculate score | |
try: | |
responseScore = scores[responseSide] | |
stimScore = scores[stimSide] | |
# score = responseScore - dictStim['score'] | |
score = responseScore - stimScore | |
except KeyError: | |
score = '' | |
# save stim info and response | |
with open(fnameSave, "a") as f: | |
strResults = ",".join(map(str,[ | |
idxTrial+1, # trial no. | |
stimSide, # stim side: lt, rt or mid | |
np.abs(horz_line.start[0]), # left segment length | |
horz_line.end[0], # right segment length | |
responseSide, | |
score # response side: lt, rt or mid | |
]))+"\n" | |
f.write(strResults) | |
# Inter trial interval | |
if idxTrial == trialN-1: | |
# shoter interval for last trial | |
ITI_sec = 0.5 | |
core.wait(ITI_sec) | |
# ----------- Start Routine "Finish" ---------------- | |
showMessage(u"テストは終了です\nお疲れさまでした", 2.0) | |
#thisExp.abort() # or data files will save again on exit | |
win.close() | |
core.quit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Psychopyで線分二等分課題をするスクリプト
Fierro et al. (2000) PMID: 10841369を参照しました
使い方
タスクの内容
結果一覧用CSVの表示について
設定 25-81行目まで
線の長さ関係(L27-28)
ウィンドウ関係 (L31-34)
時間関係 (L37-43)
刺激関係 (L46-47)
刺激パターン (L61-67): なんとなく察してください.
項目の説明: 普通はtrialNを変更するだけと思います
刺激の縦線の関係 (L70-71)
ボタン関係 (L75-80)
位置はモニタの中心が(0, 0)で、そこを基準にした座標になる
左右ボタン
中ボタン