예제 #1
0
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)
예제 #2
0
    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
예제 #3
0
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
예제 #4
0
    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
예제 #5
0
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)
예제 #6
0
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')
예제 #7
0
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