Delay for mouse-tracking data

Hi,

I am having some problems with the delay (about 280ms) of mouse position data.
When I am running the mouse-tracking experiment using the code below, there is always such a delay showing in my data. Even I keep the mouse constantly moving, there is still about 17 entries (17 * (1/60s) = 280ms) of data showing the mouse position is in the default position ([0,0]).

I would appreciate any help a lot!

Thank you!

Best,
Jason

    fixSpot = visual.Circle(win,
                        pos=(0, 0), radius=0.01, color='black') # set cursor stimuli

    # set mouse position and reset trialTimer
    myMouse.setPos(newPos=[0, 0])
    trialTimer.reset()
    while 1:
        # get mouse events
        fixSpot.setPos(myMouse.getPos())

        # drawing cursor
        fixSpot.draw()

        # saving data
        thisExp.nextEntry()
        thisExp.addData('position_cursor_x', fixSpot.pos[0])
        thisExp.addData('position_cursor_y', fixSpot.pos[1])

        event.clearEvents()
        win.flip()

Hi Jason,

Could you post an entire (but still minimal) example script so we can run it?

Hi Michael,

Sure! Here is the code:

Thank you very much for your help!

Best,
Jason

from __future__ import absolute_import, division, print_function
from psychopy import locale_setup
from psychopy import sound, gui, visual, core, data, event, logging, clock

import numpy as np  # whole numpy lib is available, prepend 'np.'
from numpy import (sin, cos, tan, log, log10, pi, average,
                   sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle

import os  # handy system and path functions
import sys  # to get file system encoding

from psychopy.hardware import keyboard

from psychopy.iohub import launchHubServer

_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)

# Store info about the experiment session
psychopyVersion = '3.2.4'
expName = 'Mouse-tracking-study'  # from the Builder filename that created this script
expInfo = {'participant': '', 'session': '001'}
dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
if dlg.OK == False:
    core.quit()  # user pressed cancel
expInfo['date'] = data.getDateStr()  # add a simple timestamp
expInfo['expName'] = expName
expInfo['psychopyVersion'] = psychopyVersion

filename = _thisDir + os.sep + u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])

# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
                                 extraInfo=expInfo, runtimeInfo=None,
                                 originPath='/Users/gongxuanjun/OneDrive - University of California, Davis/projects/mouse_tracking/psychopy/mouse_tracking.py',
                                 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

win = visual.Window(
    size=(1024, 768), fullscr=True, screen=0,
    winType='pyglet', allowGUI=False, allowStencil=False,
    monitor='testMonitor', color=[0, 0, 0], colorSpace='rgb',
    blendMode='avg', useFBO=True,
    units='height')

thisExp.addData('exp_session', "mouse-tracking")

fixSpot = visual.Circle(win,
                        pos=(0, 0), radius=0.01, color='black')  # set cursor stimuli

myMouse = event.Mouse(visible=False)  # will use win by default
globalClock = core.Clock()
trialTimer = core.Clock()

# check fps
frame_rate = win.getActualFrameRate(nIdentical=60, nMaxFrames=100,
                                    nWarmUpFrames=10, threshold=1)

# set mouse position and reset trialTimer
count = 0
for i in range(1, 4):
    count += 1
    thisExp.nextEntry()
    thisExp.addData('count', count)
    myMouse.setPos(newPos=[0, 0])
    trialTimer.reset()
    while trialTimer.getTime() <= 3:
        # get mouse events
        fixSpot.setPos(myMouse.getPos())

        # drawing cursor
        fixSpot.draw()

        # saving data
        thisExp.nextEntry()
        thisExp.addData('position_cursor_x', fixSpot.pos[0])
        thisExp.addData('position_cursor_y', fixSpot.pos[1])

        event.clearEvents()
        win.flip()

I haven’t run your code yet, but I wonder if the issue might simply be this line:

It shouldn’t be necessary here at all, and could be deleting some (but not all) mouse movement information.

Hi Micheal,

Thanks for your suggestion. But removing this line does not resolve the issue. There still exists the delay of recording mouse position data.

I wonder if it is due to loss/delay of frames when processing the function getPos() or due to the getPos() function itself.

The interesting part is that the delay of recording mouse position is consistent across trials, which is 17 frames (280ms).

Thank you very much for your help!

Best,
Jason

Even do not draw the cursor and just simply store the position of the mouse, the delay is still 17 frames…

count = 0
for i in range(1, 4):
    count += 1
    thisExp.nextEntry()
    thisExp.addData('count', count)
    myMouse.setPos(newPos=[0, 0])
    trialTimer.reset()
    while trialTimer.getTime() <= 3:
        # get mouse events

        # saving data
        thisExp.nextEntry()
        thisExp.addData('position_cursor_x', myMouse.getPos())

        win.flip()

Hi Jason,

Thanks for the useful reproducible example.

Perhaps not, but you still should remove it here - all it would do in this part of the code would be to possibly delete mouse movement data.

No, it is absolutely standard practice to use this function in the way you are, to achieve real-time animation.

In my testing, the issue actually seems to be with this line:

myMouse.setPos(newPos=[0, 0])

To be honest, I’m surprised that even works: I thought it was something that could only be done with pygame rather than pyglet windows. If you remove that line, and keep the mouse continually in motion, you will no longer see the series of constant values at the start of each trial. For some reason here, this function seems to be be introducing some sort of problem in subsequently updating the mouse data buffer. Note that the issue isn’t likely one of dropping frames: your code is still running and saving values on the screen refresh cycle, or else you wouldn’t even know that there was a problem unless you checked the time as well. That is, there isn’t a function call happening here that is introducing a delay, or else you wouldn’t see the stream of values being recorded.

Re-setting the mouse position causes other issues anyway - e.g. in physical space, the mouse may be drifting across the desk because you, rather than the user, are zeroing it, in software rather than in the world, introducing a progressive mismatch that the user will eventually have to correct by picking up and the mouse and moving it back to a comfortable central position.

Instead, is it compatible with your research design that before each trial, you wait until the user brings the pointer to within some tolerance of the central point, before commencing the trial? That way the mouse is truly being kept central in physical space (i.e. on the desk) at the start of each trial.

PS this does seem somehow related to an issue dealt with earlier by @jon:

Hi Michael,

Thank you for your help! I think you are right! I will look at how to get around the issue with gyglet. Otherwise, just insert core.wait(0.25) after myMouse.setPos([x, y]) might be a simplistic solution.

And also really appreciate your suggestion about the physical mouse location. I think I need a better experiment design to conduct this mouse-tracking experiment. Possible solutions could be using a joystick or a touchpad on a laptop.

Thanks again!

Best,
Jason

It’s probably not much of an issue, if the movements can be in any direction and hence tend to cancel themselves out over time.

But if, say, one consistently moves to the right in successive trials, then there would be a cumulative drift of the mouse in physical space that would need to be corrected physically before the mouse runs out-of-bounds.

Yes, that might be a viable work-around.