Skip to content

Instantly share code, notes, and snippets.

@dtxe
Last active July 6, 2023 03:25
Show Gist options
  • Save dtxe/694b3cabd034bc560c458d844d47fd56 to your computer and use it in GitHub Desktop.
Save dtxe/694b3cabd034bc560c458d844d47fd56 to your computer and use it in GitHub Desktop.
Convert psychopy visual degrees to monitor pixels. Originally designed for iohub tobii eyetracker recordings.
import math
from typing import Tuple
def vdeg_to_px(
gaze_vdeg:Tuple[float, float],
screen_size_cm:Tuple[float, float],
screen_size_px:Tuple[int, int],
viewer_distance_cm:float,
) -> Tuple[float, float]:
'''
Convert from psychopy visdeg (0,0 at center, +y up, +x right)
to normalized screen coordinates (0,0 at topleft, +y down, +x right)
:param gaze_vdeg: (x,y) coordinates in visual degrees
:param screen_size_cm: (width, height) of screen in cm
:param screen_size_px: (width, height) of screen in px
:param viewer_distance_cm: distance from screen to viewer in cm
:return: (x,y) coordinates in px
'''
# to normalized screen coordinates
norm_psychopy = [math.tan(math.radians(this_vdeg)) * viewer_distance_cm / this_scn_sz_cm
for this_vdeg, this_scn_sz_cm
in zip(gaze_vdeg, screen_size_cm)]
# change from psychopy coordinate frame (0,0 at center, +y up, +x right)
# to normalized monitor coordinate frame (0,0 at topleft, +y down, +x right)
norm_monitor = [norm_psychopy[0] + 0.5, norm_psychopy[1] * -1 + 0.5]
# to pixels: norm x/y * screen resolution in pixels
px = [this_norm_monitor * this_scn_sz_px
for this_norm_monitor, this_scn_sz_px
in zip(norm_monitor, screen_size_px)]
return px
##################################################################
# test the function
vdeg_to_px_args = {
'screen_size_cm' : (34.5, 19.5),
'screen_size_px' : (1920, 1080),
'viewer_distance_cm' : 65,
}
coords_to_test_visdeg = [
(0, 0), # center of the screen
(0, 8.5), # near top, horizontally centered
(14, 0), # near left, vertically centered
(0, -8.5), # near bottom, horizontally centered
(-14, 0), # near right, vertically centered
]
for coord in coords_to_test_visdeg:
print(f"{str(coord):<12}=> {vdeg_to_px(coord, **vdeg_to_px_args)}")
# (0, 0) => [960.0, 540.0]
# (0, 8.5) => [960.0, 1.976395143139884]
# (14, 0) => [1861.9169494153318, 540.0]
# (0, -8.5) => [960.0, 1078.0236048568602]
# (-14, 0) => [58.08305058466807, 540.0]
##################################################################
# example with pandas
import pandas as pd
df = pd.DataFrame(coords_to_test_visdeg, columns=['gaze_x_deg', 'gaze_y_deg'])
print('Pre-conversion:')
print(df)
# convert to pixels
df[['gaze_x_px', 'gaze_y_px']] = df.apply(lambda row: vdeg_to_px((row['gaze_x_deg'], row['gaze_y_deg']), **vdeg_to_px_args), axis=1).apply(pd.Series)
print('\n\nPost-conversion:')
print(df)
# Pre-conversion:
# gaze_x_deg gaze_y_deg
# 0 0 0.0
# 1 0 8.5
# 2 14 0.0
# 3 0 -8.5
# 4 -14 0.0
# Post-conversion:
# gaze_x_deg gaze_y_deg gaze_x_px gaze_y_px
# 0 0 0.0 960.000000 540.000000
# 1 0 8.5 960.000000 1.976395
# 2 14 0.0 1861.916949 540.000000
# 3 0 -8.5 960.000000 1078.023605
# 4 -14 0.0 58.083051 540.000000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment