class Experiment(object): def __init__(self, exp_dir, version_file): """ Start the experiment by saving the experiment directory and loading the version parameters stored in .yaml format. """ self.exp_dir = exp_dir version_file_with_path = os.path.join(exp_dir, version_file) self.version = yaml.load(open(version_file_with_path, "r")) self.version_name = self.version["version_name"] self.version_dir = os.path.join(self.exp_dir, self.version_name) if not os.path.isdir(self.version_dir): os.mkdir(self.version_dir) self._get_session_info() self._initialize_display() self._load_trials_and_stims() def _get_session_info(self): """ Pop up a GUI to get popup variables, such as the subject code, randomization seed, and experimenter initials. """ subj_info = { "1": {"name": "subj_id", "prompt": "EXP_XXX", "options": "any", "default": self.version_name + "_101"}, "2": {"name": "seed", "prompt": "Seed: ", "options": "any", "default": 101}, "3": {"name": "initials", "prompt": "Experimenter initials", "options": "any", "default": ""}, } data_dir = os.path.join(self.version_dir, "data") if not os.path.isdir(data_dir): os.mkdir(data_dir) received = False file_opened = False while not file_opened: received, self.subj_vars = enter_subj_info(self.version_name, exp_dir=self.exp_dir, options=subj_info) if self.subj_vars["subj_id"] == "test": data_file = os.path.join(self.version_dir, "sample_data.txt") try: os.remove(data_file) except OSError: pass else: data_file = os.path.join(data_dir, self.subj_vars["subj_id"] + ".txt") if not received: popup_error(self.subj_vars) elif not os.path.isfile(data_file): file_opened = True self.data_file = open(data_file, "w") else: popup_error("That subject code already exists!") self.subj_vars["room"] = socket.gethostname() # record experiment comp. self.subj_vars["seed"] = int(self.subj_vars["seed"]) # save order of subject variables in the data file self.subj_vars_fields = ["subj_id", "seed", "initials", "date", "exp_name", "room"] def _initialize_display(self): """ Make the window and all static objects to be used in the experiment. """ self.win = visual.Window( fullscr=True, color=[0.6, 0.6, 0.6], allowGUI=False, monitor=self.subj_vars["room"] + "-Monitor", units="pix", winType="pyglet", ) self.stim = {} # store static objects self.stim["text"] = visual.TextStim( self.win, height=30, pos=[0, 350], color="black", wrapWidth=int(self.win.size[0] * 0.8) ) self.stim["fix"] = visual.TextStim(self.win, text="+", height=100, color="black") self.stim["prompt"] = visual.TextStim(self.win, text="?", height=100, color="black") feedback_dir = os.path.join(self.exp_dir, "stimuli", "feedback") self.feedback = {} self.feedback[0] = sound.Sound(os.path.join(feedback_dir, "buzz.wav")) self.feedback[1] = sound.Sound(os.path.join(feedback_dir, "bleep.wav")) frame_params = {} frame_params["win"] = self.win frame_params["lineColor"] = "black" frame_params["lineWidth"] = 2.0 frame_params["fillColor"] = None frame_params["width"] = self.version["task"]["pic_size"][0] + 10 frame_params["height"] = self.version["task"]["pic_size"][1] + 10 locs = self.version["task"]["pic_locs"] self.stim["left_frame"] = visual.Rect(pos=locs["left"], **frame_params) self.stim["right_frame"] = visual.Rect(pos=locs["right"], **frame_params) mask_params = {} mask_params["win"] = self.win mask_params["size"] = self.version["task"]["pic_size"] mask_dir = os.path.join(self.exp_dir, "stimuli", "dynamic_mask") self.mask_left = DynamicMask(mask_dir, "colored", ori=0.0, pos=locs["left"], **mask_params) self.mask_right = DynamicMask(mask_dir, "colored", ori=180.0, pos=locs["right"], **mask_params) def _load_trials_and_stims(self): """ Load the trials and stimuli from external files. """ show_text(self.win, "Loading...", color="black", waitForKey=False) trials_dir = os.path.join(self.version_dir, "trials") if not os.path.isdir(trials_dir): os.mkdir(trials_dir) if self.subj_vars["subj_id"] == "test": trials_file = os.path.join(self.version_dir, "sample_trials.csv") try: os.remove(trials_file) except OSError: pass else: trials_file = "seed_{0}.csv".format(self.subj_vars["seed"]) trials_file = os.path.join(trials_dir, trials_file) if not os.path.isfile(trials_file): write_trials( trials_file, exp_dir=self.exp_dir, trial_params=self.version["trials"], seed=self.subj_vars["seed"] ) (self.trials, self.trials_fields) = import_trials(trials_file, method="sequential") pic_params = {} pic_params["win"] = self.win pic_params["size"] = self.version["task"]["pic_size"] locs = self.version["task"]["pic_locs"] pic_dir = os.path.join(self.exp_dir, "stimuli", "pics") self.pics_left = load_images(pic_dir, "bmp", pos=locs["left"], **pic_params) self.pics_right = load_images(pic_dir, "bmp", pos=locs["right"], **pic_params) cue_dir = os.path.join(self.exp_dir, "stimuli", "cues") self.cues = load_sounds(cue_dir, "wav") noise_dir = os.path.join(self.exp_dir, "stimuli", "noise") self.noises = load_sounds(noise_dir, "wav") def _show_instructions(self): instructions = self.version["instructions"] for page in instructions["pages"]: self.stim["text"].setText(instructions["text"][page]) self.stim["text"].draw() if page == 2: self.pics_left["donkey.bmp"].setOri(180.0) self.pics_left["donkey.bmp"].draw() self.pics_right["donkey.bmp"].draw() advance_keys = ["j"] elif page == 3: self.pics_right["piano.bmp"].setOri(180.0) self.pics_right["piano.bmp"].draw() self.pics_left["piano.bmp"].draw() advance_keys = ["f"] elif page == 4: self.mask_left.draw() self.mask_right.draw() advance_keys = ["space"] else: advance_keys = ["space"] self.win.flip() event.waitKeys(keyList=advance_keys) def run(self): """ Run through text screens and trials. """ self._show_instructions() self.exp_timer = core.Clock() self.resp_keys = self.version["task"]["resp_keys"] # save columns for the DVs in the data file self.resp_fields = ["exp_timer", "response", "rt", "is_correct"] header_pth = os.path.join(self.version_dir, "data", "_header.txt") if not os.path.isfile(header_pth): mutable_trials_fields = list(self.trials_fields) header = self.subj_vars_fields + mutable_trials_fields + self.resp_fields header_file = open(header_pth, "w") write_list_to_file(header, header_file, close=True) cur_block_ix = -1 for cur_trial in self.trials: if cur_trial["block_ix"] > cur_block_ix: self._break_for_new_block(cur_block_ix) cur_block_ix += 1 self._present_trial(cur_trial) self.data_file.close() def _break_for_new_block(self, prev_block_ix): """ Show text screens between blocks """ if prev_block_ix == -1: end_practice_txt = self.version["end_of_practice"] for page in end_practice_txt["pages"]: show_text(self.win, end_practice_txt["text"][page], color="black") else: break_txt = self.version["break_text"] show_text(self.win, break_txt, color="black") def _give_feedback(self, is_correct): self.feedback[is_correct].play() def _present_trial(self, trial): """ Present a single trial """ if trial["cue_type"] == "noise": cue = self.noises[trial["cue_file"]] else: cue = self.cues[trial["cue_file"]] cue_dur = cue.getDuration() stims_after_cue = [] if trial["mask_type"] == "visual": stims_after_cue.extend([self.mask_left, self.mask_right]) left_pic = self.pics_left[trial["pic_file"]] # just a shortcut right_pic = self.pics_right[trial["pic_file"]] # if trial["up_pic"] == "left": left_pic.setOri(0.0) right_pic.setOri(180.0) else: left_pic.setOri(180.0) right_pic.setOri(0.0) ############################ # BEGIN TRIAL PRESENTATION # ############################ self.stim["fix"].autoDraw = True self.stim["left_frame"].autoDraw = True self.stim["right_frame"].autoDraw = True self.win.flip() core.wait(self.version["task"]["fix_dur"]) # play cue, if there is one if cue: cue.play() self.win.flip() core.wait(cue_dur) # if its an interference trial, present the interference # play auditory interference before entering visual interference loop if trial["mask_type"] == "auditory": self.noises["noise.wav"].play() post_timer = core.Clock() while post_timer.getTime() < cue_dur: # draw the masks if they were added to stims_after_cue [stim.draw() for stim in stims_after_cue] self.win.flip() core.wait(0.01) self.win.flip() self.win.flip() core.wait(self.version["task"]["cue_offset_to_pic_onset"]) # show pic and start RT timer left_pic.draw() right_pic.draw() rt_timer = core.Clock() self.win.flip() core.wait(self.version["task"]["pic_dur"]) # remove images and replace fix with prompt self.stim["fix"].autoDraw = False self.stim["prompt"].draw() self.win.flip() response = event.waitKeys( maxWait=self.version["task"]["max_wait"], keyList=self.resp_keys.keys(), timeStamped=rt_timer ) try: key, rt = response[0] response = self.resp_keys[key] except TypeError: rt = self.version["task"]["max_wait"] response = "timeout" is_correct = int(response == trial["up_pic"]) # end trial self.win.flip() if trial["block_ix"] == -1: self._give_feedback(is_correct) if response == "timeout": show_text(self.win, self.version["timeout_text"], color="black") core.wait(self.version["task"]["iti"] - rt) ########################## # END TRIAL PRESENTATION # ########################## # record response variables resp = {} resp["exp_timer"] = self.exp_timer.getTime() resp["response"] = response resp["rt"] = rt * 1000.0 resp["is_correct"] = is_correct # write trial data to file trial_data = [] [trial_data.append(self.subj_vars[v]) for v in self.subj_vars_fields] [trial_data.append(trial[v]) for v in self.trials_fields] [trial_data.append(resp[v]) for v in self.resp_fields] write_list_to_file(trial_data, self.data_file) def end(self): end_txt = self.version["end_text"] show_text(self.win, end_txt, color="black") # core.quit() surveyURL = self.version["survey_url"] surveyURL += "&subj_id={0}&room={1}&version={2}".format( self.subj_vars["subj_id"], self.subj_vars["room"], self.version_name ) webbrowser.open(surveyURL)
class Experiment(object): def __init__(self, exp_dir, version_file): """ Start the experiment by saving the experiment directory and loading the version parameters stored in .yaml format. """ self.exp_dir = exp_dir version_file_with_path = os.path.join(exp_dir, version_file) self.version = yaml.load(open(version_file_with_path, 'r')) self.version_name = self.version['version_name'] self.version_dir = os.path.join(self.exp_dir, self.version_name) if not os.path.isdir(self.version_dir): os.mkdir(self.version_dir) self._get_session_info() self._initialize_display() self._load_trials_and_stims() def _get_session_info(self): """ Pop up a GUI to get popup variables, such as the subject code, randomization seed, and experimenter initials. """ subj_info = {'1': { 'name':'subj_id', 'prompt':'EXP_XXX', 'options':'any', 'default': self.version_name+'_101'}, '2': { 'name':'seed', 'prompt':'Seed: ', 'options':'any', 'default': 101}, '3' : { 'name':'initials', 'prompt':'Experimenter initials', 'options':'any', 'default':''}} data_dir = os.path.join(self.version_dir, 'data') if not os.path.isdir(data_dir): os.mkdir(data_dir) received = False file_opened = False while not file_opened: received, self.subj_vars = enter_subj_info(self.version_name, exp_dir=self.exp_dir, options=subj_info) if self.subj_vars['subj_id'] == 'test': data_file = os.path.join(self.version_dir, 'sample_data.txt') try: os.remove(data_file) except OSError: pass else: data_file = os.path.join(data_dir, self.subj_vars['subj_id']+'.txt') if not received: popup_error(self.subj_vars) elif not os.path.isfile(data_file): file_opened = True self.data_file = open(data_file, 'w') else: popup_error('That subject code already exists!') self.subj_vars['room'] = socket.gethostname() # record experiment comp. self.subj_vars['seed'] = int(self.subj_vars['seed']) # save order of subject variables in the data file self.subj_vars_fields = ['subj_id', 'seed', 'initials', 'date', 'exp_name', 'room'] def _initialize_display(self): """ Make the window and all static objects to be used in the experiment. """ self.win = visual.Window(fullscr=True, color=[.6,.6,.6], allowGUI=False, monitor=self.subj_vars['room']+'-Monitor', units='pix', winType='pyglet') self.stim = {} # store static objects self.stim['text'] = visual.TextStim(self.win, height=30, pos=[0,350], color='black', wrapWidth=int(self.win.size[0]*.8)) self.stim['fix'] = visual.TextStim(self.win, text='+', height=100, color='black') self.stim['prompt'] = visual.TextStim(self.win, text='?', height=100, color='black') feedback_dir = os.path.join(self.exp_dir, 'stimuli', 'feedback') self.feedback = {} self.feedback[0] = sound.Sound(os.path.join(feedback_dir, 'buzz.wav')) self.feedback[1] = sound.Sound(os.path.join(feedback_dir, 'bleep.wav')) frame_params = {} frame_params['win'] = self.win frame_params['lineColor'] = 'black' frame_params['lineWidth'] = 2.0 frame_params['fillColor'] = None frame_params['width'] = self.version['task']['pic_size'][0] + 10 frame_params['height'] = self.version['task']['pic_size'][1] + 10 locs = self.version['task']['pic_locs'] self.stim['left_frame'] = visual.Rect(pos=locs['left'], **frame_params) self.stim['right_frame']= visual.Rect(pos=locs['right'], **frame_params) mask_params = {} mask_params['win'] = self.win mask_params['size'] = self.version['task']['pic_size'] mask_dir = os.path.join(self.exp_dir, 'stimuli', 'dynamic_mask') self.mask_left = DynamicMask(mask_dir, 'colored', ori=0.0, pos=locs['left'], **mask_params) self.mask_right = DynamicMask(mask_dir, 'colored', ori=180.0, pos=locs['right'], **mask_params) def _load_trials_and_stims(self): """ Load the trials and stimuli from external files. """ show_text(self.win, 'Loading...', color='black', waitForKey=False) trials_dir = os.path.join(self.version_dir, 'trials') if not os.path.isdir(trials_dir): os.mkdir(trials_dir) if self.subj_vars['subj_id'] == 'test': trials_file = os.path.join(self.version_dir, 'sample_trials.csv') try: os.remove(trials_file) except OSError: pass else: trials_file = 'seed_{0}.csv'.format(self.subj_vars['seed']) trials_file = os.path.join(trials_dir, trials_file) if not os.path.isfile(trials_file): write_trials(trials_file, exp_dir=self.exp_dir, trial_params=self.version['trials'], seed=self.subj_vars['seed']) (self.trials, self.trials_fields) = import_trials(trials_file, method='sequential') pic_params = {} pic_params['win'] = self.win pic_params['size'] = self.version['task']['pic_size'] locs = self.version['task']['pic_locs'] pic_dir = os.path.join(self.exp_dir, 'stimuli', 'pics') self.pics_left = load_images(pic_dir, 'bmp', pos=locs['left'], **pic_params) self.pics_right = load_images(pic_dir, 'bmp', pos=locs['right'], **pic_params) cue_dir = os.path.join(self.exp_dir, 'stimuli', 'cues') self.cues = load_sounds(cue_dir, 'wav') noise_dir = os.path.join(self.exp_dir, 'stimuli', 'noise') self.noises = load_sounds(noise_dir, 'wav') def _show_instructions(self): instructions = self.version['instructions'] for page in instructions['pages']: self.stim['text'].setText(instructions['text'][page]) self.stim['text'].draw() if page == 2: self.pics_left['donkey.bmp'].setOri(180.0) self.pics_left['donkey.bmp'].draw() self.pics_right['donkey.bmp'].draw() advance_keys = ['j',] elif page == 3: self.pics_right['piano.bmp'].setOri(180.0) self.pics_right['piano.bmp'].draw() self.pics_left['piano.bmp'].draw() advance_keys = ['f',] elif page == 4: self.mask_left.draw() self.mask_right.draw() advance_keys = ['space',] else: advance_keys = ['space',] self.win.flip() event.waitKeys(keyList = advance_keys) def run(self): """ Run through text screens and trials. """ self._show_instructions() self.exp_timer = core.Clock() self.resp_keys = self.version['task']['resp_keys'] # save columns for the DVs in the data file self.resp_fields = ['exp_timer','response','rt','is_correct'] header_pth = os.path.join(self.version_dir, 'data', '_header.txt') if not os.path.isfile(header_pth): mutable_trials_fields = list(self.trials_fields) header = self.subj_vars_fields+mutable_trials_fields+self.resp_fields header_file = open(header_pth, 'w') write_list_to_file(header, header_file, close=True) cur_block_ix = -1 for cur_trial in self.trials: if cur_trial['block_ix'] > cur_block_ix: self._break_for_new_block(cur_block_ix) cur_block_ix += 1 self._present_trial(cur_trial) self.data_file.close() def _break_for_new_block(self, prev_block_ix): """ Show text screens between blocks """ if prev_block_ix == -1: end_practice_txt = self.version['end_of_practice'] for page in end_practice_txt['pages']: show_text(self.win, end_practice_txt['text'][page], color='black') else: break_txt = self.version['break_text'] show_text(self.win, break_txt, color='black') def _give_feedback(self, is_correct): self.feedback[is_correct].play() def _present_trial(self, trial): """ Present a single trial """ if trial['cue_type'] == 'noise': cue = self.noises[trial['cue_file']] else: cue = self.cues[trial['cue_file']] cue_dur = cue.getDuration() stims_during_cue = [] stims_after_cue = [] if trial['is_mask_overlap'] == 1: stims_during_cue.extend([self.mask_left, self.mask_right]) elif trial['is_mask_overlap'] == 0: stims_after_cue.extend([self.mask_left, self.mask_right]) left_pic = self.pics_left[trial['pic_file']] # just a shortcut right_pic = self.pics_right[trial['pic_file']] # if trial['up_pic'] == 'left': left_pic.setOri(0.0) right_pic.setOri(180.0) else: left_pic.setOri(180.0) right_pic.setOri(0.0) ############################ # BEGIN TRIAL PRESENTATION # ############################ # show fixation cross self.stim['fix'].draw() self.win.flip() core.wait(self.version['task']['fix_dur']) # play cue and show mask cue_timer = core.Clock() cue.play() while cue_timer.getTime() < cue_dur: [stim.draw() for stim in stims_during_cue] self.win.flip() core.wait(0.01) self.win.flip() post_timer = core.Clock() while post_timer.getTime() < cue_dur: [stim.draw() for stim in stims_after_cue] self.win.flip() core.wait(0.01) self.win.flip() # show frames self.stim['left_frame'].draw() self.stim['right_frame'].draw() self.win.flip() core.wait(self.version['task']['cue_offset_to_pic_onset']) # show pic and start RT timer self.stim['left_frame'].draw() self.stim['right_frame'].draw() left_pic.draw() right_pic.draw() rt_timer = core.Clock() self.win.flip() core.wait(self.version['task']['pic_dur']) # replace images with prompt self.stim['prompt'].draw() self.win.flip() response = event.waitKeys(maxWait=self.version['task']['max_wait'], keyList=self.resp_keys.keys(), timeStamped=rt_timer) try: key, rt = response[0] response = self.resp_keys[key] except TypeError: rt = self.version['task']['max_wait'] response = 'timeout' is_correct = int(response == trial['up_pic']) # end trial self.win.flip() if trial['block_ix'] == -1: self._give_feedback(is_correct) if response == 'timeout': show_text(self.win, self.version['timeout_text'], color='black') core.wait(self.version['task']['iti'] - rt) ########################## # END TRIAL PRESENTATION # ########################## # record response variables resp = {} resp['exp_timer'] = self.exp_timer.getTime() resp['response'] = response resp['rt'] = rt*1000.0 resp['is_correct'] = is_correct # write trial data to file trial_data = [] [trial_data.append(self.subj_vars[v]) for v in self.subj_vars_fields] [trial_data.append(trial[v]) for v in self.trials_fields] [trial_data.append(resp[v]) for v in self.resp_fields] write_list_to_file(trial_data, self.data_file) def end(self): end_txt = self.version['end_text'] show_text(self.win, end_txt, color='black') #core.quit() surveyURL = self.version['survey_url'] surveyURL += '&subj_id={0}&room={1}&version={2}'.format( self.subj_vars['subj_id'], self.subj_vars['room'], self.version_name ) webbrowser.open(surveyURL)