Skip to content

Instantly share code, notes, and snippets.

@cosacog
Last active December 10, 2018 15:25
Show Gist options
  • Save cosacog/93d5012d35398e135e714cc7552e1331 to your computer and use it in GitHub Desktop.
Save cosacog/93d5012d35398e135e714cc7552e1331 to your computer and use it in GitHub Desktop.
Psychopyでflash + TMSトリガーを出すスクリプト。説明は下の方にあります。
#!/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()
@cosacog
Copy link
Author

cosacog commented Dec 10, 2018

Psychopyで半視野刺激+TMSパルスを出す

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment