def presentStimuli(stimuli, attribute, duration, clk=None, on_jitter=None, ISI=None, ISI_jitter=None): """ Present stimuli using their default present method. INPUT ARGS: stimuli- A Pool of stimuli to present. attribute- The attribute to present, like 'image', 'sound', or 'stimtext' duration/on_jitter- The duration to display the stimulus. ISI/ISI_jitter- The blank ISI between stimuli. """ if clk is None: clk = exputils.PresentationClock() # loop over the stimuli for stim in stimuli.iterAttr(attribute): # show the stimuli stim.present(clk, duration, on_jitter) # do the isi between stimuli if ISI is not None: clk.delay(ISI, ISI_jitter)
def present(self, clk=None, duration=None, jitter=None, bc=None, minDuration=None, doDelay=True): """ Present an AudioClip. If provided, the clock will be advanced by the duration/jitter passed in, otherwise it will advance the duration of the audio clip. If a ButtonChooser is provided, then present waits until the button is pressed before returning, advancing the clock to the point when the button was pressed. INPUT ARGS: clk- Optional PresentationClock for timing. duration/jitter- Duration to keep the stimulus on. bc - Optional ButtonChooser object. OUTPUT ARGS: timestamp- time and latency of when the sound was played. button- Button pressed if we passed in bc. bc_time- Time and latency of when the button was pressed (if provided) """ a = AudioTrack.lastInstance() # get the clock if needed if clk is None: clk = exputils.PresentationClock() # play the sound timestamp = a.play(self, t=clk, doDelay=doDelay) if bc: # wait for button press button, bc_time = bc.waitWithTime(minDuration, duration, clk) return timestamp, button, bc_time elif duration: # reset to before play and just advance the duration+jitter clk.delay(duration, jitter) return timestamp else: # keep the clock advanced the duration of the sound return timestamp
def flashStimulus(showable, duration=1000, x=0.5, y=0.5, jitter=None, clk=None): """ Flash a showable on the screen for a specified duration. INPUT ARGS: showable- Object to display. duration- Duration to display the image. x,y- Location of the showable. jitter- Amount to jitter the presentation duration. clk- PresentationClock for timing. OUTPUT ARGS: timestamp- Time/latency when stimulus was presented on the screen. """ if clk is None: # if no PresentationClock is given, create one clk = exputils.PresentationClock() # get the VideoTrack v = display.VideoTrack.lastInstance() # show the stimulus shown = v.showProportional(showable, x, y) # update the screen timestamp = v.updateScreen(clk) # delay clk.delay(duration, jitter) # unshow the stimulus v.unshow(shown) # update the screen v.updateScreen(clk) # return ontime return timestamp
def playLoop(self, soundClip, t=None, ampFactor=1.0, doDelay=True): """ Play an AudioClip and return the time and latency of when the sound played. INPUT ARGS: soundClip- AudioClip object of the sound to be played t- Optional PresentationClock for timing. ampFactor- Optional amplification of sound. (default value is 1) doDelay- Optionally do not tare and move the presentation clock forward. Defaults to True (moving the clock forward) OUTPUT ARGS: timestamp- time and latency when sound playing began. """ # Must be sure to not get multiple callbacks at once, so # playing a soundclip while another is running causes that # other one to stop immediately, even if it is not done playing. # self.playStop() # handle special case: if it's a FileAudioClip and needs loading, # load it. self.currentClip = soundClip if isinstance(soundClip, FileAudioClip): if not soundClip.isLoaded(): # load and append the sound soundClip.load() # for logging shortName = soundClip.filename else: shortName = "NOFILE" if isinstance(t, exputils.PresentationClock): clk = t else: clk = exputils.PresentationClock() t = clk.get() if not soundClip.snd is None: # first, compute how many bytes our initial chunk # to append is. ASSUMPTION: always starting from byte 0. firstbytes = min(self.bytes_per_append, len(soundClip.snd)) self.total_samples = int( math.floor(len(soundClip.snd) / self.eplsound.FORMAT_SIZE)) if self.playing: # stop the playing sound 5ms prior to the new time timing.timedCall(t - 5, self.playStop, False) self.playing = True self.eplsound.resetSamplesPlayed() (timeInterval, appended) = timing.timedCall( t, self.eplsound.append, soundClip.snd[0:firstbytes], len(soundClip.snd[0:firstbytes]) / self.eplsound.FORMAT_SIZE, 0, ampFactor) if doDelay: # accumulate the error clk.accumulatedTimingError += timeInterval[0] - t # tare the clock and delay the proper amount clk.tare(timeInterval[0]) clk.delay(soundClip.getDuration()) # it would be great if the soundClip knew the formatsize... # mark the offset into the sound clip self.startInd = appended * self.eplsound.FORMAT_SIZE self.endInd = len( soundClip.snd) #self.total_samples*self.eplsound.FORMAT_SIZE # Add the callback to continue playing self.last_play = timeInterval[0] #addPollCallback(self.__playLoopCallback__, soundClip.snd, 0, ampFactor) addPollCallback(self.__playLoopCallback__, 0, ampFactor) dur = soundClip.getDuration() else: dur = 0 # log message self.logMessage("%s\t%s\t%s" % ("P", shortName, dur), timeInterval) return timeInterval
def mathDistract(clk=None, mathlog=None, problemTimeLimit=None, numVars=2, maxNum=9, minNum=1, maxProbs=50, plusAndMinus=False, minDuration=20000, textSize=None, correctBeepDur=500, correctBeepFreq=400, correctBeepRF=50, correctSndFile=None, incorrectBeepDur=500, incorrectBeepFreq=200, incorrectBeepRF=50, incorrectSndFile=None, tfKeys=None, ansMod=[0, 1, -1, 10, -10], ansProb=[.5, .125, .125, .125, .125], visualFeedback=False): """ Math distractor for specified period of time. Logs to a math_distract.log if no log is passed in. INPUT ARGS: clk - Optional PresentationClock for timing. mathlog - Optional Logtrack for logging. problemTimeLimit - set this param for non-self-paced distractor; buzzer sounds when time's up; you get at least minDuration/problemTimeLimit problems. numVars - Number of variables in the problem. maxNum - Max possible number for each variable. minNum - Min possible number for each varialbe. maxProbs - Max number of problems. plusAndMinus - True will have both plus and minus. minDuration - Minimum duration of distractor. textSize - Vertical height of the text. correctBeepDur - Duration of correct beep. correctBeepFreq - Frequency of correct beep. correctBeepRF - Rise/Fall of correct beep. correctSndFile - Optional Audio clip to use for correct notification. incorrectBeepDur - Duration of incorrect beep. incorrectBeepFreq - Frequency of incorrect beep. incorrectBeepRF - Rise/Fall of incorrect beep incorrectSndFile - Optional AudioClip used for incorrect notification. tfKeys - Tuple of keys for true/false problems. e.g., tfKeys = ('T','F') ansMod - For True/False problems, the possible values to add to correct answer. ansProb - The probability of each modifer on ansMod (must add to 1). visualFeedback - Whether to provide visual feedback to indicate correctness. """ # start the timing start_time = timing.now() # get the tracks v = display.VideoTrack.lastInstance() a = sound.AudioTrack.lastInstance() k = keyboard.KeyTrack.lastInstance() # see if need logtrack if mathlog is None: mathlog = LogTrack('math_distract') # log the start mathlog.logMessage('START') # start timing if clk is None: clk = exputils.PresentationClock() # set the stop time if not minDuration is None: stop_time = start_time + minDuration else: stop_time = None # generate the beeps correctBeep = sound.Beep(correctBeepFreq, correctBeepDur, correctBeepRF) incorrectBeep = sound.Beep(incorrectBeepFreq, incorrectBeepDur, incorrectBeepRF) # clear the screen (now left up to caller of function) #v.clear("black") # generate a bunch of math problems vars = numpy.random.randint(minNum, maxNum + 1, [maxProbs, numVars]) if plusAndMinus: pm = numpy.sign(numpy.random.uniform(-1, 1, [maxProbs, numVars - 1])) else: pm = numpy.ones([maxProbs, numVars - 1]) # see if T/F or numeric answers if isinstance(tfKeys, tuple): # do true/false problems tfProblems = True # check the ansMod and ansProb if len(ansMod) != len(ansProb): # raise error pass if sum(ansProb) != 1.0: # raise error pass ansProb = numpy.cumsum(ansProb) else: # not t/f problems tfProblems = False # set up the answer button if tfProblems: # set up t/f keys ans_but = k.keyChooser(*tfKeys) else: # set up numeric entry ans_but = k.keyChooser('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', 'RETURN', '[0]', '[1]', '[2]', '[3]', '[4]', '[5]', '[6]', '[7]', '[8]', '[9]', '[-]', 'ENTER', 'BACKSPACE') # do equations till the time is up curProb = 0 while not (not stop_time is None and timing.now() >= stop_time) and curProb < maxProbs: # generate the string and result # loop over each variable to generate the problem probtxt = '' for i, x in enumerate(vars[curProb, :]): if i > 0: # add the sign if pm[curProb, i - 1] > 0: probtxt += ' + ' else: probtxt += ' - ' # add the number probtxt += str(x) # calc the correct answer cor_ans = eval(probtxt) # add the equal sign probtxt += ' = ' # do tf or numeric problem if tfProblems: # determine the displayed answer # see which answermod ansInd = numpy.nonzero(ansProb >= numpy.random.uniform(0, 1)) if isinstance(ansInd, tuple): ansInd = ansInd[0] ansInd = min(ansInd) disp_ans = cor_ans + ansMod[ansInd] # see if is True or False if disp_ans == cor_ans: # correct response is true corRsp = tfKeys[0] else: # correct response is false corRsp = tfKeys[1] # set response str rstr = str(disp_ans) else: rstr = '' # display it on the screen pt = v.showProportional(display.Text(probtxt, size=textSize), .4, .5) rt = v.showRelative(display.Text(rstr, size=textSize), display.RIGHT, pt) probstart = v.updateScreen(clk) # wait for input answer = .12345 # not an int hasMinus = False if problemTimeLimit: probStart = timing.now() probEnd = probStart + problemTimeLimit curProbTimeLimit = probEnd - probStart else: curProbTimeLimit = None # wait for keypress kret, timestamp = ans_but.waitWithTime(maxDuration=curProbTimeLimit, clock=clk) # process as T/F or as numeric answer if tfProblems: # check the answer if not kret is None and kret.name == corRsp: isCorrect = 1 else: isCorrect = 0 else: # is part of numeric answer while kret and \ ((kret.name != "RETURN" and kret.name != "ENTER") or \ (hasMinus is True and len(rstr)<=1) or (len(rstr)==0)): # process the response if kret.name == 'BACKSPACE': # remove last char if len(rstr) > 0: rstr = rstr[:-1] if len(rstr) == 0: hasMinus = False elif kret.name == '-' or kret.name == '[-]': if len(rstr) == 0 and plusAndMinus: # append it rstr = '-' hasMinus = True elif kret.name == 'RETURN' or kret.name == 'ENTER': # ignore cause have minus without number pass elif len(rstr) == 0 and (kret.name == '0' or kret.name == '[0]'): # Can't start a number with 0, so pass pass else: # if its a number, just append numstr = kret.name.strip('[]') rstr = rstr + numstr # update the text rt = v.replace(rt, display.Text(rstr, size=textSize)) v.updateScreen(clk) # wait for another response if problemTimeLimit: curProbTimeLimit = probEnd - timing.now() else: curProbTimeLimit = None kret, timestamp = ans_but.waitWithTime( maxDuration=curProbTimeLimit, clock=clk) # check the answer if len(rstr) == 0 or eval(rstr) != cor_ans: isCorrect = 0 else: isCorrect = 1 # give feedback if isCorrect == 1: # play the beep pTime = a.play(correctBeep, t=clk, doDelay=False) #clk.tare(pTime[0]) #correctBeep.present(clk) # see if set color of text if visualFeedback: pt = v.replace( pt, display.Text(probtxt, size=textSize, color='green')) rt = v.replace( rt, display.Text(rstr, size=textSize, color='green')) v.updateScreen(clk) clk.delay(correctBeepDur) else: # play the beep pTime = a.play(incorrectBeep, t=clk, doDelay=False) #clk.tare(pTime[0]) #incorrectBeep.present(clk) # see if set color of text if visualFeedback: pt = v.replace( pt, display.Text(probtxt, size=textSize, color='red')) rt = v.replace(rt, display.Text(rstr, size=textSize, color='red')) v.updateScreen(clk) clk.delay(incorrectBeepDur) # calc the RT as (RT, maxlatency) prob_rt = (timestamp[0] - probstart[0], timestamp[1] + probstart[1]) # log it # probstart, PROB, prob_txt, ans_txt, Correct(1/0), RT mathlog.logMessage( 'PROB\t%r\t%r\t%d\t%ld\t%d' % (probtxt, rstr, isCorrect, prob_rt[0], prob_rt[1]), probstart) # clear the problem v.unshow(pt, rt) v.updateScreen(clk) # increment the curprob curProb += 1 # log the end mathlog.logMessage('STOP', timestamp)
def recognition(targets, lures, attribute, clk=None, log=None, duration=None, jitter=None, minDuration=None, ISI=None, ISI_jitter=None, targetKey="RCTRL", lureKey="LCTRL", judgeRange=None): """ Run a generic recognition task. You supply the targets and lures as Pools, which get randomized and presented one at a time awaiting a user response. The attribute defines which present method is used, such as image, sound, or stimtext This function generates two types of log lines, one for the presentation (RECOG_PRES) and the other for the response (RECOG_RESP). The columns in the log files are as follows: RECOG_PRES -> ms_time, max dev., RECOG_PRES, Pres_type, What_present, isTarget RECOG_RESP -> ms_time, max dev., RECOG_RESP, key_pressed, RT, max dev. isCorrect INPUT ARGS: targets- Pool of targets. lures- Pool of lures. attribute- String representing the Pool attribute to present. clk- Optional PresentationClock log- Log to put entries in. If no log is specified, the method will log to recognition.log. duration/jitter- Passed into the attribute's present method. Jitter will be ignored since we wait for a keypress. minDuration- Passed into the present method as a min time they must wait before providing a response. ISI/ISI_jitter- Blank ISI and jitter between stimuli, after a response if given. targetKey- String representing the key representing targets. lureKey- String representing the key for the lure response. judgeRange- Tuple of strings representing keys for confidence judgements. If provided will replace targetKey and lureKey OUTPUT ARGS: TO DO: Try and see if mixed up the keys (lots wrong in a row) Pick percentage of targets from each list. """ # get the tracks v = display.VideoTrack.lastInstance() a = sound.AudioTrack.lastInstance() k = keyboard.KeyTrack.lastInstance() # see if there is a presentation clock if not clk: clk = exputils.PresentationClock() # see if need logtrack if log is None: log = LogTrack('recognition') # Log start of recognition log.logMessage('RECOG_START') # add an attribute to keep track of them for stim in targets: stim.isTarget = True for stim in lures: stim.isTarget = False # concatenate the targets and lures stims = targets + lures # randomize them stims.shuffle() # make the ButtonChooser if not judgeRange: # use the target and lure keys provided bc = mechinput.ButtonChooser(Key(targetKey), Key(lureKey)) else: # use the range #bc = mechinput.ButtonChooser(*map(lambda x: Key(str(x)), xrange(*judgeRange))) bc = mechinput.ButtonChooser(*map(Key, judgeRange)) # delay before first stim if wanted if ISI: clk.delay(ISI, ISI_jitter) # present and wait for response for stim in stims: # optionally put answer choices up # present stimulus prestime, button, bc_time = getattr(stim, attribute).present( clk=clk, duration=duration, jitter=jitter, bc=bc, minDuration=minDuration) # clear the optional answer choices # see if target or not if stim.isTarget: isT = 1 else: isT = 0 # Process the response if button is None: # They did not respone in time # Must give message or something bname = "None" #isCorrect = -1 else: # get the button name bname = button.name #isCorrect = -1 # delay if wanted if ISI: clk.delay(ISI, ISI_jitter) # Log it, once for the presentation, one for the response log.logMessage('RECOG_PRES\t%s\t%s\t%d' % (attribute, stim.name, isT), prestime) log.logMessage( 'RECOG_RESP\t%s\t%ld\t%d' % (bname, bc_time[0] - prestime[0], bc_time[1] + prestime[1]), bc_time) # Log end of recognition log.logMessage('RECOG_END')
def micTest(recDuration=2000, ampFactor=1.0, clk=None, excludeKeys=None): """ Microphone test function. Requires VideoTrack, AudioTrack, KeyTrack to already exist. INPUT ARGS: recDuration- Duration to record during the test. ampFactor- Amplification factor for playback of the sound. clk- Optional PresentationClock for timing. OUTPUT ARGS: status- True if you should continue the experiment. False if the sound was not good and you should quit the program. """ v = display.VideoTrack.lastInstance() a = sound.AudioTrack.lastInstance() k = keyboard.KeyTrack.lastInstance() if clk is None: clk = exputils.PresentationClock() done = False while not done: v.clear() v.showProportional(display.Text("Microphone Test", size=.1), .5, .1) waitForAnyKey(clk, showable=display.Text( "Press any key to\nrecord a sound after the beep."), excludeKeys=excludeKeys) # clear screen and say recording beep1 = sound.Beep(400, 500, 100) beep1.present(clk) t = v.showCentered(display.Text("Recording...", color=(1, 0, 0))) v.updateScreen(clk) (testsnd, x) = a.record(recDuration, t=clk) v.unshow(t) v.updateScreen(clk) # play sound t = v.showCentered(display.Text("Playing...")) v.updateScreen(clk) a.play(testsnd, t=clk, ampFactor=ampFactor) v.unshow(t) v.updateScreen(clk) # ask if they were happy with the sound t = v.showCentered(display.Text("Did you hear the recording?")) v.showRelative(display.Text("(Y=Continue / N=Try Again / C=Cancel)"), display.BELOW, t) v.updateScreen(clk) response = buttonChoice(clk, yes=(Key('Y') | Key('RETURN')), no=Key('N'), cancel=Key('C')) status = True if response == "cancel": status = False elif response == "no": # do it again continue done = True # clear before returning v.clear() return status