|
| 1 | +## Python Standard Library |
| 2 | +import sys |
| 3 | +import os |
| 4 | +import random |
| 5 | +import csv |
| 6 | + |
| 7 | +## External Dependencies |
| 8 | +from psychopy import core, gui, visual, event |
| 9 | + |
| 10 | +## Internal Dependencies |
| 11 | +sys.path.append(os.path.abspath(os.path.join(__file__, '..'))) |
| 12 | +import event_utils |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | +##__Forced Choice Phase |
| 17 | +def run( |
| 18 | + stimuli, |
| 19 | + labels = None, |
| 20 | + randomize_presentation = True, |
| 21 | + |
| 22 | + supervised = True, # this determines whether participants recieve feedback |
| 23 | + num_blocks = 1, |
| 24 | + |
| 25 | + experiment_id = 'experiment', |
| 26 | + phase_id = 'forced_choice', |
| 27 | + subject_info = None, |
| 28 | + save_data = False, |
| 29 | + |
| 30 | + window = None, |
| 31 | + |
| 32 | + stim_position = [0,0], |
| 33 | + |
| 34 | + fixation_symbol = '', |
| 35 | + |
| 36 | + click_instructions_txt = 'Click a button to select the correct category.', |
| 37 | + click_instructions_txt_color = [-1,-1,-1], |
| 38 | + click_instructions_position = [0, -120], |
| 39 | + |
| 40 | + feedback_txt_position = [0, -90], |
| 41 | + feedback_txt_color = [-1,-1,-1], |
| 42 | + |
| 43 | + continue_txt_position = [0, -145], |
| 44 | + continue_txt_color = [-1,-1,-1], |
| 45 | + |
| 46 | + response_btn_labels = None, |
| 47 | + response_btn_ypos = -280, |
| 48 | + response_btn_padding = 150, |
| 49 | + response_btn_box_size = [160, 80], |
| 50 | + response_btn_box_color = [.5, .8, .5], |
| 51 | + response_btn_txt_size = 22, |
| 52 | + response_btn_txt_font = 'Consolas', |
| 53 | + response_btn_txt_color = [-1,-1,-1], |
| 54 | + |
| 55 | + quit_keys = ['escape'], |
| 56 | + debug_mode = False, |
| 57 | +): |
| 58 | + |
| 59 | + # Initialize Main Psychopy win if one isn't included |
| 60 | + if window == None: |
| 61 | + window = event_utils.build_window() |
| 62 | + |
| 63 | + cursor = event.Mouse() # set initial cursor |
| 64 | + timer = core.Clock() |
| 65 | + |
| 66 | + if subject_info == None: |
| 67 | + subject_info = { |
| 68 | + 'id': '0000', |
| 69 | + 'condition': 0, |
| 70 | + 'datafile_path': './subject_data.csv' |
| 71 | + } |
| 72 | + |
| 73 | + if (labels == None) & (supervised == True): |
| 74 | + print(phase_id + ' is set to "supeprvised", but no labels were provided. quitting experiment.') |
| 75 | + sys.exit() |
| 76 | + |
| 77 | + if response_btn_labels == None: |
| 78 | + if labels != None: |
| 79 | + response_btn_labels = list(set(labels)) |
| 80 | + else: |
| 81 | + response_btn_labels = ['no', 'labels', 'provided'] |
| 82 | + |
| 83 | + |
| 84 | + ## Prepare Stimuli and Text Objects |
| 85 | + object_bin = {} |
| 86 | + |
| 87 | + object_bin['stim'] = visual.ImageStim( |
| 88 | + window, |
| 89 | + pos = stim_position, |
| 90 | + name = 'image_stim', |
| 91 | + interpolate = True, |
| 92 | + ) |
| 93 | + |
| 94 | + object_bin['response_btns'] = event_utils.make_button_row( |
| 95 | + window, # psychopy win object (required argument) |
| 96 | + labels = response_btn_labels, |
| 97 | + ypos = response_btn_ypos, |
| 98 | + padding = 100, # this determines how far apart butons will be evenly placed |
| 99 | + btn_box_size = response_btn_box_size, |
| 100 | + btn_box_color = response_btn_box_color, |
| 101 | + btn_txt_size = response_btn_txt_size, |
| 102 | + btn_txt_color = response_btn_txt_color, |
| 103 | + btn_txt_font = response_btn_txt_font, |
| 104 | + ) |
| 105 | + |
| 106 | + object_bin['click_msg'] = visual.TextStim( |
| 107 | + window, |
| 108 | + text = click_instructions_txt, |
| 109 | + pos = click_instructions_position, |
| 110 | + color = click_instructions_txt_color |
| 111 | + ) |
| 112 | + |
| 113 | + object_bin['feedback_msg'] = visual.TextStim( |
| 114 | + window, |
| 115 | + pos = feedback_txt_position, |
| 116 | + color = feedback_txt_color, |
| 117 | + ) |
| 118 | + |
| 119 | + object_bin['click_anywhere_msg'] = visual.TextStim( |
| 120 | + window, |
| 121 | + text = 'Click anywhere to continue...', |
| 122 | + pos = continue_txt_position, |
| 123 | + color = continue_txt_color, |
| 124 | + ) |
| 125 | + |
| 126 | + |
| 127 | + ##__ Start Phase |
| 128 | + trial_num = 0 |
| 129 | + accuracy = [] |
| 130 | + presentation_order = list(range(len(stimuli))) |
| 131 | + |
| 132 | + for block in range(num_blocks): |
| 133 | + if randomize_presentation == True: |
| 134 | + random.shuffle(presentation_order) |
| 135 | + |
| 136 | + for i in presentation_order: |
| 137 | + |
| 138 | + # set stimulus image |
| 139 | + object_bin['stim'].setImage(stimuli[i]) |
| 140 | + |
| 141 | + # initially draw stimulus and response buttons |
| 142 | + event_utils.draw_objects_in_bin( |
| 143 | + window, |
| 144 | + object_bin, |
| 145 | + object_list = ['stim', 'response_btns'], |
| 146 | + ) |
| 147 | + |
| 148 | + # wait some time |
| 149 | + if debug_mode == False: core.wait(.77) |
| 150 | + |
| 151 | + # draw click message |
| 152 | + event_utils.draw_objects_in_bin( |
| 153 | + window, |
| 154 | + object_bin, |
| 155 | + object_list = ['stim', 'response_btns', 'click_msg'], |
| 156 | + ) |
| 157 | + |
| 158 | + # wait some time |
| 159 | + if debug_mode == False: core.wait(.35) |
| 160 | + |
| 161 | + # wait for user to click a response button |
| 162 | + response, rt = event_utils.wait_for_btn_response(cursor, timer, object_bin['response_btns'], quit_keys=quit_keys) |
| 163 | + |
| 164 | + # check accuracy of subject's response |
| 165 | + correct_category = labels[i] |
| 166 | + if (supervised == True) & (correct_category != 'gen'): |
| 167 | + hit = response == correct_category |
| 168 | + else: |
| 169 | + hit = 'gen' |
| 170 | + accuracy.append(hit) |
| 171 | + |
| 172 | + if supervised == True: |
| 173 | + if response == correct_category: |
| 174 | + object_bin['feedback_msg'].setText(correct_feedback + correct_category) |
| 175 | + else: |
| 176 | + object_bin['feedback_msg'].setText('Incorrect... This is a member of category: ' + correct_category) |
| 177 | + |
| 178 | + # draw feedback message |
| 179 | + event_utils.draw_objects_in_bin( |
| 180 | + window, |
| 181 | + object_bin, |
| 182 | + object_list = ['stim', 'response_btns', 'feedback_msg'], |
| 183 | + ) |
| 184 | + else: |
| 185 | + event_utils.draw_objects_in_bin( |
| 186 | + window, |
| 187 | + object_bin, |
| 188 | + object_list = ['stim', 'response_btns'], |
| 189 | + ) |
| 190 | + |
| 191 | + |
| 192 | + if debug_mode == False: core.wait(1) |
| 193 | + |
| 194 | + # draw continue message |
| 195 | + if supervised == True: |
| 196 | + event_utils.draw_objects_in_bin( |
| 197 | + window, |
| 198 | + object_bin, |
| 199 | + object_list = ['stim', 'response_btns', 'feedback_msg', 'click_anywhere_msg'], |
| 200 | + ) |
| 201 | + |
| 202 | + else: |
| 203 | + event_utils.draw_objects_in_bin( |
| 204 | + window, |
| 205 | + object_bin, |
| 206 | + object_list = ['stim', 'response_btns', 'click_anywhere_msg'], |
| 207 | + ) |
| 208 | + |
| 209 | + if debug_mode == False: core.wait(.35) |
| 210 | + |
| 211 | + event_utils.wait_for_click_response(cursor, timer, quit_keys=quit_keys) |
| 212 | + |
| 213 | + event_utils.draw_objects_in_bin( |
| 214 | + window, |
| 215 | + object_bin, |
| 216 | + object_list = ['response_btns'], |
| 217 | + ) |
| 218 | + |
| 219 | + if save_data == True: |
| 220 | + subject_data = [ |
| 221 | + subject_info['id'], |
| 222 | + phase_id, |
| 223 | + subject_info['condition'], |
| 224 | + block, |
| 225 | + trial_num, |
| 226 | + stimuli[i], |
| 227 | + correct_category, |
| 228 | + response, |
| 229 | + hit, |
| 230 | + rt, |
| 231 | + ] |
| 232 | + with open(subject_info['datafile_path'], 'a') as file: |
| 233 | + csv_object = csv.writer(file) |
| 234 | + csv_object.writerow(subject_data) |
| 235 | + |
| 236 | + |
| 237 | + |
| 238 | + # if early_finish_accuracy_criterion != None: |
| 239 | + # if trial_num > minimum_trials: |
| 240 | + # if sum(accuracy[-early_finish_lookback:]) / len(accuracy[-early_finish_lookback:]) >= early_finish_accuracy_criterion: |
| 241 | + # return |
| 242 | + |
| 243 | + trial_num = trial_num + 1 |
| 244 | + |
| 245 | + |
| 246 | + |
| 247 | + |
| 248 | + |
| 249 | + |
| 250 | + |
| 251 | + |
| 252 | + |
0 commit comments