I’ve narrowed down the bug somewhat. The problem is that mouse.setPos(new_coordinate)
does not result in mouse.getPos()
to return exactly new_coordinate
but rather something quite close. Therefore, mouse.getRel()
detects this as a movement. The problem is in GL, so it’s out of reach for PsychoPy.
Solution #1
The solution is to only call mouse.setPos
when the system cursor reaches the screen edge instead of doing it every frame. In my case, the code is
if any(abs(mouse.getPos()*1.1) >= 1): # if system mouse is outside screen border
mouse.setPos(cursor.pos) # to allow for continued movement even though a regular cursor would have exited the monitor edges
core.wait(0.008) # this solves a weird mouse problem, where it "jumps" randomly on mouse.setPos.
mouse.lastPos = cursor.pos # Needed to not count the above move as an acceleration.
The if-statement is a bit more convoluted if you work in different window units. For instance, I do it in degrees visual angle, so it’s:
if any(abs(mouse.getPos()*1.1) >= pix2deg(np.array(MON_SIZE)/2, my_monitor)): # if system mouse is outside screen border
… where MON_SIZE=[1440, 2560]
on my system (monitor size in pixels) and my_monitor
is an psychopy.monitors.Monitor
object. pix2deg is psychopy.misc.monitortools.pix2deg
.
Solution #2
A different is to specify a minimum movement distance to seperate physical mouse movements from system-noise-movement. In my original example, outcomment all code and then substitute
if np.any(move):
with
if np.any(np.abs(move) >= 0.005):
… or whatever criterion you find appropriate. For units=‘pix’, 1 is a good threshold. FWIW it seems like the signal-to-noise ratio seems higher for units='pix'
than for units='norm'
, i.e. the threshold does better in distinguishing physical mouse movement from noise. I haven’t tested it much.
Other event.Mouse bugs
Thanks a bunch @rob.stone and @porcu.emanuele for your code examples. This made the debugging a lot easier. In debugging this, I discovered other bugs in the event.Mouse class. I will submit fixes to GitHub. Until then, here they are for completeness:
- Mouse.setPos(x) where x is not the current mouse position does not result in mouse.getPos() in returning x but something rather close. This is a deterministic behavior (it’s repeatable) but with no obvious mechanism. For example, the error seems to be somewhat proportional to the amount of mouse displacement, but not exactly. There also seems to be a minimum movement below which there is no inaccuracy.
- mouse.mouseMoved() will only run if mouse.lastPos is set and that is only set if mouse.getPos() has been called. This should not be necessary.
- mouse.mouseMoved(reset=False) still resets the position so that a subsequent mouse.getRel() will return [0,0] even on movement.
- Perhaps not a bug, but surprising behavior to me: mouse.setPos(x) counts as movement, i.e. detected by mouse.getRel() and mouse.mouseMoved(). If this is intended, there should be an argument like mouse.setPos(x, as_movement=True)