psychopy.org | Reference | Downloads | Github

Choppy slider controlled by keyboard

Hi all,

Question
I have coded a task containing a slider controlled by the keys “up” and “down”, using the method KeyStateHandler() (mentioned here) .

The movement of the rating worked perfectly smooth on my own laptop, but when I switched to another laptop the movement among the slider became a bit choppy. I am wondering if this is because of the inefficiency of my codes, or it’s more related to the hardware? (codes and description of the task will follow)

Any comments or suggestions on codes would be helpful!

Flow of the task
Here is a simplified flow chart of the timeline in case that I don’t do a good job explaining the flow:

trialTimeline

After the first set of image and text (img1 + txt1) appears, the participant is allowed to make response by pressing “↑” or “↓” to control the slider.

If he or she has made any response (hasRespond = True) and if there is no keyboard input for some specific time (= timeConfidence), it’s assumed that the participant has made their decision. The slider rating and the reaction time, and the keyboard history during the picture will be recorded.

Then the next set (img2+txt2) will be set to start.

After the same decision period for img2+txt2, img3+txt3 will be started. But for these two, the slider is set to read-only and we ignore the inputs from keyboard.

During the entire time, the mouse is disabled (by fixate it at the corner). All three pictures appear by fading in.

Codes
(I only paste the critical section of trial here, if need more info please let me know)


for thisRepeat in Repeat:

    
    event.clearEvents(eventType='keyboard')
    
    currentLoop = Repeat
    # abbreviate parameter names if possible (e.g. rgb = thisRepeat.rgb)
    if thisRepeat != None:
        for paramName in thisRepeat:
            exec('{} = thisRepeat[paramName]'.format(paramName))
            
            
            
    
    
    
    
    #--------------------#
    #-------Trials-------#
    #--------------------#
    
    
    
    
    
    
    # update component parameters for each repeat
    slider.reset()
    text1.setText(txt_1)
    text2.setText(txt_2)
    text3.setText(txt_3)
    image1.setImage(img_1)
    image2.setImage(img_2)
    image3.setImage(img_3)
    

    
    # ------Prepare to start Routine "Trials"-------
    
    
    
    
    sliderHistorySecond = [] # store here the rating and the time at point wanted
    
    TrialsComponents = [slider, text1, text2, text3, image1, image2, image3] # keep track of which components have finished
    
    for thisComponent in TrialsComponents:
        initiateComponent(thisComponent)  # set the values to None
    

    
    core.wait(timeBuffering)  #blank screen for time buffering
    
    
    hasRespond = False  # check if the participant has responded or not
    continueRoutine = True  # check if continue routine or not
    
    frameN = -1
    t = 0
    entering = 0  # for each repeat, entering start from 0, therefore need to be initialized outside of the loop
    
    
    # initialize some clocks
    TrialsClock = core.Clock()  #  Create some handy timers
    countDown = core.CountdownTimer()   # Used to detect whether we reached the timeConfidence
    
    
    # create a keyboard to record all the keys pressed during one repeat
    trialKeyboard = keyboard.Keyboard()
    
    
    event.clearEvents()  # clean up buffer before entering to the loop (not sure why it couldn't remove the keys with negative reaction time, and this is why I added getKeys() to remove)
    trialKeyboard.getKeys()  # to remove the keys that was pressed during the buffering periods, or we will get negative reaction time for those keys
    
    
    # reset the clocks to starting time
    TrialsClock.reset()  # clock of the trial
    countDown.reset(t=timeConfidence)  # clock for timeConfidence
    
    
    
    
    
    # -------Start Routine "Trials"-------
    while continueRoutine:
        # get current time
        
        
        t = TrialsClock.getTime()  # current time
        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)

    
    
    
        
        if myMouse.getPos()[0]!= 1 or myMouse.getPos()[1] != 1:# Fixate the mouse
            myMouse.setPos(newPos = (1,1))
            
            # notice: I added this step because on my computer the setVisible of mouse event didn't work
            # may be due to the mouse initialization in the slider class
            # Therefore I just fixiated it s.t. it won't be used
            
            
            
            
        # update/draw components on each frame
        
        
        
        #---------------#
        #---------------#
        #-slider-update-#
        #---------------#
        #---------------#
        
        
        #  Movement and record updates
        #  changed to DOWN and UP according since it's a verticle slider
        #  only detect during the first two pictures
        
        if entering < 2 :
            
            
            if keyState[key.DOWN]:
                newRat = slider.rating - sliderSpeed
                slider.rating = newRat
                countDown.reset(t = timeConfidence)
                hasRespond = True
            
            if keyState[key.UP]:
                newRat = slider.rating + sliderSpeed
                slider.rating = newRat
                countDown.reset (t = timeConfidence)
                hasRespond = True
                
            
        if t >= timeStartOne:
        
            #record starting position, starting slider
            if slider.status == NOT_STARTED:
                slider.recordRating(rating = sliderStartP)
                slider.setAutoDraw(True)
                
            #update slider
            componentUpdate(slider, t, frameN, False)
        
        
        
        
        #-------------------------#
        #-------------------------#
        #-switch-between-pictures-#
        #-------------------------#
        #-------------------------#
        
        
        #    see whether the decision is made, more specifically: 
        #       Not entering unles the participant has responded
        #       when non of the keys are pressed for the confidence time, record the rating, redo clock
        #       add count for entering this, set the True/False value accordingly for image and text
        #       note that when setAutoDraw = True, status changes to STARTED automatically
        
        #    when entering = 1, image1 & txt1 finished, image2 & txt2 starts
        #    when entering = 2, image2 & txt2 finished, image3 & txt3 starts, and set the slider to read-only
        #    when entering = 3, image3 & txt3 finished, stop the entire trial
        
        
        
        if countDown.getTime() <= 0 and hasRespond:  # countDown is a count-down timer, with parameter "timeConfidence"
            
            
            
                 
            
            entering += 1  #implement by 1
            command = "pass"  #place holder
            

            #entering = 1, 2
            
            if entering < 3:
                
                
                hasRespond = False  # flip it back to false
                 
                command = "text{}.setAutoDraw(False)\nimage{}.setAutoDraw(True)\ntext{}.setAutoDraw(True)".format(str(entering), str(entering+1), str(entering+1))  # starts the next image
                command +="\nkeyRecord{} = trialKeyboard.getKeys(['up','down'])".format(str(entering))#  get the keys here (getKeys get the respond starting from the last call of getKeys)
                
                
                if entering == 2: # after the end of the second image
                    
                    
                    command += "\nslider.readOnly = True"  # set slider to read only for the third picture
                    
                    hasRespond = True  # When we are at the second image, set it True for the third image
                    
                    
                    
                    # Note: since the keyboards seems to be using the same buffer, if stop recording the keyboard input here, the defaultKeyboard won't be able to catch escape and space for exiting the program later
                    
                    
                exec(command)
                exec("tmp = image{}.tStart".format(str(entering)))
                
                
                sliderHistorySecond.append([slider.getRating(), (t-timeConfidence-tmp)])  # record rating and the time of decision, append it to the list
                # This is the rating and time for making the decision, should get two pairs since there shouldn't be any rating for the third picture
               
                
            
            #entering = 3
            
            if entering == 3:
                
                continueRoutine = False  # stop the routine
                
            
            
            countDown.reset(t = timeConfidence)  # reset the clock at the end s.t can count down again
            
            
            
            
            
            
        #------------------------#
        #------------------------#
        #-image-and-text-updates-#
        #------------------------#
        #------------------------#
        
        
        # Notice that this can't be moved before the switching, because
        
        
        # text1 + image 1 updates after the "timeStartedOne"
        if t >= timeStartOne:
            
            componentUpdate(text1, t, frameN, True)
            componentUpdate(image1, t, frameN, True)
            
            
            
        # update opacity of img1
        imgFadeIn(image1, frameN, timeFadePara)
        
        
        # text2 + image2 updates
        componentUpdate(text2, t, frameN, False)
        componentUpdate(image2, t, frameN, False)
        
        # update opacity of img2
        imgFadeIn(image2, frameN, timeFadePara)
        
        
        
        
        # text3 + image3 updates
        componentUpdate(text3, t, frameN, False)
        componentUpdate(image3, t, frameN, False)
        
        # update opacity of img3
        imgFadeIn(image3, frameN, timeFadePara)

Thanks in advanced!

Just an update: that linked post is nearly two years old. These days, PsychoPy can detect whether a key has been released, using its new Keyboard hardware class, which is meant to supersede the old event module and its event.getKeys() and event.waitKeys() functions:

https://www.psychopy.org/api/hardware/keyboard.html

Thank you for your update! I will update the method!