Polygon motion not in sync with what is coded, issues not seen with different speed

Monitor info:

  • 59.4 cm width, 57 cm distance
  • 1920 x 1080, 60 Hz, 92 ppi
  • 18 deg (one-sided) vertical max

What are you trying to achieve?:

  • A circle (3x3), moving at 0.2 deg / frame vertically, that is capable of switching directions when hitting edge of screen. Its size makes it hit the edge at 15 deg, thus it must switch directions when Target.pos[1] >= 15 or Target.pos[1] <= -15

What did you try to make it work?:

###begin experiment
moveSpeed = 0.2
###begin routine; can ignore, purely randomizing target
#annuli data
if Position == [-19.5, 0]:
    Target = leftAnnuli
    leftStop = 1200
    rightStop = 60
    thisExp.addData('targetPos', 'Left')

if Position == [19.5, 0]:
    Target = rightAnnuli
    rightStop = 1200
    leftStop = 60
    thisExp.addData('targetPos', 'Right')

#positions of R/L annuli
movingUp = None

direction = randint(0, 2)

if direction == 0:
    moveSpeed *= -1
###each frame - the guts
#beginning mvmt
if frameN >= 61:
    Target.pos[1] += moveSpeed
    Target._needVertexUpdate = True

if Target.pos[1] >= 15 or Target.pos[1] <= -15:
    moveSpeed *= -1
    print('Switching frame - ', frameN)

if Target.pos[1] == 3:
    print('3 = ', frameN)

if Target.pos[1] == 9:
    print('9 = ', frameN)

if Target.pos[1] == 15:
    print('15 = ', frameN)

What specifically went wrong when you tried that?:

  • Through logging its positions, it seems that the movement is out of sync with the frames. Not sure if that’s a good analysis of the problem - don’t know how else to word it.
  • It should be hitting 3 deg at the 75th frame, 9 deg at 105th, 15 deg at 135th. Instead, it seems to be out of sync slightly in the beginning, then later the incongruence is greatly magnified, likely through switching directions. Note that it never logs 3 deg or 15 deg, since it seems to skip over those.

image

Note
This doesn’t occur if I set the speed to 0.25 deg / frame. See these logs:

image

Could it be that my frame rate isn’t truly 60 Hz? Data file indicates that it flies around 59.9xx. Anyone know how to deal with this?

There is a general principle here that computers aren’t perfect at dealing with floating point numbers. They are natively built to deal with integers (at heart, binary). So while you can always compare two integers exactly, you can’t expect to reliably compare two floating point values.

i.e. a computer will store an integer of 3 exactly. So you can do tests like this reliably:

if number_of_targets == 3: # comparing two integers
    # do something every time

But a floating point value of 3.0 may actually be stored as 3.0000000000000004 or 2.999999999997 or similar. It will vary depending on your platform and the series of calculations taken to reach it. So never try to test two floating point values for equality. Instead, take the absolute difference between the two and see if it is below some acceptable minimum threshold.

e.g. instead of:

if Target.pos[1] == 3.0:

try something like:

if abs(Target.pos[1] - 3.0) <= 0.0001:

The size of the threshold is situation-dependent. In this case, an angle smaller than one ten thousandth of a degree is not meaningful, so we can use that as a (relatively high) threshold.

Note that the deviations you are seeing are absolutely infinitesimal fractions of the width of a pixel. The target will be displayed exactly where you intend it to be, as its displayed location will effectively be rounded to the nearest integer pixel value. There is not only no perceptible difference between a stimulus intended to be displayed at 3.0 degrees and one whose coordinate is 3.00000000000000004 degrees, but also no physical difference, due to the quantised nature of a pixel-based display.

This issue of floating point precision is not a restriction unique to PsychoPy or Python, but common to all computing systems.

Hey, thank you. That is some great insight and it’s completely fixed the discrepancies. I really appreciate your help.

Glad that helped. The frustrating thing about this is that it does sometimes work (as you found when stepping by 0.25 instead of 0.2) - it is the unpredictability that can make this issue hard to track down. Everyone falls for it at some stage.

1 Like

Socratic paradox hits me every time I post here…