def RunSequence( sequence ): #setup rules global rules global currentRule global ruleList; ruleList = [] #a list of tuples containing the sequence of sorting rules (0, 1, 2, 3) and required n of correct repeats per set(5, 6, 7) for item in sequence['blocks']: ruleList.append( (int(item['rule']), int(item['reps'])) ) print 'RULELIST:', ruleList global RULE_COUNT; RULE_COUNT = len( ruleList ) global ruleCount, cardCount, rightAnswers; ruleCount = 0; ShowInstruction( u'Aloita painamalla jotain näppäintä', -1 ) while ruleCount < RULE_COUNT: currentRule = rules[ruleList[ruleCount][0]] SetupTrial() NextTrial( taskType ) answer = GetResponse() if answer == 0: #ESC break else: GiveFeedback( taskType, answer ) #if enough right answers given, update rule if answer > 0: if rightAnswers % ruleRepeats == 0: # rightAnswers can't be 0 here since retVal wouldn't be > 0 ruleCount += 1 rightAnswers = 0 logging.flush() #now with every rule change! cardCount +=1
def doIdleTasks(app=None): global currentTask if currentTask and currentTask['thread'] and \ currentTask['thread'].is_alive(): # is currently tunning in a thread return 0 for taskName in tasks: thisTask = tasks[taskName] thisStatus = tasks[taskName]['status'] if thisStatus == NOT_STARTED: currentTask = thisTask currentTask['tStart'] = time.time() - _t0 currentTask['status'] = STARTED logging.debug('Started {} at {}'.format(taskName, currentTask['tStart'])) _doTask(taskName, app) return 0 # something is in motion elif thisStatus == STARTED: if not currentTask['thread'] \ or not currentTask['thread'].is_alive(): # task finished so take note and pick another currentTask['status'] = FINISHED currentTask['thread'] = None currentTask['tEnd'] = time.time() - _t0 logging.debug('Finished {} at {}'.format(taskName, currentTask['tEnd'])) currentTask = None continue else: return 0 logging.flush() return 1
def getEvents(self, timeout=10): """Look for a string that matches SDAT;\n.........EDAT;\n and process it as events """ foundDataStart=False t0=time.time() while not foundDataStart and (time.time()-t0)<timeout: if self.com.readline().startswith('SDAT'): foundDataStart=True logging.info("BBTK.getEvents() found data. Processing...") logging.flush() #we aren't in a time-critical period so flush messages break #check if we're processing data if not foundDataStart: logging.warning("BBTK.getEvents() found no data (SDAT was not found on serial port inputs") return [] #helper function to parse time and event code def parseEventsLine(line, lastState=None): """Returns a list of dictionaries, one for each change detected in the state """ state = line[:12] timeSecs = int(line[-14:-2])/10.0**6 evts=[] evt='' if lastState is None: evts.append({'evt':'', 'state':state, 'time':timeSecs}) else: for n in evtChannels.keys(): if state[n]!=lastState[n]: if state[n]=='1': evt = evtChannels[n]+"_on" else: evt = evtChannels[n]+"_off" evts.append({'evt':evt, 'state':state, 'time':timeSecs}) return evts #we've been sent data so work through it events=[] eventLines=[] lastState=None #try to read from port self.pause() self.com.setTimeout(2.0) nEvents = int(self.com.readline()[:-2]) #last two chars are ;\n self.com.readline()[:-2] # microseconds recorded (ignore) self.com.readline()[:-2] #samples recorded (ignore) while True: line = self.com.readline() if line.startswith('EDAT'): #end of data stream break events.extend(parseEventsLine(line, lastState)) lastState = events[-1]['state'] eventLines.append(line) if nEvents != len(eventLines): logging.warning("BBTK reported %i events but told us to expect %i events!!" %(len(events), nEvents)) logging.flush() #we aren't in a time-critical period so flush messages return events
def finish_sync(self): """Rebuilds index and saves project file when the sync has finished """ proj = self.proj() # when local/remote updates are complete refresh index based on local proj.local.rebuild_index() proj.index = proj.local.index self._set_empty() proj.save() if hasattr(logging, 'flush'): # psychopy.logging has control of flush logging.flush()
def _warnTesting(self): msg = "We need to run some tests on your graphics card (hopefully just once). " + \ "The BitsSharp will go into status mode while this is done. " + \ "It can take a minute or two." logging.warn(msg) logging.flush() msgOnScreen = visual.TextStim(self.win, msg) msgOnScreen.draw() self.win.flip() core.wait(1.0) self.win.flip()
def quit(): """Close everything and exit nicely (ending the experiment) """ #pygame.quit() #safe even if pygame was never initialised logging.flush() for thisThread in threading.enumerate(): if hasattr(thisThread,'stop') and hasattr(thisThread,'running'): #this is one of our event threads - kill it and wait for success thisThread.stop() while thisThread.running==0: pass#wait until it has properly finished polling sys.exit(0)#quits the python session entirely
def sendMessage(self, message, autoLog=True): """Send a command to the device (does not wait for a reply or sleep()) """ if self.com.inWaiting(): inStr = self.com.read(self.com.inWaiting()) logging.warning("Sending '%s' to %s but found '%s' on the input buffer" %(message, self.name, inStr)) if not message.endswith(self.eol): message += self.eol #append a newline if necess self.com.write(message) self.com.flush() if autoLog: logging.debug('Sent %s message:' %(self.name) +message.replace(self.eol, ''))#send complete message logging.flush() #we aren't in a time-critical period so flush messages
def shut_down_cleanly(subdata,win): """ shut down experiment and try to save data """ win.close() logging.flush() try: f=open('Output/%s_%s_subdata.pkl'%(subdata['subcode'],subdata['datestamp']),'wb') pickle.dump(subdata,f) f.close() except: pass
def throw_ball(fromP, toP): global trialCnt, holder, rndCnt key = "%ito%i" % (fromP,toP) logging.log(level=logging.DATA, msg="round %i - trial %i - throw: %s - %s" % (round, trialCnt, key, condition)) for s in throw[key]: players.setImage('images/%s/%s' % (key,s)) players.draw() win.flip() core.wait(0.15) trialCnt+=1 rndCnt+=1 holder=toP logging.flush() select_throw()
def sendMessage(self, message, autoLog=True): """Send a command to the device (does not wait for a reply or sleep()) """ if self.com.inWaiting(): inStr = self.com.read(self.com.inWaiting()) msg = "Sending '%s' to %s but found '%s' on the input buffer" logging.warning(msg % (message, self.name, inStr)) if type(message) is not bytes: message = bytes(message, 'utf-8') if not message.endswith(self.eol): message += self.eol # append a newline if necess self.com.write(message) self.com.flush() if autoLog: msg = b'Sent %s message:' % (self.name) logging.debug(msg + message.replace(self.eol, b'')) # complete msg # we aren't in a time-critical period so flush msg logging.flush()
def quit(): """Close everything and exit nicely (ending the experiment) """ #pygame.quit() #safe even if pygame was never initialised logging.flush() for thisThread in threading.enumerate(): if hasattr(thisThread,'stop') and hasattr(thisThread,'running'): #this is one of our event threads - kill it and wait for success thisThread.stop() while thisThread.running==0: pass#wait until it has properly finished polling # could check serverCreated() serverBooted() but then need to import pyo # checking serverCreated() does not tell you whether it was shutdown or not for ps in pyoServers: # should only ever be one Server instance... ps.stop() wait(.25) ps.shutdown() sys.exit(0)#quits the python session entirely
def clearMemory(self): """ """ self.sendMessage('SPIE') self.pause() reply = self.getResponse(timeout=10) # should return either FRMT or ESEC to indicate it started if reply.startswith('FRMT'): logging.info("BBTK.clearMemory(): " "Starting full format of BBTK memory") elif reply.startswith('ESEC'): logging.info("BBTK.clearMemory(): " "Starting quick erase of BBTK memory") else: logging.error("BBTK.clearMemory(): " "didn't get a reply from %s" % str(self.com)) return False # we aren't in a time-critical period so flush messages logging.flush() # now wait until we get told 'DONE' self.com.timeout = 20 retVal = self.com.readline() if retVal.startswith("DONE"): logging.info("BBTK.clearMemory(): completed") # we aren't in a time-critical period so flush messages logging.flush() return True else: logging.error("BBTK.clearMemory(): " "Stalled waiting for %s" % str(self.com)) # we aren't in a time-critical period so flush messages logging.flush() return False
def _upload(stuff): """assumes that SELECTOR_FOR_TEST_UPLOAD is a configured http server """ selector = SELECTOR_FOR_TEST_UPLOAD basicAuth = BASIC_AUTH_CREDENTIALS # make a tmp dir just for testing: tmp = mkdtemp() filename = "test.txt" tmp_filename = os.path.join(tmp, filename) f = open(tmp_filename, "w+") f.write(stuff) f.close() # get local sha256 before cleanup: digest = hashlib.sha256() digest.update(open(tmp_filename).read()) dgst = digest.hexdigest() # upload: status = upload(selector, tmp_filename, basicAuth) shutil.rmtree(tmp) # cleanup; do before asserts # test good_upload = True disgest_match = False if not status.startswith("success"): good_upload = False elif status.find(dgst) > -1: logging.exp("digests match") digest_match = True else: logging.error("digest mismatch") logging.flush() assert good_upload # remote server FAILED to report success assert digest_match # sha256 mismatch local vs remote file return int(status.split()[3]) # bytes
def recordStimulusData(self, duration): """Record data for a given duration (seconds) and return a list of events that occured in that period. """ self.sendMessage("DSCM") logging.flush() #we aren't in a time-critical period so flush messages time.sleep(5.0) self.sendMessage("TIML") logging.flush() #we aren't in a time-critical period so flush messages self.pause() self.sendMessage("%i" %(int(duration*1000000)), autoLog=False) #BBTK expects this in microsecs self.pause() self.sendMessage("RUDS") logging.flush() #we aren't in a time-critical period so flush messages
global lastScore global triggers; triggers=(confInfo[4]=='True') # flag as True when actually recording #SETUP CARDS cardPrototype = {'G1':0, 'G2':0, 'L1':0, 'L2':0, 'fn':''} #setup deck of four cards from the whole deck #deck = SelectCardSubset( 4, N_OF_CARDS ) global currentTgt; currentTgt = (-1, -1, -1, -1) #TODO: ADD ERROR CHECKING! Here we trust the json files to be correctly formed and valid confFile = open( '.'+s+'configs'+s+confInfo[3]+'.json' ) config = json.loads( confFile.read() ) confFile.close() logging.flush() gameScore = 0 lastScore = 0 triggerAndLog(portCodes['start'], "STR", 0, 0, "START: " + str( startTime ) ) win.setMouseVisible( False ) # - BASELINE VIDEO ------------------------------------------------------------------------------# movie_filename = "D:/Experiments/video/habit_video_01_wcst.avi" movie_baseline = visual.MovieStim(win = win, filename = movie_filename, pos = [0,0], size = (1350,1080)) if DEBUGGING_MODE: for i in range(25 * 3): movie_baseline.draw() win.flip()
def findIdentityLUT( self, maxIterations=1000, errCorrFactor=1.0 / 5000, # amount of correction done for each iteration nVerifications=50, #number of repeats (successful) to check dithering has been eradicated demoMode=True, #generate the screen but don't go into status mode logFile='', ): """Search for the identity LUT for this card/operating system. This requires that the window being tested is fullscreen on the Bits# monitor (or at least occupys the first 256 pixels in the top left corner!) :params: LUT: The lookup table to be tested (256x3). If None then the LUT will not be altered :returns: a 256x3 array of error values (integers in range 0:255) """ t0 = time.time() #create standard options LUTs = {} LUTs['intel'] = np.repeat(np.linspace(.05, .95, 256), 3).reshape([-1, 3]) LUTs['0-255'] = np.repeat(np.linspace(0, 1.0, 256), 3).reshape([-1, 3]) LUTs['0-65535'] = np.repeat( np.linspace(0.0, 65535.0 / 65536.0, num=256), 3).reshape([-1, 3]) LUTs['1-65536'] = np.repeat( np.linspace(0.0, 65535.0 / 65536.0, num=256), 3).reshape([-1, 3]) if logFile: self.logFile = open(logFile, 'w') if plotResults: pyplot.Figure() pyplot.subplot(1, 2, 1) pyplot.plot([0, 255], [0, 255], '-k') errPlot = pyplot.plot(range(256), range(256), '.r')[0] pyplot.subplot(1, 2, 2) pyplot.plot(200, 0.01, '.w') pyplot.show(block=False) lowestErr = 1000000000 bestLUTname = None logging.flush() for LUTname, currentLUT in LUTs.items(): sys.stdout.write('Checking %r LUT:' % (LUTname)) errs = self.testLUT(currentLUT, demoMode) if plotResults: errPlot.set_ydata(range(256) + errs[:, 0]) pyplot.draw() print('mean err = %.3f per LUT entry' % (abs(errs).mean())) if abs(errs).mean() < abs(lowestErr): lowestErr = abs(errs).mean() bestLUTname = LUTname if lowestErr == 0: print("The %r identity LUT produced zero error. We'll use that!" % (LUTname)) self.identityLUT = LUTs[bestLUTname] self.save() #it worked so save this configuration for future return print("Best was %r LUT (mean err = %.3f). Optimising that..." % (bestLUTname, lowestErr)) currentLUT = LUTs[bestLUTname] errProgression = [] corrInARow = 0 for n in range(maxIterations): errs = self.testLUT(currentLUT) tweaks = errs * errCorrFactor currentLUT -= tweaks currentLUT[currentLUT > 1] = 1.0 currentLUT[currentLUT < 0] = 0.0 meanErr = abs(errs).mean() errProgression.append(meanErr) if plotResults: errPlot.set_ydata(range(256) + errs[:, 0]) pyplot.subplot(1, 2, 2) if meanErr == 0: point = '.k' else: point = '.r' pyplot.plot(n, meanErr, '.k') pyplot.draw() if meanErr > 0: sys.stdout.write("%.3f " % meanErr) corrInARow = 0 else: sys.stdout.write(". ") corrInARow += 1 if corrInARow >= nVerifications: print('success in a total of %.1fs' % (time.time() - t0)) self.identityLUT = currentLUT self.save() #it worked so save this configuration for future break elif len(errProgression) > 10 and max(errProgression) - min( errProgression) < 0.001: print( "Trying to correct the gamma table was having no effect. Make sure the window was fullscreen and on the Bits# screen" ) break #did we get here by failure?! if n == (maxIterations - 1): print( "failed to converge on a successful identity LUT. This is BAD!" ) if plotResults: pyplot.figure(figsize=[18, 12]) pyplot.subplot(1, 3, 1) pyplot.plot(errProgression) pyplot.title('Progression of errors') pyplot.ylabel("Mean error per LUT entry (0-1)") pyplot.xlabel("Test iteration") r256 = np.reshape(range(256), [256, 1]) pyplot.subplot(1, 3, 2) pyplot.plot(r256, r256, 'k-') pyplot.plot(r256, currentLUT[:, 0] * 255, 'r.', markersize=2.0) pyplot.plot(r256, currentLUT[:, 1] * 255, 'g.', markersize=2.0) pyplot.plot(r256, currentLUT[:, 2] * 255, 'b.', markersize=2.0) pyplot.title('Final identity LUT') pyplot.ylabel("LUT value") pyplot.xlabel("LUT entry") pyplot.subplot(1, 3, 3) deviations = currentLUT - r256 / 255.0 pyplot.plot(r256, deviations[:, 0], 'r.') pyplot.plot(r256, deviations[:, 1], 'g.') pyplot.plot(r256, deviations[:, 2], 'b.') pyplot.title('LUT deviations from sensible') pyplot.ylabel("LUT value") pyplot.xlabel("LUT deviation (multiples of 1024)") pyplot.savefig("bitsSharpIdentityLUT.pdf") pyplot.show()
def main(): global PART_ID # PART_ID is used in case of error on @atexit, that's why it must be global # === Dialog popup === info = {'IDENTYFIKATOR': '', u'P\u0141EC': ['M', "K"], 'WIEK': '20'} dictDlg = gui.DlgFromDict(dictionary=info, title='Czas detekcji wzrokowej') if not dictDlg.OK: abort_with_error('Info dialog terminated.') # === Scene init === win = visual.Window(SCREEN_RES, fullscr=True, monitor='testMonitor', units='pix', screen=0, color='black') event.Mouse(visible=False, newPos=None, win=win) # Make mouse invisible PART_ID = info['IDENTYFIKATOR'] + info[u'P\u0141EC'] + info['WIEK'] logging.LogFile(join( '.', 'results', f"{PART_ID}_{str(random.choice(range(100, 1000)))}.log"), level=logging.INFO) # errors logging logging.info('FRAME RATE: {}'.format(FRAME_RATE)) logging.info('SCREEN RES: {}'.format(SCREEN_RES)) pos_feedb = visual.TextStim(win, text=u'Poprawna odpowied\u017A', color='grey', height=40) neg_feedb = visual.TextStim(win, text=u'Niepoprawna odpowied\u017A', color='grey', height=40) no_feedb = visual.TextStim(win, text=u'Nie udzieli\u0142e\u015B odpowiedzi', color='grey', height=40) show_info(win, join('.', 'messages', 'hello.txt')) for proc_version in ['SQUARES', 'CIRCLES']: left_stim = visual.ImageStim(win, image=join('.', 'stims', f'{proc_version}_LEFT.bmp')) right_stim = visual.ImageStim(win, image=join('.', 'stims', f'{proc_version}_RIGHT.bmp')) mask_stim = visual.ImageStim(win, image=join('.', 'stims', f'{proc_version}_MASK.bmp')) # fix_stim = visual.TextStim(win, text='+', height=100, color='grey') fix_stim = visual.ImageStim(win, image=join('.', 'stims', 'PRE_STIMULI.bmp')) arrow_label = visual.TextStim(win, text=u"\u2190 \u2192", color='grey', height=30, pos=(0, -200)) if proc_version == 'SQUARES': question = u'Gdzie pojawi\u0142 si\u0119 OBROCONY kwadrat?' elif proc_version == 'CIRCLES': question = u'Gdzie pojawi\u0142 si\u0119 WI\u0118KSZY okr\u0119g?' else: raise NotImplementedError( f'Stimulus type: {proc_version} not implemented.') question_text = visual.TextStim(win, text=question, color='grey', height=20, pos=(0, -180)) # === Load data, configure log === response_clock = core.Clock() conf = yaml.load( open(join('.', 'configs', f'{proc_version}_config.yaml'))) show_info(win, join('.', 'messages', f'{proc_version}_before_training.txt')) # === Training === training = NUpNDown(start_val=conf['START_SOA'], max_revs=conf['MAX_REVS']) old_rev_count_val = -1 correct_trials = 0 soas = [] fix_time = conf['TRAIN_FIX_TIME'] for idx, soa in enumerate(training): corr, rt, rating = run_trial(conf, fix_stim, left_stim, mask_stim, fix_time, right_stim, soa, win, arrow_label, question_text, response_clock) training.set_corr(corr) level, reversal, revs_count = map(int, training.get_jump_status()) if reversal: soas.append(soa) if old_rev_count_val != revs_count: old_rev_count_val = revs_count rev_count_val = revs_count else: rev_count_val = '-' RESULTS.append([ PART_ID, idx, proc_version, 1, fix_time, conf['MTIME'], int(corr), soa, level, reversal, rev_count_val, rt, rating ]) ### FEEDBACK if corr == 1: feedb_msg = pos_feedb correct_trials += 1 elif corr == 0: feedb_msg = neg_feedb else: feedb_msg = no_feedb for _ in range(30): feedb_msg.draw() check_exit() win.flip() # break + jitter wait_time_in_secs = 1 + random.choice(range(0, 60)) / 60.0 core.wait(wait_time_in_secs) # === experiment === soa = int(np.mean(soas[:int(0.6 * len(soas))])) experiment = [soa] * conf['NO_TRIALS'] fix_time = conf['EXP_FIX_TIME'] show_info(win, join('.', 'messages', f'{proc_version}_feedback.txt')) for idx in range(idx, conf['NO_TRIALS'] + idx): corr, rt, rating = run_trial(conf, fix_stim, left_stim, mask_stim, fix_time, right_stim, soa, win, arrow_label, question_text, response_clock) corr = int(corr) correct_trials += corr RESULTS.append([ PART_ID, idx, proc_version, 0, fix_time, conf['MTIME'], corr, soa, '-', '-', '-', rt, rating ]) # break + jitter wait_time_in_secs = 1 + random.choice(range(0, 60)) / 60.0 core.wait(wait_time_in_secs) show_info(win, join('.', 'messages', 'iaf.txt')) fix_cross = visual.TextStim(win, text=u"+", color='grey', height=60, pos=(0, 0)) fix_cross.draw() win.callOnFlip(PORT.setData, TriggerTypes.REST_START) win.flip() core.wait(0.04) PORT.setData(TriggerTypes.CLEAR) core.wait(conf['RESTTIME']) win.flip() PORT.setData(TriggerTypes.REST_END) core.wait(0.04) PORT.setData(TriggerTypes.CLEAR) # === Cleaning time === save_beh_results() logging.flush() show_info(win, join('.', 'messages', 'end.txt')) win.close()
def main(info): # save log of subjects write_subjectlog(subjectlog, info) run_nr = int(info['run_nr']) subj = info['subject_id'] fullscr = info['fullscr'] time = core.Clock() subj_dir = pjoin(RESDIR, 'sub-' + subj) if not pexists(subj_dir): os.makedirs(subj_dir) log_fn = config['log_template'].format( subj=subj, task_name=config['task_name'], runnr=run_nr, timestamp=ptime.strftime(time_template), ) log_fn = pjoin(subj_dir, log_fn) log_responses = logging.LogFile(log_fn, level=logging.INFO) # set up global key for quitting; if that happens, log will be moved to # {log_fn}__halted.txt event.globalKeys.add(key='q', modifiers=['ctrl'], func=move_halted_log, func_args=[log_fn], name='quit experiment gracefully') # --- LOAD STIMULI ORDER FOR THIS PARTICIPANT --- stim_json = pjoin(PWD, 'cfg', 'sub-{0}_task-localizer_4runs.json'.format(subj)) # create stimulus order if not existing if not os.path.exists(stim_json): logging.warning("Creating stimulus order for {0}".format(subj)) MAKESTIMPY = pjoin(HERE, 'make_stim_order.py') cmd = "python {cmd} --subid {subj} --output {output} " \ "--nruns 4".format(cmd=MAKESTIMPY, subj=subj, output=dirname(stim_json)) logging.warning("Running '{0}'".format(cmd)) sp.check_call(cmd.split()) with open(stim_json, 'rb') as f: stimuli = json.load(f)[str(run_nr)] # ------------------------ print "Opening screen" tbegin = time.getTime() using_scanner = info['scanner?'] # Setting up visual size = [1280, 1024] scrwin = visual.Window(size=size, allowGUI=False, units='pix', screen=1, rgb=[-1, -1, -1], fullscr=fullscr) # load clips print "Loading stimuli" loading = visual.TextStim(scrwin, text="Loading stimuli...", height=31) loading.draw() scrwin.flip() stimuli_clip = dict() for stim in stimuli: if stim['stim_type'] != 'fixation': stim_fn = stim['stim_fn'] print("Loading {0}".format(stim_fn)) stimuli_clip[stim_fn] = \ visual.MovieStim3(scrwin, pjoin(PWD, stim_fn), size=(1280, 940), name=stim_fn, noAudio=True, loop=True) scrwin.flip() cross_hair = visual.TextStim(scrwin, text='+', height=31, pos=(0, 0), color='#FFFFFF') if using_scanner: intro_msg = "Waiting for trigger..." else: intro_msg = "Press Enter to start" intro_msg = instructions + '\n' + intro_msg intro = visual.TextStim(scrwin, text=intro_msg, height=31, wrapWidth=900) # Start of experiment intro.draw() scrwin.flip() # open up serial port and wait for first trigger if using_scanner: ser_port = '/dev/ttyUSB0' ser = serial.Serial(ser_port, 115200, timeout=.0001) ser.flushInput() trigger = '' while trigger != '5': trigger = ser.read() else: from psychopy.hardware.emulator import launchScan event.waitKeys(keyList=['return']) # XXX: set up TR here MR_settings = { 'TR': 1, 'volumes': 280, 'sync': '5', 'skip': 3, 'sound': False, } vol = launchScan(scrwin, MR_settings, globalClock=time, mode='Test') class FakeSerial(object): @staticmethod def read(): k = event.getKeys(['1', '2', '5']) return k[-1] if k else '' ser = FakeSerial() # set up timer for experiment starting from first trigger timer_exp = core.Clock() trunbegin = timer_exp.getTime() # setup bids log logbids("onset\tduration\tstim_type\trepetition") # duration will be filled later template_bids = '{onset:.3f}\t{duration:.3f}\t{stim_type}\t{stim_fn}\t' \ '{repetition}' # and now we just loop through the trials for trial in stimuli: stim_type = trial['stim_type'] stim_fn = trial['stim_fn'] duration = trial['duration'] logbids(template_bids.format( onset=timer_exp.getTime(), duration=duration, stim_type=stim_type, stim_fn=stim_fn, repetition=trial.get('repetition', 0)), ) trial_counter = core.CountdownTimer(duration) if stim_type == 'fixation': cross_hair.draw() scrwin.flip() logging.flush() while trial_counter.getTime() > 0: pass else: movie = stimuli_clip[stim_fn] while trial_counter.getTime() > 0: key = ser.read() if key in ['1', '2']: logbids(template_bids.format( onset=timer_exp.getTime(), duration=0., stim_type='button_press', stim_fn=None, repetition=0) ) if movie.status != visual.FINISHED: movie.draw() scrwin.flip() else: cross_hair.draw() scrwin.flip() logging.exp("Done in {0:.2f}s".format(timer_exp.getTime())) logging.flush() scrwin.close() core.quit()
# check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine break continueRoutine = False # will revert to True if at least one component still running for thisComponent in trialComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # check for quit (the Esc key) if endExpNow or event.getKeys(keyList=["escape"]): core.quit() # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # -------Ending Routine "trial"------- for thisComponent in trialComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) # these shouldn't be strictly necessary (should auto-save) thisExp.saveAsWideText(filename + '.csv') thisExp.saveAsPickle(filename) logging.flush() # make sure everything is closed down thisExp.abort() # or data files will save again on exit win.close() core.quit()
def handle_audio(G): ''' this should handle the audio stimuli, using the async programming style. it starts a new clock and depending on timings, will start some audio samples, L or R, 40 or 55 Hz. ''' audio_stim_list = G['A']['audio_stim_list'] astims = G['astims'] eh = G['eh'] audioClock = clock.Clock() playing = False withinAudioBlock = False prevWithinAudioBlock = False RunAudio = True currentTime = audioClock.getTime() logging.data('aud_BEGIN') eh.send_message('aud_BEGIN') # just before going in here -- LOG it. # log the beginning... while currentTime < 340.: #currentTime < 340.: # print('hello') # print(currentTime) if not playing: # I can safely use this since only one audio is playing at a time. withinAudioBlock = False for item in audio_stim_list: b, e, stim = item if b < currentTime < e: currentStim = stim withinAudioBlock = True astims[stim].play() playDuration = astims[stim].getDuration() playing = True playClock = clock.Clock() # print(stim) logging.data(stim) eh.send_message(stim) logging.flush() else: if playClock.getTime( ) > playDuration: # figure out if something is playing playing = False # try dealing with begin and ending markers: if withinAudioBlock and not prevWithinAudioBlock: messg = currentStim.replace('_', '_b') # print(messg) logging.data(messg) eh.send_message(messg) prevWithinAudioBlock = True elif prevWithinAudioBlock and not withinAudioBlock: messg = currentStim.replace('_', '_e') # print(messg) logging.data(messg) eh.send_message(messg) prevWithinAudioBlock = False # this will stop this loop, probably: currentTime = audioClock.getTime() #if currentTime > 340.: # print('Stopping!') # RunAudio=False yield From(asyncio.sleep( 0)) # pass control to someone else, while this guy sleeps a bit. logging.data('aud_END') eh.send_message('aud_END')
def run1_func(expInfo): # main function # Ensure that relative paths start from the same directory as this script _thisDir = os.path.dirname(os.path.abspath(__file__)).decode( sys.getfilesystemencoding()) os.chdir(_thisDir) # Store info about the experiment session expName = u'tnac_exp' # from the Builder filename that created this script expInfo['expName'] = expName # Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc filename = _thisDir + os.sep + u'data/%s_%s_%s' % ( expInfo['participant'], expName, expInfo['date']) trg_dict = { "music": 1, "voice": 2, "song": 3, "sound_off": 100, "pause_block": 200, "stop_run": 300, "start_run": 400, "start_block": 500, } #define path to csvs run_var = _thisDir + '/run1.csv' # An ExperimentHandler isn't essential but helps with data saving thisExp = data.ExperimentHandler(name=expName, version='', extraInfo=expInfo, runtimeInfo=None, originPath=None, savePickle=True, saveWideText=True, dataFileName=filename) # save a log file for detail verbose info logFile = logging.LogFile(filename + '.log', level=logging.EXP) logging.console.setLevel( logging.WARNING) # this outputs to the screen, not a file endExpNow = False # flag for 'escape' or other condition => quit the exp # Start Code - component code to be run before the window creation # Setup the Window ## TODO: set window to fullscreen win = visual.Window(size=[1366, 768], fullscr=True, screen=0, allowGUI=True, allowStencil=False, monitor='testMonitor', color=[0, 0, 0], colorSpace='rgb', blendMode='avg', useFBO=True) # store frame rate of monitor if we can measure it expInfo['frameRate'] = win.getActualFrameRate() if expInfo['frameRate'] != None: frameDur = 1.0 / round(expInfo['frameRate']) else: frameDur = 1.0 / 60.0 # could not measure, so guess # Initialize components for Routine "trial" trialClock = core.Clock() stim_1 = sound.Sound('A', secs=-1) stim_1.setVolume(1) fix_1 = visual.TextStim(win=win, name='fix_1', text='+', font='Arial', pos=(0, 0), height=0.1, wrapWidth=None, ori=0, color='white', colorSpace='rgb', opacity=1, depth=-1.0) # Initialize components for Routine "run_start" run_startClock = core.Clock() run_start_msg_screen = visual.TextStim(win=win, name='run_start_msg_screen', text=u'Kurze Pause.', font='Arial', units='norm', pos=[0, 0], height=0.12, wrapWidth=2, ori=0, color='white', colorSpace='rgb', opacity=1, depth=0.0) # Initialize components for Routine "run_trigger_sync" StartClock = core.Clock() run_trigger_syncClock = core.Clock() run_start_msg = visual.TextStim(win=win, name='run_start_msg', text='Durchgang beginnt!', font='Arial', units='norm', pos=[0, 0], height=0.15, wrapWidth=2, ori=0, color='white', colorSpace='rgb', opacity=1, depth=-1.0) movie = visual.MovieStim3( win=win, name='movie', units='pix', noAudio=True, # rename path filename='C:\Paradigmen\AG_Brain\Peer\TNAC\movies\mov1.mkv', ori=0, pos=(0, 0), opacity=1, depth=0.0, ) # Create some handy timers globalClock = core.Clock() # to track the time since experiment started routineTimer = core.CountdownTimer( ) # to track time remaining of each (non-slip) routine block_delay = [4, 5, 6] * 12 random.shuffle(block_delay) #print(block_delay) # ------Prepare to start Routine "run_start"------- t = 0 run_startClock.reset() # clock frameN = -1 continueRoutine = True # update component parameters for each repeat run_start_trigger_key = event.BuilderKeyResponse() # keep track of which components have finished run_startComponents = [run_start_msg_screen, run_start_trigger_key] for thisComponent in run_startComponents: if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # -------Start Routine "run_start"------- while continueRoutine: # get current time t = run_startClock.getTime() thisExp.addData('start_run', globalClock.getTime()) frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *run_start_msg_screen* updates if t >= 0.0 and run_start_msg_screen.status == NOT_STARTED: # keep track of start time/frame for later run_start_msg_screen.tStart = t run_start_msg_screen.frameNStart = frameN # exact frame index run_start_msg_screen.setAutoDraw(True) # *run_start_trigger_key* updates if t >= 0.0 and run_start_trigger_key.status == NOT_STARTED: # keep track of start time/frame for later run_start_trigger_key.tStart = t run_start_trigger_key.frameNStart = frameN # exact frame index run_start_trigger_key.status = STARTED # keyboard checking is just starting event.clearEvents(eventType='keyboard') if run_start_trigger_key.status == STARTED: theseKeys = event.getKeys(keyList=['s']) # check for quit: if "escape" in theseKeys: endExpNow = True if len(theseKeys) > 0: # at least one key was pressed # a response ends the routine continueRoutine = False # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine break continueRoutine = False # will revert to True if at least one component still running for thisComponent in run_startComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # check for quit (the Esc key) if endExpNow or event.getKeys(keyList=["escape"]): core.quit() # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # -------Ending Routine "run_start"------- for thisComponent in run_startComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) # the Routine "run_start" was not non-slip safe, so reset the non-slip timer routineTimer.reset() # ------Prepare to start Routine "run_trigger_sync"------- t = 0 run_trigger_syncClock.reset() # clock frameN = -1 continueRoutine = True # update component parameters for each repeat run_trigger_sync_ = event.BuilderKeyResponse() # keep track of which components have finished run_trigger_syncComponents = [run_trigger_sync_, run_start_msg] for thisComponent in run_trigger_syncComponents: if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # -------Start Routine "run_trigger_sync"------- while continueRoutine: # get current time #print('waiting for scanner trigger....') t = run_trigger_syncClock.getTime() frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *run_trigger_sync_* updates if t >= 0.0 and run_trigger_sync_.status == NOT_STARTED: # keep track of start time/frame for later run_trigger_sync_.tStart = t run_trigger_sync_.frameNStart = frameN # exact frame index run_trigger_sync_.status = STARTED # keyboard checking is just starting win.callOnFlip( run_trigger_sync_.clock.reset) # t=0 on next screen flip event.clearEvents(eventType='keyboard') if run_trigger_sync_.status == STARTED: theseKeys = event.getKeys(keyList=['t']) # check for quit: if "escape" in theseKeys: endExpNow = True if len(theseKeys) > 0: # at least one key was pressed run_trigger_sync_.keys = theseKeys[ -1] # just the last key pressed run_trigger_sync_.rt = run_trigger_sync_.clock.getTime() # a response ends the routine continueRoutine = False # *run_start_msg* updates if t >= 0.0 and run_start_msg.status == NOT_STARTED: # keep track of start time/frame for later run_start_msg.tStart = t run_start_msg.frameNStart = frameN # exact frame index run_start_msg.setAutoDraw(True) frameRemains = 0.0 + 5 - win.monitorFramePeriod * 0.75 # most of one frame period left if run_start_msg.status == STARTED and t >= frameRemains: run_start_msg.setAutoDraw(False) # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine break continueRoutine = False # will revert to True if at least one component still running for thisComponent in run_trigger_syncComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # check for quit (the Esc key) if endExpNow or event.getKeys(keyList=["escape"]): core.quit() # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # -------Ending Routine "run_trigger_sync"------- for thisComponent in run_trigger_syncComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) # check responses if run_trigger_sync_.keys in ['', [], None]: # No response was made run_trigger_sync_.keys = None thisExp.addData('run_trigger_sync_.keys', run_trigger_sync_.keys) if run_trigger_sync_.keys != None: # we had a response thisExp.addData('run_trigger_sync_.rt', run_trigger_sync_.rt) run_start_timestamp = StartClock.getTime() send_trigger(400) # the Routine "run_trigger_sync" was not non-slip safe, so reset the non-slip timer routineTimer.reset() start_delay = False delay_counter = 0 #print(block_delay) print(delay_counter) # start movie for whole run (loop over trials) mov = 'movies/mov1.mkv' #print(mov) movie.setMovie(mov) if t >= 0.0 and movie.status == NOT_STARTED: # keep track of start time/frame for later movie.tStart = t movie.frameNStart = frameN # exact frame index movie.setAutoDraw(True) frameRemains = 0.0 + 2 - win.monitorFramePeriod * 0.75 # most of one frame period left # set up handler to look after randomisation of conditions etc trials = data.TrialHandler(nReps=1, method='sequential', extraInfo=expInfo, originPath=-1, trialList=data.importConditions(run_var), seed=None, name='trials') thisExp.addLoop(trials) # add the loop to the experiment thisTrial = trials.trialList[ 0] # so we can initialise stimuli with some values # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb) if thisTrial != None: for paramName in thisTrial.keys(): exec(paramName + '= thisTrial.' + paramName) stimuli_played = 0 for thisTrial in trials: currentLoop = trials # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb) if thisTrial != None: for paramName in thisTrial.keys(): exec(paramName + '= thisTrial.' + paramName) # ------Prepare to start Routine "trial"------- t = 0 trialClock.reset() # clock frameN = -1 continueRoutine = True routineTimer.add(2.000000) # update component parameters for each repeat stim_1.setSound(stimuli, secs=2) #read stimuli into dict and set port value abc = stimuli.split('/')[0] trg = trg_dict.get(abc, 100) # keep track of which components have finished trialComponents = [stim_1, fix_1] for thisComponent in trialComponents: if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # -------Start Routine "trial"------- while continueRoutine and routineTimer.getTime() > 0: # get current time t = trialClock.getTime() frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # start/stop stim_1 if t >= 0.0 and stim_1.status == NOT_STARTED: # keep track of start time/frame for later stim_1.tStart = t stim_1.frameNStart = frameN # exact frame index ## TODO reinstate: send_trigger(abc) #print(abc) stim_1.play( ) # start the sound (it finishes automatically) send_trigger(trg) # send block specific trigger # get time for stimuls start thisExp.addData('stimulus_start_global', globalClock.getTime()) thisExp.addData('stimulus_start_routineTimer', routineTimer.getTime()) thisExp.addData('stimulus_start_', frameN) #print(stim_1) stimuli_played += 1 if stimuli_played % 5 == 0: start_delay = True print('stimuli_nr:' + str(stimuli_played)) frameRemains = 0.0 + 1.5 - win.monitorFramePeriod * 0.75 # most of one frame period left #frameRemainsdelay = 0.0 + 1.5- win.monitorFramePeriod * 0.75 # most of one frame period left if stim_1.status == STARTED and t >= frameRemains: stim_1.stop() # stop the sound (if longer than duration) send_trigger(100) # send sound off trigger # get info on stim end thisExp.addData('stimulus_end_global', globalClock.getTime()) thisExp.addData('stimulus_end_routineTimer', routineTimer.getTime()) # add delay intervall after 5 stimuli if stimuli_played % 5 == 0 and start_delay and delay_counter != 35: send_trigger(200) delay = block_delay[delay_counter] routineTimer.add(block_delay[delay_counter]) #frameRemainsdelay = 0.0 + 1.5 + delay - win.monitorFramePeriod * 0.75 # most of one frame period left #print('delay='+str(delay_counter)) delay_counter += 1 thisExp.addData('delay_counter', block_delay[delay_counter]) thisExp.addData('block_end_global', globalClock.getTime()) start_delay = False if stim_1.status == STARTED and t >= frameRemains: stim_1.stop() # stop the sound (if longer than duration) send_trigger(100) # send sound off trigger # get info on stim end thisExp.addData('stimulus_end_global', globalClock.getTime()) thisExp.addData('stimulus_end_routineTimer', routineTimer.getTime()) # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine break continueRoutine = False # will revert to True if at least one component still running for thisComponent in trialComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # check for quit (the Esc key) if endExpNow or event.getKeys(keyList=["escape"]): core.quit() # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() stim_1.stop() # ensure sound has stopped at end of routine thisExp.nextEntry() # completed 1 repeats of 'trials' thisExp.nextEntry() # completed 1 repeats of 'block' if stimuli_played == 180: movie.setAutoDraw(False) send_trigger(300) # END RUN thisExp.saveAsWideText(filename + 'run1' + '.csv') thisExp.saveAsPickle(filename + 'run1') logging.flush() # make sure everything is closed down thisExp.abort() # or data files will save again on exit win.close()
def main_loop( all_tasks, subject, session, output_ds, enable_eyetracker=False, use_fmri=False, use_meg=False, show_ctl_win=False, allow_run_on_battery=False, enable_ptt=False, record_movie=False, ): # force screen resolution to solve issues with video splitter at scanner """xrandr = Popen([ 'xrandr', '--output', 'eDP-1', '--mode', '%dx%d'%config.EXP_WINDOW['size'], '--rate', str(config.FRAME_RATE)]) time.sleep(5)""" if not utils.check_power_plugged(): print("*" * 25 + "WARNING: the power cord is not connected" + "*" * 25) if not allow_run_on_battery: return bids_sub_ses = ("sub-%s" % subject, "ses-%s" % session) log_path = os.path.abspath( os.path.join(output_ds, "sourcedata", *bids_sub_ses)) if not os.path.exists(log_path): os.makedirs(log_path, exist_ok=True) log_name_prefix = "sub-%s_ses-%s_%s" % ( subject, session, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"), ) logfile_path = os.path.join(log_path, log_name_prefix + ".log") log_file = logging.LogFile(logfile_path, level=logging.INFO, filemode="w") exp_win = visual.Window(**config.EXP_WINDOW, monitor=config.EXP_MONITOR) exp_win.mouseVisible = False if show_ctl_win: ctl_win = visual.Window(**config.CTL_WINDOW) ctl_win.name = "Stimuli" else: ctl_win = None ptt = None if enable_ptt: from .ptt import PushToTalk ptt = PushToTalk() eyetracker_client = None gaze_drawer = None if enable_eyetracker: print("creating et client") eyetracker_client = eyetracking.EyeTrackerClient( output_path=log_path, output_fname_base=log_name_prefix, profile=False, debug=False, ) print("starting et client") eyetracker_client.start() print("done") all_tasks = sum(([ eyetracking.EyetrackerCalibration(eyetracker_client, name="EyeTracker-Calibration"), t ] for t in all_tasks), []) if show_ctl_win: gaze_drawer = eyetracking.GazeDrawer(ctl_win) if use_fmri: all_tasks = itertools.chain( [ task_base.Pause( """We are completing the setup and initializing the scanner. We will start the tasks in a few minutes. Please remain still.""") ], all_tasks, [ task_base.Pause("""We are done for today. The scanner might run for a few seconds to acquire reference images. Please remain still. We are coming to get you out of the scanner shortly.""") ], ) if not skip_soundcheck: setup_video_path = utils.get_subject_soundcheck_video(subject) all_tasks = itertools.chain( [ video.VideoAudioCheckLoop( setup_video_path, name="setup_soundcheck_video", ) ], all_tasks, ) else: all_tasks = itertools.chain( all_tasks, [ task_base.Pause("""We are done with the tasks for today. Thanks for your participation!""") ], ) if not isinstance(all_tasks, Iterator): # list of tasks to be ran in a session print("Here are the stimuli planned for today\n" + "_" * 50) for task in all_tasks: print(f"- {task.name} {getattr(task,'duration','')}") print("_" * 50) try: for task in all_tasks: # clear events buffer in case the user pressed a lot of buttoons event.clearEvents() use_eyetracking = False if enable_eyetracker and task.use_eyetracking: use_eyetracking = True # setup task files (eg. video) task.setup( exp_win, log_path, log_name_prefix, use_fmri=use_fmri, use_eyetracking=use_eyetracking, use_meg=use_meg, ) print("READY") while True: # force focus on the task window to ensure getting keys, TTL, ... exp_win.winHandle.activate() # record frame intervals for debug shortcut_evt = run_task( task, exp_win, ctl_win, eyetracker_client, gaze_drawer, record_movie=record_movie, ) if shortcut_evt == "n": # restart the task logging.exp(msg="task - %s: restart" % str(task)) task.restart() continue elif shortcut_evt: # abort/skip or quit logging.exp(msg="task - %s: abort" % str(task)) break else: # task completed logging.exp(msg="task - %s: complete" % str(task)) # send stop trigger/marker to MEG + Biopac (or anything else on parallel port) break logging.flush() if record_movie: out_fname = os.path.join( task.output_path, "%s_%s.mp4" % (task.output_fname_base, task.name)) print(f"saving movie as {out_fname}") exp_win.saveMovieFrames(out_fname, fps=10) task.unload() if shortcut_evt == "q": print("quit") break elif shortcut_evt is None: # add a delay between tasks to avoid remaining TTL to start next task # do that only if the task was not aborted to save time # there is anyway the duration of the instruction before listening to TTL for i in range(DELAY_BETWEEN_TASK * config.FRAME_RATE): exp_win.flip(clearBuffer=False) exp_win.saveFrameIntervals("exp_win_frame_intervals.txt") if ctl_win: ctl_win.saveFrameIntervals("ctl_win_frame_intervals.txt") except KeyboardInterrupt as ki: print(traceback.format_exc()) logging.exp(msg="user killing the program") print("you killing me!") finally: if enable_eyetracker: eyetracker_client.join(TIMEOUT)
def loadFromXML(self, filename): """Loads an xml file and parses the builder Experiment from it """ self._doc.parse(filename) root = self._doc.getroot() # some error checking on the version (and report that this isn't valid # .psyexp)? filenameBase = os.path.basename(filename) if root.tag != "PsychoPy2experiment": logging.error('%s is not a valid .psyexp file, "%s"' % (filenameBase, root.tag)) # the current exp is already vaporized at this point, oops return self.psychopyVersion = root.get('version') # Parse document nodes # first make sure we're empty self.flow = Flow(exp=self) # every exp has exactly one flow self.routines = {} self.namespace = NameSpace(self) # start fresh modifiedNames = [] duplicateNames = [] # fetch exp settings settingsNode = root.find('Settings') for child in settingsNode: self._getXMLparam(params=self.settings.params, paramNode=child, componentNode=settingsNode) # name should be saved as a settings parameter (only from 1.74.00) if self.settings.params['expName'].val in ['', None, 'None']: shortName = os.path.splitext(filenameBase)[0] self.setExpName(shortName) # fetch routines routinesNode = root.find('Routines') allCompons = getAllComponents(self.prefsBuilder['componentsFolders'], fetchIcons=False) # get each routine node from the list of routines for routineNode in routinesNode: routineGoodName = self.namespace.makeValid(routineNode.get('name')) if routineGoodName != routineNode.get('name'): modifiedNames.append(routineNode.get('name')) self.namespace.user.append(routineGoodName) routine = Routine(name=routineGoodName, exp=self) # self._getXMLparam(params=routine.params, paramNode=routineNode) self.routines[routineNode.get('name')] = routine for componentNode in routineNode: componentType = componentNode.tag if componentType in allCompons: # create an actual component of that type component = allCompons[componentType]( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) else: # create UnknownComponent instead component = allCompons['UnknownComponent']( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) # check for components that were absent in older versions of # the builder and change the default behavior # (currently only the new behavior of choices for RatingScale, # HS, November 2012) # HS's modification superceded Jan 2014, removing several # RatingScale options if componentType == 'RatingScaleComponent': if (componentNode.get('choiceLabelsAboveLine') or componentNode.get('lowAnchorText') or componentNode.get('highAnchorText')): pass # if not componentNode.get('choiceLabelsAboveLine'): # # this rating scale was created using older version # component.params['choiceLabelsAboveLine'].val=True # populate the component with its various params for paramNode in componentNode: self._getXMLparam(params=component.params, paramNode=paramNode, componentNode=componentNode) compGoodName = self.namespace.makeValid( componentNode.get('name')) if compGoodName != componentNode.get('name'): modifiedNames.append(componentNode.get('name')) self.namespace.add(compGoodName) component.params['name'].val = compGoodName routine.append(component) # for each component that uses a Static for updates, we need to set # that for thisRoutine in list(self.routines.values()): for thisComp in thisRoutine: for thisParamName in thisComp.params: thisParam = thisComp.params[thisParamName] if thisParamName == 'advancedParams': continue # advanced isn't a normal param elif thisParam.updates and "during:" in thisParam.updates: # remove the part that says 'during' updates = thisParam.updates.split(': ')[1] routine, static = updates.split('.') if routine not in self.routines: msg = ("%s was set to update during %s Static " "Component, but that component no longer " "exists") logging.warning(msg % (thisParamName, static)) else: self.routines[routine].getComponentFromName( static).addComponentUpdate( thisRoutine.params['name'], thisComp.params['name'], thisParamName) # fetch flow settings flowNode = root.find('Flow') loops = {} for elementNode in flowNode: if elementNode.tag == "LoopInitiator": loopType = elementNode.get('loopType') loopName = self.namespace.makeValid(elementNode.get('name')) if loopName != elementNode.get('name'): modifiedNames.append(elementNode.get('name')) self.namespace.add(loopName) loop = eval('%s(exp=self,name="%s")' % (loopType, loopName)) loops[loopName] = loop for paramNode in elementNode: self._getXMLparam(paramNode=paramNode, params=loop.params) # for conditions convert string rep to list of dicts if paramNode.get('name') == 'conditions': param = loop.params['conditions'] # e.g. param.val=[{'ori':0},{'ori':3}] try: param.val = eval('%s' % (param.val)) except SyntaxError: # This can occur if Python2.7 conditions string # contained long ints (e.g. 8L) and these can't be # parsed by Py3. But allow the file to carry on # loading and the conditions will still be loaded # from the xlsx file pass # get condition names from within conditionsFile, if any: try: # psychophysicsstaircase demo has no such param conditionsFile = loop.params['conditionsFile'].val except Exception: conditionsFile = None if conditionsFile in ['None', '']: conditionsFile = None if conditionsFile: try: trialList, fieldNames = data.importConditions( conditionsFile, returnFieldNames=True) for fname in fieldNames: if fname != self.namespace.makeValid(fname): duplicateNames.append(fname) else: self.namespace.add(fname) except Exception: pass # couldn't load the conditions file for now self.flow.append(LoopInitiator(loop=loops[loopName])) elif elementNode.tag == "LoopTerminator": self.flow.append( LoopTerminator(loop=loops[elementNode.get('name')])) elif elementNode.tag == "Routine": if elementNode.get('name') in self.routines: self.flow.append(self.routines[elementNode.get('name')]) else: logging.error("A Routine called '{}' was on the Flow but " "could not be found (failed rename?). You " "may need to re-insert it".format( elementNode.get('name'))) logging.flush() if modifiedNames: msg = 'duplicate variable name(s) changed in loadFromXML: %s\n' logging.warning(msg % ', '.join(list(set(modifiedNames)))) if duplicateNames: msg = 'duplicate variable names: %s' logging.warning(msg % ', '.join(list(set(duplicateNames)))) # if we succeeded then save current filename to self self.filename = filename
def _getXMLparam(self, params, paramNode, componentNode=None): """params is the dict of params of the builder component (e.g. stimulus) into which the parameters will be inserted (so the object to store the params should be created first) paramNode is the parameter node fetched from the xml file """ name = paramNode.get('name') valType = paramNode.get('valType') val = paramNode.get('val') # many components need web char newline replacement if not name == 'advancedParams': val = val.replace(" ", "\n") # custom settings (to be used when if valType == 'fixedList': # convert the string to a list params[name].val = eval('list({})'.format(val)) elif name == 'storeResponseTime': return # deprecated in v1.70.00 because it was redundant elif name == 'nVertices': # up to 1.85 there was no shape param # if no shape param then use "n vertices" only if _findParam('shape', componentNode) is None: if val == '2': params['shape'].val = "line" elif val == '3': params['shape'].val = "triangle" elif val == '4': params['shape'].val = "rectangle" else: params['shape'].val = "regular polygon..." params['nVertices'].val = val elif name == 'startTime': # deprecated in v1.70.00 params['startType'].val = "{}".format('time (s)') params['startVal'].val = "{}".format(val) return # times doesn't need to update its type or 'updates' rule elif name == 'forceEndTrial': # deprecated in v1.70.00 params['forceEndRoutine'].val = bool(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'forceEndTrialOnPress': # deprecated in v1.70.00 params['forceEndRoutineOnPress'].val = bool(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'forceEndRoutineOnPress': if val == 'True': val = "any click" elif val == 'False': val = "never" params['forceEndRoutineOnPress'].val = val return elif name == 'trialList': # deprecated in v1.70.00 params['conditions'].val = eval(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'trialListFile': # deprecated in v1.70.00 params['conditionsFile'].val = "{}".format(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'duration': # deprecated in v1.70.00 params['stopType'].val = u'duration (s)' params['stopVal'].val = "{}".format(val) return # times doesn't need to update its type or 'updates' rule elif name == 'allowedKeys' and valType == 'str': # changed v1.70.00 # ynq used to be allowed, now should be 'y','n','q' or # ['y','n','q'] if len(val) == 0: newVal = val elif val[0] == '$': newVal = val[1:] # they were using code (which we can reuse) elif val.startswith('[') and val.endswith(']'): # they were using code (slightly incorectly!) newVal = val[1:-1] elif val in ['return', 'space', 'left', 'right', 'escape']: newVal = val # they were using code else: # convert string to list of keys then represent again as a # string! newVal = repr(list(val)) params['allowedKeys'].val = newVal params['allowedKeys'].valType = 'code' elif name == 'correctIf': # deprecated in v1.60.00 corrIf = val corrAns = corrIf.replace('resp.keys==unicode(', '').replace(')', '') params['correctAns'].val = corrAns name = 'correctAns' # then we can fetch other aspects below elif 'olour' in name: # colour parameter was Americanised v1.61.00 name = name.replace('olour', 'olor') params[name].val = val elif name == 'times': # deprecated in v1.60.00 times = eval('%s' % val) params['startType'].val = "{}".format('time (s)') params['startVal'].val = "{}".format(times[0]) params['stopType'].val = "{}".format('time (s)') params['stopVal'].val = "{}".format(times[1]) return # times doesn't need to update its type or 'updates' rule elif name in ('Begin Experiment', 'Begin Routine', 'Each Frame', 'End Routine', 'End Experiment'): params[name].val = val params[name].valType = 'extendedCode' # changed in 1.78.00 return # so that we don't update valTyp again below elif name == 'Saved data folder': # deprecated in 1.80 for more complete data filename control params[name] = Param(val, valType='code', allowedTypes=[], hint=_translate( "Name of the folder in which to save data" " and log files (blank defaults to the " "builder pref)"), categ='Data') elif name == 'channel': # was incorrectly set to be valType='str' until 3.1.2 params[name].val = val params[name].valType = 'code' # override elif 'val' in list(paramNode.keys()): if val == 'window units': # changed this value in 1.70.00 params[name].val = 'from exp settings' # in v1.80.00, some RatingScale API and Param fields were changed # Try to avoid a KeyError in these cases so can load the expt elif name in ('choiceLabelsAboveLine', 'lowAnchorText', 'highAnchorText'): # not handled, just ignored; want labels=[lowAnchor, # highAnchor] return elif name == 'customize_everything': # Try to auto-update the code: v = val # python code, not XML v = v.replace('markerStyle', 'marker').replace('customMarker', 'marker') v = v.replace('stretchHoriz', 'stretch').replace('displaySizeFactor', 'size') v = v.replace('textSizeFactor', 'textSize') v = v.replace('ticksAboveLine=False', 'tickHeight=-1') v = v.replace('showScale=False', 'scale=None').replace('allowSkip=False', 'skipKeys=None') v = v.replace('showAnchors=False', 'labels=None') # lowAnchorText highAnchorText will trigger obsolete error # when run the script params[name].val = v elif name == 'storeResponseTime': return # deprecated in v1.70.00 because it was redundant elif name == 'Resources': # if the xml import hasn't automatically converted from string? if type(val) == str: resources = data.utils.listFromString(val) if self.psychopyVersion == '2020.2.5': # in 2020.2.5 only, problems were: # a) resources list was saved as a string and # b) with wrong root folder resList = [] for resourcePath in resources: # doing this the blunt way but should we check for existence? resourcePath = resourcePath.replace( "../", "") # it was created using wrong root resourcePath = resourcePath.replace( "\\", "/") # created using windows \\ resList.append(resourcePath) resources = resList # push our new list back to resources params[name].val = resources else: if name in params: params[name].val = val else: # we found an unknown parameter (probably from the future) params[name] = Param( val, valType=paramNode.get('valType'), allowedTypes=[], hint=_translate( "This parameter is not known by this version " "of PsychoPy. It might be worth upgrading")) params[name].allowedTypes = paramNode.get('allowedTypes') if params[name].allowedTypes is None: params[name].allowedTypes = [] params[name].readOnly = True if name not in ['JS libs', 'OSF Project ID']: # don't warn people if we know it's OK (e.g. for params # that have been removed msg = _translate( "Parameter %r is not known to this version of " "PsychoPy but has come from your experiment file " "(saved by a future version of PsychoPy?). This " "experiment may not run correctly in the current " "version.") logging.warn(msg % name) logging.flush() # get the value type and update rate if 'valType' in list(paramNode.keys()): params[name].valType = paramNode.get('valType') # compatibility checks: if name in ['allowedKeys'] and paramNode.get('valType') == 'str': # these components were changed in v1.70.00 params[name].valType = 'code' elif name == 'Selected rows': # changed in 1.81.00 from 'code' to 'str': allow string or var params[name].valType = 'str' # conversions based on valType if params[name].valType == 'bool': params[name].val = eval("%s" % params[name].val) if 'updates' in list(paramNode.keys()): params[name].updates = paramNode.get('updates')
def launch(): """Launch a test battery. The test battery is stored in testlist.py. This is to be called from the RT_test folder, otherwise some tests may not be loaded when they contain a reference to other resources (like the stimuli list in the VerbCRT). """ setup_console() # This part aims to fix the problem with multitasking when making # executable using pyinstaller if getattr(sys, 'frozen', False): # we are running in a bundle HOME_FOLDER = os.path.dirname(sys.executable) else: # we are running in a normal Python environment HOME_FOLDER = os.path.dirname(os.path.abspath(__file__)) os.chdir(HOME_FOLDER) # Make folders and attach output table if not os.access('data', os.F_OK): os.mkdir('data') if os.path.isfile('data/participants.csv'): try: out_file = open('data/participants.csv', mode='a') except: print('The directory is not writable, cannot write the data') else: out_file = open('data/participants.csv', mode='w') out_file.write('test_mode;id;name;age;sex;status;start_time;end_time;') for i in test_battery: out_file.write(i + '_status;' + \ i + '_start_time;' + \ i + '_end_time;') out_file.write('\n') # Showing information dialog intro = u''' Спасибо, что согласились поучаствовать в исследовании времени реакции! Предлагаю Вам пройти набор тестов, в которых нужно как можно быстрее и точнее реагировать на стимулы (изображения или слова). Время выполнения заданий составляет ~20 минут в демо-версии и ~50 минут в полной версии. В тесте установлено общее ограничение времени 90 минут. Тест можно прервать во время демонстрации или между тренировочной и основной сериями, нажав кнопку Y.\ Перед началом тестов программа соберет информацию о характеристиках Вашего компьютера: - Название операционной системы - Тип процессора - Количество оперативной памяти - Размер и частота обновления экрана - Используемую версию Питона Данные и логи записываются в папке data. \ В данный момент батарея находится на стадии тестирования, \ поэтому я буду очень благодарен любой обратной связи. \ Отправляйте обратную связь и результаты из папки data по адресу: [email protected] Страница проекта на гитхабе: https://github.com/IvanVoronin/RT_tests С уважением, Иван Воронин''' app = wx.App() intro_dlg = wx.MessageDialog( None, intro, u'Добро пожаловать', wx.OK_DEFAULT | wx.CANCEL | wx.OK | wx.ALIGN_LEFT | wx.ICON_INFORMATION) resp = intro_dlg.ShowModal() if resp != wx.ID_OK: core.wait(1) core.quit() sys.exit(0) # Show the ID questionnaire info_dlg = gui.Dlg(title=u'Начать эксперимент') info_dlg.addField(u'ID:', u'0001') info_dlg.addField(u'Имя:', u'') info_dlg.addField(u'Дата рождения:', u'01.01.1990') info_dlg.addField(u'Пол:', choices=[u'Мужской', u'Женский']) info_dlg.addField(u'Режим теста:', choices=[u'Демо', u'Полный']) info_dlg.addField(u'Я согласен/согласна участвовать', True) info_dlg.addText(u'\nВыберите тесты для выполнения') for i in test_battery.keys(): info_dlg.addField(i, True) # TODO: Make a separate window info_dlg.addText( u'\nНе забудьте включить английскую расскладку клавиатуры!\n') info_dlg.show() if not info_dlg.OK: core.wait(1) core.quit() sys.exit(0) START_TIME = datetime.now() (ID, name, age, sex, test_mode, agr), run_tests = \ info_dlg.data[0:6], info_dlg.data[6:] if not agr: core.wait(1) core.quit() sys.exit(0) out_file.write(test_mode + ';' + ID + ';' + name + ';' + str(age) + ';' + sex + ';') out_dir = START_TIME.strftime('%Y-%m-%d_%H%M__') + str(ID) os.mkdir('data/' + out_dir) for test, run in zip(test_battery.values(), run_tests): if not run: test.status = 'skipped' # Log the warnings # TODO: Log in-test warnings, log interruptions, pauses and demonstrations logging.console.setLevel(logging.WARNING) log = logging.LogFile('data/' + out_dir + '/log.log', level=logging.INFO, filemode='w') # The same window can be shared by tests # Here you can put window specifications test_screen = psychopy.visual.Window(size=(1024, 768), fullscr=True, units='pix', monitor=0, winType='pyglet') test_screen.winHandle.activate() test_screen.mouseVisible = False try: screen_size = test_screen.size fps = test_screen.getActualFrameRate() frame_duration = test_screen.getMsPerFrame() test_screen.flip() # Gather system information with open('data/' + out_dir + '/specs.txt', mode='w') as specs: specs.write('Platform: %s\n' % platform.platform()) specs.write('Machine: %s\n' % platform.machine()) specs.write('Processor: %s\n' % platform.processor()) specs.write('Number of CPUs: %d\n' % psutil.cpu_count(logical=False)) specs.write('Available CPUs: %d\n' % len(psutil.Process().cpu_affinity())) specs.write('Current CPU load: %0.1f%%\n' % psutil.cpu_percent()) specs.write('Total RAM: %dMb\n' % int(psutil.virtual_memory().total / (1024 * 1024))) specs.write('Available RAM: %dMb\n' % int(psutil.virtual_memory().available / (1024 * 1024))) specs.write('Screen size: %dx%d\n' % tuple(screen_size)) specs.write('FPS: %0.1f\n' % fps) specs.write( 'Frame duration: mean=%0.1fms, SD=%0.1fms, median=%0.1fms\n' % frame_duration) specs.write('Python version: %s\n' % platform.python_version()) specs.write('Python implementation: %s\n' % platform.python_implementation()) specs.write('PsychoPy version: %s\n' % psychopy.__version__) specs.write('Battery version: %s\n' % VERSION) specs.write('Battery ID: %s\n' % BATTERY_ID) except Exception as e: log.write('EXCEPTION: %s\n' % e) logging.flush() # Run the tests, collect stats skip_the_rest = False for test in test_battery.itervalues(): if skip_the_rest: test.status = 'skipped' if test.status != 'skipped': log.write( '\n======================================================================\n' + 'STARTING ' + test.name + '\n') exc_info = sys.exc_info() try: test.start(out_dir, mode=test_mode, test_screen=test_screen) except Exception as e: test.status = 'failed' test.end_time = datetime.now() log.write('SOMETHING HAPPENED!\n') log.write('EXCEPTION: %s\n' % e) logging.flush() # If something went wrong we open the test screen again # FIXME: Probably not working as expected if test_screen not in locals() or test_screen._closed: log.write('OPENING A NEW WINDOW\n') test_screen = psychopy.visual.Window(size=(1024, 768), fullscr=True, units='pix', monitor=0, winType='pyglet') test_screen.winHandle.activate() test_screen.mouseVisible = False logging.flush() finally: log.write(traceback.format_exc(exc_info)) del exc_info log.write( 'FINISHING ' + test.name + '\n======================================================================\n' ) else: test.start_time = datetime.now() test.end_time = datetime.now() log.write(test.name + ' SKIPPED\n') current_dur = test.end_time - START_TIME if current_dur.seconds / 60.0 > TIME_LIMIT: skip_the_rest = True log.write('TIME LIMIT REACHED\n') END_TIME = datetime.now() BAT_STATUS = all( [test.status == 'complete' for test in test_battery.values()]) # TODO: Ввести секретную комбинацию клавиш для определения набора тестов out_file.write(('complete' if BAT_STATUS else 'incomplete') + ';' + START_TIME.strftime('%Y-%m-%d %H:%M:%S') + ';' + END_TIME.strftime('%Y-%m-%d %H:%M:%S') + ';') for test in test_battery.itervalues(): out_file.write(test.status + ';' + test.start_time.strftime('%Y-%m-%d %H:%M:%S') + ';' + test.end_time.strftime('%Y-%m-%d %H:%M:%S') + ';') out_file.write('\n') out_file.close() debriefing = psychopy.visual.TextStim(test_screen, text=u'\ Благодарим за участие в эксперименте!\n\n\ Окно закроется автоматически') debriefing.draw() test_screen.flip() core.wait(3) test_screen.close() core.quit()
def endExp(): win.flip() logging.flush() win.close() core.quit()
def tick(self): # getting in the variables: G = self.G # from efl.efl_v6 import * # put everything here, which is in from __name__ == "__main__" # so this is one 'tick', but that's OK - one tick is all we need from pyff. try: wait_for_key(G) measure_artifact_program(G) test_buttons(G) instr_screen0(G) eo_stim(G) ec_stim(G) logging.flush() test_buttons(G) instr_screen(G) logging.flush() # print(G['eh'].is_alive()) # print('----><----') # G['eh'].send_message('boe!') # print('----><----') run_main_loop(G) logging.flush() eo_stim(G) ec_stim(G) end_task(G) logging.flush() # close window here. G['win'].close() logging.flush() except KeyboardInterrupt: G['eh'].shutdown() G['eh'].join() G['win'].close() logging.flush() # once done, stop the FB with this: # so we can re-start it, right? self.on_stop()
def initPyo(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex, maxChnls Sound = SoundPyo if not "pyo" in locals(): import pyo # microphone.switchOn() calls initPyo even if audioLib is something else # subclass the pyo.Server so that we can insert a __del__ function that shuts it down class _Server(pyo.Server): core = core # make libs class variables so they don't get deleted first logging = logging def __del__(self): self.stop() self.core.wait(0.5) # make sure enough time passes for the server to shutdown self.shutdown() self.core.wait(0.5) # make sure enough time passes for the server to shutdown self.logging.debug("pyo sound server shutdown") # this may never get printed if ".".join(map(str, pyo.getVersion())) < "0.6.4": Server = _Server else: Server = pyo.Server # if we already have a server, just re-initialize it if globals().has_key("pyoSndServer") and hasattr(pyoSndServer, "shutdown"): pyoSndServer.stop() core.wait(0.5) # make sure enough time passes for the server to shutdown pyoSndServer.shutdown() core.wait(0.5) pyoSndServer.reinit(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) pyoSndServer.boot() else: if platform == "win32": # check for output device/driver devNames, devIDs = pyo.pa_get_output_devices() audioDriver, outputID = _bestDriver(devNames, devIDs) if outputID: logging.info("Using sound driver: %s (ID=%i)" % (audioDriver, outputID)) maxOutputChnls = pyo.pa_get_output_max_channels(outputID) else: logging.warning("No audio outputs found (no speakers connected?") return -1 # check for valid input (mic) devNames, devIDs = pyo.pa_get_input_devices() audioInputName, inputID = _bestDriver(devNames, devIDs) if inputID is not None: logging.info("Using sound-input driver: %s (ID=%i)" % (audioInputName, inputID)) maxInputChnls = pyo.pa_get_input_max_channels(inputID) duplex = bool(maxInputChnls > 0) else: duplex = False else: # for other platforms set duplex to True (if microphone is available) audioDriver = prefs.general["audioDriver"][0] maxInputChnls = pyo.pa_get_input_max_channels(pyo.pa_get_default_input()) maxOutputChnls = pyo.pa_get_output_max_channels(pyo.pa_get_default_output()) duplex = bool(maxInputChnls > 0) maxChnls = min(maxInputChnls, maxOutputChnls) if maxInputChnls < 1: logging.warning("%s.initPyo could not find microphone hardware; recording not available" % __name__) maxChnls = maxOutputChnls if maxOutputChnls < 1: logging.error("%s.initPyo could not find speaker hardware; sound not available" % __name__) return -1 # create the instance of the server: if platform in ["darwin", "linux2"]: # for mac/linux we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) else: # with others we just use portaudio and then set the OutputDevice below pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer) pyoSndServer.setVerbosity(1) if platform == "win32": pyoSndServer.setOutputDevice(outputID) if inputID: pyoSndServer.setInputDevice(inputID) # do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.setDuplex(duplex) pyoSndServer.boot() core.wait(0.5) # wait for server to boot before starting te sound stream pyoSndServer.start() try: Sound() # test creation, no play except pyo.PyoServerStateException: msg = "Failed to start pyo sound Server" if platform == "darwin" and audioDriver != "portaudio": msg += "; maybe try prefs.general.audioDriver 'portaudio'?" logging.error(msg) core.quit() logging.debug("pyo sound server started") logging.flush()
def onInit(self, showSplash=True, testMode=False): """This is launched immediately *after* the app initialises with wx :Parameters: testMode: bool """ self.SetAppName('PsychoPy3') if showSplash: #showSplash: # show splash screen splashFile = os.path.join(self.prefs.paths['resources'], 'psychopySplash.png') splashImage = wx.Image(name=splashFile) splashImage.ConvertAlphaToMask() splash = AS.AdvancedSplash( None, bitmap=splashImage.ConvertToBitmap(), timeout=3000, agwStyle=AS.AS_TIMEOUT | AS.AS_CENTER_ON_SCREEN, ) # transparency? w, h = splashImage.GetSize() splash.SetTextPosition((int(340), h - 30)) splash.SetText( _translate("Copyright (C) 2020 OpenScienceTools.org")) else: splash = None # SLOW IMPORTS - these need to be imported after splash screen starts # but then that they end up being local so keep track in self from psychopy.compatibility import checkCompatibility # import coder and builder here but only use them later from psychopy.app import coder, builder, runner, dialogs if '--firstrun' in sys.argv: del sys.argv[sys.argv.index('--firstrun')] self.firstRun = True if 'lastVersion' not in self.prefs.appData: # must be before 1.74.00 last = self.prefs.appData['lastVersion'] = '1.73.04' self.firstRun = True else: last = self.prefs.appData['lastVersion'] if self.firstRun and not self.testMode: pass # setup links for URLs # on a mac, don't exit when the last frame is deleted, just show menu if sys.platform == 'darwin': self.menuFrame = MenuFrame(parent=None, app=self) # fetch prev files if that's the preference if self.prefs.coder['reloadPrevFiles']: scripts = self.prefs.appData['coder']['prevFiles'] else: scripts = [] appKeys = list(self.prefs.appData['builder'].keys()) if self.prefs.builder['reloadPrevExp'] and ('prevFiles' in appKeys): exps = self.prefs.appData['builder']['prevFiles'] else: exps = [] runlist = [] self.dpi = int(wx.GetDisplaySize()[0] / float(wx.GetDisplaySizeMM()[0]) * 25.4) if not (50 < self.dpi < 120): self.dpi = 80 # dpi was unreasonable, make one up if sys.platform == 'win32': # wx.SYS_DEFAULT_GUI_FONT is default GUI font in Win32 self._mainFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) else: self._mainFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) try: self._codeFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) except wx._core.wxAssertionError: # if no SYS_ANSI_FIXED_FONT then try generic FONTFAMILY_MODERN self._codeFont = wx.Font(self._mainFont.GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) # that gets most of the properties of _codeFont but the FaceName # FaceName is set in the setting of the theme: self.theme = self.prefs.app['theme'] # removed Aug 2017: on newer versions of wx (at least on mac) # this looks too big # if hasattr(self._mainFont, 'Larger'): # # Font.Larger is available since wyPython version 2.9.1 # # PsychoPy still supports 2.8 (see ensureMinimal above) # self._mainFont = self._mainFont.Larger() # self._codeFont.SetPointSize( # self._mainFont.GetPointSize()) # unify font size # create both frame for coder/builder as necess if splash: splash.SetText(_translate(" Creating frames...")) # Parse incoming call parser = argparse.ArgumentParser(prog=self) parser.add_argument('--builder', dest='builder', action="store_true") parser.add_argument('-b', dest='builder', action="store_true") parser.add_argument('--coder', dest='coder', action="store_true") parser.add_argument('-c', dest='coder', action="store_true") parser.add_argument('--runner', dest='runner', action="store_true") parser.add_argument('-r', dest='runner', action="store_true") view, args = parser.parse_known_args(sys.argv) print(args) # Check from filetype if any windows need to be open if any(arg.endswith('.psyexp') for arg in args): view.builder = True exps = [file for file in args if file.endswith('.psyexp')] if any(arg.endswith('.psyrun') for arg in args): view.runner = True runlist = [file for file in args if file.endswith('.psyrun')] # If still no window specified, use default from prefs if not any( getattr(view, key) for key in ['builder', 'coder', 'runner']): if self.prefs.app['defaultView'] in view: setattr(view, self.prefs.app['defaultView'], True) elif self.prefs.app['defaultView'] == 'all': view.builder = True view.coder = True view.runner = True # Create windows if view.runner: self.showRunner(fileList=runlist) if view.coder: self.showCoder(fileList=scripts) if view.builder: self.showBuilder(fileList=exps) # send anonymous info to www.psychopy.org/usage.php # please don't disable this, it's important for PsychoPy's development self._latestAvailableVersion = None self.updater = None self.news = None self.tasks = None prefsConn = self.prefs.connections ok, msg = checkCompatibility(last, self.version, self.prefs, fix=True) # tell the user what has changed if not ok and not self.firstRun and not self.testMode: title = _translate("Compatibility information") dlg = dialogs.MessageDialog(parent=None, message=msg, type='Info', title=title) dlg.ShowModal() if (self.prefs.app['showStartupTips'] and not self.testMode and not blockTips): tipFile = os.path.join(self.prefs.paths['resources'], _translate("tips.txt")) tipIndex = self.prefs.appData['tipIndex'] if parse_version(wx.__version__) >= parse_version('4.0.0a1'): tp = wx.adv.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.adv.ShowTip(None, tp) else: tp = wx.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.ShowTip(None, tp) self.prefs.appData['tipIndex'] = tp.GetCurrentTip() self.prefs.saveAppData() self.prefs.app['showStartupTips'] = showTip self.prefs.saveUserPrefs() self.Bind(wx.EVT_IDLE, self.onIdle) # doing this once subsequently enables the app to open & switch among # wx-windows on some platforms (Mac 10.9.4) with wx-3.0: v = parse_version if sys.platform == 'darwin': if v('3.0') <= v(wx.version()) < v('4.0'): _Showgui_Hack() # returns ~immediately, no display # focus stays in never-land, so bring back to the app: if prefs.app['defaultView'] in [ 'all', 'builder', 'coder', 'runner' ]: self.showBuilder() else: self.showCoder() # after all windows are created (so errors flushed) create output self._appLoaded = True if self.coder: self.coder.setOutputWindow() # takes control of sys.stdout # flush any errors to the last run log file logging.flush() sys.stdout.flush() # we wanted debug mode while loading but safe to go back to info mode if not self.prefs.app['debugMode']: logging.console.setLevel(logging.INFO) return True
def getEvents(self, timeout=10): """Look for a string that matches SDAT;\n.........EDAT;\n and process it as events """ foundDataStart = False t0 = time.time() while not foundDataStart and time.time() - t0 < timeout: startLine = self.com.readline() if startLine == b'\n': startLine = self.com.readline() if startLine.startswith(b'SDAT'): foundDataStart = True logging.info("BBTK.getEvents() found data. Processing...") logging.flush() # we aren't in a time-critical period break # check if we're processing data if not foundDataStart: logging.warning("BBTK.getEvents() found no data " "(SDAT was not found on serial port inputs") return [] # helper function to parse time and event code def parseEventsLine(line, lastState=None): """Returns a list of dictionaries, one for each change detected in the state """ state = line[:12] timeSecs = int(line[-14:-2]) / 10.0**6 evts = [] evt = '' if lastState is None: evts.append({'evt': '', 'state': state, 'time': timeSecs}) else: for n in evtChannels: if state[n] != lastState[n]: if chr(state[n]) =='1': evt = evtChannels[n] + "_on" else: evt = evtChannels[n] + "_off" evts.append({'evt': evt, 'state': state, 'time': timeSecs}) return evts # we've been sent data so work through it events = [] eventLines = [] lastState = None # try to read from port self.pause() self.com.timeout = 5.0 nEvents = int(self.com.readline()[:-2]) # last two chars are ;\n self.com.readline()[:-2] # microseconds recorded (ignore) self.com.readline()[:-2] # samples recorded (ignore) while True: line = self.com.readline() if line.startswith(b'EDAT'): # end of data stream break events.extend(parseEventsLine(line, lastState)) lastState = events[-1]['state'] eventLines.append(line) if nEvents != len(eventLines): msg = "BBTK reported %i events but told us to expect %i events!!" logging.warning(msg % (len(events), nEvents)) logging.flush() # we aren't in a time-critical period return events
def run_block(fix): # Await scan trigger while True: scan_trigger_text.draw() win.flip() if 'o' in event.waitKeys(): logging.log(logging.DATA, "start key press") break event.clearEvents() clock=core.Clock() t = clock.getTime() #set up the fixation ratings_and_onsets.append(['fixation',t]) logging.log(logging.DATA, "fixation %f"%t) show_stim(fixation_text, fix) # 8 sec blank screen with fixation cross #log fixation logging.log(logging.DATA, "fixation end %f"%t) t = clock.getTime() #reset the clock so the onsets are correct (if onsets have the 8 sec in them then you dont need this) clock.reset() ratings_and_onsets.append(['start',t]) logging.log(logging.DATA, "START") #start the taste loop for trial in range(ntrials): #check for quit if check_for_quit(subdata,win): exptutils.shut_down_cleanly(subdata,win) sys.exit() #empty trial data trialdata={} trialdata['onset']=onsets[trial] #shuffle the positions shuffle(pos_ind) visual_stim1=visual.ImageStim(win, image=N.zeros((300,300)),pos=positions[pos_ind[0]], size=(0.25,0.25),units='height') visual_stim2=visual.ImageStim(win, image=N.zeros((300,300)),pos=positions[pos_ind[1]], size=(0.25,0.25),units='height') #set which image is which x=int(N.random.choice(prob_index, 1, p=[0.34, 0.33,0.33])) print(x) stim_images=stim_list[x] trial_prob=prob_list[x] trial_inv_prob=inv_prob_list[x] visual_stim1.setImage(stim_images[indices[0]])#set which image appears visual_stim2.setImage(stim_images[indices[1]])#set which image appears master_prob_list=[trial_prob,trial_inv_prob] shuffle(indices) #creating a dictory which will store the postion with the image and pump, the image and pump need to match mydict={} # mydict[positions_eng[pos_ind[1]]] = [stim_images[indices[1]], master_prob_list[indices[1]]] # mydict[positions_eng[pos_ind[0]]] = [stim_images[indices[0]], master_prob_list[indices[0]]] mydict[positions_scan[pos_ind[1]]] = [stim_images[indices[1]], master_prob_list[indices[1]]] mydict[positions_scan[pos_ind[0]]] = [stim_images[indices[0]], master_prob_list[indices[0]]] print(mydict) #which is sweet? message=visual.TextStim(win, text='Which is Correct?',pos=(0,5)) print trial t = clock.getTime() #get the time of the image and log, this log is appending it to the csv file visual_stim1.draw()#making image of the logo appear visual_stim2.draw()#making image of the logo appear message.draw() RT = core.Clock() #this is logging when the message is shown logging.log(logging.DATA, "%s at position=%s and %s at position=%s"%(stim_images[indices[0]],positions_eng[pos_ind[0]],stim_images[indices[1]],positions_eng[pos_ind[1]])) logging.flush() while clock.getTime()<trialdata['onset']: pass win.flip() RT.reset() # reaction time starts immediately after flip while clock.getTime()<(trialdata['onset']+cue_time):#show the image, while clock is less than onset and cue, show cue pass keys = event.getKeys(keyList=['1','2'],timeStamped=RT) message=visual.TextStim(win, text='')#blank screen while the taste is delivered message.draw() win.flip() # get the key press logged, and time stamped if len(keys)>0: logging.log(logging.DATA, "keypress=%s at time= %f"%(keys[0][0],keys[0][1])) print("here are the keys:") print(keys) t = clock.getTime() logging.flush() #back up of the key press tempArray = [t, keys[0]] key_responses.append(tempArray) ratings_and_onsets.append(["keypress=%s"%keys[0][0],t]) if keys[0][0] == '1': # draw the image for keypress 2 visual_stim1.draw() #from the dictionary find the image code associated with the key press #taste=int(mydict['left'][1]) image=(mydict['1'][0]) trial_prob=(mydict['1'][1]) taste=int(N.random.choice(pump_responses, 1, p=trial_prob)) #if image=='sweet.jpg': #taste=int(N.random.choice(pump_responses, 1, p=[0.5, 0.5])) #elif image=='unsweet.jpg': #taste=int(N.random.choice(pump_responses, 1, p=[0.5, 0.5])) print(image) print(taste) #log the pump used, time, and key press print 'injecting via pump at address %s'%taste logging.log(logging.DATA,"injecting via pump at address %d and a keypress of %s and image of %s"%(taste,keys[0][0], image)) t = clock.getTime() ratings_and_onsets.append(["injecting via pump at address %d"%taste, t, keys[0][0]]) #trigger pump with the numeral from the dictonary above ser.write('%dRUN\r'%taste) logging.flush() elif keys[0][0] == '2': # draw the image for keypress 2 visual_stim2.draw() #from the dictonary get the image associated with the right key press image=(mydict['2'][0]) trial_prob=(mydict['2'][1]) taste=int(N.random.choice(pump_responses, 1, p=trial_prob)) print(image) print(taste) #log the time, keypress, and pump print 'injecting via pump at address %s'%taste logging.log(logging.DATA,"injecting via pump at address %d and a keypress of %s and image of %s"%(taste,keys[0][0], image)) t = clock.getTime() ratings_and_onsets.append(["injecting via pump at address %d"%taste, t]) #trigger the pump with the numeral from the dictionary ser.write('%dRUN\r'%taste) logging.flush() else: taste=0 t = clock.getTime() logging.log(logging.DATA,"Key Press Missed!") keys=keys.append(['MISS',t]) logging.flush() message=visual.TextStim(win, text='Please answer quicker', pos=(0, 0), height=2)#this lasts throught the taste message.draw() win.flip() while clock.getTime()<(trialdata['onset']+cue_time+delivery_time): pass message=visual.TextStim(win, text='+', pos=(0, 0), height=2) #this lasts throught the wait message.draw() win.flip() t = clock.getTime() ratings_and_onsets.append(["wait", t]) trialdata['dis']=[ser.write('0DIS\r'),ser.write('1DIS\r')] print(trialdata['dis']) while clock.getTime()<(trialdata['onset']+cue_time+delivery_time+wait_time): pass message=visual.TextStim(win, text='', pos=(0, 0), height=2)#this lasts throught the rinse message.draw() win.flip() print 'injecting rinse via pump at address %d'%0 t = clock.getTime() ratings_and_onsets.append(['injecting rinse via pump at address %d'%0, t]) ser.write('%dRUN\r'%0) logging.log(logging.DATA, "RINSE") logging.flush() while clock.getTime()<(trialdata['onset']+cue_time+delivery_time+wait_time+rinse_time): pass message=visual.TextStim(win, text='+', pos=(0, 0), height=2)#lasts through the jitter message.draw() win.flip() t = clock.getTime() ratings_and_onsets.append(["jitter", t]) while clock.getTime()<(trialdata['onset']+cue_time+delivery_time+wait_time+rinse_time+jitter[trial]): pass t = clock.getTime() ratings_and_onsets.append(['end time', t]) logging.log(logging.DATA,"finished") logging.flush() subdata['trialdata'][trial]=trialdata print(key_responses) win.close()
def init(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex, maxChnls Sound = SoundPyo global pyo try: assert pyo except NameError: # pragma: no cover import pyo # can be needed for microphone.switchOn(), which calls init even # if audioLib is something else # subclass the pyo.Server so that we can insert a __del__ function that # shuts it down skip coverage since the class is never used if we have # a recent version of pyo class _Server(pyo.Server): # pragma: no cover # make libs class variables so they don't get deleted first core = core logging = logging def __del__(self): self.stop() # make sure enough time passes for the server to shutdown self.core.wait(0.5) self.shutdown() # make sure enough time passes for the server to shutdown self.core.wait(0.5) # this may never get printed self.logging.debug('pyo sound server shutdown') if '.'.join(map(str, pyo.getVersion())) < '0.6.4': Server = _Server else: Server = pyo.Server # if we already have a server, just re-initialize it if 'pyoSndServer' in globals() and hasattr(pyoSndServer, 'shutdown'): pyoSndServer.stop() # make sure enough time passes for the server to shutdown core.wait(0.5) pyoSndServer.shutdown() core.wait(0.5) pyoSndServer.reinit(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) pyoSndServer.boot() else: if platform == 'win32': # check for output device/driver devNames, devIDs = pyo.pa_get_output_devices() audioDriver, outputID = _bestDriver(devNames, devIDs) if outputID is None: # using the default output because we didn't find the one(s) # requested audioDriver = 'Windows Default Output' outputID = pyo.pa_get_default_output() if outputID is not None: logging.info('Using sound driver: %s (ID=%i)' % (audioDriver, outputID)) maxOutputChnls = pyo.pa_get_output_max_channels(outputID) else: logging.warning( 'No audio outputs found (no speakers connected?') return -1 # check for valid input (mic) # If no input device is available, devNames and devIDs are empty # lists. devNames, devIDs = pyo.pa_get_input_devices() audioInputName, inputID = _bestDriver(devNames, devIDs) # Input devices were found, but requested devices were not found if len(devIDs) > 0 and inputID is None: defaultID = pyo.pa_get_default_input() if defaultID is not None and defaultID != -1: # default input is found # use the default input because we didn't find the one(s) # requested audioInputName = 'Windows Default Input' inputID = defaultID else: # default input is not available inputID = None if inputID is not None: msg = 'Using sound-input driver: %s (ID=%i)' logging.info(msg % (audioInputName, inputID)) maxInputChnls = pyo.pa_get_input_max_channels(inputID) duplex = bool(maxInputChnls > 0) else: maxInputChnls = 0 duplex = False # for other platforms set duplex to True (if microphone is available) else: audioDriver = prefs.general['audioDriver'][0] maxInputChnls = pyo.pa_get_input_max_channels( pyo.pa_get_default_input()) maxOutputChnls = pyo.pa_get_output_max_channels( pyo.pa_get_default_output()) duplex = bool(maxInputChnls > 0) maxChnls = min(maxInputChnls, maxOutputChnls) if maxInputChnls < 1: # pragma: no cover msg = ('%s.init could not find microphone hardware; ' 'recording not available') logging.warning(msg % __name__) maxChnls = maxOutputChnls if maxOutputChnls < 1: # pragma: no cover msg = ('%s.init could not find speaker hardware; ' 'sound not available') logging.error(msg % __name__) return -1 # create the instance of the server: if platform in ['darwin', 'linux2']: # for mac/linux we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) else: # with others we just use portaudio and then set the OutputDevice # below pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer) pyoSndServer.setVerbosity(1) if platform == 'win32': pyoSndServer.setOutputDevice(outputID) if inputID is not None: pyoSndServer.setInputDevice(inputID) # do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.setDuplex(duplex) pyoSndServer.boot() core.wait(0.5) # wait for server to boot before starting te sound stream pyoSndServer.start() try: Sound() # test creation, no play except pyo.PyoServerStateException: msg = "Failed to start pyo sound Server" if platform == 'darwin' and audioDriver != 'portaudio': msg += "; maybe try prefs.general.audioDriver 'portaudio'?" logging.error(msg) core.quit() logging.debug('pyo sound server started') logging.flush()
def initPyo(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex Sound = SoundPyo if not 'pyo' in locals(): import pyo # microphone.switchOn() calls initPyo even if audioLib is something else #subclass the pyo.Server so that we can insert a __del__ function that shuts it down class Server(pyo.Server): core=core #make libs class variables so they don't get deleted first logging=logging def __del__(self): self.stop() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.shutdown() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.logging.debug('pyo sound server shutdown')#this may never get printed maxInputChnls = pyo.pa_get_input_max_channels(pyo.pa_get_default_input()) maxOutputChnls = pyo.pa_get_output_max_channels(pyo.pa_get_default_output()) maxChnls = min(maxInputChnls, maxOutputChnls) if maxInputChnls < 1: logging.warning('%s.initPyo could not find microphone hardware; recording not available' % __name__) maxChnls = maxOutputChnls if maxOutputChnls < 1: logging.error('%s.initPyo could not find speaker hardware; sound not available' % __name__) core.quit() #check if we already have a server and kill it if globals().has_key('pyoSndServer') and hasattr(pyoSndServer,'shutdown'): #if it exists and isn't None! #this doesn't appear to work! pyoSndServer.stop() core.wait(0.5)#make sure enough time passes for the server to shutdown pyoSndServer.shutdown() core.wait(0.5) pyoSndServer.reinit(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) pyoSndServer.boot() else: if platform=='win32': #check for output device/driver devNames, devIDs=pyo.pa_get_output_devices() audioDriver,outputID=_bestDriver(devNames, devIDs) if outputID: logging.info('Using sound driver: %s (ID=%i)' %(audioDriver, outputID)) else: logging.warning('No audio outputs found (no speakers connected?') return -1 #check for valid input (mic) devNames, devIDs = pyo.pa_get_input_devices() junk, inputID=_bestDriver(devNames, devIDs) if inputID: duplex = bool(maxInputChnls > 0) else: duplex=False else:#for other platforms set duplex to True (if microphone is available) audioDriver = prefs.general['audioDriver'][0] duplex = bool(maxInputChnls > 0) # create the instance of the server: if platform=='darwin': #for mac we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) else: #with others we just use portaudio and then set the OutputDevice below pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer) pyoSndServer.setVerbosity(1) if platform=='win32': pyoSndServer.setOutputDevice(outputID) if inputID: pyoSndServer.setInputDevice(inputID) #do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.setDuplex(duplex) pyoSndServer.boot() core.wait(0.5)#wait for server to boot before starting te sound stream pyoSndServer.start() try: Sound() # test creation, no play except pyo.PyoServerStateException: msg = "Failed to start pyo sound Server" if platform == 'darwin' and audioDriver != 'portaudio': msg += "; maybe try prefs.general.audioDriver 'portaudio'?" logging.error(msg) core.quit() logging.debug('pyo sound server started') logging.flush()
def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._unload() self._reset() if self._no_audio is False: self._createAudioStream() # Create Video Stream stuff self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError( "Error when reading image file") self._total_frame_count = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 cv_fps = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) if self._requested_fps: if self._no_audio is False and cv_fps != self._requested_fps: self._no_audio = True logging.error("MovieStim2 video fps != requested fps. Disabling Audio Stream.") logging.flush() if self._no_audio and self._requested_fps: self._video_frame_rate = self._requested_fps else: self._video_frame_rate = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0/self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros((self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform(flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename)
def baseline(): # Initialize components for Routine "fixation_cross" fixation_crossClock = core.Clock() cross = visual.TextStim(win=win, name='cross', text='+', font='Arial', pos=(0, 0), height=0.1, wrapWidth=None, ori=0, color='white', colorSpace='rgb', opacity=1, languageStyle='LTR', depth=0.0); # ------Prepare to start Routine "fixation_cross"------- continueRoutine = True routineTimer.add(125.000000) # update component parameters for each repeat # keep track of which components have finished fixation_crossComponents = [cross] for thisComponent in fixation_crossComponents: thisComponent.tStart = None thisComponent.tStop = None thisComponent.tStartRefresh = None thisComponent.tStopRefresh = None if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # reset timers t = 0 _timeToFirstFrame = win.getFutureFlipTime(clock="now") fixation_crossClock.reset(-_timeToFirstFrame) # t0 is time of first possible flip frameN = -1 # -------Run Routine "fixation_cross"------- while continueRoutine and routineTimer.getTime() > 0: # get current time t = fixation_crossClock.getTime() tThisFlip = win.getFutureFlipTime(clock=fixation_crossClock) tThisFlipGlobal = win.getFutureFlipTime(clock=None) frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *cross* updates if cross.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance: # keep track of start time/frame for later cross.frameNStart = frameN # exact frame index cross.tStart = t # local t and not account for scr refresh cross.tStartRefresh = tThisFlipGlobal # on global time win.timeOnFlip(cross, 'tStartRefresh') # time at next scr refresh cross.setAutoDraw(True) if cross.status == STARTED: # is it time to stop? (based on global clock, using actual start) if tThisFlipGlobal > cross.tStartRefresh + 120-frameTolerance: # keep track of stop time/frame for later cross.tStop = t # not accounting for scr refresh cross.frameNStop = frameN # exact frame index win.timeOnFlip(cross, 'tStopRefresh') # time at next scr refresh cross.setAutoDraw(False) # check for quit (typically the Esc key) if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]): core.quit() # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine break continueRoutine = False # will revert to True if at least one component still running for thisComponent in fixation_crossComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # -------Ending Routine "fixation_cross"------- for thisComponent in fixation_crossComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) # Flip one final time so any remaining win.callOnFlip() # and win.timeOnFlip() tasks get executed before quitting win.flip() logging.flush() # make sure everything is closed down win.close() core.quit()
""" sentencecomp.py - code to run the sentence comprehension task for the reading remediation study """ from psychopy import visual, core, event, logging,gui import numpy as N import pickle import datetime import sys import os import inspect import hashlib from socket import gethostname from exptutils import * # study-specific routines def load_sentences(filename): f=open(filename) ll=f.readlines() f.close() s={} s['sentence']=[] s['cond']=[] s['onset']=[] for l in ll: l_s=l.split('\t') #print l_s s['sentence'].append(' '.join(l_s[2:]).strip('\n'))
def findIdentityLUT( self, maxIterations=1000, errCorrFactor=1.0 / 5000, nVerifications=50, demoMode=True, logFile="" ): """Search for the identity LUT for this card/operating system. This requires that the window being tested is fullscreen on the Bits# monitor (or at least occupies the first 256 pixels in the top left corner!) :params: LUT: The lookup table to be tested (256 x 3). If None then the LUT will not be altered errCorrFactor: amount of correction done for each iteration number of repeats (successful) to check dithering has been eradicated demoMode: generate the screen but don't go into status mode :returns: a 256x3 array of error values (integers in range 0:255) """ t0 = time.time() # create standard options intel = np.linspace(0.05, 0.95, 256) one = np.linspace(0, 1.0, 256) fraction = np.linspace(0.0, 65535.0 / 65536.0, num=256) LUTs = { "intel": np.repeat(intel, 3).reshape([-1, 3]), "0-255": np.repeat(one, 3).reshape([-1, 3]), "0-65535": np.repeat(fraction, 3).reshape([-1, 3]), "1-65536": np.repeat(fraction, 3).reshape([-1, 3]), } if logFile: self.logFile = open(logFile, "w") if plotResults: pyplot.Figure() pyplot.subplot(1, 2, 1) pyplot.plot([0, 255], [0, 255], "-k") errPlot = pyplot.plot(range(256), range(256), ".r")[0] pyplot.subplot(1, 2, 2) pyplot.plot(200, 0.01, ".w") pyplot.show(block=False) lowestErr = 1000000000 bestLUTname = None logging.flush() for LUTname, currentLUT in LUTs.items(): sys.stdout.write("Checking %r LUT:" % LUTname) errs = self.testLUT(currentLUT, demoMode) if plotResults: errPlot.set_ydata(range(256) + errs[:, 0]) pyplot.draw() print("mean err = %.3f per LUT entry" % abs(errs).mean()) if abs(errs).mean() < abs(lowestErr): lowestErr = abs(errs).mean() bestLUTname = LUTname if lowestErr == 0: msg = "The %r identity LUT produced zero error. We'll use that!" print(msg % LUTname) self.identityLUT = LUTs[bestLUTname] # it worked so save this configuration for future self.save() return msg = "Best was %r LUT (mean err = %.3f). Optimising that..." print(msg % (bestLUTname, lowestErr)) currentLUT = LUTs[bestLUTname] errProgression = [] corrInARow = 0 for n in range(maxIterations): errs = self.testLUT(currentLUT) tweaks = errs * errCorrFactor currentLUT -= tweaks currentLUT[currentLUT > 1] = 1.0 currentLUT[currentLUT < 0] = 0.0 meanErr = abs(errs).mean() errProgression.append(meanErr) if plotResults: errPlot.set_ydata(range(256) + errs[:, 0]) pyplot.subplot(1, 2, 2) if meanErr == 0: point = ".k" else: point = ".r" pyplot.plot(n, meanErr, ".k") pyplot.draw() if meanErr > 0: sys.stdout.write("%.3f " % meanErr) corrInARow = 0 else: sys.stdout.write(". ") corrInARow += 1 if corrInARow >= nVerifications: print("success in a total of %.1fs" % (time.time() - t0)) self.identityLUT = currentLUT # it worked so save this configuration for future self.save() break elif len(errProgression) > 10 and max(errProgression) - min(errProgression) < 0.001: print( "Trying to correct the gamma table was having no " "effect. Make sure the window was fullscreen and " "on the Bits# screen" ) break # did we get here by failure?! if n == maxIterations - 1: print("failed to converge on a successful identity LUT. " "This is BAD!") if plotResults: pyplot.figure(figsize=[18, 12]) pyplot.subplot(1, 3, 1) pyplot.plot(errProgression) pyplot.title("Progression of errors") pyplot.ylabel("Mean error per LUT entry (0-1)") pyplot.xlabel("Test iteration") r256 = np.reshape(range(256), [256, 1]) pyplot.subplot(1, 3, 2) pyplot.plot(r256, r256, "k-") pyplot.plot(r256, currentLUT[:, 0] * 255, "r.", markersize=2.0) pyplot.plot(r256, currentLUT[:, 1] * 255, "g.", markersize=2.0) pyplot.plot(r256, currentLUT[:, 2] * 255, "b.", markersize=2.0) pyplot.title("Final identity LUT") pyplot.ylabel("LUT value") pyplot.xlabel("LUT entry") pyplot.subplot(1, 3, 3) deviations = currentLUT - r256 / 255.0 pyplot.plot(r256, deviations[:, 0], "r.") pyplot.plot(r256, deviations[:, 1], "g.") pyplot.plot(r256, deviations[:, 2], "b.") pyplot.title("LUT deviations from sensible") pyplot.ylabel("LUT value") pyplot.xlabel("LUT deviation (multiples of 1024)") pyplot.savefig("bitsSharpIdentityLUT.pdf") pyplot.show()
def __init__(self, port=None, baudrate=9600, byteSize=8, stopBits=1, parity="N", # 'N'one, 'E'ven, 'O'dd, 'M'ask, eol="\n", maxAttempts=1, pauseDuration=0.1, checkAwake=True): if not serial: raise ImportError('The module serial is needed to connect to this' ' device. On most systems this can be installed' ' with\n\t easy_install pyserial') # get a list of port names to try if port is None: ports = self._findPossiblePorts() elif type(port) in [int, float]: ports = ['COM%i' % port] else: ports = [port] self.pauseDuration = pauseDuration self.isOpen = False self.com = None self.OK = False self.maxAttempts = maxAttempts self.eol = eol self.type = self.name # for backwards compatibility # try to open the port for portString in ports: try: self.com = serial.Serial( portString, baudrate=baudrate, bytesize=byteSize, # number of data bits parity=parity, # enable parity checking stopbits=stopBits, # number of stop bits timeout=3, # set a timeout value, None for waiting forever xonxoff=0, # enable software flow control rtscts=0,) # enable RTS/CTS flow control self.portString = portString except Exception: if port: # the user asked for this port and we couldn't connect logging.warn("Couldn't connect to port %s" % portString) else: # we were trying this port on a guess msg = "Tried and failed to connect to port %s" logging.debug(msg % portString) continue # try the next port if not self.com.isOpen(): try: self.com.open() except Exception: msg = ("Couldn't open port %s. Is it being used by " "another program?") logging.info(msg % self.portString) continue if checkAwake and self.com.isOpen(): # we have an open com port. try to send a command self.com.flushInput() awake = False # until we confirm otherwise for repN in range(self.maxAttempts): awake = self.isAwake() if awake: msg = "Opened port %s and looks like a %s" logging.info(msg % (self.portString, self.name)) self.OK = True self.pause() break if not awake: msg = "Opened port %s but it didn't respond like a %s" logging.info(msg % (self.portString, self.name)) self.com.close() self.OK = False else: break if self.OK: # we have successfully sent and read a command msg = "Successfully opened %s with a %s" logging.info(msg % (self.portString, self.name)) # we aren't in a time-critical period so flush messages logging.flush()
def save_beh(): logging.flush() with open(join('results', 'behavioral_data', 'beh_{}_{}.csv'.format(NAME, RAND)), 'w') as csvfile: beh_writer = csv.writer(csvfile) beh_writer.writerows(RESULTS)
def createTexture(tex, id, pixFormat, stim, res=128, maskParams=None, forcePOW2=True, dataType=None): """ :params: id: is the texture ID pixFormat: GL.GL_ALPHA, GL.GL_RGB useShaders: bool interpolate: bool (determines whether texture will use GL_LINEAR or GL_NEAREST res: the resolution of the texture (unless a bitmap image is used) dataType: None, GL.GL_UNSIGNED_BYTE, GL_FLOAT. Only affects image files (numpy arrays will be float) For grating stimuli (anything that needs multiple cycles) forcePOW2 should be set to be True. Otherwise the wrapping of the texture will not work. """ """ Create an intensity texture, ranging -1:1.0 """ global _nImageResizes notSqr=False #most of the options will be creating a sqr texture wasImage=False #change this if image loading works useShaders = stim.useShaders interpolate = stim.interpolate if dataType==None: if useShaders and pixFormat==GL.GL_RGB: dataType = GL.GL_FLOAT else: dataType = GL.GL_UNSIGNED_BYTE if type(tex) == numpy.ndarray: #handle a numpy array #for now this needs to be an NxN intensity array intensity = tex.astype(numpy.float32) if intensity.max()>1 or intensity.min()<-1: logging.error('numpy arrays used as textures should be in the range -1(black):1(white)') if len(tex.shape)==3: wasLum=False else: wasLum = True ##is it 1D? if tex.shape[0]==1: stim._tex1D=True res=tex.shape[1] elif len(tex.shape)==1 or tex.shape[1]==1: stim._tex1D=True res=tex.shape[0] else: stim._tex1D=False #check if it's a square power of two maxDim = max(tex.shape) powerOf2 = 2**numpy.ceil(numpy.log2(maxDim)) if forcePOW2 and (tex.shape[0]!=powerOf2 or tex.shape[1]!=powerOf2): logging.error("Requiring a square power of two (e.g. 16x16, 256x256) texture but didn't receive one") core.quit() res=tex.shape[0] elif tex in [None,"none", "None"]: res=1 #4x4 (2x2 is SUPPOSED to be fine but generates wierd colors!) intensity = numpy.ones([res,res],numpy.float32) wasLum = True elif tex == "sin": onePeriodX, onePeriodY = numpy.mgrid[0:res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation intensity = numpy.sin(onePeriodY-pi/2) wasLum = True elif tex == "sqr":#square wave (symmetric duty cycle) onePeriodX, onePeriodY = numpy.mgrid[0:res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation sinusoid = numpy.sin(onePeriodY-pi/2) intensity = numpy.where(sinusoid>0, 1, -1) wasLum = True elif tex == "saw": intensity = numpy.linspace(-1.0,1.0,res,endpoint=True)*numpy.ones([res,1]) wasLum = True elif tex == "tri": intensity = numpy.linspace(-1.0,3.0,res,endpoint=True)#-1:3 means the middle is at +1 intensity[int(res/2.0+1):] = 2.0-intensity[int(res/2.0+1):]#remove from 3 to get back down to -1 intensity = intensity*numpy.ones([res,1])#make 2D wasLum = True elif tex == "sinXsin": onePeriodX, onePeriodY = numpy.mgrid[0:2*pi:1j*res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation intensity = numpy.sin(onePeriodX-pi/2)*numpy.sin(onePeriodY-pi/2) wasLum = True elif tex == "sqrXsqr": onePeriodX, onePeriodY = numpy.mgrid[0:2*pi:1j*res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation sinusoid = numpy.sin(onePeriodX-pi/2)*numpy.sin(onePeriodY-pi/2) intensity = numpy.where(sinusoid>0, 1, -1) wasLum = True elif tex == "circle": rad=makeRadialMatrix(res) intensity = (rad<=1)*2-1 fromFile=0 wasLum=True elif tex == "gauss": rad=makeRadialMatrix(res) sigma = 1/3.0; intensity = numpy.exp( -rad**2.0 / (2.0*sigma**2.0) )*2-1 #3sd.s by the edge of the stimulus fromFile=0 wasLum=True elif tex == "radRamp":#a radial ramp rad=makeRadialMatrix(res) intensity = 1-2*rad intensity = numpy.where(rad<-1, intensity, -1)#clip off the corners (circular) fromFile=0 wasLum=True elif tex == "raisedCos": # A raised cosine wasLum=True hamming_len = 1000 # This affects the 'granularity' of the raised cos # If no user input was provided: if maskParams is None: fringe_proportion = 0.2 # This one affects the proportion of the # stimulus diameter that is devoted to the # raised cosine. # Users can provide the fringe proportion through a dict, maskParams # input: else: fringe_proportion = maskParams['fringeWidth'] rad = makeRadialMatrix(res) intensity = numpy.zeros_like(rad) intensity[numpy.where(rad < 1)] = 1 raised_cos_idx = numpy.where( [numpy.logical_and(rad <= 1, rad >= 1-fringe_proportion)])[1:] # Make a raised_cos (half a hamming window): raised_cos = numpy.hamming(hamming_len)[:hamming_len/2] raised_cos -= numpy.min(raised_cos) raised_cos /= numpy.max(raised_cos) # Measure the distance from the edge - this is your index into the hamming window: d_from_edge = numpy.abs((1 - fringe_proportion)- rad[raised_cos_idx]) d_from_edge /= numpy.max(d_from_edge) d_from_edge *= numpy.round(hamming_len/2) # This is the indices into the hamming (larger for small distances from the edge!): portion_idx = (-1 * d_from_edge).astype(int) # Apply the raised cos to this portion: intensity[raised_cos_idx] = raised_cos[portion_idx] # Scale it into the interval -1:1: intensity = intensity - 0.5 intensity = intensity / numpy.max(intensity) #Sometimes there are some remaining artifacts from this process, get rid of them: artifact_idx = numpy.where(numpy.logical_and(intensity == -1, rad < 0.99)) intensity[artifact_idx] = 1 artifact_idx = numpy.where(numpy.logical_and(intensity == 1, rad > 0.99)) intensity[artifact_idx] = 0 else: if type(tex) in [str, unicode, numpy.string_]: # maybe tex is the name of a file: if not os.path.isfile(tex): logging.error("Couldn't find image file '%s'; check path?" %(tex)); logging.flush() raise OSError, "Couldn't find image file '%s'; check path? (tried: %s)" \ % (tex, os.path.abspath(tex))#ensure we quit try: im = Image.open(tex) im = im.transpose(Image.FLIP_TOP_BOTTOM) except IOError: logging.error("Found file '%s' but failed to load as an image" %(tex)); logging.flush() raise IOError, "Found file '%s' [= %s] but it failed to load as an image" \ % (tex, os.path.abspath(tex))#ensure we quit else: # can't be a file; maybe its an image already in memory? try: im = tex.copy().transpose(Image.FLIP_TOP_BOTTOM) # ? need to flip if in mem? except AttributeError: # nope, not an image in memory logging.error("Couldn't make sense of requested image."); logging.flush() raise AttributeError, "Couldn't make sense of requested image."#ensure we quit # at this point we have a valid im stim._origSize=im.size wasImage=True #is it 1D? if im.size[0]==1 or im.size[1]==1: logging.error("Only 2D textures are supported at the moment") else: maxDim = max(im.size) powerOf2 = int(2**numpy.ceil(numpy.log2(maxDim))) if im.size[0]!=powerOf2 or im.size[1]!=powerOf2: if not forcePOW2: notSqr=True elif _nImageResizes<reportNImageResizes: logging.warning("Image '%s' was not a square power-of-two image. Linearly interpolating to be %ix%i" %(tex, powerOf2, powerOf2)) _nImageResizes+=1 im=im.resize([powerOf2,powerOf2],Image.BILINEAR) elif _nImageResizes==reportNImageResizes: logging.warning("Multiple images have needed resizing - I'll stop bothering you!") im=im.resize([powerOf2,powerOf2],Image.BILINEAR) #is it Luminance or RGB? if im.mode=='L' and pixFormat==GL.GL_ALPHA: wasLum = True elif pixFormat==GL.GL_ALPHA:#we have RGB and need Lum wasLum = True im = im.convert("L")#force to intensity (in case it was rgb) elif pixFormat==GL.GL_RGB:#we have RGB and keep it that way #texture = im.tostring("raw", "RGB", 0, -1) im = im.convert("RGBA")#force to rgb (in case it was CMYK or L) wasLum=False if dataType==GL.GL_FLOAT: #convert from ubyte to float intensity = numpy.array(im).astype(numpy.float32)*0.0078431372549019607-1.0 # much faster to avoid division 2/255 else: intensity = numpy.array(im) if wasLum and intensity.shape!=im.size: intensity.shape=im.size if pixFormat==GL.GL_RGB and wasLum and dataType==GL.GL_FLOAT: #grating stim on good machine #keep as float32 -1:1 if sys.platform!='darwin' and stim.win.glVendor.startswith('nvidia'): #nvidia under win/linux might not support 32bit float internalFormat = GL.GL_RGB16F_ARB #could use GL_LUMINANCE32F_ARB here but check shader code? else:#we've got a mac or an ATI card and can handle 32bit float textures internalFormat = GL.GL_RGB32F_ARB #could use GL_LUMINANCE32F_ARB here but check shader code? data = numpy.ones((intensity.shape[0],intensity.shape[1],3),numpy.float32)#initialise data array as a float data[:,:,0] = intensity#R data[:,:,1] = intensity#G data[:,:,2] = intensity#B elif pixFormat==GL.GL_RGB and wasLum: #Grating on legacy hardware, or ImageStim with wasLum=True #scale by rgb and convert to ubyte internalFormat = GL.GL_RGB if stim.colorSpace in ['rgb', 'dkl', 'lms','hsv']: rgb=stim.rgb else: rgb=stim.rgb/127.5-1.0#colour is not a float - convert to float to do the scaling #scale by rgb data = numpy.ones((intensity.shape[0],intensity.shape[1],3),numpy.float32)#initialise data array as a float data[:,:,0] = intensity*rgb[0] + stim.rgbPedestal[0]#R data[:,:,1] = intensity*rgb[1] + stim.rgbPedestal[1]#G data[:,:,2] = intensity*rgb[2] + stim.rgbPedestal[2]#B #convert to ubyte data = float_uint8(stim.contrast*data) elif pixFormat==GL.GL_RGB and dataType==GL.GL_FLOAT: #probably a custom rgb array or rgb image internalFormat = GL.GL_RGB32F_ARB data = intensity elif pixFormat==GL.GL_RGB:# not wasLum, not useShaders - an RGB bitmap with no shader options internalFormat = GL.GL_RGB data = intensity #float_uint8(intensity) elif pixFormat==GL.GL_ALPHA: internalFormat = GL.GL_ALPHA if wasImage: data = intensity else: data = float_uint8(intensity) #check for RGBA textures if len(intensity.shape)>2 and intensity.shape[2] == 4: if pixFormat==GL.GL_RGB: pixFormat=GL.GL_RGBA if internalFormat==GL.GL_RGB: internalFormat=GL.GL_RGBA elif internalFormat==GL.GL_RGB32F_ARB: internalFormat=GL.GL_RGBA32F_ARB texture = data.ctypes#serialise #bind the texture in openGL GL.glEnable(GL.GL_TEXTURE_2D) GL.glBindTexture(GL.GL_TEXTURE_2D, id)#bind that name to the target GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_WRAP_S,GL.GL_REPEAT) #makes the texture map wrap (this is actually default anyway) #important if using bits++ because GL_LINEAR #sometimes extrapolates to pixel vals outside range if interpolate: GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_LINEAR) if useShaders:#GL_GENERATE_MIPMAP was only available from OpenGL 1.4 GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, internalFormat, data.shape[1],data.shape[0], 0, # [JRG] for non-square, want data.shape[1], data.shape[0] pixFormat, dataType, texture) else:#use glu GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_NEAREST) GL.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, internalFormat, data.shape[1],data.shape[0], pixFormat, dataType, texture) # [JRG] for non-square, want data.shape[1], data.shape[0] else: GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_NEAREST) GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER,GL.GL_NEAREST) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, internalFormat, data.shape[1],data.shape[0], 0, # [JRG] for non-square, want data.shape[1], data.shape[0] pixFormat, dataType, texture) GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)#?? do we need this - think not! return wasLum
def move_halted_log(fn): # flush log logging.flush() shutil.move(fn, fn.replace('.txt', '__halted.txt')) # quit core.quit()
def __init__(self, port=None, baudrate=9600, byteSize=8, stopBits=1, parity="N", # 'N'one, 'E'ven, 'O'dd, 'M'ask, eol=b"\n", maxAttempts=1, pauseDuration=0.1, checkAwake=True): if not serial: raise ImportError('The module serial is needed to connect to this' ' device. On most systems this can be installed' ' with\n\t easy_install pyserial') # get a list of port names to try if port is None: ports = self._findPossiblePorts() elif type(port) in [int, float]: ports = ['COM%i' % port] else: ports = [port] self.pauseDuration = pauseDuration self.com = None self.OK = False self.maxAttempts = maxAttempts if type(eol) is bytes: self.eol = eol else: self.eol = bytes(eol, 'utf-8') self.type = self.name # for backwards compatibility # try to open the port for portString in ports: try: self.com = serial.Serial( portString, baudrate=baudrate, bytesize=byteSize, # number of data bits parity=parity, # enable parity checking stopbits=stopBits, # number of stop bits timeout=3, # set a timeout value, None for waiting forever xonxoff=0, # enable software flow control rtscts=0,) # enable RTS/CTS flow control self.portString = portString except Exception: if port: # the user asked for this port and we couldn't connect logging.warn("Couldn't connect to port %s" % portString) else: # we were trying this port on a guess msg = "Tried and failed to connect to port %s" logging.debug(msg % portString) continue # try the next port if not self.com.isOpen(): try: self.com.open() except Exception: msg = ("Couldn't open port %s. Is it being used by " "another program?") logging.info(msg % self.portString) continue if checkAwake and self.com.isOpen(): # we have an open com port. try to send a command self.com.flushInput() awake = False # until we confirm otherwise for repN in range(self.maxAttempts): awake = self.isAwake() if awake: msg = "Opened port %s and looks like a %s" logging.info(msg % (self.portString, self.name)) self.OK = True self.pause() break if not awake: msg = "Opened port %s but it didn't respond like a %s" logging.info(msg % (self.portString, self.name)) self.com.close() self.OK = False else: break if self.OK: # we have successfully sent and read a command msg = "Successfully opened %s with a %s" logging.info(msg % (self.portString, self.name)) # we aren't in a time-critical period so flush messages logging.flush()
def _createTexture(self, tex, id, pixFormat, stim, res=128, maskParams=None, forcePOW2=True, dataType=None): """ :params: id: is the texture ID pixFormat: GL.GL_ALPHA, GL.GL_RGB useShaders: bool interpolate: bool (determines whether texture will use GL_LINEAR or GL_NEAREST res: the resolution of the texture (unless a bitmap image is used) dataType: None, GL.GL_UNSIGNED_BYTE, GL_FLOAT. Only affects image files (numpy arrays will be float) For grating stimuli (anything that needs multiple cycles) forcePOW2 should be set to be True. Otherwise the wrapping of the texture will not work. """ """ Create an intensity texture, ranging -1:1.0 """ notSqr=False #most of the options will be creating a sqr texture wasImage=False #change this if image loading works useShaders = stim.useShaders interpolate = stim.interpolate if dataType==None: if useShaders and pixFormat==GL.GL_RGB: dataType = GL.GL_FLOAT else: dataType = GL.GL_UNSIGNED_BYTE if type(tex) == numpy.ndarray: #handle a numpy array #for now this needs to be an NxN intensity array intensity = tex.astype(numpy.float32) if intensity.max()>1 or intensity.min()<-1: logging.error('numpy arrays used as textures should be in the range -1(black):1(white)') if len(tex.shape)==3: wasLum=False else: wasLum = True ##is it 1D? if tex.shape[0]==1: stim._tex1D=True res=tex.shape[1] elif len(tex.shape)==1 or tex.shape[1]==1: stim._tex1D=True res=tex.shape[0] else: stim._tex1D=False #check if it's a square power of two maxDim = max(tex.shape) powerOf2 = 2**numpy.ceil(numpy.log2(maxDim)) if forcePOW2 and (tex.shape[0]!=powerOf2 or tex.shape[1]!=powerOf2): logging.error("Requiring a square power of two (e.g. 16x16, 256x256) texture but didn't receive one") core.quit() res=tex.shape[0] elif tex in [None,"none", "None"]: res=1 #4x4 (2x2 is SUPPOSED to be fine but generates wierd colors!) intensity = numpy.ones([res,res],numpy.float32) wasLum = True elif tex == "sin": onePeriodX, onePeriodY = numpy.mgrid[0:res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation intensity = numpy.sin(onePeriodY-pi/2) wasLum = True elif tex == "sqr":#square wave (symmetric duty cycle) onePeriodX, onePeriodY = numpy.mgrid[0:res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation sinusoid = numpy.sin(onePeriodY-pi/2) intensity = numpy.where(sinusoid>0, 1, -1) wasLum = True elif tex == "saw": intensity = numpy.linspace(-1.0,1.0,res,endpoint=True)*numpy.ones([res,1]) wasLum = True elif tex == "tri": intensity = numpy.linspace(-1.0,3.0,res,endpoint=True)#-1:3 means the middle is at +1 intensity[int(res/2.0+1):] = 2.0-intensity[int(res/2.0+1):]#remove from 3 to get back down to -1 intensity = intensity*numpy.ones([res,1])#make 2D wasLum = True elif tex == "sinXsin": onePeriodX, onePeriodY = numpy.mgrid[0:2*pi:1j*res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation intensity = numpy.sin(onePeriodX-pi/2)*numpy.sin(onePeriodY-pi/2) wasLum = True elif tex == "sqrXsqr": onePeriodX, onePeriodY = numpy.mgrid[0:2*pi:1j*res, 0:2*pi:1j*res]# NB 1j*res is a special mgrid notation sinusoid = numpy.sin(onePeriodX-pi/2)*numpy.sin(onePeriodY-pi/2) intensity = numpy.where(sinusoid>0, 1, -1) wasLum = True elif tex == "circle": rad=makeRadialMatrix(res) intensity = (rad<=1)*2-1 wasLum=True elif tex == "gauss": rad=makeRadialMatrix(res) # Set SD if specified if maskParams == None: sigma = 1.0 / 3 else: sigma = 1.0 / maskParams['sd'] intensity = numpy.exp( -rad**2.0 / (2.0*sigma**2.0) )*2-1 #3sd.s by the edge of the stimulus wasLum=True elif tex == "cross": X, Y = numpy.mgrid[-1:1:1j*res, -1:1:1j*res] tf_neg_cross = ((X < -0.2) & (Y < -0.2)) | ((X < -0.2) & (Y > 0.2)) | ((X > 0.2) & (Y < -0.2)) | ((X > 0.2) & (Y > 0.2)) #tf_neg_cross == True at places where the cross is transparent, i.e. the four corners intensity = numpy.where(tf_neg_cross, -1, 1) wasLum = True elif tex == "radRamp":#a radial ramp rad=makeRadialMatrix(res) intensity = 1-2*rad intensity = numpy.where(rad<-1, intensity, -1)#clip off the corners (circular) wasLum=True elif tex == "raisedCos": # A raised cosine wasLum=True hamming_len = 1000 # This affects the 'granularity' of the raised cos # If no user input was provided: if maskParams is None: fringe_proportion = 0.2 # This one affects the proportion of the # stimulus diameter that is devoted to the # raised cosine. # Users can provide the fringe proportion through a dict, maskParams # input: else: fringe_proportion = maskParams['fringeWidth'] rad = makeRadialMatrix(res) intensity = numpy.zeros_like(rad) intensity[numpy.where(rad < 1)] = 1 raised_cos_idx = numpy.where( [numpy.logical_and(rad <= 1, rad >= 1-fringe_proportion)])[1:] # Make a raised_cos (half a hamming window): raised_cos = numpy.hamming(hamming_len)[:hamming_len/2] raised_cos -= numpy.min(raised_cos) raised_cos /= numpy.max(raised_cos) # Measure the distance from the edge - this is your index into the hamming window: d_from_edge = numpy.abs((1 - fringe_proportion)- rad[raised_cos_idx]) d_from_edge /= numpy.max(d_from_edge) d_from_edge *= numpy.round(hamming_len/2) # This is the indices into the hamming (larger for small distances from the edge!): portion_idx = (-1 * d_from_edge).astype(int) # Apply the raised cos to this portion: intensity[raised_cos_idx] = raised_cos[portion_idx] # Scale it into the interval -1:1: intensity = intensity - 0.5 intensity = intensity / numpy.max(intensity) #Sometimes there are some remaining artifacts from this process, get rid of them: artifact_idx = numpy.where(numpy.logical_and(intensity == -1, rad < 0.99)) intensity[artifact_idx] = 1 artifact_idx = numpy.where(numpy.logical_and(intensity == 1, rad > 0.99)) intensity[artifact_idx] = 0 else: if type(tex) in [str, unicode, numpy.string_]: # maybe tex is the name of a file: if not os.path.isfile(tex): logging.error("Couldn't find image file '%s'; check path?" %(tex)); logging.flush() raise OSError, "Couldn't find image file '%s'; check path? (tried: %s)" \ % (tex, os.path.abspath(tex))#ensure we quit try: im = Image.open(tex) im = im.transpose(Image.FLIP_TOP_BOTTOM) except IOError: logging.error("Found file '%s' but failed to load as an image" %(tex)); logging.flush() raise IOError, "Found file '%s' [= %s] but it failed to load as an image" \ % (tex, os.path.abspath(tex))#ensure we quit else: # can't be a file; maybe its an image already in memory? try: im = tex.copy().transpose(Image.FLIP_TOP_BOTTOM) # ? need to flip if in mem? except AttributeError: # nope, not an image in memory logging.error("Couldn't make sense of requested image."); logging.flush() raise AttributeError, "Couldn't make sense of requested image."#ensure we quit # at this point we have a valid im stim._origSize=im.size wasImage=True #is it 1D? if im.size[0]==1 or im.size[1]==1: logging.error("Only 2D textures are supported at the moment") else: maxDim = max(im.size) powerOf2 = int(2**numpy.ceil(numpy.log2(maxDim))) if im.size[0]!=powerOf2 or im.size[1]!=powerOf2: if not forcePOW2: notSqr=True elif glob_vars.nImageResizes<reportNImageResizes: logging.warning("Image '%s' was not a square power-of-two image. Linearly interpolating to be %ix%i" %(tex, powerOf2, powerOf2)) glob_vars.nImageResizes+=1 im=im.resize([powerOf2,powerOf2],Image.BILINEAR) elif glob_vars.nImageResizes==reportNImageResizes: logging.warning("Multiple images have needed resizing - I'll stop bothering you!") im=im.resize([powerOf2,powerOf2],Image.BILINEAR) #is it Luminance or RGB? if pixFormat==GL.GL_ALPHA and im.mode!='L':#we have RGB and need Lum wasLum = True im = im.convert("L")#force to intensity (in case it was rgb) elif im.mode=='L': #we have lum and no need to change wasLum = True elif pixFormat==GL.GL_RGB: #we want RGB and might need to convert from CMYK or Lm #texture = im.tostring("raw", "RGB", 0, -1) im = im.convert("RGBA") wasLum=False if dataType==GL.GL_FLOAT: #convert from ubyte to float intensity = numpy.array(im).astype(numpy.float32)*0.0078431372549019607-1.0 # much faster to avoid division 2/255 else: intensity = numpy.array(im) if pixFormat==GL.GL_RGB and wasLum and dataType==GL.GL_FLOAT: #grating stim on good machine #keep as float32 -1:1 if sys.platform!='darwin' and stim.win.glVendor.startswith('nvidia'): #nvidia under win/linux might not support 32bit float internalFormat = GL.GL_RGB16F_ARB #could use GL_LUMINANCE32F_ARB here but check shader code? else:#we've got a mac or an ATI card and can handle 32bit float textures internalFormat = GL.GL_RGB32F_ARB #could use GL_LUMINANCE32F_ARB here but check shader code? data = numpy.ones((intensity.shape[0],intensity.shape[1],3),numpy.float32)#initialise data array as a float data[:,:,0] = intensity#R data[:,:,1] = intensity#G data[:,:,2] = intensity#B elif pixFormat==GL.GL_RGB and wasLum and dataType!=GL.GL_FLOAT and stim.useShaders: #was a lum image: stick with ubyte for speed internalFormat = GL.GL_RGB data = numpy.ones((intensity.shape[0],intensity.shape[1],3),numpy.ubyte)#initialise data array as a float data[:,:,0] = intensity#R data[:,:,1] = intensity#G data[:,:,2] = intensity#B elif pixFormat==GL.GL_RGB and wasLum and not stim.useShaders: #Grating on legacy hardware, or ImageStim with wasLum=True #scale by rgb and convert to ubyte internalFormat = GL.GL_RGB if stim.colorSpace in ['rgb', 'dkl', 'lms','hsv']: rgb=stim.rgb else: rgb=stim.rgb/127.5-1.0#colour is not a float - convert to float to do the scaling # if wasImage it will also have ubyte values for the intensity if wasImage: intensity = intensity/127.5-1.0 #scale by rgb data = numpy.ones((intensity.shape[0],intensity.shape[1],3),numpy.float32)#initialise data array as a float data[:,:,0] = intensity*rgb[0] + stim.rgbPedestal[0]#R data[:,:,1] = intensity*rgb[1] + stim.rgbPedestal[1]#G data[:,:,2] = intensity*rgb[2] + stim.rgbPedestal[2]#B #convert to ubyte data = float_uint8(stim.contrast*data) elif pixFormat==GL.GL_RGB and dataType==GL.GL_FLOAT: #probably a custom rgb array or rgb image internalFormat = GL.GL_RGB32F_ARB data = intensity elif pixFormat==GL.GL_RGB:# not wasLum, not useShaders - an RGB bitmap with no shader options internalFormat = GL.GL_RGB data = intensity #float_uint8(intensity) elif pixFormat==GL.GL_ALPHA: internalFormat = GL.GL_ALPHA if wasImage: data = intensity else: data = float_uint8(intensity) #check for RGBA textures if len(intensity.shape)>2 and intensity.shape[2] == 4: if pixFormat==GL.GL_RGB: pixFormat=GL.GL_RGBA if internalFormat==GL.GL_RGB: internalFormat=GL.GL_RGBA elif internalFormat==GL.GL_RGB32F_ARB: internalFormat=GL.GL_RGBA32F_ARB texture = data.ctypes#serialise #bind the texture in openGL GL.glEnable(GL.GL_TEXTURE_2D) GL.glBindTexture(GL.GL_TEXTURE_2D, id)#bind that name to the target GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_WRAP_S,GL.GL_REPEAT) #makes the texture map wrap (this is actually default anyway) #important if using bits++ because GL_LINEAR #sometimes extrapolates to pixel vals outside range if interpolate: GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_LINEAR) if useShaders:#GL_GENERATE_MIPMAP was only available from OpenGL 1.4 GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, internalFormat, data.shape[1],data.shape[0], 0, # [JRG] for non-square, want data.shape[1], data.shape[0] pixFormat, dataType, texture) else:#use glu GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_NEAREST) GL.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, internalFormat, data.shape[1],data.shape[0], pixFormat, dataType, texture) # [JRG] for non-square, want data.shape[1], data.shape[0] else: GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_NEAREST) GL.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER,GL.GL_NEAREST) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, internalFormat, data.shape[1],data.shape[0], 0, # [JRG] for non-square, want data.shape[1], data.shape[0] pixFormat, dataType, texture) GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)#?? do we need this - think not! return wasLum
def _vlc_start(self): """ Create the vlc stream player for the video using python-vlc. """ if not os.access(self.filename, os.R_OK): raise RuntimeError('Error: %s file not readable' % self.filename) if self.no_audio: instance = vlc.Instance("--no-audio") else: instance = vlc.Instance() try: stream = instance.media_new(self.filename) except NameError: msg = 'NameError: %s vs LibVLC %s' raise ImportError(msg % (vlc.__version__, vlc.libvlc_get_version())) player = instance.media_player_new() player.set_media(stream) # Load up the file stream.parse() size = player.video_get_size() self.video_width = size[0] self.video_height = size[1] self.frame_rate = player.get_fps() self.frame_counter = 0 # TODO: Why is duration -1 still even after parsing? Newer vlc docs seem to hint this won't work until playback starts duration = player.get_length() logging.warning( "Video is %ix%i, duration %s, fps %s" % (self.video_width, self.video_height, duration, self.frame_rate)) logging.flush() # We assume we can use the RGBA format here player.video_set_format("RGBA", self.video_width, self.video_height, self.video_width << 2) # Configure a lock and a buffer for the pixels coming from VLC self.pixel_lock = threading.Lock() self.pixel_buffer = (ctypes.c_ubyte * self.video_width * self.video_height * 4)() # Once you set these callbacks, you are in complete control of what to do with the video buffer selfref = ctypes.cast(ctypes.pointer(ctypes.py_object(self)), ctypes.c_void_p) player.video_set_callbacks(vlcLockCallback, vlcUnlockCallback, vlcDisplayCallback, selfref) manager = player.event_manager() manager.event_attach(vlc.EventType.MediaPlayerTimeChanged, vlcTimeCallback, weakref.ref(self), player) manager.event_attach(vlc.EventType.MediaPlayerEndReached, vlcEndReached, weakref.ref(self), player) # Keep references self._self_ref = selfref self._instance = instance self._player = player self._stream = stream self._manager = manager logging.info("Initialized VLC...") self._vlc_initialized = True
if len(thisInfo[dlgLabelsOrdered.index('staircaseTrials')]) >0: staircaseTrials = int( thisInfo[ dlgLabelsOrdered.index('staircaseTrials') ] ) #convert string to integer print('staircaseTrials entered by user='******'staircaseTrials entered by user='******'easyTrials')]) >0: prefaceStaircaseTrialsN = int( thisInfo[ dlgLabelsOrdered.index('easyTrials') ] ) #convert string to integer print('prefaceStaircaseTrialsN entered by user='******'easyTrials')]) logging.info('prefaceStaircaseTrialsN entered by user='******'trialsPerCondition') ] ) #convert string to integer print('trialsPerCondition=',trialsPerCondition) logging.info('trialsPerCondition =',trialsPerCondition) defaultNoiseLevel = int (thisInfo[ dlgLabelsOrdered.index('defaultNoiseLevel') ]) else: print('User cancelled from dialog box.') logging.flush() core.quit() if not demo: allowGUI = False myWin = openMyStimWindow() #set up output data file, log file, copy of program code, and logging infix = '' if doStaircase: infix = 'staircase_' fileName = os.path.join(dataDir, subject + '_' + infix+ timeAndDateStr) if not demo and not exportImages: dataFile = open(fileName+'.txt', 'w') saveCodeCmd = 'cp \'' + sys.argv[0] + '\' '+ fileName + '.py' os.system(saveCodeCmd) #save a copy of the code as it was when that subject was run logFname = fileName+'.log'
def findIdentityLUT(self, maxIterations = 1000, errCorrFactor = 1.0/2048, # amount of correction done for each iteration nVerifications = 50, #number of repeats (successful) to check dithering has been eradicated demoMode = True, #generate the screen but don't go into status mode logFile = '', ): """Search for the identity LUT for this card/operating system. This requires that the window being tested is fullscreen on the Bits# monitor (or at least occupys the first 256 pixels in the top left corner!) :params: LUT: The lookup table to be tested (256x3). If None then the LUT will not be altered :returns: a 256x3 array of error values (integers in range 0:255) """ t0 = time.time() #create standard options LUTs = {} LUTs['intel'] = np.repeat(np.linspace(.05,.95,256),3).reshape([-1,3]) LUTs['0-255'] = np.repeat(np.linspace(0,1.0,256),3).reshape([-1,3]) LUTs['0-65535'] = np.repeat(np.linspace(0.0, 65535.0/65536.0, num=256),3).reshape([-1,3]) LUTs['1-65536'] = np.repeat(np.linspace(0.0, 65535.0/65536.0, num=256),3).reshape([-1,3]) if logFile: self.logFile = open(logFile,'w') if plotResults: pyplot.Figure() pyplot.plot([0,255],[0,255], '-k') errPlot = pyplot.plot(range(256), range(256), '.r')[0] pyplot.show(block=False) lowestErr = 1000000000 bestLUTname = None logging.flush() for LUTname, currentLUT in LUTs.items(): print 'Checking %r LUT:' %(LUTname), errs = self.testLUT(currentLUT, demoMode) if plotResults: errPlot.set_ydata(range(256)+errs[:,0]) pyplot.draw() print 'mean err = %.3f per LUT entry' %(abs(errs).mean()) if abs(errs).mean()< abs(lowestErr): lowestErr = abs(errs).mean() bestLUTname = LUTname if lowestErr==0: print "The %r identity LUT produced zero error. We'll use that!" %(LUTname) return print "Best was %r LUT (mean err = %.3f). Optimising that..." %(bestLUTname, lowestErr) currentLUT = LUTs[bestLUTname] errProgression=[] corrInARow=0 for n in range(maxIterations): errs = self.testLUT(currentLUT) tweaks = errs*errCorrFactor currentLUT -= tweaks currentLUT[currentLUT>1] = 1.0 currentLUT[currentLUT<0] = 0.0 meanErr = abs(errs).mean() errProgression.append(meanErr) if plotResults: errPlot.set_ydata(range(256)+errs[:,0]) pyplot.draw() if meanErr>0: print "%.3f" %meanErr, corrInARow=0 else: print ".", corrInARow+=1 if corrInARow>=nVerifications: print 'success in a total of %.1fs' %(time.time()-t0) self.identityLUT = currentLUT self.save() #it worked so save this configuration for future break elif len(errProgression)>10 and max(errProgression)-min(errProgression)<0.001: print "Trying to correct the gamma table was having no effect. Make sure the window was fullscreen and on the Bits# screen" break #did we get here by failure?! if n==(maxIterations-1): print "failed to converge on a successful identity LUT. This is BAD!" if plotResults: pyplot.figure(figsize=[18,12]) pyplot.subplot(1,3,1) pyplot.plot(errProgression) pyplot.title('Progression of errors') pyplot.ylabel("Mean error per LUT entry (0-1)") pyplot.xlabel("Test iteration") r256 = np.reshape(range(256),[256,1]) pyplot.subplot(1,3,2) pyplot.plot(r256, r256, 'k-') pyplot.plot(r256, currentLUT[:,0]*255, 'r.', markersize=2.0) pyplot.plot(r256, currentLUT[:,1]*255, 'g.', markersize=2.0) pyplot.plot(r256, currentLUT[:,2]*255, 'b.', markersize=2.0) pyplot.title('Final identity LUT') pyplot.ylabel("LUT value") pyplot.xlabel("LUT entry") pyplot.subplot(1,3,3) deviations = currentLUT-r256/255.0 pyplot.plot(r256, deviations[:,0], 'r.') pyplot.plot(r256, deviations[:,1], 'g.') pyplot.plot(r256, deviations[:,2], 'b.') pyplot.title('LUT deviations from sensible') pyplot.ylabel("LUT value") pyplot.xlabel("LUT deviation (multiples of 1024)") pyplot.savefig("bitsSharpIdentityLUT.pdf") pyplot.show()
def initPyo(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex Sound = SoundPyo #subclass the pyo.Server so that we can insert a __del__ function that shuts it down class Server(pyo.Server): core=core #make libs class variables so they don't get deleted first logging=logging def __del__(self): self.stop() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.shutdown() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.logging.debug('pyo sound server shutdown')#this may never get printed #check if we already have a server and kill it if hasattr(pyoSndServer,'shutdown'): #this doesn't appear to work! pyoSndServer.stop() core.wait(0.5)#make sure enough time passes for the server to shutdown pyoSndServer.shutdown() pyoSndServer.reinit(sr=rate, nchnls=2, buffersize=buffer, duplex=1, audio=audioDriver) pyoSndServer.boot() else: #create the instance of the server if platform=='win32': #check for output device/driver devNames, devIDs=pyo.pa_get_output_devices() audioDriver,outputID=_bestDriver(devNames, devIDs) if outputID: logging.info('Using sound driver: %s (ID=%i)' %(audioDriver, outputID)) else: logging.warning('No audio outputs found (no speakers connected?') return -1 #check for valid input (mic) devNames, devIDs = pyo.pa_get_input_devices() junk, inputID=_bestDriver(devNames, devIDs) if inputID: duplex=True else: duplex=False else:#for other platforms set duplex to True audioDriver = prefs.general['audioDriver'][0] duplex=True if platform=='darwin': #for mac we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=2, buffersize=buffer, audio=audioDriver, duplex=duplex) else: #with others we just use portaudio and then set the OutputDevice below pyoSndServer = Server(sr=rate, nchnls=2, buffersize=buffer, duplex=duplex) pyoSndServer.setVerbosity(1) if platform=='win32': pyoSndServer.setOutputDevice(outputID) if inputID: pyoSndServer.setInputDevice(inputID) #do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.boot() core.wait(0.5)#wait for server to boot before starting te sound stream pyoSndServer.start() logging.debug('pyo sound server started') logging.flush()
def loadFromXML(self, filename): """Loads an xml file and parses the builder Experiment from it """ self._doc.parse(filename) root = self._doc.getroot() # some error checking on the version (and report that this isn't valid # .psyexp)? filenameBase = os.path.basename(filename) if root.tag != "PsychoPy2experiment": logging.error('%s is not a valid .psyexp file, "%s"' % (filenameBase, root.tag)) # the current exp is already vaporized at this point, oops return self.psychopyVersion = root.get('version') versionf = float(self.psychopyVersion.rsplit('.', 1)[0]) if versionf < 1.63: msg = 'note: v%s was used to create %s ("%s")' vals = (self.psychopyVersion, filenameBase, root.tag) logging.warning(msg % vals) # Parse document nodes # first make sure we're empty self.flow = Flow(exp=self) # every exp has exactly one flow self.routines = {} self.namespace = NameSpace(self) # start fresh modifiedNames = [] duplicateNames = [] # fetch exp settings settingsNode = root.find('Settings') for child in settingsNode: self._getXMLparam(params=self.settings.params, paramNode=child, componentNode=settingsNode) # name should be saved as a settings parameter (only from 1.74.00) if self.settings.params['expName'].val in ['', None, 'None']: shortName = os.path.splitext(filenameBase)[0] self.setExpName(shortName) # fetch routines routinesNode = root.find('Routines') allCompons = getAllComponents( self.prefsBuilder['componentsFolders'], fetchIcons=False) # get each routine node from the list of routines for routineNode in routinesNode: routineGoodName = self.namespace.makeValid( routineNode.get('name')) if routineGoodName != routineNode.get('name'): modifiedNames.append(routineNode.get('name')) self.namespace.user.append(routineGoodName) routine = Routine(name=routineGoodName, exp=self) # self._getXMLparam(params=routine.params, paramNode=routineNode) self.routines[routineNode.get('name')] = routine for componentNode in routineNode: componentType = componentNode.tag if componentType in allCompons: # create an actual component of that type component = allCompons[componentType]( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) else: # create UnknownComponent instead component = allCompons['UnknownComponent']( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) # check for components that were absent in older versions of # the builder and change the default behavior # (currently only the new behavior of choices for RatingScale, # HS, November 2012) # HS's modification superceded Jan 2014, removing several # RatingScale options if componentType == 'RatingScaleComponent': if (componentNode.get('choiceLabelsAboveLine') or componentNode.get('lowAnchorText') or componentNode.get('highAnchorText')): pass # if not componentNode.get('choiceLabelsAboveLine'): # # this rating scale was created using older version # component.params['choiceLabelsAboveLine'].val=True # populate the component with its various params for paramNode in componentNode: self._getXMLparam(params=component.params, paramNode=paramNode, componentNode=componentNode) compGoodName = self.namespace.makeValid( componentNode.get('name')) if compGoodName != componentNode.get('name'): modifiedNames.append(componentNode.get('name')) self.namespace.add(compGoodName) component.params['name'].val = compGoodName routine.append(component) # for each component that uses a Static for updates, we need to set # that for thisRoutine in list(self.routines.values()): for thisComp in thisRoutine: for thisParamName in thisComp.params: thisParam = thisComp.params[thisParamName] if thisParamName == 'advancedParams': continue # advanced isn't a normal param elif thisParam.updates and "during:" in thisParam.updates: # remove the part that says 'during' updates = thisParam.updates.split(': ')[1] routine, static = updates.split('.') if routine not in self.routines: msg = ("%s was set to update during %s Static " "Component, but that component no longer " "exists") logging.warning(msg % (thisParamName, static)) else: self.routines[routine].getComponentFromName( static).addComponentUpdate( thisRoutine.params['name'], thisComp.params['name'], thisParamName) # fetch flow settings flowNode = root.find('Flow') loops = {} for elementNode in flowNode: if elementNode.tag == "LoopInitiator": loopType = elementNode.get('loopType') loopName = self.namespace.makeValid(elementNode.get('name')) if loopName != elementNode.get('name'): modifiedNames.append(elementNode.get('name')) self.namespace.add(loopName) loop = eval('%s(exp=self,name="%s")' % (loopType, loopName)) loops[loopName] = loop for paramNode in elementNode: self._getXMLparam(paramNode=paramNode, params=loop.params) # for conditions convert string rep to list of dicts if paramNode.get('name') == 'conditions': param = loop.params['conditions'] # e.g. param.val=[{'ori':0},{'ori':3}] try: param.val = eval('%s' % (param.val)) except SyntaxError: # This can occur if Python2.7 conditions string # contained long ints (e.g. 8L) and these can't be # parsed by Py3. But allow the file to carry on # loading and the conditions will still be loaded # from the xlsx file pass # get condition names from within conditionsFile, if any: try: # psychophysicsstaircase demo has no such param conditionsFile = loop.params['conditionsFile'].val except Exception: conditionsFile = None if conditionsFile in ['None', '']: conditionsFile = None if conditionsFile: try: trialList, fieldNames = data.importConditions( conditionsFile, returnFieldNames=True) for fname in fieldNames: if fname != self.namespace.makeValid(fname): duplicateNames.append(fname) else: self.namespace.add(fname) except Exception: pass # couldn't load the conditions file for now self.flow.append(LoopInitiator(loop=loops[loopName])) elif elementNode.tag == "LoopTerminator": self.flow.append(LoopTerminator( loop=loops[elementNode.get('name')])) elif elementNode.tag == "Routine": if elementNode.get('name') in self.routines: self.flow.append(self.routines[elementNode.get('name')]) else: logging.error("A Routine called '{}' was on the Flow but " "could not be found (failed rename?). You " "may need to re-insert it".format( elementNode.get('name'))) logging.flush() if modifiedNames: msg = 'duplicate variable name(s) changed in loadFromXML: %s\n' logging.warning(msg % ', '.join(list(set(modifiedNames)))) if duplicateNames: msg = 'duplicate variable names: %s' logging.warning(msg % ', '.join(list(set(duplicateNames)))) # if we succeeded then save current filename to self self.filename = filename
def handle_gonogo(G): ''' This contains the experimenal logic of the Stop Task. A lot of work went into constructing the stimuli. Stimuli parameters are loaded into variables in the code above. Runs 136 trials of Go-Nogo. This function is to be run within the asyncio loop. ''' # we just need it here... STOP = 1 GO = 0 tooSoonTime = G['S']['tooSoonTime'] myMultiStairs = G['S']['myMultiStairs'] # if the time it took tov respond is smaller than this time --> invalid. numberOfResponses = 0 G['S']['nextFLipTasks'] = [] # stuff winflupper needs to do later on.. # set the visual contents here... # INITIAL SETTING G['S']['goNogoStim'] = G['vstims']['S']['fix'] # yeah, do all kinds of init here. for trialNumber in range(len(G['S']['SSstopgo'])): thisDirection = random.choice( ('al', 'ar')) # obtain this from the file!! LorR = thisDirection[1].upper() thisTrialType_num = G['S']['SSstopgo'][ trialNumber] # this is a 0 (GO) or 1 (STOP) thisTrialType = [GO, STOP][int( thisTrialType_num )] # shady practices indeed -- so later on I cany say 'if this TrialType is GO:, etc' GorNG = ['Go', 'Stop'][int(thisTrialType)] thisISIWaitTime = G['S']['ISIwaitTime'][trialNumber] correctResponseSide = G['S']['correctResponseSides'][trialNumber] wrongResponseSide = G['S']['wrongResponseSides'][trialNumber] allResponses = [] responded = False # subj responded? tooManyResponses = False trialHandled = False if thisTrialType is STOP: # this should be called only 40 times, since there are 40 stop trials... thisSSD, thisCondition = myMultiStairs.next( ) # I defined the myMultiStairs above. # this code tells the loop to only continue when continueTroutine is not False # otherwise it'll just keep yielding. # let winflipper make new clock G['S']['continueRoutine'] = False G['S']['nextFlipTasks'].append( [G['S']['resetClock'], G]) # the makeNewClock automatically makes things continue while G['S']['continueRoutine'] is False: yield From(asyncio.sleep(0)) cl = G['S']['clock'] # obtain the clock that was just made. # ok, we can proceed -- the clock has been set. G['S']['goNogoStim'] = G['vstims']['S']['pre'] while cl.getTime() < 0.5: yield From(asyncio.sleep(0)) # obtain our next clock... # this code tells the loop to only continue when continueTroutine is not False # otherwise it'll just keep yielding. # let winflipper make new clock G['S']['continueRoutine'] = False # make sure upon next window flow, we have a new clock set, and also - that marker is sent signalling the start of the new go/stop trial. G['S']['nextFlipTasks'].append( [G['S']['resetClock'], G]) # the makeNewClock automatically makes things continue # send the trigger regarding the arrow, as soon as the windows flips G['S']['nextFlipTasks'].append( [G['eh'].send_message, ['Begin' + GorNG + LorR]]) while G['S']['continueRoutine'] is False: yield From(asyncio.sleep(0)) cl = G['S']['clock'] # obtain the clock that was just made. # this is where we show the arrow + find out whether a key is pressed: G['S']['goNogoStim'] = G['vstims']['S'][thisDirection] currentTime = 0.0 while currentTime < 1.0: currentTime = cl.getTime() # set the stimulus to the proper direction (it's a choice, for now... -- but it's much much better to hard-code it) # make the arrow (+ circle) evs = event.getKeys(timeStamped=cl) if len(evs) > 0: buttonsPressed, timesPressed = zip(*evs) # it's highly unlikely that two buttons are pressed in a signle # frame, but control for that anyway. allResponses.append((buttonsPressed[0], timesPressed[0])) numberOfResponses += 1 # LOG this event... (i.e. send trigger) # handle event: # once a button is pressed -- display fixation point again. if len(allResponses) > 0 and not responded: # 'clear' the visual window --> fixation cross, again: G['S']['goNogoStim'] = G['vstims']['S']['fix'] responded = True buttonPressed, RTime = allResponses[0] if RTime < tooSoonTime: G['eh'].send_message('PressedTooSoon') else: if buttonsPressed[0] == BUTTONS[0]: G['eh'].send_message('RespL') elif buttonsPressed[0] == BUTTONS[1]: G['eh'].send_message('RespR') # if it's a stop trial, then make arrow red after X time if thisTrialType is STOP and not responded: if currentTime > thisSSD: G['S']['goNogoStim'] = G['vstims']['S'][thisDirection + 'r'] # here we let the screen flip, for example... yield From(asyncio.sleep(0)) # # Stop / Inhibit Response Codes # 'BeginGoL':1, # 'BeginGoR':2, # 'BeginStopL':3, # 'BeginStopR':4, # # 'RespL':5, # 'RespR':6, # # 'CorrectGoL':11, # 'CorrectGoR':12, # 'CorrectStopL':13, # 'CorrectStopR':14, # 'ErrorCommission':15, # # # don't expect too many of these: # 'ErrorOmission':21, # 'PressedTooSoon':22, # 'TooManyResponses':23, # 'WrongSideErrorCommission':24, # 'WrongSideGo':25, # so the loop is done -- let's figure out what kind of trial this was. # taking care of the button press itself, as soon as button is pressed: if not trialHandled and responded: trialHandled = True if len(allResponses) > 1: trialOutcome = 'TooManyResponses' if thisTrialType is STOP: myMultiStairs.addResponse(0) else: if RTime < tooSoonTime: trialOutcome = 'PressedTooSoon' if thisTrialType is STOP: myMultiStairs.addResponse(0) else: if thisTrialType is STOP: if buttonPressed == correctResponseSide: trialOutcome = 'ErrorCommission' myMultiStairs.addResponse(0) elif buttonPressed == wrongResponseSide: trialOutcome = 'WrongSideErrorCommission' myMultiStairs.addResponse(0) elif thisTrialType is GO: if buttonPressed == correctResponseSide: trialOutcome = 'CorrectGo' + correctResponseSide # not yet... elif buttonPressed == wrongResponseSide: trialOutcome = 'WrongSideGo' # handle the 'response' if the button was NOT pressed: if not trialHandled and not responded: trialHandled = True if thisTrialType is GO: trialOutcome = 'ErrorOmission' if thisTrialType is STOP: trialOutcome = 'CorrectStop' + LorR myMultiStairs.addResponse(1) # so we send it out: G['eh'].send_message(trialOutcome) # this code tells the loop to only continue when continueTroutine is not False # otherwise it'll just keep yielding. # let winflipper make new clock # this code tells the loop to only continue when continueTroutine is not False # otherwise it'll just keep yielding. # let winflipper make new clock G['S']['continueRoutine'] = False G['S']['nextFlipTasks'].append( [G['S']['resetClock'], G]) # the makeNewClock automatically makes things continue while G['S']['continueRoutine'] is False: yield From(asyncio.sleep(0)) cl = G['S']['clock'] # obtain the clock that was just made. # ok, we can proceed -- the clock has been set. flushed = False G['S']['goNogoStim'] = G['vstims']['S']['fix'] while cl.getTime() < thisISIWaitTime: if not flushed: # this is a nice place to save it to logfile: before the # send a report about the STOP trial, write a nice line: # logging.data('messa') logging.flush() flused = True yield From(asyncio.sleep(0))
def pre_mainloop(self): MostBasicPsychopyFeedback.pre_mainloop(self) # do the trick -- SAVE all of those things! --> and put it in settings.pkl. v = dict() v['caption'] = self.caption v['color'] = self.color v['fontheight'] = self.fontheight v['STARTKEYS'] = self.STARTKEYS v['EX_THRLINEWIDTH'] = self.EX_THRLINEWIDTH v['EX_COLORGAP'] = self.EX_COLORGAP v['EX_TVSP'] = self.EX_TVSP v['EX_TPAUSE'] = self.EX_TPAUSE v['EX_NREGULATE'] = self.EX_NREGULATE v['EX_NTRANSFER'] = self.EX_NTRANSFER v['EX_SHOWCHECKORCROSSTRANSFER'] = self.EX_SHOWCHECKORCROSSTRANSFER v['EX_SQUARESIZE'] = self.EX_SQUARESIZE v['EX_UPREGTEXT'] = self.EX_UPREGTEXT v['EX_TESTSIGNALUPDATEINTERVAL'] = self.EX_TESTSIGNALUPDATEINTERVAL v['EX_NREST'] = self.EX_NREST v['EX_SCALING'] = self.EX_SCALING v['EX_INTERACTIONMODE'] = self.EX_INTERACTIONMODE v['EX_NOBSERVE'] = self.EX_NOBSERVE v['EX_NOREGTEXT'] = self.EX_NOREGTEXT v['EX_TINSTR'] = self.EX_TINSTR v['EX_THERMOCLIMS'] = self.EX_THERMOCLIMS v['EX_GRAPHICSMODE'] = self.EX_GRAPHICSMODE v['EX_SHOWCHECKORCROSS'] = self.EX_SHOWCHECKORCROSS v['EX_STAIRCASEMANIPULATION'] = self.EX_STAIRCASEMANIPULATION v['EX_POINTS_PENALTY'] = self.EX_POINTS_PENALTY v['EX_TESTSIGNALPERIOD'] = self.EX_TESTSIGNALPERIOD v['EX_TMARK'] = self.EX_TMARK v['EX_TESTNFNOISE'] = self.EX_TESTNFNOISE v['EX_PATCHCOLOR'] = self.EX_PATCHCOLOR v['EX_TJITT'] = self.EX_TJITT v['EX_TFB'] = self.EX_TFB v['EX_POINTS_REWARD'] = self.EX_POINTS_REWARD v['EX_PR_SLEEPTIME'] = self.EX_PR_SLEEPTIME v['EX_TESTSIGNALTYPE'] = self.EX_TESTSIGNALTYPE v['EX_BUTTONS'] = self.EX_BUTTONS v['EX_INSTR'] = self.EX_INSTR v['EX_RUNS'] = self.EX_RUNS v['MONITOR_PIXWIDTH'] = self.MONITOR_PIXWIDTH v['MONITOR_PIXHEIGHT'] = self.MONITOR_PIXHEIGHT v['MONITOR_WIDTH'] = self.MONITOR_WIDTH v['MONITOR_HEIGHT'] = self.MONITOR_HEIGHT v['MONITOR_DISTANCE'] = self.MONITOR_DISTANCE v['MONITOR_GAMMA'] = self.MONITOR_GAMMA v['MONITOR_FPS'] = self.MONITOR_FPS v['MONITOR_USEDEGS'] = self.MONITOR_USEDEGS v['MONITOR_DEGS_WIDTHBASE'] = self.MONITOR_DEGS_WIDTHBASE v['MONITOR_DEGS_HEIGHTBASE'] = self.MONITOR_DEGS_HEIGHTBASE v['MONITOR_FLIPHORIZONTAL'] = self.MONITOR_FLIPHORIZONTAL v['MONITOR_FLIPVERTICAL'] = self.MONITOR_FLIPVERTICAL v['MONITOR_RECORDFRAMEINTERVALS'] = self.MONITOR_RECORDFRAMEINTERVALS v['MONITOR_NSCREENS'] = self.MONITOR_NSCREENS v['MONITOR_DISPLAYONSCREEN'] = self.MONITOR_DISPLAYONSCREEN v['MONITOR_FULLSCR'] = self.MONITOR_FULLSCR v['MONITOR_ALLOWGUI'] = self.MONITOR_ALLOWGUI v['LOG_PATHFILE'] = self.LOG_PATHFILE v['LOG_PATHFILE_EVENT'] = self.LOG_PATHFILE_EVENT v['EVENT_LPT_TRIGGER_WAIT'] = self.EVENT_LPT_TRIGGER_WAIT v['EVENT_destip'] = self.EVENT_destip v['EVENT_destport'] = self.EVENT_destport v['EVENT_LPTAddress'] = self.EVENT_LPTAddress v['EVENT_LPTTrigWaitTime'] = self.EVENT_LPTTrigWaitTime v['EVENT_TRIGLOG'] = self.EVENT_TRIGLOG v['EVENT_sendParallel'] = self.EVENT_sendParallel v['EVENT_sendTcpIp'] = self.EVENT_sendTcpIp v['EVENT_sendLogFile'] = self.EVENT_sendLogFile v['EVENT_printToTerminal'] = self.EVENT_printToTerminal v['EVENT_printToTerminalAllowed'] = self.EVENT_printToTerminalAllowed # use the Cpntrol Parameters: CP = self.CP # control parameters... # create G, put it into self too.. G = dict() G['v'] = v self.G = G # we need this in order to continue working as if we're doing it using the normal (test) script... for key in G['v']: G[key] = G['v'][ key] # this is actually superfluous. But removing it might possibly break things. # the main clock mainClock = clock.Clock() G['mainClock'] = mainClock # screen/monitor... G = init_screen(G) # we need to do this # logging... logging.setDefaultClock(G['mainClock']) newLogFile = create_incremental_filename(G['v']['LOG_PATHFILE']) expLogger = logging.LogFile( newLogFile, logging.EXP) # the correct loglevel should be EXP! print(expLogger) logging.LogFile(newLogFile, logging.EXP) # the correct loglevel should be EXP! print('made new logfile: ' + newLogFile) for key in G['v'].keys(): logging.data("{key}: {value}".format(key=key, value=G['v'][key])) logging.flush() G['logging'] = logging # put into the G, which is in self # event handler... G = init_eventcodes(G) # and this?? G = start_eh(G) st = make_stimuli(G, CP) pr = init_programs(G, st, CP) ex = define_experiment( G, st, pr, CP) # pr is passed to define_experiment, but then we won't need... self.st = st self.ex = ex # take care of the randomization(s)... trialopts = [] trialopts.append([1, 1, 2, 1, 3, 4]) trialopts.append([1, 1, 2, 1, 4, 3]) trialopts.append([1, 1, 2, 3, 1, 4]) trialopts.append([1, 1, 2, 4, 1, 3]) trialopts.append([1, 1, 3, 1, 2, 4]) trialopts.append([1, 1, 3, 1, 4, 2]) trialopts.append([1, 1, 3, 2, 1, 4]) trialopts.append([1, 1, 3, 4, 1, 2]) trialopts.append([1, 1, 4, 1, 3, 2]) trialopts.append([1, 1, 4, 1, 2, 3]) trialopts.append([1, 1, 4, 3, 1, 2]) trialopts.append([1, 1, 4, 2, 1, 3]) trialopts.append([1, 2, 1, 4, 1, 3]) trialopts.append([1, 2, 1, 3, 1, 4]) trialopts.append([1, 3, 1, 4, 1, 2]) trialopts.append([1, 3, 1, 2, 1, 4]) trialopts.append([1, 4, 1, 2, 1, 3]) trialopts.append([1, 4, 1, 3, 1, 2]) random.shuffle(trialopts) random.shuffle(trialopts) random.shuffle(trialopts) # 3 time shuffle, for good luck :-) # computational anathema and heretic! my_trial_sequence = flatten( trialopts[0:G['v']['EX_RUNS']]) # we do 5 of them. my_trial_definitions = { 1: 'train', 2: 'transfer', 3: 'observe', 4: 'rest' } # so to debug, just run tasks_dbg instead of tasks. for t_i in my_trial_sequence: self.runlist = iter( [my_trial_definitions[i] for i in my_trial_sequence]) # the ev loop we're going to be using.. loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) self.loop = loop if G['EX_TESTNFNOISE'] is True: self.loop.create_task(pr['GenTestSignal'](G, st, CP)) logging.flush() G['logging'] = logging G['cl'] = clock.Clock( ) # init the trial-by-trial clock here and put into G...
def initPyo(rate=44100, stereo=True, buffer=128): """setup the pyo (sound) server """ global pyoSndServer, Sound, audioDriver, duplex, maxChnls Sound = SoundPyo global pyo try: assert pyo except NameError: # pragma: no cover import pyo # microphone.switchOn() calls initPyo even if audioLib is something else #subclass the pyo.Server so that we can insert a __del__ function that shuts it down # skip coverage since the class is never used if we have a recent version of pyo class _Server(pyo.Server): # pragma: no cover core=core #make libs class variables so they don't get deleted first logging=logging def __del__(self): self.stop() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.shutdown() self.core.wait(0.5)#make sure enough time passes for the server to shutdown self.logging.debug('pyo sound server shutdown')#this may never get printed if '.'.join(map(str, pyo.getVersion())) < '0.6.4': Server = _Server else: Server = pyo.Server # if we already have a server, just re-initialize it if 'pyoSndServer' in globals() and hasattr(pyoSndServer,'shutdown'): pyoSndServer.stop() core.wait(0.5)#make sure enough time passes for the server to shutdown pyoSndServer.shutdown() core.wait(0.5) pyoSndServer.reinit(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) pyoSndServer.boot() else: if platform=='win32': #check for output device/driver devNames, devIDs=pyo.pa_get_output_devices() audioDriver,outputID=_bestDriver(devNames, devIDs) if outputID is None: audioDriver = 'Windows Default Output' #using the default output because we didn't find the one(s) requested outputID = pyo.pa_get_default_output() if outputID is not None: logging.info('Using sound driver: %s (ID=%i)' %(audioDriver, outputID)) maxOutputChnls = pyo.pa_get_output_max_channels(outputID) else: logging.warning('No audio outputs found (no speakers connected?') return -1 #check for valid input (mic) devNames, devIDs = pyo.pa_get_input_devices() audioInputName, inputID = _bestDriver(devNames, devIDs) if inputID is None: audioInputName = 'Windows Default Input' #using the default input because we didn't find the one(s) requested inputID = pyo.pa_get_default_input() if inputID is not None: logging.info('Using sound-input driver: %s (ID=%i)' %(audioInputName, inputID)) maxInputChnls = pyo.pa_get_input_max_channels(inputID) duplex = bool(maxInputChnls > 0) else: maxInputChnls = 0 duplex=False else:#for other platforms set duplex to True (if microphone is available) audioDriver = prefs.general['audioDriver'][0] maxInputChnls = pyo.pa_get_input_max_channels(pyo.pa_get_default_input()) maxOutputChnls = pyo.pa_get_output_max_channels(pyo.pa_get_default_output()) duplex = bool(maxInputChnls > 0) maxChnls = min(maxInputChnls, maxOutputChnls) if maxInputChnls < 1: # pragma: no cover logging.warning('%s.initPyo could not find microphone hardware; recording not available' % __name__) maxChnls = maxOutputChnls if maxOutputChnls < 1: # pragma: no cover logging.error('%s.initPyo could not find speaker hardware; sound not available' % __name__) return -1 # create the instance of the server: if platform in ['darwin', 'linux2']: #for mac/linux we set the backend using the server audio param pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer, audio=audioDriver) else: #with others we just use portaudio and then set the OutputDevice below pyoSndServer = Server(sr=rate, nchnls=maxChnls, buffersize=buffer) pyoSndServer.setVerbosity(1) if platform=='win32': pyoSndServer.setOutputDevice(outputID) if inputID is not None: pyoSndServer.setInputDevice(inputID) #do other config here as needed (setDuplex? setOutputDevice?) pyoSndServer.setDuplex(duplex) pyoSndServer.boot() core.wait(0.5)#wait for server to boot before starting te sound stream pyoSndServer.start() try: Sound() # test creation, no play except pyo.PyoServerStateException: msg = "Failed to start pyo sound Server" if platform == 'darwin' and audioDriver != 'portaudio': msg += "; maybe try prefs.general.audioDriver 'portaudio'?" logging.error(msg) core.quit() logging.debug('pyo sound server started') logging.flush()
def runPsychopy(self): # make the marker stream. Must do before the window setup to be able to start Lab Recorder self.__marker_outlet = self.__createMarkerStream() # Get experiement details and filename filename, thisExp, expInfo = self.__getDatafilenameAndSetupWindow() # save a log file for detail verbose info # logFile = logging.LogFile(filename+'.log', level=logging.EXP) logging.console.setLevel( logging.WARNING) # this outputs to the screen, not a file ### ESC flag ### self.__endExpNow = False # flag for 'escape' or other condition => quit the exp # Create some handy timers globalClock = core.Clock( ) # to track the time since experiment started self.__routineTimer = core.CountdownTimer( ) # to track time remaining of each (non-slip) routine self.__kb = keyboard.Keyboard() # Flag the start of the Psychopy experiment self.__marker_outlet.push_sample([RECORDING_START_MARKER]) self.__showTextWithSpaceExit(instructions_text) self.__marker_outlet.push_sample([CALIBRATION_START_MARKER]) self.__marker_outlet.push_sample([BLINK_START_MARKER]) self.__showTextWithSpaceExit(blink_text) self.__marker_outlet.push_sample([BLINK_END_MARKER]) self.__marker_outlet.push_sample([CLOSE_EYE_START_MARKER]) self.__showTextWithSpaceExit(close_eye_text) self.__marker_outlet.push_sample([CLOSE_EYE_END_MARKER]) self.__marker_outlet.push_sample([CALIBRATION_END_MARKER]) for i in range(NUM_IMAGES_TO_SHOW): if i % 140 == 0: print("shuffling!") rd.shuffle(faces_filenames) rd.shuffle(landscape_filenames) # Reset the timers self.__routineTimer.reset() self.__kb.clock.reset() time_shown = 0 self.__marker_outlet.push_sample([CROSS_START_MARKER]) self.__showTimedText( "+", rd.randrange((NUM_SECONDS_TO_SHOW_CROSS) * 10, (NUM_SECONDS_TO_SHOW_CROSS + 0.2) * 10) / 10) self.__marker_outlet.push_sample([CROSS_END_MARKER]) self.__marker_outlet.push_sample([NEW_IMAGE_START_MARKER]) self.__marker_outlet.push_sample( [FACE_IMAGE_MARKER] if self.__current_type == FACE else [LANDSCAPE_IMAGE_MARKER]) self.__getNextImage() self.__startRoutine([self.__image_stim]) self.__marker_outlet.push_sample([self.__image_filename]) print(self.__image_filename) self.__setDrawOn([self.__image_stim]) self.__showTimedText( "", rd.randrange((NUM_SECONDS_TO_SHOW_IMAGE) * 10, (NUM_SECONDS_TO_SHOW_IMAGE + 0.2) * 10) / 10) self.__endRoutine([self.__image_stim]) self.__marker_outlet.push_sample([NEW_IMAGE_END_MARKER]) # Flag the end of the Psychopy experiment self.__marker_outlet.push_sample([RECORDING_END_MARKER]) logging.flush() # make sure everything is closed down thisExp.abort() # or data files will save again on exit self.__win.close()
def findPhotometer(ports=None, device=None): """Try to find a connected photometer/photospectrometer! PsychoPy will sweep a series of serial ports trying to open them. If a port successfully opens then it will try to issue a command to the device. If it responds with one of the expected values then it is assumed to be the appropriate device. :parameters: ports : a list of ports to search Each port can be a string (e.g. 'COM1', ''/dev/tty.Keyspan1.1') or a number (for win32 comports only). If none are provided then PsychoPy will sweep COM0-10 on win32 and search known likely port names on macOS and Linux. device : string giving expected device (e.g. 'PR650', 'PR655', 'LS100', 'LS110'). If this is not given then an attempt will be made to find a device of any type, but this often fails :returns: * An object representing the first photometer found * None if the ports didn't yield a valid response * None if there were not even any valid ports (suggesting a driver not being installed) e.g.:: # sweeps ports 0 to 10 searching for a PR655 photom = findPhotometer(device='PR655') print(photom.getLum()) if hasattr(photom, 'getSpectrum'): # can retrieve spectrum (e.g. a PR650) print(photom.getSpectrum()) """ if isinstance(device, basestring): photometers = [getPhotometerByName(device)] elif isinstance(device, Iterable): # if we find a string assume it is a name, otherwise treat it like a # photometer photometers = [getPhotometerByName(d) if isinstance(d, basestring) else d for d in device] else: photometers = getAllPhotometers() # determine candidate ports if ports is None: ports = getSerialPorts() elif type(ports) in (int, float) or isinstance(ports, basestring): ports = [ports] # so that we can iterate # go through each port in turn photom = None logging.info('scanning serial ports...') logging.flush() for thisPort in ports: logging.info('...{}'.format(thisPort)) logging.flush() for Photometer in photometers: # Looks like we got an invalid photometer, carry on if Photometer is None: continue try: photom = Photometer(port=thisPort) except Exception as ex: msg = "Couldn't initialize photometer {0}: {1}" logging.error(msg.format(Photometer.__name__, ex)) # We threw an exception so we should just skip ahead continue if photom.OK: logging.info(' ...found a %s\n' % (photom.type)) logging.flush() # we're now sure that this is the correct device and that # it's configured now increase the number of attempts made # to communicate for temperamental devices! if hasattr(photom, 'setMaxAttempts'): photom.setMaxAttempts(10) # we found one so stop looking return photom else: if photom.com and photom.com.isOpen: logging.info('closing port') photom.com.close() # If we got here we didn't find one logging.info('...nope!\n\t') logging.flush() return None
def __init__(self, arg=0, testMode=False, **kwargs): """With a wx.App some things get done here, before App.__init__ then some further code is launched in OnInit() which occurs after """ if profiling: import cProfile, time profile = cProfile.Profile() profile.enable() t0 = time.time() self._appLoaded = False # set to true when all frames are created self.coder = None self.runner = None self.version = psychopy.__version__ # set default paths and prefs self.prefs = psychopy.prefs self._currentThemeSpec = None self.keys = self.prefs.keys self.prefs.pageCurrent = 0 # track last-viewed page, can return there self.IDs = IDStore() self.urls = urls.urls self.quitting = False # check compatibility with last run version (before opening windows) self.firstRun = False self.testMode = testMode self._stdout = sys.stdout self._stderr = sys.stderr self._stdoutFrame = None self.iconCache = themes.IconCache() if not self.testMode: self._lastRunLog = open(os.path.join( self.prefs.paths['userPrefsDir'], 'last_app_load.log'), 'w') sys.stderr = sys.stdout = lastLoadErrs = self._lastRunLog logging.console.setLevel(logging.DEBUG) # indicates whether we're running for testing purposes self.osfSession = None self.pavloviaSession = None self.copiedRoutine = None self.copiedCompon = None self._allFrames = frametracker.openFrames # ordered; order updated with self.onNewTopWindow wx.App.__init__(self, arg) # import localization after wx: from psychopy import localization # needed by splash screen self.localization = localization self.locale = localization.setLocaleWX() self.locale.AddCatalog(self.GetAppName()) logging.flush() self.onInit(testMode=testMode, **kwargs) if profiling: profile.disable() print("time to load app = {:.2f}".format(time.time()-t0)) profile.dump_stats('profileLaunchApp.profile') logging.flush() # set the exception hook to present unhandled errors in a dialog if not travisCI: from psychopy.app.errorDlg import exceptionCallback sys.excepthook = exceptionCallback
def _getXMLparam(self, params, paramNode, componentNode=None): """params is the dict of params of the builder component (e.g. stimulus) into which the parameters will be inserted (so the object to store the params should be created first) paramNode is the parameter node fetched from the xml file """ name = paramNode.get('name') valType = paramNode.get('valType') val = paramNode.get('val') # many components need web char newline replacement if not name == 'advancedParams': val = val.replace(" ", "\n") # custom settings (to be used when if valType == 'fixedList': # convert the string to a list params[name].val = eval('list({})'.format(val)) elif name == 'storeResponseTime': return # deprecated in v1.70.00 because it was redundant elif name == 'nVertices': # up to 1.85 there was no shape param # if no shape param then use "n vertices" only if _findParam('shape', componentNode) is None: if val == '2': params['shape'].val = "line" elif val == '3': params['shape'].val = "triangle" elif val == '4': params['shape'].val = "rectangle" else: params['shape'].val = "regular polygon..." params['nVertices'].val = val elif name == 'startTime': # deprecated in v1.70.00 params['startType'].val = "{}".format('time (s)') params['startVal'].val = "{}".format(val) return # times doesn't need to update its type or 'updates' rule elif name == 'forceEndTrial': # deprecated in v1.70.00 params['forceEndRoutine'].val = bool(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'forceEndTrialOnPress': # deprecated in v1.70.00 params['forceEndRoutineOnPress'].val = bool(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'forceEndRoutineOnPress': if val == 'True': val = "any click" elif val == 'False': val = "never" params['forceEndRoutineOnPress'].val = val return elif name == 'trialList': # deprecated in v1.70.00 params['conditions'].val = eval(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'trialListFile': # deprecated in v1.70.00 params['conditionsFile'].val = "{}".format(val) return # forceEndTrial doesn't need to update type or 'updates' elif name == 'duration': # deprecated in v1.70.00 params['stopType'].val = u'duration (s)' params['stopVal'].val = "{}".format(val) return # times doesn't need to update its type or 'updates' rule elif name == 'allowedKeys' and valType == 'str': # changed v1.70.00 # ynq used to be allowed, now should be 'y','n','q' or # ['y','n','q'] if len(val) == 0: newVal = val elif val[0] == '$': newVal = val[1:] # they were using code (which we can reuse) elif val.startswith('[') and val.endswith(']'): # they were using code (slightly incorectly!) newVal = val[1:-1] elif val in ['return', 'space', 'left', 'right', 'escape']: newVal = val # they were using code else: # convert string to list of keys then represent again as a # string! newVal = repr(list(val)) params['allowedKeys'].val = newVal params['allowedKeys'].valType = 'code' elif name == 'correctIf': # deprecated in v1.60.00 corrIf = val corrAns = corrIf.replace( 'resp.keys==unicode(', '').replace(')', '') params['correctAns'].val = corrAns name = 'correctAns' # then we can fetch other aspects below elif 'olour' in name: # colour parameter was Americanised v1.61.00 name = name.replace('olour', 'olor') params[name].val = val elif name == 'times': # deprecated in v1.60.00 times = eval('%s' % val) params['startType'].val = "{}".format('time (s)') params['startVal'].val = "{}".format(times[0]) params['stopType'].val = "{}".format('time (s)') params['stopVal'].val = "{}".format(times[1]) return # times doesn't need to update its type or 'updates' rule elif name in ('Begin Experiment', 'Begin Routine', 'Each Frame', 'End Routine', 'End Experiment'): params[name].val = val params[name].valType = 'extendedCode' # changed in 1.78.00 return # so that we don't update valTyp again below elif name == 'Saved data folder': # deprecated in 1.80 for more complete data filename control params[name] = Param( val, valType='code', allowedTypes=[], hint=_translate("Name of the folder in which to save data" " and log files (blank defaults to the " "builder pref)"), categ='Data') elif 'val' in list(paramNode.keys()): if val == 'window units': # changed this value in 1.70.00 params[name].val = 'from exp settings' # in v1.80.00, some RatingScale API and Param fields were changed # Try to avoid a KeyError in these cases so can load the expt elif name in ('choiceLabelsAboveLine', 'lowAnchorText', 'highAnchorText'): # not handled, just ignored; want labels=[lowAnchor, # highAnchor] return elif name == 'customize_everything': # Try to auto-update the code: v = val # python code, not XML v = v.replace('markerStyle', 'marker').replace( 'customMarker', 'marker') v = v.replace('stretchHoriz', 'stretch').replace( 'displaySizeFactor', 'size') v = v.replace('textSizeFactor', 'textSize') v = v.replace('ticksAboveLine=False', 'tickHeight=-1') v = v.replace('showScale=False', 'scale=None').replace( 'allowSkip=False', 'skipKeys=None') v = v.replace('showAnchors=False', 'labels=None') # lowAnchorText highAnchorText will trigger obsolete error # when run the script params[name].val = v else: if name in params: params[name].val = val else: # we found an unknown parameter (probably from the future) params[name] = Param( val, valType=paramNode.get('valType'), allowedTypes=[], hint=_translate( "This parameter is not known by this version " "of PsychoPy. It might be worth upgrading")) params[name].allowedTypes = paramNode.get('allowedTypes') if params[name].allowedTypes is None: params[name].allowedTypes = [] params[name].readOnly = True msg = _translate( "Parameter %r is not known to this version of " "PsychoPy but has come from your experiment file " "(saved by a future version of PsychoPy?). This " "experiment may not run correctly in the current " "version.") logging.warn(msg % name) logging.flush() # get the value type and update rate if 'valType' in list(paramNode.keys()): params[name].valType = paramNode.get('valType') # compatibility checks: if name in ['allowedKeys'] and paramNode.get('valType') == 'str': # these components were changed in v1.70.00 params[name].valType = 'code' elif name == 'Selected rows': # changed in 1.81.00 from 'code' to 'str': allow string or var params[name].valType = 'str' # conversions based on valType if params[name].valType == 'bool': params[name].val = eval("%s" % params[name].val) if 'updates' in list(paramNode.keys()): params[name].updates = paramNode.get('updates')
def __init__(self, arg=0, testMode=False, **kwargs): """With a wx.App some things get done here, before App.__init__ then some further code is launched in OnInit() which occurs after """ if profiling: import cProfile import time profile = cProfile.Profile() profile.enable() t0 = time.time() self._appLoaded = False # set to true when all frames are created self.coder = None self.runner = None self.version = psychopy.__version__ # set default paths and prefs self.prefs = psychopy.prefs self._currentThemeSpec = None self.keys = self.prefs.keys self.prefs.pageCurrent = 0 # track last-viewed page, can return there self.IDs = IDStore() self.urls = urls.urls self.quitting = False # check compatibility with last run version (before opening windows) self.firstRun = False self.testMode = testMode self._stdout = sys.stdout self._stderr = sys.stderr self._stdoutFrame = None # Shared memory used for messaging between app instances, this gets # allocated when `OnInit` is called. self._sharedMemory = None self._singleInstanceChecker = None # checker for instances self._timer = None # Size of the memory map buffer, needs to be large enough to hold UTF-8 # encoded long file paths. self.mmap_sz = 2048 # mdc - removed the following and put it in `app.startApp()` to have # error logging occur sooner. # # if not self.testMode: # self._lastRunLog = open(os.path.join( # self.prefs.paths['userPrefsDir'], 'last_app_load.log'), # 'w') # sys.stderr = sys.stdout = lastLoadErrs = self._lastRunLog # logging.console.setLevel(logging.DEBUG) # indicates whether we're running for testing purposes self.osfSession = None self.pavloviaSession = None self.copiedRoutine = None self.copiedCompon = None self._allFrames = frametracker.openFrames # ordered; order updated with self.onNewTopWindow wx.App.__init__(self, arg) # import localization after wx: from psychopy import localization # needed by splash screen self.localization = localization self.locale = localization.setLocaleWX() self.locale.AddCatalog(self.GetAppName()) logging.flush() self.onInit(testMode=testMode, **kwargs) if profiling: profile.disable() print("time to load app = {:.2f}".format(time.time() - t0)) profile.dump_stats('profileLaunchApp.profile') logging.flush() # if we're on linux, check if we have the permissions file setup from psychopy.app.linuxconfig import (LinuxConfigDialog, linuxConfigFileExists) if not linuxConfigFileExists(): linuxConfDlg = LinuxConfigDialog( None, timeout=1000 if self.testMode else None) linuxConfDlg.ShowModal() linuxConfDlg.Destroy()
def findPhotometer(ports=None, device=None): """Try to find a connected photometer/photospectrometer! PsychoPy will sweep a series of serial ports trying to open them. If a port successfully opens then it will try to issue a command to the device. If it responds with one of the expected values then it is assumed to be the appropriate device. :parameters: ports : a list of ports to search Each port can be a string (e.g. 'COM1', ''/dev/tty.Keyspan1.1') or a number (for win32 comports only). If none are provided then PsychoPy will sweep COM0-10 on win32 and search known likely port names on OS X and linux. device : string giving expected device (e.g. 'PR650', 'PR655', 'LS110'). If this is not given then an attempt will be made to find a device of any type, but this often fails :returns: * An object representing the first photometer found * None if the ports didn't yield a valid response * None if there were not even any valid ports (suggesting a driver not being installed) e.g.:: photom = findPhotometer(device='PR655') #sweeps ports 0 to 10 searching for a PR655 print photom.getLum() if hasattr(photom, 'getSpectrum'):#can retrieve spectrum (e.g. a PR650) print photom.getSpectrum() """ if isinstance(device,basestring): photometers = [getPhotometerByName(device)] elif isinstance(device,collections.Iterable): # if we find a string assume it is a name, otherwise treat it like a photometer photometers = [getPhotometerByName(d) if isinstance(d,basestring) else d for d in device] else: photometers = getAllPhotometers() #determine candidate ports if ports is None: ports = getSerialPorts() elif type(ports) in [int,float] or isinstance(ports,basestring): ports=[ports] #so that we can iterate #go through each port in turn photom=None logging.info('scanning serial ports...') logging.flush() for thisPort in ports: logging.info('...'+str(thisPort)); logging.flush() for Photometer in photometers: # Looks like we got an invalid photometer, carry on if Photometer is None: continue try: photom = Photometer(port=thisPort) except Exception as ex: logging.error("Couldn't initialize photometer {0}: {1}".format(Photometer.__name__,ex)) continue # We threw an exception so we should just skip ahead if photom.OK: logging.info(' ...found a %s\n' %(photom.type)); logging.flush() #we're now sure that this is the correct device and that it's configured #now increase the number of attempts made to communicate for temperamental devices! if hasattr(photom,'setMaxAttempts'):photom.setMaxAttempts(10) return photom#we found one so stop looking else: if photom.com and photom.com.isOpen: logging.info('closing port') photom.com.close() #If we got here we didn't find one logging.info('...nope!\n\t'); logging.flush() return None
def onInit(self, showSplash=True, testMode=False): """This is launched immediately *after* the app initialises with wx :Parameters: testMode: bool """ self.SetAppName('PsychoPy3') # Single instance check is done here prior to loading any GUI stuff. # This permits one instance of PsychoPy from running at any time. # Clicking on files will open them in the extant instance rather than # loading up a new one. # # Inter-process messaging is done via a memory-mapped file created by # the first instance. Successive instances will write their args to # this file and promptly close. The main instance will read this file # periodically for data and open and file names stored to this buffer. # # This uses similar logic to this example: # https://github.com/wxWidgets/wxPython-Classic/blob/master/wx/lib/pydocview.py # Create the memory-mapped file if not present, this is handled # differently between Windows and UNIX-likes. if wx.Platform == '__WXMSW__': tfile = tempfile.TemporaryFile(prefix="ag", suffix="tmp") fno = tfile.fileno() self._sharedMemory = mmap.mmap(fno, self.mmap_sz, "shared_memory") else: tfile = open( os.path.join( tempfile.gettempdir(), tempfile.gettempprefix() + self.GetAppName() + '-' + wx.GetUserId() + "AGSharedMemory"), 'w+b') # insert markers into the buffer tfile.write(b"*") tfile.seek(self.mmap_sz) tfile.write(b" ") tfile.flush() fno = tfile.fileno() self._sharedMemory = mmap.mmap(fno, self.mmap_sz) # use wx to determine if another instance is running self._singleInstanceChecker = wx.SingleInstanceChecker( self.GetAppName() + '-' + wx.GetUserId(), tempfile.gettempdir()) # If another instance is running, message our args to it by writing the # path the the buffer. if self._singleInstanceChecker.IsAnotherRunning(): # Message the extant running instance the arguments we want to # process. args = sys.argv[1:] # if there are no args, tell the user another instance is running if not args: errMsg = "Another instance of PsychoPy is already running." errDlg = wx.MessageDialog(None, errMsg, caption="PsychoPy Error", style=wx.OK | wx.ICON_ERROR, pos=wx.DefaultPosition) errDlg.ShowModal() errDlg.Destroy() self.quit(None) # serialize the data data = pickle.dumps(args) # Keep alive until the buffer is free for writing, this allows # multiple files to be opened in succession. Times out after 5 # seconds. attempts = 0 while attempts < 5: # try to write to the buffer self._sharedMemory.seek(0) marker = self._sharedMemory.read(1) if marker == b'\0' or marker == b'*': self._sharedMemory.seek(0) self._sharedMemory.write(b'-') self._sharedMemory.write(data) self._sharedMemory.seek(0) self._sharedMemory.write(b'+') self._sharedMemory.flush() break else: # wait a bit for the buffer to become free time.sleep(1) attempts += 1 else: if not self.testMode: # error that we could not access the memory-mapped file errMsg = \ "Cannot communicate with running PsychoPy instance!" errDlg = wx.MessageDialog(None, errMsg, caption="PsychoPy Error", style=wx.OK | wx.ICON_ERROR, pos=wx.DefaultPosition) errDlg.ShowModal() errDlg.Destroy() # since were not the main instance, exit ... self.quit(None) # ---- if showSplash: # show splash screen splashFile = os.path.join(self.prefs.paths['resources'], 'psychopySplash.png') splashImage = wx.Image(name=splashFile) splashImage.ConvertAlphaToMask() splash = AS.AdvancedSplash(None, bitmap=splashImage.ConvertToBitmap(), timeout=3000, agwStyle=AS.AS_TIMEOUT | AS.AS_CENTER_ON_SCREEN) w, h = splashImage.GetSize() splash.SetTextPosition((340, h - 30)) splash.SetText( _translate("Copyright (C) 2022 OpenScienceTools.org")) else: splash = None # SLOW IMPORTS - these need to be imported after splash screen starts # but then that they end up being local so keep track in self from psychopy.compatibility import checkCompatibility # import coder and builder here but only use them later from psychopy.app import coder, builder, runner, dialogs if '--firstrun' in sys.argv: del sys.argv[sys.argv.index('--firstrun')] self.firstRun = True if 'lastVersion' not in self.prefs.appData: # must be before 1.74.00 last = self.prefs.appData['lastVersion'] = '1.73.04' self.firstRun = True else: last = self.prefs.appData['lastVersion'] if self.firstRun and not self.testMode: pass # setup links for URLs # on a mac, don't exit when the last frame is deleted, just show menu if sys.platform == 'darwin': self.menuFrame = MenuFrame(parent=None, app=self) # fetch prev files if that's the preference if self.prefs.coder['reloadPrevFiles']: scripts = self.prefs.appData['coder']['prevFiles'] else: scripts = [] appKeys = list(self.prefs.appData['builder'].keys()) if self.prefs.builder['reloadPrevExp'] and ('prevFiles' in appKeys): exps = self.prefs.appData['builder']['prevFiles'] else: exps = [] runlist = [] self.dpi = int(wx.GetDisplaySize()[0] / float(wx.GetDisplaySizeMM()[0]) * 25.4) # detect retina displays self.isRetina = self.dpi > 80 and wx.Platform == '__WXMAC__' if self.isRetina: fontScale = 1.2 # fonts are looking tiny on macos (only retina?) right now # mark icons as being retina icons.retStr = "@2x" else: fontScale = 1 # adjust dpi to something reasonable if not (50 < self.dpi < 120): self.dpi = 80 # dpi was unreasonable, make one up # Manage fonts if sys.platform == 'win32': # wx.SYS_DEFAULT_GUI_FONT is default GUI font in Win32 self._mainFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) else: self._mainFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) # rescale for tiny retina fonts if hasattr(wx.Font, "AddPrivateFont") and sys.platform != "darwin": # Load packaged fonts if possible for fontFile in (Path(__file__).parent / "Resources" / "fonts").glob("*"): if fontFile.suffix in ['.ttf', '.truetype']: wx.Font.AddPrivateFont(str(fontFile)) # Set fonts as those loaded self._codeFont = wx.Font( wx.FontInfo( self._mainFont.GetPointSize()).FaceName("JetBrains Mono")) else: # Get system defaults if can't load fonts try: self._codeFont = wx.SystemSettings.GetFont( wx.SYS_ANSI_FIXED_FONT) except wx._core.wxAssertionError: # if no SYS_ANSI_FIXED_FONT then try generic FONTFAMILY_MODERN self._codeFont = wx.Font(self._mainFont.GetPointSize(), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) if self.isRetina: self._codeFont.SetPointSize( int(self._codeFont.GetPointSize() * fontScale)) self._mainFont.SetPointSize( int(self._mainFont.GetPointSize() * fontScale)) # that gets most of the properties of _codeFont but the FaceName # FaceName is set in the setting of the theme: self.theme = prefs.app['theme'] # removed Aug 2017: on newer versions of wx (at least on mac) # this looks too big # if hasattr(self._mainFont, 'Larger'): # # Font.Larger is available since wyPython version 2.9.1 # # PsychoPy still supports 2.8 (see ensureMinimal above) # self._mainFont = self._mainFont.Larger() # self._codeFont.SetPointSize( # self._mainFont.GetPointSize()) # unify font size # create both frame for coder/builder as necess if splash: splash.SetText(_translate(" Creating frames...")) # Parse incoming call parser = argparse.ArgumentParser(prog=self) parser.add_argument('--builder', dest='builder', action="store_true") parser.add_argument('-b', dest='builder', action="store_true") parser.add_argument('--coder', dest='coder', action="store_true") parser.add_argument('-c', dest='coder', action="store_true") parser.add_argument('--runner', dest='runner', action="store_true") parser.add_argument('-r', dest='runner', action="store_true") parser.add_argument('-x', dest='direct', action='store_true') view, args = parser.parse_known_args(sys.argv) # Check from filetype if any windows need to be open if any(arg.endswith('.psyexp') for arg in args): view.builder = True exps = [file for file in args if file.endswith('.psyexp')] if any(arg.endswith('.psyrun') for arg in args): view.runner = True runlist = [file for file in args if file.endswith('.psyrun')] # If still no window specified, use default from prefs if not any( getattr(view, key) for key in ['builder', 'coder', 'runner']): if self.prefs.app['defaultView'] in view: setattr(view, self.prefs.app['defaultView'], True) elif self.prefs.app['defaultView'] == 'all': view.builder = True view.coder = True view.runner = True # set the dispatcher for standard output # self.stdStreamDispatcher = console.StdStreamDispatcher(self) # self.stdStreamDispatcher.redirect() # Create windows if view.runner: self.showRunner(fileList=runlist) if view.coder: self.showCoder(fileList=scripts) if view.builder: self.showBuilder(fileList=exps) if view.direct: self.showRunner() for exp in [ file for file in args if file.endswith('.psyexp') or file.endswith('.py') ]: self.runner.panel.runFile(exp) # send anonymous info to www.psychopy.org/usage.php # please don't disable this, it's important for PsychoPy's development self._latestAvailableVersion = None self.updater = None self.news = None self.tasks = None prefsConn = self.prefs.connections ok, msg = checkCompatibility(last, self.version, self.prefs, fix=True) # tell the user what has changed if not ok and not self.firstRun and not self.testMode: title = _translate("Compatibility information") dlg = dialogs.MessageDialog(parent=None, message=msg, type='Info', title=title) dlg.ShowModal() if self.prefs.app['showStartupTips'] and not self.testMode: tipFile = os.path.join(self.prefs.paths['resources'], _translate("tips.txt")) tipIndex = self.prefs.appData['tipIndex'] if parse_version(wx.__version__) >= parse_version('4.0.0a1'): tp = wx.adv.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.adv.ShowTip(None, tp) else: tp = wx.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.ShowTip(None, tp) self.prefs.appData['tipIndex'] = tp.GetCurrentTip() self.prefs.saveAppData() self.prefs.app['showStartupTips'] = showTip self.prefs.saveUserPrefs() self.Bind(wx.EVT_IDLE, self.onIdle) # doing this once subsequently enables the app to open & switch among # wx-windows on some platforms (Mac 10.9.4) with wx-3.0: v = parse_version if sys.platform == 'darwin': if v('3.0') <= v(wx.version()) < v('4.0'): _Showgui_Hack() # returns ~immediately, no display # focus stays in never-land, so bring back to the app: if prefs.app['defaultView'] in [ 'all', 'builder', 'coder', 'runner' ]: self.showBuilder() else: self.showCoder() # after all windows are created (so errors flushed) create output self._appLoaded = True if self.coder: self.coder.setOutputWindow() # takes control of sys.stdout # flush any errors to the last run log file logging.flush() sys.stdout.flush() # we wanted debug mode while loading but safe to go back to info mode if not self.prefs.app['debugMode']: logging.console.setLevel(logging.INFO) # if the program gets here, there are no other instances running self._timer = wx.PyTimer(self._bgCheckAndLoad) self._timer.Start(250) return True
break continueRoutine = False # will revert to True if at least one component still running for thisComponent in trialComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # -------Ending Routine "trial"------- for thisComponent in trialComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) thisExp.addData('polygon.started', polygon.tStartRefresh) thisExp.addData('polygon.stopped', polygon.tStopRefresh) # Flip one final time so any remaining win.callOnFlip() # and win.timeOnFlip() tasks get executed before quitting win.flip() # these shouldn't be strictly necessary (should auto-save) thisExp.saveAsWideText(filename+'.csv') thisExp.saveAsPickle(filename) logging.flush() # make sure everything is closed down thisExp.abort() # or data files will save again on exit win.close() core.quit()
def onInit(self, showSplash=True, testMode=False): """This is launched immediately *after* the app initialises with wx :Parameters: testMode: bool """ self.SetAppName('PsychoPy3') if showSplash: # show splash screen splashFile = os.path.join(self.prefs.paths['resources'], 'psychopySplash.png') splashImage = wx.Image(name=splashFile) splashImage.ConvertAlphaToMask() splash = AS.AdvancedSplash( None, bitmap=splashImage.ConvertToBitmap(), timeout=3000, agwStyle=AS.AS_TIMEOUT | AS.AS_CENTER_ON_SCREEN, ) # transparency? w, h = splashImage.GetSize() splash.SetTextPosition((int(200), h - 20)) splash.SetText( _translate("Copyright (C) 2020 OpenScienceTools.org")) else: splash = None # SLOW IMPORTS - these need to be imported after splash screen starts # but then that they end up being local so keep track in self from psychopy.compatibility import checkCompatibility # import coder and builder here but only use them later from psychopy.app import coder, builder, runner, dialogs if '--firstrun' in sys.argv: del sys.argv[sys.argv.index('--firstrun')] self.firstRun = True if 'lastVersion' not in self.prefs.appData: # must be before 1.74.00 last = self.prefs.appData['lastVersion'] = '1.73.04' self.firstRun = True else: last = self.prefs.appData['lastVersion'] if self.firstRun and not self.testMode: pass # setup links for URLs # on a mac, don't exit when the last frame is deleted, just show menu if sys.platform == 'darwin': self.menuFrame = MenuFrame(parent=None, app=self) # get preferred view(s) from prefs and previous view if self.prefs.app['defaultView'] == 'last': mainFrame = self.prefs.appData['lastFrame'] else: # configobjValidate should take care of this situation allowed = ['last', 'coder', 'builder', 'both'] if self.prefs.app['defaultView'] in allowed: mainFrame = self.prefs.app['defaultView'] else: self.prefs.app['defaultView'] = 'both' mainFrame = 'both' # fetch prev files if that's the preference if self.prefs.coder['reloadPrevFiles']: scripts = self.prefs.appData['coder']['prevFiles'] else: scripts = [] appKeys = list(self.prefs.appData['builder'].keys()) if self.prefs.builder['reloadPrevExp'] and ('prevFiles' in appKeys): exps = self.prefs.appData['builder']['prevFiles'] else: exps = [] # then override the prev files by command options and passed files if len(sys.argv) > 1: if sys.argv[1] == __name__: # program was executed as "python.exe psychopyApp.py %1' args = sys.argv[2:] else: # program was executed as "psychopyApp.py %1' args = sys.argv[1:] # choose which frame to start with if args[0] in ['builder', '--builder', '-b']: mainFrame = 'builder' args = args[1:] # can remove that argument elif args[0] in ['coder', '--coder', '-c']: mainFrame = 'coder' args = args[1:] # can remove that argument # did we get .py or .psyexp files? elif args[0][-7:] == '.psyexp': mainFrame = 'builder' exps = [args[0]] elif args[0][-3:] == '.py': mainFrame = 'coder' scripts = [args[0]] else: args = [] self.dpi = int(wx.GetDisplaySize()[0] / float(wx.GetDisplaySizeMM()[0]) * 25.4) if not (50 < self.dpi < 120): self.dpi = 80 # dpi was unreasonable, make one up if sys.platform == 'win32': # wx.SYS_DEFAULT_GUI_FONT is default GUI font in Win32 self._mainFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) else: self._mainFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) try: self._codeFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT) except wx._core.wxAssertionError: # if no SYS_ANSI_FIXED_FONT then try generic FONTFAMILY_MODERN self._codeFont = wx.Font(self._mainFont.GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) self._codeFont.SetFaceName(self.prefs.coder['codeFont']) # removed Aug 2017: on newer versions of wx (at least on mac) # this looks too big # if hasattr(self._mainFont, 'Larger'): # # Font.Larger is available since wyPython version 2.9.1 # # PsychoPy still supports 2.8 (see ensureMinimal above) # self._mainFont = self._mainFont.Larger() # self._codeFont.SetPointSize( # self._mainFont.GetPointSize()) # unify font size # create both frame for coder/builder as necess if splash: splash.SetText(_translate(" Creating frames...")) # Always show runner self.showRunner() if mainFrame in ['both', 'coder']: self.showCoder(fileList=scripts) if mainFrame in ['both', 'builder']: self.showBuilder(fileList=exps) # if darwin, check for inaccessible keyboard if sys.platform == 'darwin': from psychopy.hardware import keyboard if keyboard.macPrefsBad: title = _translate("Mac keyboard security") if platform.mac_ver()[0] < '10.15': settingName = 'Accessibility' setting = 'Privacy_Accessibility' else: setting = 'Privacy_ListenEvent' settingName = 'Input Monitoring' msg = _translate( "To use high-precision keyboard timing you should " "enable {} for PsychoPy in System Preferences. " "Shall we go there (and you can drag PsychoPy app into " "the box)?").format(settingName) dlg = dialogs.MessageDialog(title=title, message=msg, type='Query') resp = dlg.ShowModal() if resp == wx.ID_YES: from AppKit import NSWorkspace from Foundation import NSURL sys_pref_link = ('x-apple.systempreferences:' 'com.apple.preference.security?' '{}'.format(setting)) # create workspace object workspace = NSWorkspace.sharedWorkspace() # Open System Preference workspace.openURL_(NSURL.URLWithString_(sys_pref_link)) # send anonymous info to www.psychopy.org/usage.php # please don't disable this, it's important for PsychoPy's development self._latestAvailableVersion = None self.updater = None self.news = None self.tasks = None prefsConn = self.prefs.connections ok, msg = checkCompatibility(last, self.version, self.prefs, fix=True) # tell the user what has changed if not ok and not self.firstRun and not self.testMode: title = _translate("Compatibility information") dlg = dialogs.MessageDialog(parent=None, message=msg, type='Info', title=title) dlg.ShowModal() if (self.prefs.app['showStartupTips'] and not self.testMode and not blockTips): tipFile = os.path.join(self.prefs.paths['resources'], _translate("tips.txt")) tipIndex = self.prefs.appData['tipIndex'] if parse_version(wx.__version__) >= parse_version('4.0.0a1'): tp = wx.adv.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.adv.ShowTip(None, tp) else: tp = wx.CreateFileTipProvider(tipFile, tipIndex) showTip = wx.ShowTip(None, tp) self.prefs.appData['tipIndex'] = tp.GetCurrentTip() self.prefs.saveAppData() self.prefs.app['showStartupTips'] = showTip self.prefs.saveUserPrefs() self.Bind(wx.EVT_IDLE, self.onIdle) # doing this once subsequently enables the app to open & switch among # wx-windows on some platforms (Mac 10.9.4) with wx-3.0: v = parse_version if sys.platform == 'darwin': if v('3.0') <= v(wx.version()) < v('4.0'): _Showgui_Hack() # returns ~immediately, no display # focus stays in never-land, so bring back to the app: if mainFrame in ['both', 'builder']: self.showBuilder() else: self.showCoder() # after all windows are created (so errors flushed) create output self._appLoaded = True if self.coder: self.coder.setOutputWindow() # takes control of sys.stdout # flush any errors to the last run log file logging.flush() sys.stdout.flush() # we wanted debug mode while loading but safe to go back to info mode if not self.prefs.app['debugMode']: logging.console.setLevel(logging.INFO) # Runner captures standard streams until program closed if not self.testMode: sys.stdout = self.runner.stdOut sys.stderr = self.runner.stdOut return True