Skip to main content

Stampe (1993) gaze-mapping polynomial: per-eye 5-term biquadratic + per-quadrant corner correction, staged-fit variant matching EyeLink's stored coefficients.

Project description

Stampe (1993) gaze-mapping polynomial

PyPI version Downloads License DOI

Per-eye Stampe (1993) gaze-mapping polynomial in Python, generalised to arbitrary degree.

This package fits a polynomial without cross terms (1, x, y, x², y², ..., xᵈ, yᵈ) plus an optional per-quadrant corner correction that supplies the missing xy cross-term coefficient on outer points.

degree polynomial terms min inner points maps to
d = 1 1, x, y 3 HV3 (bilinear)
d = 2 1, x, y, x², y² 5 HV5, HV9 — Stampe 1993 canonical
d = 3 1, x, y, x², y², x³, y³ 7 HV13-style extension

Corner correction is independent of d: pass exactly 4 outer points (one per quadrant relative to the centroid) and the model adds one xy coefficient per quadrant. With no outer points, the polynomial alone is used.

The staged inner/outer fit matches what the EyeLink 1000 Plus does at calibration and has been verified against EyeLink HREF P-CR data.

Reference: Stampe, D. M. (1993). "Heuristic filtering and reliable calibration methods for video-based pupil-tracking systems." Behavior Research Methods, 25(2), 137-142.

Stampe's published mapping function (paper eq., p. 141, HV9 case):

x₁ = a + b·x + c·y + d·x² + e·y²
y₁ = f + g·x + h·y + i·x² + j·y²
X  = x₁ + m[q]·x₁·y₁
Y  = y₁ + n[q]·x₁·y₁

where (x, y) is the tracker feature, (X, Y) is the screen coordinate, and q is the quadrant into which (x₁, y₁) falls.


Installation

From PyPI:

pip install stampe1993-gaze-mapping

With uv:

uv add stampe1993-gaze-mapping

For a local checkout, install editable:

pip install -e .
# or
uv add --editable .

Quick start

import numpy as np
from stampe1993_gaze_mapping import StampeModel, angular_error

# HV9: 5 inner + 4 outer corner targets
inner_pcr = np.array([...])  # (5, 2) P-CR features at centre + 4 edges
inner_targets = np.array([...])  # (5, 2) screen-pixel targets
outer_pcr = np.array([...])  # (4, 2) P-CR features at the 4 corners
outer_targets = np.array([...])  # (4, 2) screen targets at the 4 corners

model = StampeModel(degree=2)
model.fit(inner_pcr, inner_targets, outer_pcr, outer_targets)

# Predict screen coordinates from a new (or training) P-CR vector
predicted_xy = model.predict(inner_pcr)

# Pixel error → degrees of visual angle (per-axis signed + magnitude)
err_px = predicted_xy - inner_targets
deg_x, deg_y, mag_deg = angular_error(
    err_px,
    pitch_x_mm=screen_width_mm / screen_res_x,
    pitch_y_mm=screen_height_mm / screen_res_y,
    viewing_dist_mm=eye_to_screen_mm,
)
print(f"calibration residual: mean {mag_deg.mean():.3f}° max {mag_deg.max():.3f}°")

For HV5 (no corners) skip the outer arguments — outer_* defaults to None:

model = StampeModel(degree=2)
model.fit(inner5_pcr, inner5_targets)

For HV3 (bilinear):

model = StampeModel(degree=1)
model.fit(inner3_pcr, inner3_targets)

For HV13 (bicubic), either use the model as a pure polynomial fitter on all 13 points, or split 9 inner + 4 outer for Stampe-style correction. The caller decides the split:

# pure polynomial: all 13 as inner, no corners
model = StampeModel(degree=3)
model.fit(all13_pcr, all13_targets)

# Stampe-style: caller picks which 9 are inner and which 4 are outer
model.fit(inner9_pcr, inner9_targets, corners4_pcr, corners4_targets)

Cross terms above xy (x²y, xy², etc.) are not modelled in any configuration. The Stampe-style polynomial drops them and corner correction only puts back xy per quadrant. If you need a full bicubic with all cross terms, use a different fitter.

The fitted model is fully described by these attributes after fit:

  • model.degree — polynomial degree.
  • model.coef_x — shape (1 + 2*degree,), polynomial coefficients for the X axis.
  • model.coef_y — shape (1 + 2*degree,), same for Y.
  • model.centroid — shape (2,), the screen-coord centroid of the calibration targets (used as the quadrant origin).
  • model.corner — shape (4, 2), the per-quadrant (cx, cy) corner-correction coefficients. Quadrants are indexed (top-left, top-right, bottom-left, bottom-right) in image-y-down coordinates. All zeros if no outer points were supplied.

API

call what it does
StampeModel(degree=2) Unfitted model. degree=1 bilinear, degree=2 biquadratic (Stampe default), degree=3 bicubic. Raises ValueError if degree < 1.
model.fit(inner_pcr, inner_targets, outer_pcr=None, outer_targets=None) Fits polynomial on inner; per-quadrant corner correction if outer_* given. All arrays (N, 2).
model.predict(pcr_xy) Evaluate on (2,) or (N, 2) P-CR features; same shape out. Raises RuntimeError if called before fit.
angular_error(err_px, pitch_x_mm, pitch_y_mm, viewing_dist_mm) Convert (N, 2) pixel-error to (deg_x, deg_y, mag_deg). Lives in stampe1993_gaze_mapping.angular, re-exported from package root.

fit raises ValueError on: shape mismatch, fewer than 1 + 2*degree inner points, asymmetric outer (one None, one not), outer_* length ≠ 4, outer points not spanning all 4 quadrants around the centroid, or an outer-predicted point on a cardinal axis through the centroid.

Internals (also exported): design_row(pcr, degree=2) returns the [1, x, y, x², y², ..., xᵈ, yᵈ] feature row for one P-CR vector. quadrant_code(dx, dy) returns 0..3 from the sign of (dx, dy), image-y-down.


Acknowledgments

This project has received funding from the European Union's Horizon Europe research and innovation funding program under grant agreement No 101072410, Eyes4ICU project.

Funded by EU Eyes4ICU

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stampe1993_gaze_mapping-1.0.1.tar.gz (425.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stampe1993_gaze_mapping-1.0.1-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file stampe1993_gaze_mapping-1.0.1.tar.gz.

File metadata

  • Download URL: stampe1993_gaze_mapping-1.0.1.tar.gz
  • Upload date:
  • Size: 425.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for stampe1993_gaze_mapping-1.0.1.tar.gz
Algorithm Hash digest
SHA256 21a9fee83c3bc8d2005a76770c97f89444ff0d7c01d6b42afd5a91cc5f45079e
MD5 64d7fac87b619b51face4db0ce1ff7ce
BLAKE2b-256 f5f633862b5f1f1d45a58400aae42293ba2b98a2cad3b04009941ac15046129d

See more details on using hashes here.

File details

Details for the file stampe1993_gaze_mapping-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for stampe1993_gaze_mapping-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 81566ae2ea4efd38cdb430db6a6454cdad1789aff2c3d371508bb421f4fd4a00
MD5 c24f96c5ff02f3de31a72b907b197ea0
BLAKE2b-256 55816d9002fde41d60d4bb791847e3963f3e4f9e57203b91c11a9ed9bbaca655

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page