Pupil Capture screen surface

This small Python program fills the primary screen and displays four unique tag36h11 AprilTags in its corners. It has no third-party dependencies; the standard Python installation used on Windows normally includes Tkinter.

Run

Close or move anything important from the primary screen, then run:

python show_apriltag_surface.py

Press Esc or Q to close it. For a preview in a normal resizable window:

python show_apriltag_surface.py --windowed

By default, every marker is flush with both adjacent screen edges. Useful options include --tag-size 240, --margin 32, and --tag-ids 10 11 12 13. The requested size is rounded down to a multiple of 10 pixels so that the source grid is always scaled without interpolation.

Define the surface in Pupil Capture

  1. Start this program and look at the screen through the headset.
  2. Enable Surface Tracker in Pupil Capture.
  3. Confirm all four markers are outlined/detected in the World window.
  4. Select Add surface, name it (for example, screen), then freeze the scene and edit the surface corners to match the usable screen area.
  5. Keep this program running whenever the surface should remain trackable.

If detection is unreliable, increase --tag-size, reduce glare, and ensure the world camera can see the markers. Do not reuse any of these four marker IDs elsewhere in the camera's view.

For the next integration step, enable Pupil Capture's Network API plugin and keep Surface Tracker active. Surface-relative gaze is broadcast on a topic beginning with surfaces.; its normalized (x, y) coordinates use the bottom-left as (0, 0) and the top-right as (1, 1).

Live gaze visualization

Set the screen surface Width to 2560 and Height to 1440, keep Surface Tracker and Network API enabled, and install the two network dependencies once:

python -m pip install -r requirements.txt

Then close the marker-only program and run:

python visualize_surface_gaze.py

This program displays the same four corner tags, subscribes to surfaces.screen, draws a red gaze dot, and prints rows containing Pupil timestamp, screen x, screen y, and confidence. Coordinates are converted to the Windows top-left origin. It connects to 127.0.0.1:50020 by default; use --host 192.168.0.47 only when this script runs on another computer.

The displayed point passes through a 5-sample median filter, exponential smoothing, and a 3-pixel dead zone. Terminal rows contain timestamp, raw x, raw y, filtered x, filtered y, and confidence. For a steadier but slower point:

python visualize_surface_gaze.py --smoothing 0.10 --median-window 7

For a more responsive point, use --smoothing 0.30 --median-window 3.

Guided gaze exercises

With Pupil Capture configured the same way, run:

python gaze_exercises.py

Press Space to begin and between blocks. The sequence contains 10 three-second steady fixations, 10 rapid target acquisitions, 20 two-color selective-attention trials, and three 10-second smooth-pursuit paths. A live yellow dot shows the raw gaze when the program is run with --cursor; it is hidden by default. No median or smoothing filter is applied, and every received sample that meets the --confidence threshold is written to a timestamped CSV in results/, including the target, gaze coordinates, pixel error, and whether gaze was on target. During every color trial, green is the target and red is the distractor.

Data collection for ML-based adaptive filtering

Each run writes results/gaze_exercises_<timestamp>.csv plus a sidecar ..._meta.json recording subject, session, screen, and distance metadata. Tag every recording with --subject-id and, when varying setup, --session-tag:

python gaze_exercises.py --subject-id you --session-tag laptop_50cm --screen-diagonal 15.6 --viewing-distance 50
python gaze_exercises.py --subject-id you --session-tag monitor_90cm --screen-diagonal 27 --viewing-distance 90

--screen-diagonal (inches) and --viewing-distance (cm) must match the physical setup for that recording — they drive the pixel-to-degree conversion used for both the logged error and velocity columns, so an incorrect value silently skews every derived feature in that session.

CSV columns beyond the basic error metrics:

  • confidence — Pupil Capture's per-sample confidence (already thresholded by --confidence, but the value itself is kept for weighting/filtering).
  • velocity_px_s, velocity_deg_s — instantaneous gaze speed between consecutive samples; blank when the gap since the previous sample exceeds 0.5s (block/trial boundaries).
  • accel_px_s2 — change in velocity_px_s between consecutive samples.
  • dispersion_px — spread (bounding-box width + height) of the last 5 raw samples; low during fixation/pursuit, spikes during saccades.
  • event_label — coarse ground truth derived from task design: saccade until gaze first lands within the hit radius of the trial's target, then fixation (or always pursuit during the pursuit block). This is a block design label, not a precise per-sample velocity-threshold classification — refine saccade on/offset from velocity_deg_s during post-processing if the paper needs tighter boundaries.

subject_id and session_id (subject + recording timestamp) are included on every row so CSVs from different subjects, screens, and distances can be concatenated directly for training/evaluation.

Visualize the newest result file with:

python visualize_results.py

Or select a particular run with python visualize_results.py results\file.csv. The script opens a six-panel dashboard, prints summary statistics, and saves a *_dashboard.png beside the source CSV. Use --no-show to only save the PNG.

S
Description
Adaptive filtering of eye_tracking data
Readme MIT
30 MiB
Languages
Python 100%