01_localiser_task.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. subj_id_str = input('Input participant id (integer): ')
  2. if subj_id_str.isdigit():
  3. subj_id = int(subj_id_str)
  4. else:
  5. subj_id = subj_id_str
  6. # %% General Setup
  7. from psychopy import parallel, visual, core, monitors, event
  8. import os
  9. import sys
  10. import datetime
  11. import numpy as np
  12. from matplotlib import pyplot as plt
  13. import pandas as pd
  14. from string import ascii_lowercase
  15. from random import choices
  16. from stim import *
  17. from randphase import draw, phase
  18. # %% Participant Info
  19. info_participants = pd.read_csv('participants.csv')
  20. info_counterbalance = pd.read_csv('counterbalance.csv')
  21. participant_info = {
  22. 'subj_id': subj_id,
  23. 'session_nr': int(info_participants.session_nr[info_participants.subj_id==subj_id_str]),
  24. 'stim_grp': int(info_counterbalance.stim_grp[info_counterbalance.subj_id==subj_id_str]), # 1/2
  25. 'resp_grp': int(info_counterbalance.resp_grp[info_counterbalance.subj_id==subj_id_str]), # 1/2
  26. 'sex': str(info_participants.sex[info_participants.subj_id==subj_id_str].tolist()[0]), # m/f/nb
  27. 'age': int(info_participants.age[info_participants.subj_id==subj_id_str])
  28. }
  29. # %% Check participant info makes sense
  30. def is_int(s):
  31. try:
  32. int(s)
  33. return True
  34. except ValueError:
  35. return False
  36. pinfo_check = {
  37. 'subj_id_is_int': isinstance(participant_info['subj_id'], int) | (participant_info['subj_id']=='0_test'),
  38. 'stim_grp_1_or_2': participant_info['stim_grp'] in [1, 2],
  39. 'resp_grp_1_or_2': participant_info['resp_grp'] in [1, 2],
  40. 'sex_m_f_or_nb': participant_info['sex'] in ['m', 'f', 'nb'],
  41. 'age_is_int': isinstance(participant_info['age'], int)
  42. }
  43. if not all(value for value in pinfo_check.values()):
  44. print('Participant info error!')
  45. print(pinfo_check)
  46. exit()
  47. for f in participant_info:
  48. print(' {0}: {1}'.format(f, participant_info[f]))
  49. # %% Output setup
  50. subj_file_name = '{0}_{1}_{2}.csv'.format(participant_info['subj_id'], datetime.datetime.now().strftime('%d-%m-%y'), participant_info['session_nr'])
  51. subj_data_path = os.path.join('data', 'localiser', subj_file_name)
  52. if os.path.isfile(subj_data_path):
  53. sys.exit('File {} already exists!'.format(subj_data_path))
  54. # %% Set up psychopy and eeg
  55. # set up triggers
  56. # (send trigger with `port.setData(x)`, where `x` is a number from 0 to 255)
  57. port = parallel.ParallelPort()
  58. # class fake_parallelport:
  59. # def setData(self, x):
  60. # print('Sent {} to fake parallel port'.format(int(x)))
  61. # port = fake_parallelport()
  62. # port.setData(99)
  63. # set up ViewPix monitor
  64. mon_Hz = 60 # refresh rate
  65. mon_framelen = 1/mon_Hz # length of one frame in seconds
  66. mon = monitors.Monitor(name='ViewPix', notes='{0}Hz'.format(mon_Hz))
  67. mon.setWidth(52)
  68. mon.setDistance(48)
  69. mon.setSizePix((1920, 1080))
  70. # mon.setSizePix((750, 500))
  71. mon.saveMon()
  72. # set up the Psychopy window
  73. win = visual.Window(fullscr=True,
  74. size=mon.getSizePix(),
  75. screen=1,
  76. monitor=mon,
  77. units='deg',
  78. color=[0, 0, 0])
  79. # hide the mouse
  80. event.Mouse(visible=False)
  81. win.refreshThreshold = mon_framelen + 0.001 # tolerance of 1 ms (any refresh that takes more than the tolerance longer than expected = dropped frame)
  82. #win.recordFrameIntervals = True # record actual frame lengths
  83. # set up which button is affirmative, and which negative
  84. if participant_info['resp_grp'] == 1:
  85. resp_setup = {'yes': 'rctrl', 'no': 'lctrl'}
  86. elif participant_info['resp_grp'] == 2:
  87. resp_setup = {'yes': 'lctrl', 'no': 'rctrl'}
  88. resp_setup_names = {'lctrl': 'Left Control', 'rctrl': 'Right Control'}
  89. init_stim = visual.TextStim(
  90. win,
  91. units='norm',
  92. height=0.07,
  93. wrapWidth=1.75,
  94. text='Initialising...'
  95. )
  96. init_stim.draw()
  97. win.flip()
  98. # %% Trial Setup
  99. # get proportional difference between descenders' heights and other characters
  100. # (psychopy seems to ignore descenders in calculating text height)
  101. desc = ['g', 'j', 'p', 'q', 'y']
  102. non_desc = [l for l in ascii_lowercase if l not in desc]
  103. desc_im = draw.text(''.join(ascii_lowercase), font='cour.ttf', size=1000)
  104. non_desc_im = draw.text(''.join(non_desc), font='cour.ttf', size=1000)
  105. font_desc_prop_diff = desc_im.size[0] / non_desc_im.size[0]
  106. # options for creating the text images
  107. text_va_height = 1.5 # text height in visual angle
  108. text_va_rad = (text_va_height) / (180/np.pi)
  109. text_height_cm = 2 * mon.getDistance() * np.tan(text_va_rad / 2)
  110. font_size = text_height_cm * 28.3464567 * font_desc_prop_diff
  111. font_size_ceil = np.uint8(np.ceil(font_size))
  112. font_disp_prop = font_size / font_size_ceil # the proportion of original height that the image should be presented at for the desired visual angle
  113. text_dpi = 72
  114. # import words for trials
  115. trls_words = pd.read_csv(os.path.join('stim', 'localiser_stim.csv')).string.tolist()
  116. practice_trls_words = pd.read_csv(os.path.join('stim', 'practice_localiser_stim.csv')).string.tolist()
  117. # create a dataframe of all trials
  118. trls = pd.DataFrame(data={'string': trls_words + trls_words + trls_words,
  119. 'condition': ['word']*len(trls_words) + ['bacs']*len(trls_words) + ['noise']*len(trls_words),
  120. 'item_nr': np.concatenate([np.arange(1, len(trls_words)+1),
  121. np.arange(1, len(trls_words)+1),
  122. np.arange(1, len(trls_words)+1)])})
  123. # get the file names for each trial
  124. trls['file'] = ['{0}{1:03d}.png'.format(trls.condition[i], trls.item_nr[i]) for i in range(len(trls))]
  125. trls['file_green'] = ['green_{0}'.format(trls.file[i]) for i in range(len(trls))]
  126. # store what the correct response for each trial will be
  127. trls['corr_ans'] = [resp_setup['yes'] if trls.condition[trl_nr]=='word' else resp_setup['no'] for trl_nr in range(len(trls))]
  128. # create and save each trial's image (noise images are unique to each session)
  129. for i in set(trls.item_nr):
  130. print('Generating stimuli {0}/{1}'.format(i, len(set(trls.item_nr))))
  131. trls_i = trls[trls.item_nr==i]
  132. w_idx = (trls.item_nr==i) & (trls.condition=='word')
  133. b_idx = (trls.item_nr==i) & (trls.condition=='bacs')
  134. n_idx = (trls.item_nr==i) & (trls.condition=='noise')
  135. # save this item's word image
  136. w_i = draw.text(trls.string[w_idx].item(), font='cour.ttf', size=font_size_ceil, crop_x='font', crop_y='font')
  137. w_i.save(os.path.join('stim', 'img', trls.file[w_idx].item()))
  138. # save this item's word image in green
  139. w_gr_i = draw.text(trls.string[w_idx].item(), font='cour.ttf', size=font_size_ceil, crop_x='font', crop_y='font', colour=(0,255,0))
  140. w_gr_i.save(os.path.join('stim', 'img', trls.file_green[w_idx].item()))
  141. # save this item's BACS string image
  142. b_i = draw.text(trls.string[b_idx].item(), font='BACS2serif.otf', size=font_size_ceil, crop_x='font', crop_y='font')
  143. b_i.save(os.path.join('stim', 'img', trls.file[b_idx].item()))
  144. # save this item's BACS string image in green
  145. b_gr_i = draw.text(trls.string[b_idx].item(), font='BACS2serif.otf', size=font_size_ceil, crop_x='font', crop_y='font', colour=(0,255,0))
  146. b_gr_i.save(os.path.join('stim', 'img', trls.file_green[b_idx].item()))
  147. # get phase-randomised word image
  148. n_i = phase.randomise(w_i, noise='permute', contrast_adj=0.5)
  149. n_i.save(os.path.join('stim', 'img', trls.file[n_idx].item()))
  150. # get the phase-randomised image in green
  151. n_gr_i = greenify_noise(n_i)
  152. n_gr_i.save(os.path.join('stim', 'img', trls.file_green[n_idx].item()))
  153. # generate the practice stimuli
  154. practice_trls = pd.DataFrame(data={'string': practice_trls_words + practice_trls_words + practice_trls_words,
  155. 'condition': ['word']*len(practice_trls_words) + ['bacs']*len(practice_trls_words) + ['noise']*len(practice_trls_words),
  156. 'item_nr': np.concatenate([np.arange(1, len(practice_trls_words)+1),
  157. np.arange(1, len(practice_trls_words)+1),
  158. np.arange(1, len(practice_trls_words)+1)])})
  159. # get the file names for each practice trial
  160. practice_trls['file'] = ['prac_{0}{1:03d}.png'.format(practice_trls.condition[i], practice_trls.item_nr[i]) for i in range(len(practice_trls))]
  161. practice_trls['file_green'] = ['green_{0}'.format(practice_trls.file[i]) for i in range(len(practice_trls))]
  162. # store what the correct response for each trial will be
  163. practice_trls['corr_ans'] = [resp_setup['yes'] if practice_trls.condition[trl_nr]=='word' else resp_setup['no'] for trl_nr in range(len(practice_trls))]
  164. # create and save each practice trial's image
  165. for i in set(practice_trls.item_nr):
  166. print('Generating practice stimuli {0}/{1}'.format(i, len(set(practice_trls.item_nr))))
  167. trls_i = practice_trls[practice_trls.item_nr==i]
  168. w_idx = (practice_trls.item_nr==i) & (practice_trls.condition=='word')
  169. b_idx = (practice_trls.item_nr==i) & (practice_trls.condition=='bacs')
  170. n_idx = (practice_trls.item_nr==i) & (practice_trls.condition=='noise')
  171. # save this item's word image
  172. w_i = draw.text(practice_trls.string[w_idx].item(), font='cour.ttf', size=font_size_ceil, crop_x='font', crop_y='font')
  173. w_i.save(os.path.join('stim', 'img', practice_trls.file[w_idx].item()), dpi=(text_dpi, text_dpi))
  174. # save this item's word image in green
  175. w_gr_i = draw.text(practice_trls.string[w_idx].item(), font='cour.ttf', size=font_size_ceil, crop_x='font', crop_y='font', colour=(0,255,0))
  176. w_gr_i.save(os.path.join('stim', 'img', practice_trls.file_green[w_idx].item()))
  177. # save this item's BACS string image
  178. b_i = draw.text(practice_trls.string[b_idx].item(), font='BACS2serif.otf', size=font_size_ceil, crop_x='font', crop_y='font')
  179. b_i.save(os.path.join('stim', 'img', practice_trls.file[b_idx].item()))
  180. # save this item's BACS string image in green
  181. b_gr_i = draw.text(practice_trls.string[b_idx].item(), font='BACS2serif.otf', size=font_size_ceil, crop_x='font', crop_y='font', colour=(0,255,0))
  182. b_gr_i.save(os.path.join('stim', 'img', practice_trls.file_green[b_idx].item()))
  183. # get phase-randomised word image
  184. n_i = phase.randomise(w_i, noise='permute', contrast_adj=0.5)
  185. n_i.save(os.path.join('stim', 'img', practice_trls.file[n_idx].item()))
  186. # get the phase-randomised image in green
  187. n_gr_i = greenify_noise(n_i)
  188. n_gr_i.save(os.path.join('stim', 'img', practice_trls.file_green[n_idx].item()))
  189. # reorder trials randomly, and ensure no more than five of the same type follow each other
  190. shuffle_iter = 0
  191. max_consec_trltypes = 0
  192. while shuffle_iter==0 or max_consec_trltypes>5:
  193. shuffle_iter+=1
  194. # shuffle randomly
  195. trls = trls.sample(frac=1).reset_index(drop=True)
  196. # count maximum number of consecutive trial types
  197. max_consec_trltypes = max(get_consec_lens(trls.condition))
  198. print('Reshuffled trials (iter {}), with a maximum of {} consecutive trial types'.format(shuffle_iter, max_consec_trltypes))
  199. print('Successfully reshuffled trials\n')
  200. # %% Instructions
  201. def instructions(reminder = False, practice = False, wait_time = 3):
  202. print('DISPLAYING INSTRUCTIONS')
  203. if practice:
  204. practice_txt = u'For the practice trials, you will be given feedback on your accuracy for each trial.'
  205. else:
  206. practice_txt = u'Unlike the practice trials, you will not be given feedback on your accuracy for each trial.'
  207. instr_txt = u'In each trial, the following things will happen:\n\n1) You will be shown a picture of a real word, a fake font word, or a noise image.\n2) The image will turn green.\n3) When the image turns green:\n Press the {0} key if the image is of a real word.\n OR\n Press the {1} key if it is not of a real word.\n\n\n Once the image changes colour, try to respond as quickly and accurately as possible.\n\n {2}\n\n\nWhen you have read these instructions, press the space key to begin...'.format(resp_setup_names[resp_setup['yes']], resp_setup_names[resp_setup['no']], practice_txt)
  208. if reminder:
  209. instr_txt = u'{0}{1}'.format('Here\'s a reminder of the task instructions.\n\n\n', instr_txt)
  210. instr_screen = visual.TextStim(
  211. win,
  212. units='norm',
  213. height=0.07,
  214. wrapWidth=1.75,
  215. text=instr_txt,
  216. anchorHoriz='center',
  217. alignText='left'
  218. )
  219. instr_screen.draw()
  220. win.flip()
  221. core.wait(wait_time) # ensure that instructions have been read
  222. event.waitKeys(keyList='space')
  223. print('SPACE PRESSED; CONTINUING')
  224. # %% Trials setup
  225. # function that will be called for each trial
  226. def trial(image_path=os.path.join('stim', 'img', 'word001.png'), green_image_path=os.path.join('stim', 'img', 'green_word001.png'), stim_trigger=101):
  227. # a fixation point in style of Thaler et al.
  228. thaler_fix_stim = thaler_list_fix(win=win)
  229. # a blank stimulus
  230. blank_stim = visual.TextStim(win=win, text='')
  231. # the image to display
  232. img_stim = visual.ImageStim(
  233. win = win,
  234. image = image_path,
  235. pos = (0.0, 0.0)
  236. )
  237. # the image in green
  238. img_stim_green = visual.ImageStim(
  239. win = win,
  240. image = green_image_path,
  241. pos = (0.0, 0.0)
  242. )
  243. # fixation 1
  244. display_list_ms(win, thaler_fix_stim, 300) # note that the list version of the function is required, as thaler_list_fix returns a list object
  245. fix_jitt = display_jitter_ms(win, blank_stim, 300, 1300)
  246. # image
  247. display_x_ms(win, img_stim, 500, port = port, trigg = stim_trigger)
  248. # image in green
  249. img_stim_green.draw()
  250. win.flip()
  251. stim_rt_clock = core.MonotonicClock()
  252. # response
  253. stim_resp = event.waitKeys(keyList=['lctrl', 'rctrl'], timeStamped = stim_rt_clock)
  254. port.setData(0)
  255. trl_dat = {
  256. 'fix_jitt': fix_jitt,
  257. 'stim_resp': stim_resp
  258. }
  259. return(trl_dat)
  260. def block_break():
  261. # read the subject's data
  262. subj_data_so_far = pd.read_csv(subj_data_path)
  263. accuracies = subj_data_so_far['acc'].to_numpy()
  264. rts = subj_data_so_far['rt'].to_numpy()
  265. blocks = subj_data_so_far['block_nr'].to_numpy()
  266. block_summ_txt = ''
  267. for block_nr in np.unique(blocks):
  268. block_summ_txt = '{0}\n Block {1}: {2}%, {3} ms'.format(
  269. block_summ_txt,
  270. block_nr,
  271. np.round(np.mean(accuracies[blocks==block_nr] * 100), 1),
  272. np.round(np.median(rts[blocks==block_nr])), 1)
  273. # give subject summary of their data
  274. break_msg = visual.TextStim(
  275. win,
  276. units='norm',
  277. height=0.07,
  278. wrapWidth=1.75,
  279. text=u'Block {0} complete. Time for a break!\n\n\nYour overall accuracy so far is: {1}%\nYour median response time is {2} ms\n\nHere\'s a per-block summary of your experiment so far:{3}'.format(
  280. np.max(blocks),
  281. np.round(np.mean(accuracies * 100), 1),
  282. np.round(np.median(rts), 1),
  283. block_summ_txt)
  284. )
  285. break_msg.draw()
  286. win.flip()
  287. print('BREAK BEGUN, PRESS \'Y\' KEY TO CONTINUE THE EXPERIMENT')
  288. event.waitKeys(keyList='y')
  289. print('BREAK ENDED')
  290. # %% Practice Trials
  291. practice = True
  292. this_is_a_repeat_practice_block = False
  293. while practice:
  294. instructions(reminder = this_is_a_repeat_practice_block, practice = True)
  295. practice_accuracies = []
  296. practice_rts = []
  297. practice_trls = practice_trls.sample(frac=1).reset_index(drop=True)
  298. for trl_i in range(len(practice_trls.index)):
  299. condition = practice_trls.condition[trl_i]
  300. corr_ans = practice_trls.corr_ans[trl_i]
  301. trl_dat = trial(
  302. image_path = os.path.join('stim', 'img', practice_trls.file[trl_i]),
  303. green_image_path = os.path.join('stim', 'img', practice_trls.file_green[trl_i]),
  304. stim_trigger = 25
  305. )
  306. if corr_ans == trl_dat['stim_resp'][0][0]:
  307. trl_corr = 1
  308. else:
  309. trl_corr = 0
  310. practice_accuracies.append(trl_corr)
  311. practice_rts.append(round(trl_dat['stim_resp'][0][1] * 1000, 1))
  312. print('PRACTICE TRL', '{}/{}'.format(trl_i+1, len(practice_trls.index)), ', COND', condition, ', CORR_ANS', corr_ans, ', RESP', trl_dat['stim_resp'][0][0], ', ACC', trl_corr, ', RT', round(trl_dat['stim_resp'][0][1] * 1000, 1), ', IMAGE', practice_trls['file'][trl_i], ', IMAGE_GREEN', practice_trls['file_green'][trl_i], ', STRING', practice_trls['string'][trl_i], ', FIX_JITT', round(trl_dat['fix_jitt'][1], 1))
  313. if trl_corr == 1:
  314. feedback_txt = 'CORRECT!'
  315. feedback_col = [-1, 1, -1]
  316. else:
  317. feedback_txt = 'INCORRECT!'
  318. feedback_col = [1, -1, -1]
  319. feedback_msg = visual.TextStim(
  320. win = win,
  321. text = feedback_txt,
  322. height = 1.5,
  323. units = 'deg',
  324. color = feedback_col,
  325. colorSpace = 'rgb',
  326. font = 'Courier New'
  327. )
  328. display_x_ms(win, feedback_msg, 1000)
  329. wait_msg = visual.TextStim(
  330. win = win,
  331. text = 'Accuracy: {0}%\nMedian RT: {1} ms\n\nPlease wait for Jack...'.format(
  332. np.round(np.sum(practice_accuracies)/len(practice_trls.index)*100, 1),
  333. np.round(np.median(practice_rts), 1)),
  334. height = 0.07,
  335. units = 'norm',
  336. wrapWidth=1.75,
  337. )
  338. wait_msg.draw()
  339. win.flip()
  340. print('PRACTICE BLOCK ACCURACY: {0}/{1} ({2}%)'.format(np.sum(practice_accuracies), len(practice_trls.index), np.round(np.sum(practice_accuracies)/len(practice_trls.index)*100, 1)))
  341. print('PRACTICE BLOCK MEDIAN RT: {0} (MIN={1}, MAX={2})'.format(np.round(np.median(practice_rts), 1), np.min(practice_rts), np.max(practice_rts)))
  342. print('REPEAT PRACTICE TRIALS? Y/N')
  343. repeat_yn = event.waitKeys(keyList=['y', 'n'])
  344. if repeat_yn == ['y']:
  345. print('REPEATING PRACTICE TRIALS')
  346. this_is_a_repeat_practice_block = True
  347. else:
  348. print('ENDING PRACTICE TRIALS')
  349. practice = repeat_yn == ['y']
  350. # %% Experimental Trials
  351. instructions(reminder = True, wait_time = 2)
  352. block_nr = 1
  353. for trl_i in range(len(trls.index)):
  354. # at start of trial, check if time for a break
  355. # (as trl_i is an index, will take a break if previous trial was the last before a break is due)
  356. if trl_i % 60 == 0 and trl_i != 0:
  357. block_break()
  358. instructions(reminder = True, wait_time = 2)
  359. block_nr += 1
  360. condition = trls['condition'][trl_i]
  361. corr_ans = trls.corr_ans[trl_i]
  362. if condition == 'word':
  363. trigg = 101
  364. elif condition == 'bacs':
  365. trigg = 102
  366. elif condition == 'noise':
  367. trigg = 103
  368. else:
  369. print('UNKNOWN CONDITION IN TRIAL {0}! USING TRIGGER OF 50'.format(trl_i+1))
  370. trigg = 50
  371. trl_dat = trial(
  372. image_path = os.path.join('stim', 'img', trls.file[trl_i]),
  373. green_image_path = os.path.join('stim', 'img', trls.file_green[trl_i]),
  374. stim_trigger = trigg
  375. )
  376. if corr_ans == trl_dat['stim_resp'][0][0]:
  377. trl_corr = 1
  378. else:
  379. trl_corr = 0
  380. trl_dat_tidy = {
  381. 'date': datetime.datetime.now().strftime('%d-%m-%y'),
  382. 'trial_save_time': datetime.datetime.now().strftime('%H:%M:%S'),
  383. 'subj_id': participant_info['subj_id'],
  384. 'stim_grp': participant_info['stim_grp'],
  385. 'resp_grp': participant_info['resp_grp'],
  386. 'sex': participant_info['sex'],
  387. 'age': participant_info['age'],
  388. 'block_nr': block_nr,
  389. 'trl_nr': trl_i + 1,
  390. 'item_nr': trls['item_nr'][trl_i],
  391. 'condition': condition,
  392. 'eeg_trigg': trigg,
  393. 'image': trls['file'][trl_i],
  394. 'image_green': trls['file_green'][trl_i],
  395. 'string': trls['string'][trl_i],
  396. 'corr_ans': corr_ans,
  397. 'resp': trl_dat['stim_resp'][0][0],
  398. 'acc': trl_corr,
  399. 'rt': trl_dat['stim_resp'][0][1] * 1000,
  400. 'fix_jitt_flip': trl_dat['fix_jitt'][0],
  401. 'fix_jitt_ms': trl_dat['fix_jitt'][1]
  402. }
  403. write_csv(subj_data_path, trl_dat_tidy)
  404. print('TRL', '{}/{}'.format(trl_i+1, len(trls.index)), ', COND', condition, ', CORR_ANS', corr_ans, ', RESP', trl_dat['stim_resp'][0][0], ', ACC', trl_corr, ', RT', round(trl_dat['stim_resp'][0][1] * 1000, 1), ', IMAGE', trls['file'][trl_i], ', WORD', trls['string'][trl_i], ', FIX_JITT', round(trl_dat['fix_jitt'][1], 1))
  405. # %% Close
  406. end_stim = visual.TextStim(
  407. win,
  408. units='norm',
  409. height=0.07,
  410. wrapWidth=1.75,
  411. text='Task Complete! Please wait...'
  412. )
  413. end_stim.draw()
  414. win.flip()
  415. print('EXPERIMENT END. Overall, {0} frames were dropped.'.format(win.nDroppedFrames))
  416. core.wait(5)