def test_comparison_not_equal_after_iteration(self): q1 = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q2 = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=2) q1.__next__() q2.__next__() assert q1 != q2
def run_quest_procedure(self, n_trials=10, start=20, start_sd=10, maxvalue=40, beta=3.5, delta=0.01, gamma=0.01, grain=0.01): print("RUNNING QUEST\n" \ "-------------\n") self.quest = data.QuestHandler(start, start_sd, pThreshold=0.75, nTrials=n_trials, minVal=0, maxVal=maxvalue, beta=beta, delta=delta, gamma=gamma, grain=grain) for n, stim_level in enumerate(self.quest): print("Calibration trial {0} / {1}\n" \ "Stimulation level = {2}\n" \ "-----------------------------".format(n + 1, n_trials, stim_level)) response = None # Repeat if subject doesn't respond while response == None: trial = ConditioningTrial(self, n, True, max_voltage=stim_level) response = trial.run() # Add the response if response: print("Stimulation detected") else: print("Stimulation not detected") if n >= self.config['quest_settings']['n_ignored']: self.quest.addResponse(int(response)) p0 = self.quest.quantile(0.01) p25 = self.quest.quantile(.25) p50 = self.quest.quantile(.50) p75 = self.quest.quantile(.75) print("1% detection probability level = {0}\n" "25% detection probability level = {1}\n" "50% detection probability level = {2}\n" "75% detection probability level = {3}\n".format( self.quest.quantile(0.01), self.quest.quantile(.25), self.quest.quantile(.5), self.quest.quantile(.75))) return p0, p25, p50, p75
def test_json_dump(self): q = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) dump = q.saveAsJson() q.origin = '' assert q == json_tricks.np.loads(dump)
def test_comparison_not_equal(self): q1 = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q2 = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=2) assert q1 != q2
def test_epsilon(self): # Values used by Harvey (1986), Table 3. beta = 3.5 gamma = 0 delta = 0 epsilon = 0.0571 # QuestHandler needs this, but it doesn't influence our # test. Values chosen arbitrarily. startVal = 0.5 startValSd = 1 # Estimate the target proportion correct based on epsilon. # (The values provided by Harvey (1986) are rounded to two # decimal places and, therefore, too imprecise. So we # have to we calculate it again.) def weibull(x, beta, gamma, delta): p = delta * gamma + (1 - delta) * ( 1 - (1 - gamma) * np.exp(-10**(beta * x))) return p p = weibull(x=epsilon, beta=beta, gamma=gamma, delta=delta) q = data.QuestHandler(startVal=startVal, startValSd=startValSd, pThreshold=p, beta=beta, gamma=gamma, delta=delta) assert np.isclose(q.epsilon, epsilon, atol=1e-4)
def test_QuestHandler(self): nTrials = 10 startVal, minVal, maxVal = 50, 0, 100 range = maxVal - minVal startValSd = 50 grain = 0.01 pThreshold = 0.82 beta, gamma, delta = 3.5, 0.5, 0.01 stopInterval = None method = 'quantile' self.stairs = data.QuestHandler(startVal, startValSd, pThreshold=pThreshold, nTrials=nTrials, stopInterval=stopInterval, method=method, beta=beta, gamma=gamma, delta=delta, grain=grain, range=range, minVal=minVal, maxVal=maxVal) self.stairs.nReversals = None self.responses = makeBasicResponseCycles(cycles=3, nCorrect=2, nIncorrect=2, length=10) self.intensities = [ 50, 45.139710407074872, 37.291086503930742, 58.297413127139947, 80.182967131096547, 75.295251409003527, 71.57627192423783, 79.881680484036906, 90.712313302815517, 88.265808957695796 ] mean = 86.0772169427 mode = 80.11 quantile = 86.3849031085 self.simulate() self.checkSimulationResults() assert self.stairs.startVal == startVal assert self.stairs.startValSd == startValSd assert np.allclose(self.stairs.mean(), mean) assert np.allclose(self.stairs.mode(), mode) assert np.allclose(self.stairs.quantile(), quantile) # Check if the internal grid has the expected dimensions. assert len(self.stairs._quest.x) == (maxVal - minVal) / grain + 1 assert np.allclose(self.stairs._quest.x[1] - self.stairs._quest.x[0], grain) assert self.stairs._quest.x[0] == -range / 2 assert self.stairs._quest.x[-1] == range / 2
def test_json_dump_with_data(self): q = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q.addResponse(1) q.addOtherData('foo', 'bar') dump = q.saveAsJson() q.origin = '' assert q == json_tricks.np.loads(dump)
def test_json_dump_to_file(self): _, path = mkstemp(dir=self.tmp_dir, suffix='.json') q = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q.saveAsJson(fileName=path, fileCollisionMethod='overwrite')
def get_blocks(self): blocks = [] for name in self.oris.keys(): blocks.append( data.QuestHandler(15, 3, pThreshold=.63, nTrials=self.ntrials_diff, minVal=0, maxVal=25, name=name)) self.blocks = blocks
def test_json_dump_and_reopen_file(self): q = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q.addResponse(1) q.addOtherData('foo', 'bar') q.__next__() _, path = mkstemp(dir=self.tmp_dir, suffix='.json') q.saveAsJson(fileName=path, fileCollisionMethod='overwrite') q.origin = '' q_loaded = fromFile(path) assert q == q_loaded
def test_attributes(self): beta = 3.5 gamma = 0.01 delta = 0.02 grain = 0.01 range = 1 q = data.QuestHandler(startVal=0.5, startValSd=0.2, pThreshold=0.63, nTrials=10, beta=beta, gamma=gamma, delta=delta, grain=grain, range=range) assert q.beta == beta assert q.gamma == gamma assert q.delta == delta assert q.grain == grain assert q.range == range
def run_session(self): path = os.path.join(self.res_dir, self.subject) if not os.path.exists(path): os.makedirs(path) # welcome msg = visual.TextStim(self.win, 'Welcome!' + '\n' + ' Press any key to start this session :)', color='black', units='deg', pos=(0, 0), height=0.8) msg.draw() self.win.mouseVisible = False self.win.flip() event.waitKeys() # read staircase parameters conditions = [ dict({'stimulus': key}, **value) for key, value in self.param.items() if key.startswith('stimulus') ] if conditions[0]['stairType'] == 'simple': stairs = data.MultiStairHandler(stairType='simple', conditions=conditions, nTrials=self.trial_nmb, method='sequential') elif conditions[0]['stairType'] == 'quest': stairs = [] for cond in conditions: if self.priors_file_path: prior_file = self.priors_file_path + cond[ 'label'] + '.psydat' print(prior_file) prior_handler = misc.fromFile(prior_file) else: prior_handler = None cur_handler = data.QuestHandler(cond['startVal'], cond['startValSd'], pThreshold=cond['pThreshold'], nTrials=self.trial_nmb, minVal=cond['min_val'], maxVal=cond['max_val'], staircase=prior_handler, extraInfo=cond) stairs.append(cur_handler) elif conditions[0]['stairType'] == 'psi': stairs = [] for cond in conditions: if self.priors_file_path: prior_file = self.priors_file_path + cond['label'] + '.npy' else: prior_file = None print(prior_file) cur_handler = data.PsiHandler(nTrials=self.trial_nmb, intensRange=[1, 10], alphaRange=[1, 10], betaRange=[0.01, 10], intensPrecision=0.1, alphaPrecision=0.1, betaPrecision=0.01, delta=0.01, extraInfo=cond, prior=prior_file, fromFile=(prior_file is not None)) stairs.append(cur_handler) # write configuration files xpp = config_tools.WriteXpp(self.subject, self.idx) xpp_file = xpp.head(self.cfg_file, self.par_file) config_tools.write_xrl(self.subject, cfg_file=self.cfg_file, par_file=self.par_file, xpp_file=xpp_file) xlsname = path + '/' + self.idx + self.param[ 'noise_condition'] + '.xlsx' """ running staircase """ if isinstance(stairs, data.MultiStairHandler): # start running the staircase using the MultiStairHandler for the up-down method count = 0 for rot, cond in stairs: count += 1 judge, thiskey, trial_time = self.run_trial(rot, cond, count) # check whether the theta is valid - if not, the rotation given by staircase should be corrected by # realizable values valid_theta = np.round(np.load(self.hue_list), decimals=1) disp_standard = self.take_closest( valid_theta, cond['standard']) # theta actually displayed stair_test = cond['standard'] + stairs._nextIntensity * (-1)**( cond['label'].endswith('m')) # theta for staircase if stair_test < 0: stair_test += 360 disp_test = self.take_closest(valid_theta, stair_test) disp_intensity = abs(disp_test - disp_standard) if disp_intensity > 300: disp_intensity = 360 - (disp_test + disp_standard) stairs.addResponse(judge, disp_intensity) xpp.task(count, cond, rot, float(disp_intensity), judge, trial_time) event.waitKeys(keyList=[ thiskey ]) # press the response key again to start the next trial config_tools.write_xrl(self.subject, xls_file=xlsname) stairs.saveAsExcel(xlsname) # save results psydat_file_path = os.path.join( path, "psydat", self.idx + self.param['condition'] + '.psydat') # save the handler into a psydat-file misc.toFile(psydat_file_path, stairs) elif isinstance(stairs, list): # start running the staircase using custom interleaving stairs for the quest and psi methods count = 0 rot_all = [] rot_all_disp = [] judge_all = [] estimates = {s.extraInfo['label']: [] for s in stairs} for trial_n in range(self.trial_nmb): for handler_idx, cur_handler in enumerate(stairs): count += 1 rot = next(cur_handler) if len(rot_all) <= handler_idx: rot_all.append([]) rot_all[handler_idx].append(rot) cond = cur_handler.extraInfo judge, thiskey, trial_time = self.run_trial( rot, cond, count) if len(judge_all) <= handler_idx: judge_all.append([]) judge_all[handler_idx].append(judge) # cur_handler.addResponse(judge) # to the next trial valid_theta = np.round(np.load(self.hue_list), decimals=1) disp_standard = self.take_closest( valid_theta, cond['standard']) # theta actually displayed stair_test = cond[ 'standard'] + cur_handler._nextIntensity * (-1)**( cond['label'].endswith('m')) # theta for staircase if stair_test < 0: stair_test += 360 disp_test = self.take_closest(valid_theta, stair_test) disp_intensity = abs(disp_test - disp_standard) if disp_intensity > 300: disp_intensity = 360 - (disp_test + disp_standard) cur_handler.addResponse(judge, disp_intensity) if len(rot_all_disp ) <= handler_idx: # add displayed intensities rot_all_disp.append([]) rot_all_disp[handler_idx].append(disp_intensity) print('stair test: ' + str(stair_test) + ', ' + 'disp_test:' + str(disp_test)) if isinstance(cur_handler, data.PsiHandler): estimates[cur_handler.extraInfo['label']].append([ cur_handler.estimateLambda()[0], # location cur_handler.estimateLambda768()[1], # slope cur_handler.estimateThreshold(0.75) ]) elif isinstance(cur_handler, data.QuestHandler): estimates[cur_handler.extraInfo['label']].append([ cur_handler.mean(), cur_handler.mode(), cur_handler.quantile(0.5) ]) xpp.task(count, cond, rot, disp_intensity, judge, trial_time) event.waitKeys(keyList=[ thiskey ]) # press the response key again to start the next trial config_tools.write_xrl(self.subject, xls_file=xlsname) # save results in xls-file workbook = xlsxwriter.Workbook(xlsname) for handler_idx, cur_handler in enumerate(stairs): worksheet = workbook.add_worksheet( cur_handler.extraInfo['label']) worksheet.write('A1', 'Reversal Intensities') worksheet.write('B1', 'Reversal Indices') worksheet.write('C1', 'All Intensities') worksheet.write('D1', 'All Responses') for i in range(len(rot_all[handler_idx])): # worksheet.write('C' + str(i + 2), rot_all[handler_idx][i]) worksheet.write('C' + str(i + 2), rot_all_disp[handler_idx][i]) worksheet.write('D' + str(i + 2), judge_all[handler_idx][i]) workbook.close() # print resulting parameters and estimates for each step res_file_path = os.path.join(path, self.idx + '_estimates.csv') res_writer = csv.writer(open(res_file_path, 'w')) for res_stim, res_vals in estimates.items(): for res_val_id, res_val in enumerate(res_vals): res_writer.writerow([ res_stim, res_val_id, res_val[0], res_val[1], res_val[2] ]) # save each handler into a psydat-file and save posterior into a numpy-file for cur_handler in stairs: file_name = os.path.join( path, self.idx + self.param['noise_condition'] + cur_handler.extraInfo['label']) misc.toFile(file_name + '.psydat', cur_handler) if isinstance(cur_handler, data.PsiHandler): cur_handler.savePosterior(file_name + '.npy')
thisStair.addResponse(correct, isoi) trialData.write('{},{},{},{}\n' .format(thisStair.extraInfo['Label'],isoi, direction, int(correct))) print('{} of {} trials complete\n' .format(trialNum+1, nTrials)) print('\n=== EXPERIMENT FINISHED ===\n') ## ---- ## -- use quest to estimate threshold -- if exptInfo['09. Number of trials per staircase'] > 0: for s in staircases: s.saveAsPickle(fileName+'_'+s.extraInfo['Label']) #special python binary file to save all the info q = data.QuestHandler(log(exptInfo['06. First ISOI (ms)']), log(exptInfo['06. First ISOI (ms)']), pThreshold = 0.82, nTrials = exptInfo['09. Number of trials per staircase'], grain=0.1, range=10, minVal = log(exptInfo['11. Min ISOI (ms)']), maxVal = log(exptInfo['12. Max ISOI (ms)'])) isois = [log(i) for i in s.intensities] q.importData(isois,s.data) threshold = exp(q.mean()) tSD = exp(q.sd()) print('\n'+s.extraInfo['Label']) print('threshold: {}' .format(threshold)) print('sd: {}' .format(tSD)) thresholdData.write('{},{},{},{},{},{},{},{},{},{}\n' .format(threshold,tSD, s.extraInfo['Label'], s.extraInfo['direction'], exptInfo['02. Test number'],
def test_json_dump_to_file(self): q = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, nTrials=20, minVal=0, maxVal=1) q.saveAsJson(fileName=self.tmp_dir, fileCollisionMethod='overwrite')
def run_session(self): path = os.path.join(self.res_dir, self.subject) if not os.path.exists(path): os.makedirs(path) psydat_path = os.path.join(path, 'psydat') if not os.path.exists(psydat_path): os.makedirs(psydat_path) # welcome msg = visual.TextStim(self.win, 'Welcome!' + '\n' + ' Press any key to start this session :)', color='black', units='deg', pos=(0, 0), height=0.8) msg.draw() self.win.mouseVisible = False self.win.flip() event.waitKeys() # read staircase parameters conditions = [ dict({'stimulus': key}, **value) for key, value in self.param.items() if key.startswith('stimulus') ] if conditions[0]['stairType'] == 'simple': stairs = data.MultiStairHandler(stairType='simple', conditions=conditions, nTrials=self.trial_nmb, method='sequential') elif conditions[0]['stairType'] == 'quest': stairs = [] for cond in conditions: if self.priors_file_path: prior_file = self.priors_file_path + cond[ 'label'] + '.psydat' print(prior_file) prior_handler = misc.fromFile(prior_file) else: prior_handler = None cur_handler = data.QuestHandler(cond['startVal'], cond['startValSd'], pThreshold=cond['pThreshold'], nTrials=self.trial_nmb, minVal=cond['min_val'], maxVal=cond['max_val'], staircase=prior_handler, extraInfo=cond, grain=0.02) stairs.append(cur_handler) elif conditions[0]['stairType'] == 'psi': stairs = [] for cond in conditions: if self.priors_file_path: prior_file = self.priors_file_path + cond['label'] + '.npy' else: prior_file = None print(prior_file) cur_handler = data.PsiHandler(nTrials=self.trial_nmb, intensRange=[1, 10], alphaRange=[1, 10], betaRange=[0.01, 10], intensPrecision=0.1, alphaPrecision=0.1, betaPrecision=0.01, delta=0.01, extraInfo=cond, prior=prior_file, fromFile=(prior_file is not None)) stairs.append(cur_handler) # write configuration files xpp = config_tools.WriteXpp(self.subject, self.idx) xpp_file = xpp.head(self.cfg_file, self.par_file) config_tools.write_xrl(self.subject, cfg_file=self.cfg_file, par_file=self.par_file, xpp_file=xpp_file) xlsname = path + '/' + self.idx + self.param[ 'noise_condition'] + '.xlsx' """ running staircase """ if isinstance(stairs, data.MultiStairHandler): # start running the staircase using the MultiStairHandler for the up-down method count = 0 for rot, cond in stairs: count += 1 direction = (-1)**(cond['label'].endswith('m') ) # direction as -1 if for minus stim rot = rot * direction # rotation for this trial judge, react_time, trial_time_start = self.run_trial( rot, cond, cond['std'], count) # check whether the theta is valid - if not, the rotation given by staircase should be corrected by # realizable values valid_theta = np.round(np.load(self.hue_list), decimals=1) disp_standard = self.take_closest( valid_theta, cond['standard']) # theta actually displayed stair_test = cond[ 'standard'] + stairs._nextIntensity * direction if stair_test < 0: stair_test += 360 disp_test = self.take_closest(valid_theta, stair_test) disp_intensity = disp_test - disp_standard if disp_intensity > 300: disp_intensity = (disp_test + disp_standard) - 360 stairs.addResponse(judge, abs(disp_intensity)) xpp.task(count, cond, rot, float(disp_intensity), judge, react_time, trial_time_start) if 'escape' in event.waitKeys(): config_tools.write_xrl(self.subject, break_info='userbreak') core.quit() config_tools.write_xrl(self.subject, xls_file=xlsname) stairs.saveAsExcel(xlsname) # save results misc.toFile( os.path.join( psydat_path, self.idx + self.param['noise_condition'] + '.psydat'), stairs) elif isinstance(stairs, list): # start running the staircase using custom interleaving stairs for the quest and psi methods count = 0 rot_all = [] rot_all_disp = [] judge_all = [] estimates = {s.extraInfo['label']: [] for s in stairs} for trial_n in range(self.trial_nmb): for handler_idx, cur_handler in enumerate(stairs): count += 1 direction = (-1)**( cur_handler.extraInfo['label'].endswith('m') ) # direction as -1 if for minus stim if cur_handler._nextIntensity >= 10.0: sys.exit( "Hue difference is out of range! Please enlarge the testing range or take more training!" ) rot = cur_handler._nextIntensity * direction # rotation for this trial if trial_n >= 5: # avoid repeating an intensity more than 3 times last_rots = [ np.round(r, decimals=1) for r in [ rot_all_disp[handler_idx][trial_n - 1], rot_all_disp[handler_idx][trial_n - 2], rot_all_disp[handler_idx][trial_n - 3] ] ] last_resp = [ judge_all[handler_idx][trial_n - 1], judge_all[handler_idx][trial_n - 2], judge_all[handler_idx][trial_n - 3] ] if last_rots[0] == last_rots[1] == last_rots[2] \ and last_resp[0] == last_resp[1] == last_resp[2]: if cur_handler._nextIntensity > 0.5: rot = (cur_handler._nextIntensity - 0.5) * direction print('Intensity decreases by 0.5!') if cur_handler._nextIntensity <= 0.5: rot = (cur_handler._nextIntensity + 0.5) * direction print('Intensity increases by 0.5!') cond = cur_handler.extraInfo judge, react_time, trial_time_start = self.run_trial( rot, cond, cond['std'], count) if len(rot_all) <= handler_idx: rot_all.append([]) rot_all[handler_idx].append(rot) if len(judge_all) <= handler_idx: judge_all.append([]) judge_all[handler_idx].append(judge) valid_theta = np.round(np.load(self.hue_list), decimals=1) disp_standard = self.take_closest(valid_theta, cond['standard']) stair_test = cond[ 'standard'] + rot # calculated test hue for this trial if stair_test < 0: stair_test += 360 disp_test = self.take_closest( valid_theta, stair_test) # actual displayed test hue for this trial disp_intensity = disp_test - disp_standard # actual displayed hue difference if disp_intensity > 300: disp_intensity = (disp_test + disp_standard) - 360 cur_handler.addResponse( judge, abs(disp_intensity) ) # only positive number is accepted by addResponse if len(rot_all_disp ) <= handler_idx: # add displayed intensities rot_all_disp.append([]) rot_all_disp[handler_idx].append(disp_intensity) if isinstance(cur_handler, data.PsiHandler): estimates[cur_handler.extraInfo['label']].append([ cur_handler.estimateLambda()[0], # location cur_handler.estimateLambda768()[1], # slope cur_handler.estimateThreshold(0.75) ]) elif isinstance(cur_handler, data.QuestHandler): estimates[cur_handler.extraInfo['label']].append([ cur_handler.mean(), cur_handler.mode(), cur_handler.quantile(0.5) ]) xpp.task(count, cond, rot, disp_intensity, judge, react_time, trial_time_start) if 'escape' in event.waitKeys(): config_tools.write_xrl(self.subject, break_info='userbreak') core.quit() config_tools.write_xrl(self.subject, xls_file=xlsname) # save results in xls-file workbook = xlsxwriter.Workbook(xlsname) for handler_idx, cur_handler in enumerate(stairs): worksheet = workbook.add_worksheet( cur_handler.extraInfo['label']) worksheet.write('A1', 'Reversal Intensities') worksheet.write('B1', 'Reversal Indices') worksheet.write('C1', 'All Intensities') worksheet.write('D1', 'All Responses') for i in range(len(rot_all[handler_idx])): # worksheet.write('C' + str(i + 2), rot_all[handler_idx][i]) worksheet.write('C' + str(i + 2), rot_all_disp[handler_idx][i]) worksheet.write('D' + str(i + 2), judge_all[handler_idx][i]) workbook.close() # print resulting parameters and estimates for each step res_file_path = os.path.join(path, self.idx + '_estimates.csv') res_writer = csv.writer(open(res_file_path, 'w')) for res_stim, res_vals in estimates.items(): for res_val_id, res_val in enumerate(res_vals): res_writer.writerow([ res_stim, res_val_id, res_val[0], res_val[1], res_val[2] ]) # save each handler into a psydat-file and save posterior into a numpy-file for cur_handler in stairs: file_name = os.path.join( psydat_path, self.idx + self.param['noise_condition'] + cur_handler.extraInfo['label']) misc.toFile(file_name + '.psydat', cur_handler) if isinstance(cur_handler, data.PsiHandler): cur_handler.savePosterior(file_name + '.npy')
ax2.tick_params(axis='x',which='minor',bottom='off') # #save figure to file # outputFile = os.path.join(dataDir, 'test.pdf') # pylab.savefig(outputFile) if __name__ == "__main__": #Test staircase functions threshCriterion = 0.25 staircaseTrials = 5 staircase = data.QuestHandler(startVal = 95, startValSd = 80, stopInterval= 1, #sd of posterior has to be this small or smaller for staircase to stop, unless nTrials reached nTrials = staircaseTrials, #extraInfo = thisInfo, pThreshold = threshCriterion, #0.25, gamma = 1./26, delta=0.02, #lapse rate, I suppose for Weibull function fit method = 'quantile', #uses the median of the posterior as the final answer stepType = 'log', #will home in on the 80% threshold. But stepType = 'log' doesn't usually work minVal=1, maxVal = 100 ) print('created QUEST staircase') descendingPsycho = False noiseEachTrial = np.array([5,5,5,5,5,5,5,5,5,10,10,10,10,10,10,10,10,10,10,10,20,20,20,20,20,20,20,20,20,20,20,20,50,50,50,50,50,50,50,60,60,60,60,60,60,60,60,60,60,70,70,70,70,70,70,70,80,80,80,80,80,80,80,80,95,95,95,95,95,95,95]) centeredOnZero = noiseEachTrial/100. -0.5 guessRate = .1 #doesnt work with guessRate=0, fitWeibull doesnt like that pCorrEachTrial = guessRate*.5 + (1-guessRate)* 1. / (1. + np.exp(-20*centeredOnZero)) #sigmoidal probability print('pCorrEachTrial=',np.around(pCorrEachTrial,2)) corrEachTrial = np.zeros( len(noiseEachTrial) )
screen.setSizePix([1680, 1050]) screen.setWidth(47.475) screen.setDistance(57) win = visual.Window(allowGUI=True, units='deg', monitor=screen) grating = visual.GratingStim(win, tex='sin', mask='gauss', contrast=1, sf=3, size=3) staircase = data.QuestHandler(0.5, 0.2, pThreshold=0.63, gamma=0.01, minVal=0, maxVal=1, ntrials=10) orientations = [-45, 45] responses = ['left', 'right'] for contrast in staircase: keys = [] # randomise orientation for this trial grating.ori = np.random.choice(orientations) # update the difficulty (the contrast) grating.contrast = contrast # before the trial: wait 500ms core.wait(0.5)
for thisComponent in instrPracticeComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) # check responses if ok1.keys in ['', [], None]: # No response was made ok1.keys=None thisExp.nextEntry() # the Routine "instrPractice" was not non-slip safe, so reset the non-slip timer routineTimer.reset() # --------Prepare to start Staircase "trials" -------- # set up handler to look after next chosen value etc if expInfo['participant'] == 'ideal' : trials = data.QuestHandler(startVal = 0.1, startValSd = 0.05, delta=0, pThreshold = 0.63, nTrials=1E3, minVal=0, maxVal=1) else : trials = data.QuestHandler(startVal = 0.5, startValSd = 0.2, pThreshold = 0.63, nTrials=50, minVal=0, maxVal=1) thisExp.addLoop(trials) # add the loop to the experiment level = thisTrial = 0.5 # initialise some vals face = visual.ImageStim(win, happy_face) key_resp_2 = event.BuilderKeyResponse() for thisTrial in trials: currentLoop = trials level = thisTrial
def runQuest(experimentStructure, stimuliStructure, data, feedbackText, pThreshold, questTrials): """ This function implements a Quest procedure to individualize the maximum contrast difference Currently not in use. Input: exerimentStructure: all general experimental properties stimuliStructure: all general stimulus properties data: PsychoPy data functions feedbackText: feedback text object instance pThreshold: threshold for quest procedure questTrials: number of trials for qQest procedure Return: trials.quantile: quantile of Quest posterior pdf accPerf: accumulated performance of current block """ # Create some shortnames thisExp = experimentStructure['thisExp'] patch1 = stimuliStructure['patch1'] patch2 = stimuliStructure['patch2'] questClock = stimuliStructure['questClock'] # Create some info to store with the data info = {} info['startPoints'] = [0.03, 0.04] info['nTrials'] = questTrials lowerBound = 0.01 upperBound = 0.05 whichLoop = 'quest' stairs = [] for thisStart in info['startPoints']: # We need a COPY of the info for each staircase # (or the changes here will be made to all the other staircases) thisInfo = copy.copy(info) # Now add any specific info for this staircase thisInfo['thisStart'] = thisStart #we might want to keep track of this trials = data.QuestHandler(startVal=thisStart, startValSd=0.1, extraInfo=thisInfo, pThreshold=pThreshold, nTrials=questTrials, minVal=lowerBound, maxVal=upperBound, name='quest', gamma=0.5) thisExp.addLoop(trials) stairs.append(trials) accPerf = 0.0 for trialN in range(info['nTrials']): for trials in stairs: thisDifference = trials.next() print 'start=%.2f, current=%.4f' % (trials.extraInfo['thisStart'], thisDifference) lowerBound = float('NaN') upperBound = float('NaN') positions = np.random.choice([0, 1]) if positions == 1: targetPatch = 'left' else: targetPatch = 'right' (decision1, trials) = createPatches(experimentStructure, stimuliStructure, questClock, lowerBound, upperBound, targetPatch, trials, whichLoop, thisDifference) if (decision1.keys == None): msg = 'Zu langsam!' giveFeedback(experimentStructure, stimuliStructure, feedbackText, msg) if decision1.corr: accPerf = accPerf + 1 thisExp.nextEntry() questQuantiles = [] for trials in stairs: questQuantiles.append(trials.quantile()) print(questQuantiles) print(np.mean(questQuantiles)) return (trials.quantile(), accPerf)
def main(): # Sets system time as the random seed np.random.seed(seed=None) # Prompt GUI for participant ID id = ask_for_id() # Set up window and how many frames image should be displayed depending on refresh rate of monitor window = visual.Window(color=bg_color, monitor='monitor', fullscr=full_screen) frame_rate = window.getActualFrameRate() # Display instructional messages for user instruction_msg = visual.TextStim( window, color=text_color, text= f"For each image, answer whether the building shown is damaged. Each image will be shown briefly.\n\nTo answer, please press {key_list[0]} for yes and {key_list[1]} for no.\n\nPress any key to continue." ) instruction_msg.draw() window.flip() event.waitKeys() instruction_msg.text = f"For the first few images, you will hear a beep if you give the correct answer.\n\nPress any key to continue." instruction_msg.draw() window.flip() event.waitKeys() plus_horiz = visual.ShapeStim(window, units='pix', vertices=[[-20, 0], [20, 0]]) plus_vert = visual.ShapeStim(window, units='pix', vertices=[[0, 20], [0, -20]]) user_data = pd.DataFrame(columns=data_columns) img = visual.ImageStim(window, size=img_dims) # Setup staircase procedures using defined parameters conds = data.importConditions('demo_params.csv') staircases = [] for cond in conds: staircases.append( data.QuestHandler(startVal=cond['startVal'], startValSd=cond['startValSd'], pThreshold=cond['pThreshold'], nTrials=cond['nTrials'], beta=cond['beta'], delta=cond['delta'], gamma=cond['gamma'], minVal=cond['minVal'], maxVal=cond['maxVal'])) img_list = get_imgs(img_dir, damage_subdir, num_each, nodamage_subdir) print(f"{len(img_list)} images loaded.") key_list.append('escape') # Run through image list with participant rounds = 1 while len(img_list) > 0: staircase = random.choice(staircases) curr_time = next(staircase) curr_time = float(format(curr_time, '.3f')) # Get and parse random image's information subdir, img_name = get_random_img(img_list) truth = 'y' if subdir == damage_subdir else 'n' img.setImage(img_dir + subdir + '/' + img_name) # Display the image for set number of frames frames_to_show = get_frames_to_show(curr_time, frame_rate) keypress = None start_time, end_time = time.time(), 0 for _ in range(frames_to_show): img.draw() window.flip() keypress = event.getKeys(keyList=key_list) if 'escape' in keypress: instruction_msg.text = 'Experiment aborted. Quitting...' instruction_msg.draw() window.flip() core.wait(3.0) core.quit() if len(keypress) > 0: end_time = time.time() break plus_horiz.draw() plus_vert.draw() window.flip() # Track reaction time for user response if keypress is None or len(keypress) == 0: keypress = event.waitKeys(keyList=key_list) if 'escape' in keypress: instruction_msg.text = 'Experiment aborted. Quitting...' instruction_msg.draw() window.flip() core.wait(3.0) core.quit() end_time = time.time() reaction_time = float(format(end_time - start_time, '.3f')) # Log and process user's input as correct or incorrect answer = 'y' if keypress[0] == key_list[0] else 'n' event.clearEvents() if answer == truth: if rounds <= feedback_rounds: beep = sound.Sound('A', secs=0.25) beep.play() staircase.addResponse(1) else: staircase.addResponse(0) user_data.loc[len(user_data)] = ([ img_name, answer, truth, curr_time, reaction_time ]) rounds += 1 if rounds == feedback_rounds + 1: instruction_msg.text = 'From now on, feedback will no longer be provided.' instruction_msg.draw() window.flip() core.wait(3.0) # Calculate the final presentation time from the average of the staircases avg = 0 for staircase in staircases: avg += next(staircase) avg /= len(staircases) with open('demo_result.txt', 'w') as f: f.write(f"The final presentation time is {avg} seconds.\n") user_data['Correct'] = ( user_data['Actual'] == user_data['Response']).astype(int) # Output individual participant data to .csv file if not os.path.exists(data_dir): os.makedirs(data_dir) user_data.to_csv(f"{data_dir}/data_{id}.csv") instruction_msg.text = "Test completed. Closing window..." instruction_msg.draw() window.flip() core.wait(3.0) core.quit()
'observer': subject_initials, } expInfo['dateStr'] = data.getDateStr() # add the current time # make a text file to save data fileName = expInfo['observer'] + expInfo['dateStr'] dataFile = open(fileName + '.csv', 'w') # a simple text file with 'comma-separated-values' dataFile.write('targetSide,volume,correct\n') # create the staircase handler staircase = data.QuestHandler(startVal=0.5, startValSd=0.05, pThreshold=0.65, gamma=0.5, beta=3.5, delta=0.01, nTrials=nr_trials, minVal=0, maxVal=1) # create window and stimuli win = visual.Window([1920, 1080], fullscr=True, allowGUI=True, monitor='testMonitor', units='pixels') fixation = visual.GratingStim(win, pos=(0, 0), tex='sin', mask='circle',
gen_concentration_steps()[exp_info['Substance']]) start_val = np.log10(get_start_val(exp_info['Substance'])) quest_params = dict( startVal=start_val, startValSd=np.log10(20), pThreshold=0.82, beta=3.5, gamma=0.01, delta=0.01, grain=0.01, range=2 * np.abs(concentration_steps.max() - concentration_steps.min()), nTrials=20, stopInterval=None) quest_handler = data.QuestHandler(**quest_params) exp_handler = data.ExperimentHandler(extraInfo=exp_info, savePickle=True, saveWideText=True, dataFileName=os.path.join( path['rawdata_dir'], path['base_filename'])) exp_handler.addLoop(quest_handler) for concentration_proposed in quest_handler: trial_number = quest_handler.thisTrialN + 1 # QuestHandler counts from 0. # QUEST proposes a concentration to present, # but we usually don't have that particular one prepared. # # So we pick the one from our set which is closest to the proposed.