Last active
December 10, 2018 15:25
-
-
Save cosacog/93d5012d35398e135e714cc7552e1331 to your computer and use it in GitHub Desktop.
Psychopyでflash + TMSトリガーを出すスクリプト。説明は下の方にあります。
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.83.04), 2017_06_14_1638 | |
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 locale_setup, visual, core, data, event, logging, sound, gui, parallel | |
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 | |
import sys # to get file system encoding | |
#%% settings | |
# trial counts | |
trialN_oneCond = 2 # actual trial count = trialN_oneCond*2(left/right)*len(list_soa) | |
trialN_4rest = 40 # take some rest every "trialN_4rest" | |
randomise_soa = True # randomise SOA | |
randomise_lr = True # randomise Lt/Rt | |
first_lr = 'RT' # or 'RT'. valid when randomise_lr is "False". | |
# port | |
PORT_ADDRESS_HEX = '0xCFF8' # parallel port address | |
portListTms = [6, 5] # list of 1-8, which must be unique numbers (not e.g. [3,3,4]). | |
portTms = np.sum([pow(2, x-1) for x in portListTms]) | |
portListFlash = [8] # list of 1-8, which must be unique numbers (not e.g. [3,3,4]). | |
portFlash = np.sum([pow(2, x-1) for x in portListFlash]) | |
# time, duration, soa, isi | |
list_soa = [0, 0.04, 0.05, 0.06, 0.07, 0.08] # 0 for control | |
dur_flash = 0.2 # sec: actual duration = ceiling(dur_flash*frame_rate) | |
isi_range = [5.0, 5.0] # isi range. sec | |
dur_offset = 1.0 # sec. duration after offset. Requires less than isi_range | |
t_correct = 0.0245 # sec to correct TMS onset for precise soa | |
# flash size, position | |
deg_circle = 12.0 # diameter, not radius | |
deg_shift = 1.0 # deg from fixation point | |
list_info_side = [ | |
{'circle_pos':-deg_shift, 'square_pos':-deg_shift+deg_circle/4, 'flash_side':'LT', 'dur_tms':0.01}, | |
{'circle_pos':deg_shift, 'square_pos':deg_shift-deg_circle/4, 'flash_side':'RT', 'dur_tms':0.03} | |
] | |
# colors | |
colWhite = [1.0, 1.0, 1.0] | |
colBackground = [0.0, 0.0, 0.0] | |
colBlack = [-1.0, -1.0, -1.0] | |
colChar = u'black' | |
#%% prepare for experiment | |
# get condition list | |
arry_soa_ltrt = np.r_[np.array(list_soa), np.array(list_soa)] | |
arry_ltrt = np.r_[np.zeros(len(list_soa)),np.ones(len(list_soa))] # list of 0/1 | |
idxs = np.arange(len(arry_ltrt)) | |
for ii in np.arange(trialN_oneCond): | |
if randomise_soa: | |
idxs_permute = np.random.permutation(idxs) | |
else: | |
idxs_permute = idxs | |
if ii==0: | |
list_soa_ltrt = list(arry_soa_ltrt[idxs_permute]) | |
list_ltrt = list(arry_ltrt[idxs_permute]) | |
else: | |
list_soa_ltrt = list_soa_ltrt + list(arry_soa_ltrt[idxs_permute]) | |
list_ltrt = list_ltrt + list(arry_ltrt[idxs_permute]) | |
# not randomise lt/rt=extract each of lt/rt | |
''' | |
params: | |
list_ltrt | |
list_soa_ltrt | |
list_info_side | |
return: | |
list_ltrt: list of 0(lt), 1(rt) | |
list_soa_ltrt: list of soa (0.06, 0.05, .. etc) | |
''' | |
## get idxLT/idxRt | |
list_side = [item['flash_side'] for item in list_info_side] # LT/RT | |
idxLt, idxRt = [0, 1] if list_side[0].upper() == 'LT' else [1, 0] | |
## sort lt/rt | |
if not randomise_lr: | |
idxs_lt = np.where(np.array(list_ltrt)==idxLt)[0] | |
idxs_rt = np.where(np.array(list_ltrt)==idxRt)[0] | |
arry_soa_lt = np.array(list_soa_ltrt)[idxs_lt] | |
arry_soa_rt = np.array(list_soa_ltrt)[idxs_rt] | |
arry_lt = np.array(list_ltrt)[idxs_lt] | |
arry_rt = np.array(list_ltrt)[idxs_rt] | |
# lt -> rt. or rt-> lt | |
if first_lr.upper() == 'LT': | |
list_ltrt = list(arry_lt) + list(arry_rt) | |
list_soa_ltrt = list(arry_soa_lt) + list(arry_soa_rt) | |
elif first_lr.upper() == 'RT': | |
list_ltrt = list(arry_rt) + list(arry_lt) | |
list_soa_ltrt = list(arry_soa_rt) + list(arry_soa_lt) | |
else: | |
raise ValueError('"first_lr" must be "LT" or "RT".') | |
#%% main | |
# Setup the Window | |
win = visual.Window(size=[1024, 768], fullscr=True, screen=1, allowGUI=True, allowStencil=False, | |
monitor='crt_monitor', color=colBackground, colorSpace='rgb', | |
blendMode='avg', useFBO=True, | |
) | |
frameRate=win.getActualFrameRate() # frame rate | |
# Initialize components for Routine "trial" | |
trialClock = core.Clock() | |
idxSide = 0 # 0 or 1 | |
circle = visual.Polygon(win=win, name='circle_pos', | |
edges = 90, size=[deg_circle, deg_circle], | |
ori=0, pos=[list_info_side[idxSide]['circle_pos'], 0],units='deg', | |
lineWidth=0, lineColor=colWhite, lineColorSpace='rgb', | |
fillColor=colWhite, fillColorSpace='rgb', | |
opacity=1,depth=-1.0, interpolate=True) | |
square = visual.Rect(win=win, name='square_pos', | |
width=deg_circle/2.0, height=deg_circle, | |
ori=0, pos=[list_info_side[idxSide]['square_pos'], 0], | |
lineWidth=1, lineColor=colBackground, lineColorSpace='rgb', | |
fillColor=colBackground, fillColorSpace='rgb', | |
opacity=1,depth=-2.0, interpolate=True) | |
msgInit = visual.TextStim(win=win, ori=0, name='msgPre', text=u'', font=u'Arial', | |
alignHoriz ='center', | |
units='deg', pos=[0, 0], height=1.0, wrapWidth=15, | |
color=colChar, colorSpace='rgb', opacity=1, | |
depth=-3.0) | |
msgInit.setAutoDraw(True) | |
fixation = visual.Polygon(win=win, name='fixation',units='deg', | |
edges = 16, size=[0.1, 0.1], | |
ori=0, pos=[0, 0], | |
lineWidth=1, lineColor=[1,-1,-1], lineColorSpace='rgb', | |
fillColor=[1,-1,-1], fillColorSpace='rgb', | |
opacity=1,depth=-4.0, interpolate=True) | |
p_port = parallel.ParallelPort(address=PORT_ADDRESS_HEX) | |
# set up handler to look after randomisation of conditions etc | |
msgTxt = """ | |
frame rate is {0:.1f} Hz | |
Total trials are {1}. | |
Rest every {2} trials. | |
Instruction: | |
Fixate on the center red point. | |
You will see flash about every 5 secs. | |
Just keep relaxed. | |
For examiner: | |
Press 'Enter' to start. | |
""".format(frameRate, len(list_ltrt), trialN_4rest) | |
msgRest = """ | |
Instruction: | |
Rest for some time | |
For examiner: | |
Press 'Enter' to restart. | |
""" | |
msgEnd = """ | |
Instruction: | |
Measurement is finished. | |
Thank you for your attention. | |
For examiner: | |
Press 'Enter' to end. | |
""" | |
fixation.setAutoDraw(True) | |
def TakeSomeRest(msgTxt, t_wait): | |
msgInit.setText(msgTxt) | |
msgInit.setAutoDraw(True) | |
continueRoutine = True | |
while continueRoutine: | |
win.flip() | |
if event.getKeys(keyList=['return']): | |
continueRoutine = False | |
if event.getKeys(keyList=['escape']): | |
core.quit() | |
msgInit.setAutoDraw(False) | |
win.flip() | |
core.wait(t_wait) | |
#%% initial message | |
TakeSomeRest(msgTxt, 1.5) | |
#%% main loop | |
numTrials = len(list_ltrt) | |
trials = np.arange(numTrials) | |
square.setAutoDraw(True) | |
circle.fillColor = colBackground | |
circle.setAutoDraw(True) | |
win.flip() | |
for idx in trials: | |
#---- rest every some time ---- | |
if (idx % trialN_4rest == 0) and (idx>0): | |
circle.fillColor = colBackground | |
win.flip() | |
TakeSomeRest(msgRest, 3.0) | |
circle.fillColor = colBlack | |
#------Prepare to start Routine "trial"------- | |
t = 0 | |
trialClock.reset() # clock | |
circle.status = NOT_STARTED | |
isi = np.random.rand()*(np.diff(isi_range)[0])+min(isi_range) | |
soa_flashOnset = isi - dur_offset | |
frameN_flashOnset = int(soa_flashOnset*frameRate) | |
idxSide = int(list_ltrt[idx]) # 0 or 1 | |
#idxSide = 1 # fix side | |
dur_tms = list_info_side[idxSide]['dur_tms'] | |
soa_tms = list_soa_ltrt[idx] | |
print(idx) | |
print("isi:{0:0.2f} sec".format(isi)) | |
print("soa:{0} ms".format(int(soa_tms*1000))) | |
print("side:{0}".format(list_info_side[idxSide]['flash_side'])) | |
colFlash = colWhite if soa_tms > 0 else colBlack | |
#-------Start Routine "trial"------- | |
continueRoutine = True | |
continueRoutineTms = False | |
frameN = -1 | |
circle.frameNStart = np.empty | |
circleOnsetStatus = NOT_STARTED | |
tmsStatus = NOT_STARTED | |
t_textOnset = np.float('inf') | |
frameN4circleFin = np.inf | |
t_flashOnset = np.float('inf') | |
square.pos = [list_info_side[idxSide]['square_pos'], 0] | |
circle.pos = [list_info_side[idxSide]['circle_pos'], 0] | |
circle.fillColor=colBlack # or colBackground | |
#win.flip() | |
while continueRoutine: | |
# get current time | |
t = trialClock.getTime() | |
if circleOnsetStatus == STARTED: | |
t_flashOnset = t | |
circleOnsetStatus = FINISHED | |
win.callOnFlip(p_port.setData, 0) # actually no use in this processing | |
frameN += 1 # number of completed frames (so 0 is the first frame) | |
# print(frameN) | |
# flash start | |
if frameN >= frameN_flashOnset and circle.status == NOT_STARTED: | |
circle.status = STARTED | |
circle.frameNStart = frameN # exact frame index | |
circle.fillColor=colFlash | |
circleOnsetStatus = STARTED | |
win.callOnFlip(p_port.setData, portFlash) # for photosensor test | |
# flash end | |
# if circle.status == STARTED and frameN >= (circle.frameNStart + frameN_durFlash): # 1 for flash | |
if circle.status == STARTED and t >= t_flashOnset + dur_flash: | |
frameN4circleFin = frameN | |
#circle4photoSensor.setAutoDraw(False) | |
circle.fillColor = colBlack | |
circle.status = FINISHED | |
# win.callOnFlip(p_port.setData, 0)#for photosensor test | |
# tms | |
#if tmsStatus == NOT_STARTED and frameN > frameN4circleFin: | |
if tmsStatus == NOT_STARTED and circleOnsetStatus==FINISHED: | |
continueRoutineTms = True | |
while continueRoutineTms: | |
t = trialClock.getTime() | |
if t >= t_flashOnset + soa_tms + t_correct: | |
p_port.setData(portTms) | |
core.wait(dur_tms) | |
p_port.setData(0) | |
tmsStatus = FINISHED | |
continueRoutineTms = False | |
# check if all components have finished | |
#if frameN > 60: | |
if t > isi: | |
continueRoutine = False | |
# check for quit (the Esc key) | |
k = event.getKeys(keyList=['escape']) | |
if k: | |
print(k) | |
core.quit() | |
# refresh the screen | |
win.flip() | |
# End message | |
TakeSomeRest(msgRest, 0.3) | |
# End process | |
win.close() | |
core.quit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Psychopyで半視野刺激+TMSパルスを出す