Using webgazeR with PsychoPy

Author

Jason Geller

PsychoPy is another platform webgazeR can read webcam eye-tracking data from via merge_webcam_files(kind = "psychopy"). This vignette walks through what a PsychoPy export looks like, how to merge it, and a real timing issue worth knowing about.

Packages

options(stringsAsFactors = FALSE)
options("scipen" = 100, "digits" = 10)
library(webgazeR)
library(dplyr)
library(knitr)

What PsychoPy exports

Unlike Gorilla, jsPsych, or Labvanced, PsychoPy has no fixed export schema – a PsychoPy experiment’s CSV is just whatever columns the experiment’s own code logged, one row per trial. That means there’s no standard column name for subject or trial id to infer automatically; you tell merge_webcam_files which columns to use via col_map. What does matter for merging:

  • Some participant id column (here, pid)
  • Some trial id column – PsychoPy doesn’t require one to be named trial_index like jsPsych does, so use whatever your experiment logged as long as it’s unique per row (here, Problem_id, a label like "4_NSNC_L")
  • array_col: the column holding per-trial gaze samples, as unkeyed triplets [[t, x, y], ...] rather than jsPsych’s keyed {x, y, t} objects – so array_key = FALSE

Merging

psychopy_file <- "data-raw/psychopy/STU172607h07554f66.csv"

edat_pp <- merge_webcam_files(
  file_paths = psychopy_file,
  kind = "psychopy",
  col_map = list(subject = "pid", trial = "Problem_id"),
  array_col = "TaskGazeArray",
  array_key = FALSE
)

webgazeR ships this example already merged as psychopy_eyedata, so the rest of this vignette uses that directly rather than needing the raw CSV on disk:

edat_pp <- webgazeR::psychopy_eyedata

The result is the same standardized subject/trial/time/x/y long format as the other platforms, plus whatever other trial-level fields the experiment recorded:

subject trial time x y task_correct hoo_id hoo_pos color space
STU172607h07554f66 4_NSNC_L 0.7999999971 864 595 1 mul L N N
STU172607h07554f66 4_NSNC_L 53.7999999974 864 595 1 mul L N N
STU172607h07554f66 4_NSNC_L 68.0999999940 864 595 1 mul L N N
STU172607h07554f66 4_NSNC_L 91.0000000003 850 612 1 mul L N N
STU172607h07554f66 4_NSNC_L 129.8999999913 850 612 1 mul L N N
STU172607h07554f66 4_NSNC_L 144.2000000034 850 612 1 mul L N N
dim(edat_pp)
[1] 13006    19
n_distinct(edat_pp$trial)
[1] 144

What’s happening under the hood

time is converted from seconds to milliseconds

This is the one thing worth knowing about PsychoPy specifically. PsychoPy’s clocks report elapsed time in seconds – confirmed directly in this file’s raw TaskGazeArray, where a trial’s timestamps run from 0.0008 to 5.04. Every other kind in this package (Gorilla, jsPsych, Labvanced) reports time in milliseconds, and so does the rest of the pipeline (e.g. binning functions that expect ms). Left unconverted, that mismatch doesn’t error – it just silently inflates any time-based statistic downstream by ~1000x.

merge_webcam_files now always multiplies PsychoPy’s time by 1000, so it’s consistent with every other platform:

range(edat_pp$time)
[1]     0.300000012 12925.799999997

Next steps

From here, everything in the main webgazeR vignette applies the same way it would to Gorilla, Labvanced, or jsPsych data – AOI assignment (assign_aoi()), downsampling/upsampling (downsample_gaze(), upsample_gaze()), smoothing and interpolation (smooth_gaze(), interpolate_gaze()), and plotting (plot_IA_proportions()) are all platform-agnostic once your data is in this standardized subject/trial/time/x/y form. As with Labvanced and jsPsych, x/y here are raw pixel coordinates, not Gorilla’s normalized 0-1 coordinates – this experiment’s win_width/win_height columns are what you’d use to normalize them or define AOI boundaries in the same units.