Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

02-experiment_rt.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. # ensure directory is set first, e.g. cd 'Documents/Jack/PictureCloze/02 Experiment'
  2. # %% General Setup
  3. from psychopy import visual, core, monitors, event, data, gui
  4. import csv
  5. import os
  6. import codecs
  7. import datetime
  8. import numpy as np
  9. from matplotlib import pyplot as plt
  10. from pandas import read_csv
  11. from itertools import groupby
  12. from random import randint
  13. # %% Participant Info
  14. comp_name = os.environ['COMPUTERNAME']
  15. print('Detected computer name: \'{}\''.format(comp_name))
  16. print('Building dialog box. Enter the participant and experiment info into the GUI...')
  17. myDlg = gui.Dlg(title="PictureCloze Behavioural Experiment")
  18. myDlg.addText('Participant Info')
  19. myDlg.addField('Subject ID:')
  20. myDlg.addField('Sex:', choices=['', 'f', 'm', 'nb'])
  21. myDlg.addField('Age:', 25)
  22. myDlg.addField('Monolingual:', choices=['', 'yes', 'no'])
  23. myDlg.addField('Native English:', choices=['', 'yes', 'no'])
  24. myDlg.addField('Dominant Hand:', choices=['', 'right', 'left'])
  25. myDlg.addText('Experiment Info')
  26. myDlg.addField('Response Group:', choices=['', 1, 2])
  27. myDlg.addField('Stimulus Group:', choices=['', 1, 2])
  28. myDlg.addField('Session Number:', 1)
  29. participant_info_gui = myDlg.show()
  30. participant_info = {
  31. 'subj_id': int(participant_info_gui[0]),
  32. 'session_nr': participant_info_gui[8],
  33. 'stim_grp': int(participant_info_gui[7]), # 1/2
  34. 'resp_grp': int(participant_info_gui[6]), # 1/2
  35. 'monolingual': participant_info_gui[3],
  36. 'native_english': participant_info_gui[4],
  37. 'dom_hand': participant_info_gui[5],
  38. 'sex': participant_info_gui[1], # m/f/nb
  39. 'age': participant_info_gui[2]
  40. }
  41. # %% Check participant info makes sense
  42. def is_int(s):
  43. try:
  44. int(s)
  45. return True
  46. except ValueError:
  47. return False
  48. pinfo_check = {
  49. 'subj_id_is_int': isinstance(participant_info['subj_id'], int),
  50. 'stim_grp_1_or_2': participant_info['stim_grp'] in [1, 2],
  51. 'resp_grp_1_or_2': participant_info['resp_grp'] in [1, 2],
  52. 'monolingual': participant_info['monolingual'] in ['yes', 'no'],
  53. 'native_english': participant_info['native_english'] in ['yes', 'no'],
  54. 'dom_hand': participant_info['dom_hand'] in ['right', 'left'],
  55. 'sex_m_f_or_nb': participant_info['sex'] in ['m', 'f', 'nb'],
  56. 'age_is_int': isinstance(participant_info['age'], int)
  57. }
  58. if not all(value for value in pinfo_check.values()):
  59. print('Participant info error!')
  60. print(pinfo_check)
  61. exit()
  62. # set up monitor
  63. mon_Hz = 60 # refresh rate
  64. mon_framelen = 1/mon_Hz # length of one frame in seconds
  65. mon = monitors.Monitor(name='UpstairsBooth', notes='{0}Hz'.format(mon_Hz))
  66. mon.setWidth(52)
  67. mon.setDistance(48)
  68. mon.setSizePix((1920, 1080))
  69. mon.saveMon()
  70. # set up the Psychopy window
  71. win = visual.Window(fullscr=True,
  72. size=mon.getSizePix(),
  73. screen=0,
  74. monitor=mon,
  75. units='deg')
  76. # hide the mouse
  77. event.Mouse(visible=False)
  78. win.refreshThreshold = mon_framelen + 0.001 # tolerance of 1 ms (any refresh that takes more than the tolerance longer than expected = dropped frame)
  79. #win.recordFrameIntervals = True # record actual frame lengths
  80. # set up which button is affirmative, and which negative
  81. if participant_info['resp_grp'] == 1:
  82. resp_setup = {'yes': 'rctrl', 'no': 'lctrl'}
  83. elif participant_info['resp_grp'] == 2:
  84. resp_setup = {'yes': 'lctrl', 'no': 'rctrl'}
  85. resp_setup_names = {'lctrl': 'Left Control', 'rctrl': 'Right Control'}
  86. # %% Trial Setup
  87. # import trials data
  88. trls = read_csv('01-stim_tidy_long.csv')
  89. practice_trls = read_csv('01-practice_stim.csv')
  90. # filter depending on stimulus group
  91. # - if subject stim group is 1 all trials with order group of 1 will be condition A1 (picture-stimulus match), and order group of 2 will be condition A2
  92. # - if subject stim group is 2 all trials with order group of 2 will be condition A1 (picture-stimulus match), and order group of 1 will be condition A2
  93. trls = trls[
  94. ( (trls.order_grp==participant_info['stim_grp']) & (trls.condition=='A1') )
  95. |
  96. ( (trls.order_grp!=participant_info['stim_grp']) & (trls.condition=='A2') )
  97. ]
  98. # reorder randomly, and ensure no more than five of the same trial type (cong/incong) follow each other
  99. shuffle_iter = 0
  100. max_consec_trltypes = 0
  101. while shuffle_iter==0 or max_consec_trltypes>5:
  102. # shuffle randomly
  103. trls = trls.sample(frac=1).reset_index(drop=True)
  104. # count maximum number of consecutive trial types
  105. trls['is_A1'] = np.where(trls['condition']=='A1', 1, 0).astype(str)
  106. trls['is_A2'] = np.where(trls['condition']=='A2', 1, 0).astype(str)
  107. A1s = ''.join(trls['is_A1'].tolist())
  108. A2s = ''.join(trls['is_A2'].tolist())
  109. max_consec_A1s = [int(len(x)) for x in A1s.split('0')]
  110. max_consec_A2s = [int(len(x)) for x in A2s.split('0')]
  111. max_consec_trltypes = max(max_consec_A1s + max_consec_A2s)
  112. shuffle_iter+=1
  113. print('Reshuffled trials (iter {}), with a maximum of {} consecutive trial types'.format(shuffle_iter, max_consec_trltypes))
  114. print('Successfully reshuffled trials\n')
  115. # %% Output setup
  116. subj_data_path = 'data{0}{1}_{2}_{3}.csv'.format(os.path.sep, participant_info['subj_id'], datetime.datetime.now().strftime('%d-%m-%y'), participant_info['session_nr'])
  117. # function to append a trial's data (in dictionary format) to csv
  118. def write_csv(fileName, thisTrial):
  119. full_path = os.path.abspath(fileName)
  120. directory = os.path.dirname(full_path)
  121. if not os.path.exists(directory):
  122. os.makedirs(directory)
  123. if not os.path.isfile(full_path):
  124. with codecs.open(full_path, 'ab+', encoding='utf8') as f:
  125. csv.writer(f, delimiter=',').writerow(thisTrial.keys())
  126. csv.writer(f, delimiter=',').writerow(thisTrial.values())
  127. else:
  128. with codecs.open(full_path, 'ab+', encoding='utf8') as f:
  129. csv.writer(f, delimiter=',').writerow(thisTrial.values())
  130. # %% Instructions
  131. def instructions(reminder = False, practice = False, wait_time = 3):
  132. print('DISPLAYING INSTRUCTIONS')
  133. if practice:
  134. practice_txt = u'For the practice trials, you will be given feedback on your accuracy for each trial.'
  135. else:
  136. practice_txt = u'Unlike the practice trials, you will not be given feedback on your accuracy for each trial.'
  137. instr_txt = u'In each trial, the following things will happen:\n\n1) You will be shown a picture of an object for 2 seconds.\n2) There will be a short delay.\n3) You will then be shown a word:\n\n Press the {0} key if the word describes the object you saw.\n OR\n Press the {1} key if it does not.\n\n Try to be as fast and accurate 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)
  138. if reminder:
  139. instr_txt = u'{0}{1}'.format('Here\'s a reminder of the task instructions.\n\n\n', instr_txt)
  140. instr_screen = visual.TextStim(
  141. win,
  142. units='norm',
  143. height=0.07,
  144. wrapWidth=1.75,
  145. text=instr_txt
  146. )
  147. instr_screen.draw()
  148. win.flip()
  149. core.wait(wait_time) # ensure that instructions have been read
  150. event.waitKeys(keyList='space')
  151. print('SPACE PRESSED; CONTINUING')
  152. # %% Trials setup
  153. # functions for precise frame-wise timing
  154. def ms_2_flips(ms = 100, Hz = 60, round_func = np.round):
  155. return int(round_func(ms / (1/Hz*1000)))
  156. def display_n_flips(stim, n_flips = 10):
  157. for frame_n in range(n_flips):
  158. stim.draw()
  159. win.flip()
  160. def display_x_ms(stim, ms = 100, Hz = 60, round_func = np.round):
  161. display_n_flips(stim, ms_2_flips(ms, Hz, round_func))
  162. def display_jitter_ms(stim, min_ms = 100, max_ms = 500, Hz = 60, round_func = np.round):
  163. flips_min = ms_2_flips(min_ms, mon_Hz, round_func)
  164. flips_max = ms_2_flips(max_ms, mon_Hz, round_func)
  165. n_flips = randint(flips_min, flips_max)
  166. display_n_flips(stim, n_flips)
  167. return(n_flips, n_flips*(1/Hz*1000))
  168. def display_list_n_flips(stim_list, n_flips = 10):
  169. for frame_n in range(n_flips):
  170. for stim_i in stim_list:
  171. stim_i.draw()
  172. win.flip()
  173. def display_list_ms(stim_list, ms = 100, Hz = 60, round_func = np.round):
  174. display_list_n_flips(stim_list, ms_2_flips(ms, Hz, round_func))
  175. def display_list_jitter_ms(stim_list, min_ms = 100, max_ms = 500, Hz = 60, round_func = np.round):
  176. flips_min = ms_2_flips(min_ms, mon_Hz, round_func)
  177. flips_max = ms_2_flips(max_ms, mon_Hz, round_func)
  178. n_flips = randint(flips_min, flips_max)
  179. display_list_n_flips(stim_list, n_flips)
  180. return(n_flips, n_flips*(1/Hz*1000))
  181. # function that returns a list containing the objects in the Thaler et al. fixation point
  182. # can then draw the list contents in a loop
  183. def thaler_list_fix(win, units='deg', circlesColor=[1,1,1], circlesColorSpace='rgb', outerCircleDiameter=0.6, innerCircleDiameter=0.2, pos = (0, 0)):
  184. crossColor=win.color.tolist()
  185. crossColorSpace=win.colorSpace
  186. outerCircle = visual.Circle(win, units=units, pos=pos, radius=outerCircleDiameter/2, lineColor=circlesColor, fillColor=circlesColor, fillColorSpace=circlesColorSpace, lineColorSpace=circlesColorSpace)
  187. crossVertical = visual.Rect(win, units=units, pos=pos, height=outerCircleDiameter*1.1, width=innerCircleDiameter/2, lineColor=None, fillColor=crossColor, fillColorSpace=crossColorSpace, lineColorSpace=crossColorSpace)
  188. crossHorizontal = visual.Rect(win, units=units, pos=pos, height=innerCircleDiameter/2, width=outerCircleDiameter*1.1, lineColor=None, fillColor=crossColor, fillColorSpace=crossColorSpace, lineColorSpace=crossColorSpace)
  189. innerCircle = visual.Circle(win, units=units, pos=pos, radius=innerCircleDiameter/2, lineColor=circlesColor, fillColor=circlesColor, fillColorSpace=circlesColorSpace, lineColorSpace=circlesColorSpace)
  190. return [outerCircle, crossVertical, crossHorizontal, innerCircle]
  191. # function that will be called for each trial
  192. def trial(image_path='boss{}8ball.jpg'.format(os.path.sep), string='ball', text_font='Courier New'):
  193. # a fixation point in style of Thaler et al.
  194. thaler_fix_stim = thaler_list_fix(win=win)
  195. # a blank stimulus
  196. blank_stim = visual.TextStim(win=win, text='')
  197. # the image to display
  198. img_stim = visual.ImageStim(
  199. win = win,
  200. image = image_path,
  201. pos = (0.0, 0.0),
  202. size = 15, # 7.0403 degrees at 48 inches distance
  203. units = 'cm'
  204. )
  205. # the text stimulus
  206. text_stim = visual.TextStim(
  207. win = win,
  208. text = string,
  209. height = 1.5,
  210. units = 'deg',
  211. font = text_font
  212. )
  213. # fixation 1
  214. display_list_ms(thaler_fix_stim, 300) # note that the list version of the function is required, as thaler_list_fix returns a list object
  215. fix1_jitt = display_jitter_ms(blank_stim, 300, 1300)
  216. # image
  217. display_x_ms(img_stim, 2000)
  218. # fixation 2
  219. display_list_ms(thaler_fix_stim, 300) # note that the list version of the function is required, as thaler_list_fix returns a list object
  220. fix2_jitt = display_jitter_ms(blank_stim, 300, 1300)
  221. # text
  222. text_stim.draw()
  223. win.flip()
  224. word_rt_clock = core.MonotonicClock()
  225. # response
  226. word_resp = event.waitKeys(keyList=['lctrl', 'rctrl'], timeStamped = word_rt_clock)
  227. trl_dat = {
  228. 'fix1_jitt': fix1_jitt,
  229. 'fix2_jitt': fix2_jitt,
  230. 'word_resp': word_resp
  231. }
  232. return(trl_dat)
  233. def block_break():
  234. # read the subject's data
  235. subj_data_so_far = read_csv(subj_data_path)
  236. accuracies = subj_data_so_far['acc'].to_numpy()
  237. blocks = subj_data_so_far['block_nr'].to_numpy()
  238. block_acc_txt = ''
  239. for block_nr in np.unique(blocks):
  240. block_acc_txt = '{0}\n Block {1}: {2}%'.format(block_acc_txt, block_nr, np.round(np.mean(accuracies[blocks==block_nr] * 100), 1))
  241. # give subject summary of their data
  242. break_msg = visual.TextStim(
  243. win,
  244. units='norm',
  245. height=0.07,
  246. wrapWidth=1.75,
  247. text=u'Block {0} complete. Time for a break!\n\n\nYour overall accuracy so far is: {1}%\n\nHere\'s a per-block summary of your experiment so far:{2}\n\nPress the space key when you\'re ready to continue...'.format(np.max(blocks), np.round(np.mean(accuracies * 100), 1), block_acc_txt)
  248. )
  249. break_msg.draw()
  250. win.flip()
  251. print('BREAK STARTED')
  252. event.waitKeys(keyList='space')
  253. print('BREAK ENDED')
  254. # %% Give feedback after experiment
  255. def experiment_end():
  256. # change background to black
  257. win.color = [-1,-1,-1]
  258. win.flip()
  259. # Tell the participant the experiment is over
  260. end_msg = visual.TextStim(
  261. win,
  262. units='norm',
  263. height=0.07,
  264. wrapWidth=1.75,
  265. text=u'Experiment complete! Please let the experimenter know...',
  266. pos=(0, 0.9)
  267. )
  268. loading_msg = visual.TextStim(
  269. win,
  270. units='norm',
  271. height=0.07,
  272. wrapWidth=1.75,
  273. text=u'Loading summary statistics...',
  274. pos=(0, 0)
  275. )
  276. end_msg.draw()
  277. loading_msg.draw()
  278. win.flip()
  279. # read the subject's data
  280. subj_data = read_csv(subj_data_path)
  281. # order by item number
  282. subj_data = subj_data.sort_values(by = ['item_nr'])
  283. # subject's data as arrays
  284. accuracies = subj_data['acc'].to_numpy()
  285. RTs = subj_data['rt'].to_numpy()
  286. blocks = subj_data['block_nr'].to_numpy()
  287. conditions = subj_data['condition'].to_numpy()
  288. # read item info to get predictability info
  289. items_data = read_csv('01-stim_tidy_long.csv')
  290. # only include items for the subject's response condition
  291. subj_stim_grp = np.unique(subj_data['stim_grp'].to_numpy()).item(0)
  292. items_data = items_data[
  293. ( (items_data.order_grp==subj_stim_grp) & (items_data.condition=='A1') )
  294. |
  295. ( (items_data.order_grp!=subj_stim_grp) & (items_data.condition=='A2') )
  296. ]
  297. # get the data as numpy arrays
  298. perc_name_agree = items_data['perc_name_agree'].to_numpy()
  299. # plot summary of subject's data
  300. plt.style.use('dark_background')
  301. figsize_inch = [16, 6]
  302. fig = plt.figure(figsize=figsize_inch, dpi=300)
  303. plt.subplot(2, 5, 1)
  304. binwidth = 20
  305. bins = np.arange(0, np.max(RTs) + binwidth, binwidth)
  306. plt.hist(RTs, bins = bins)
  307. plt.title('Your Response Times (RTs)')
  308. plt.xlabel('RT (bin width = 20ms)')
  309. plt.ylabel('Count')
  310. plt.subplot(2, 5, 2)
  311. plt.plot(range(1, len(RTs)+1), RTs)
  312. plt.title('Your RTs across Trials')
  313. plt.xlabel('Trial')
  314. plt.ylabel('RT (ms)')
  315. plt.subplot(2, 5, 3)
  316. # collect data in blocks
  317. per_block_RTs = []
  318. for block_nr in np.unique(blocks):
  319. per_block_RTs.append(RTs[blocks == block_nr])
  320. v_parts = plt.violinplot(per_block_RTs, showmedians=True, widths = 0.75)
  321. #print(v_parts['bodies'][0]._facecolors)
  322. plt.xticks(np.arange(np.min(np.unique(blocks)), np.max(np.unique(blocks))+1, 1))
  323. plt.title('Your RTs across Blocks')
  324. plt.xlabel('Block')
  325. plt.ylabel('RT (ms)')
  326. plt.subplot(2, 5, 4)
  327. # collect data in blocks
  328. per_cond_RTs = []
  329. for condition_nr in np.sort(np.unique(conditions)):
  330. per_cond_RTs.append(RTs[conditions == condition_nr])
  331. plt.violinplot(per_cond_RTs, showmedians=True, widths = 0.6)
  332. plt.xticks([1, 2], ['Congruent', 'Incongruent'])
  333. plt.title('Your RTs across Conditions')
  334. plt.xlabel('Condition')
  335. plt.ylabel('RT (ms)')
  336. plt.subplot(2, 5, 5)
  337. plt.title('Your RTs across Predictability')
  338. x = perc_name_agree[conditions == "A1"]
  339. y = RTs[conditions == "A1"]
  340. plt.scatter(x, y, c = [[0.55294118 * 0.3, 0.82745098 * 0.3, 0.78039216 * 0.3]])
  341. coef = np.polyfit(x, y, 1)
  342. poly1d_fn = np.poly1d(coef)
  343. plt.plot(x, poly1d_fn(x))
  344. plt.xlabel('Predictability (%)')
  345. plt.ylabel('RT (ms)')
  346. plt.subplot(2, 5, 6)
  347. resp_type, resp_counts = np.unique(accuracies, return_counts=True)
  348. plt.bar(resp_type, resp_counts)
  349. plt.xticks([0, 1], ['Incorrect', 'Correct'])
  350. plt.title('Your Accuracy ({0}% Overall)'.format(np.round(np.mean(accuracies * 100), 1)))
  351. plt.xlabel('Accuarcy')
  352. plt.ylabel('Count')
  353. plt.subplot(2, 5, 7)
  354. plt.title('Your Incorrect Trials')
  355. plt.bar(range(1, len(RTs)+1), np.abs(accuracies-1))
  356. plt.xlabel('Trial')
  357. plt.ylabel('Accuracy')
  358. plt.subplot(2, 5, 8)
  359. per_block_accs = []
  360. for block_nr in np.unique(blocks):
  361. per_block_accs.append(np.mean(accuracies[blocks == block_nr]) * 100)
  362. plt.bar(np.unique(blocks), per_block_accs)
  363. plt.xticks(np.arange(np.min(np.unique(blocks)), np.max(np.unique(blocks))+1, 1))
  364. plt.ylim(0, 100)
  365. plt.title('Your Acc. across Blocks')
  366. plt.xlabel('Block')
  367. plt.ylabel('Accuracy (%)')
  368. plt.subplot(2, 5, 9)
  369. per_cond_accs = []
  370. for condition_nr in np.sort(np.unique(conditions)):
  371. per_cond_accs.append(np.mean(accuracies[conditions == condition_nr]) * 100)
  372. plt.bar(['Congruent', 'Incongruent'], per_cond_accs)
  373. plt.ylim(0, 100)
  374. plt.title('Your Acc. across Conditions')
  375. plt.xlabel('Condition')
  376. plt.ylabel('Accuracy (%)')
  377. def rand_jitter(arr):
  378. stdev = .025*(max(arr)-min(arr))
  379. return arr + np.random.randn(len(arr)) * stdev
  380. def jitter_height(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs):
  381. return plt.scatter(x, rand_jitter(y), s=s, c=c, marker=marker, cmap=cmap, norm=norm, vmin=vmin, vmax=vmax, alpha=alpha, linewidths=linewidths, verts=verts, **kwargs)
  382. plt.subplot(2, 5, 10)
  383. plt.title('Your Acc. across Predictability')
  384. x = perc_name_agree[conditions == "A1"]
  385. y = accuracies[conditions == "A1"]*100
  386. jitter_height(x, y, c = [[0.55294118 * 0.3, 0.82745098 * 0.3, 0.78039216 * 0.3]])
  387. coef = np.polyfit(x, y, 1)
  388. poly1d_fn = np.poly1d(coef)
  389. plt.plot(x, poly1d_fn(x))
  390. plt.xlabel('Predictability (%)')
  391. plt.ylabel('Accuracy (jittered %)')
  392. # adjust spacing
  393. plt.subplots_adjust(left = 0.125, right = 0.9, bottom = 0.1, top = 0.9, wspace = 0.5, hspace = 0.8)
  394. # save figure
  395. image_path = 'tmp{0}subj_summ.png'.format(os.path.sep)
  396. fig.savefig(image_path)
  397. end_plot = visual.ImageStim(
  398. win,
  399. image=image_path,
  400. size=[i * 2.54 * 1.5 for i in figsize_inch],
  401. units='cm'
  402. )
  403. end_msg.draw()
  404. end_plot.draw()
  405. win.flip()
  406. print('PRESS \'SPACE\' KEY TO EXIT THE EXPERIMENT')
  407. event.waitKeys(keyList='space')
  408. print('EXITED')
  409. # %% Practice Trials
  410. practice = True
  411. this_is_a_repeat_practice_block = False
  412. while practice:
  413. instructions(reminder = this_is_a_repeat_practice_block, practice = True)
  414. practice_accuracies = []
  415. practice_trls = practice_trls.sample(frac=1).reset_index(drop=True)
  416. for trl_i in range(len(practice_trls.index)):
  417. condition = practice_trls['condition'][trl_i]
  418. trl_dat = trial(
  419. image_path = 'boss{}{}.jpg'.format(os.path.sep, practice_trls['filename'][trl_i]),
  420. string = practice_trls['string'][trl_i]
  421. )
  422. if condition == 'A1':
  423. corr_ans = resp_setup['yes']
  424. elif condition == 'A2':
  425. corr_ans = resp_setup['no']
  426. if corr_ans == trl_dat['word_resp'][0][0]:
  427. trl_corr = 1
  428. else:
  429. trl_corr = 0
  430. practice_accuracies.append(trl_corr)
  431. print('PRACTICE TRL', '{}/{}'.format(trl_i+1, len(practice_trls.index)), ', COND', condition, ', CORR_ANS', corr_ans, ', RESP', trl_dat['word_resp'][0][0], ', ACC', trl_corr, ', RT', round(trl_dat['word_resp'][0][1] * 1000, 1), ', IMAGE', practice_trls['filename'][trl_i], ', WORD', practice_trls['string'][trl_i], ', FIX1_JITT', round(trl_dat['fix1_jitt'][1], 1), ', FIX2_JITT', round(trl_dat['fix2_jitt'][1], 1))
  432. if trl_corr == 1:
  433. feedback_txt = 'CORRECT!'
  434. feedback_col = [-1, 1, -1]
  435. else:
  436. feedback_txt = 'INCORRECT!'
  437. feedback_col = [1, -1, -1]
  438. feedback_msg = visual.TextStim(
  439. win = win,
  440. text = feedback_txt,
  441. height = 1.5,
  442. units = 'deg',
  443. color = feedback_col,
  444. colorSpace = 'rgb',
  445. font = 'Courier New'
  446. )
  447. display_x_ms(feedback_msg, 1000)
  448. retry_msg = visual.TextStim(
  449. win,
  450. units='norm',
  451. height=0.07,
  452. wrapWidth=1.75,
  453. text='Practice trials complete! Your accuracy in the practice trials was {}%\n\nWould you like to retry the practice trials?\n Press \'y\' to try the practice trials again\n Press \'n\' to continue to the proper experiment'.format(np.round(np.sum(practice_accuracies)/len(practice_trls.index)*100, 1))
  454. )
  455. retry_msg.draw()
  456. win.flip()
  457. 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)))
  458. print('REPEAT PRACTICE TRIALS? Y/N')
  459. repeat_yn = event.waitKeys(keyList=['y', 'n'])
  460. if repeat_yn == ['y']:
  461. print('REPEATING PRACTICE TRIALS')
  462. this_is_a_repeat_practice_block = True
  463. else:
  464. print('ENDING PRACTICE TRIALS')
  465. practice = repeat_yn == ['y']
  466. # %% Experimental Trials
  467. instructions(reminder = True, wait_time = 2)
  468. block_nr = 1
  469. for trl_i in range(len(trls.index)):
  470. # at start of trial, check if time for a break
  471. # (as trl_i is an index, will take a break if previous trial was the last before a break is due)
  472. if trl_i % 40 == 0 and trl_i != 0:
  473. block_break()
  474. instructions(reminder = True, wait_time = 2)
  475. block_nr += 1
  476. condition = trls['condition'][trl_i]
  477. trl_dat = trial(
  478. image_path = 'boss{}{}.jpg'.format(os.path.sep, trls['filename'][trl_i]),
  479. string = trls['string'][trl_i]
  480. )
  481. if condition == 'A1':
  482. corr_ans = resp_setup['yes']
  483. elif condition == 'A2':
  484. corr_ans = resp_setup['no']
  485. if corr_ans == trl_dat['word_resp'][0][0]:
  486. trl_corr = 1
  487. else:
  488. trl_corr = 0
  489. trl_dat_tidy = {
  490. 'date': datetime.datetime.now().strftime('%d-%m-%y'),
  491. 'computer_name': comp_name,
  492. 'trial_save_time': datetime.datetime.now().strftime('%H:%M:%S'),
  493. 'subj_id': participant_info['subj_id'],
  494. 'stim_grp': participant_info['stim_grp'],
  495. 'resp_grp': participant_info['resp_grp'],
  496. 'sex': participant_info['sex'],
  497. 'age': participant_info['age'],
  498. 'dom_hand': participant_info['dom_hand'],
  499. 'monolingual': participant_info['monolingual'],
  500. 'native_english': participant_info['native_english'],
  501. 'block_nr': block_nr,
  502. 'trl_nr': trl_i + 1,
  503. 'item_nr': trls['item_nr'][trl_i],
  504. 'condition': condition,
  505. 'image': trls['filename'][trl_i],
  506. 'string': trls['string'][trl_i],
  507. 'corr_ans': corr_ans,
  508. 'resp': trl_dat['word_resp'][0][0],
  509. 'acc': trl_corr,
  510. 'rt': trl_dat['word_resp'][0][1] * 1000,
  511. 'fix1_jitt_flip': trl_dat['fix1_jitt'][0],
  512. 'fix1_jitt_ms': trl_dat['fix1_jitt'][1],
  513. 'fix2_jitt_flip': trl_dat['fix2_jitt'][0],
  514. 'fix2_jitt_ms': trl_dat['fix2_jitt'][1]
  515. }
  516. write_csv(subj_data_path, trl_dat_tidy)
  517. print('TRL', '{}/{}'.format(trl_i+1, len(trls.index)), ', COND', condition, ', CORR_ANS', corr_ans, ', RESP', trl_dat['word_resp'][0][0], ', ACC', trl_corr, ', RT', round(trl_dat['word_resp'][0][1] * 1000, 1), ', IMAGE', trls['filename'][trl_i], ', WORD', trls['string'][trl_i], ', FIX1_JITT', round(trl_dat['fix1_jitt'][1], 1), ', FIX2_JITT', round(trl_dat['fix2_jitt'][1], 1))
  518. # %% End experiment and print summary stats
  519. print('EXPERIMENT END. Overall, {0} frames were dropped.'.format(win.nDroppedFrames))
  520. experiment_end()