Skip to main content

The model

saccade.js estimates gaze in two stages. First, a neural network looks at a small picture of the participant's eyes and turns it into a list of numbers describing how the eyes look (an embedding). Then the participant's calibration turns those numbers into a point on the screen. How it works walks through the whole process.

The calibration is different for every participant. The network is the same for everyone: it was trained once and is downloaded as a single file. That makes it the part of your method you need to be able to name precisely in a paper, and this page is about how to do that.

Releases

eye-embedding@1.0.0 served

Released 2026-09-09. First public model. Hybrid backbone, embedding_dim 128, token_pool_extra 1. Two outputs: the embedding, and a per-frame calibration weight (sigmoid, [0,1]) from the trained Calibration_Weights head. The embedding is bit-identical to the pre-release export that carried only one output.

https://saccade.jspsych.org/models/eye-embedding/1.0.0/eye_embedding.onnx
sha2565a1a111e37f97bd50fcffccbf6498bf377700d27cd4e3ea5bc5d237541a3c73a
Size20.6 MB
Preprocessing contract1
Inputeye_image [1,36,144,1] float32, range uint8_0_255
Outputembedding [1,128] float32
Calibration weightcal_weight [1,1] float32, one per frame, in [0, 1]
ONNX opset17
Trained checkpointvassar-cogsci-lab/eye-tracking-multiframe/kwppnkuj
Exported2026-09-09T20:02:34Z

Each version above has its own permanent web address. Use that address in your methods section, and point your study at it, because it will always serve exactly the same file.

const tracker = new SaccadeTracker({
assets: {
modelUrl: "https://saccade.jspsych.org/models/eye-embedding/1.0.0/eye_embedding.onnx",
},
});

For a real study, it is safer still to host the model yourself: download the versioned file once and serve it from your own server. Then nothing that happens to this site can affect your study.

The table lists a fingerprint (a SHA-256 hash) for each file. To confirm that a file you downloaded is exactly the published one, compute its fingerprint and compare:

  • macOS or Linux: shasum -a 256 eye_embedding.onnx
  • Windows PowerShell: Get-FileHash eye_embedding.onnx -Algorithm SHA256

What the version numbers mean

The model's version number is separate from the version of the @saccadejs/core package. The two change for different reasons, and a new package version does not mean a new model.

  • A minor or patch release (for example, 1.0.0 to 1.1.0) is a retrained model that works exactly the same way: same input, same outputs. You can swap it in without changing anything else. Its accuracy may be different.
  • A major release (for example, 1.x to 2.0.0) changes how the model is used: how the eye picture is prepared, or what goes in and comes out. Adding or removing the frame-quality output (cal_weight, described below) counts as a major change, because it silently switches every calibration between weighted and unweighted.

How long each version stays available

Every version listed here stays online. Once a versioned address is published, we assume someone has already written it into a methods section, and we keep serving it. The files are about 20 MB each and are hosted on GitHub alongside this site, so there is no pressure to remove them, and the site's build fails if a published version ever goes missing.

That is a commitment, not a guarantee. If your study must still be able to find its model in ten years, keep your own copy: download the versioned file, serve it yourself, and record its fingerprint. That advice would be the same for any host.

The models are trained and tested in the eye-tracking repository, which trains the network and exports it; this repository publishes the result.

Recording which model you used

The jsPsych extension does this automatically. When the model loads, saccade.js computes the fingerprint of the file it actually downloaded, looks it up in the list of published releases, and adds a saccade_model column to your data:

ValueWhat it means
eye-embedding@1.0.0The file matched this published release exactly.
sha256:f4669a8398d9The file is not a published release. The value is the start of its fingerprint, so you can still identify it.
unverifiedThe fingerprint could not be computed. In practice this does not happen, because the browser only allows it on secure pages, which the camera requires anyway.

The version is not taken from the file name or the URL. It is recorded because the file itself matched, which is why one column is enough, and why a custom model still identifies itself rather than showing unknown.

Only trials that run after the model loads have this column. The model is not loaded until your first saccade-preview trial (or your own call to start()), and trials before that have no gaze data either.

For the full details, ask the tracker directly:

const id = jsPsych.extensions.saccade.getTracker().getModelIdentity();
// {
// sha256: "5a1a111e…", version: "1.0.0", contract: 1, url: "…",
// resolvedFrom: "registry",
// dim: 128, // how many numbers the model produces per frame
// emitsWeight: true // whether it also rates each frame's quality
// }

dim and emitsWeight are found by actually running the model once when it loads, not by looking them up. So they are correct for an unfamiliar model as well as a published one.

Whether calibration used the frame-quality ratings is a property of the calibration, not the model, so it is recorded separately: saccade-calibrate saves a weighting column with the value "model", "head" or "uniform", and fitCalibration() returns the same value.

Using your own model

This section is for people who have trained their own eye model and want saccade.js to use it. Set modelUrl to your model file. It must accept exactly the same eye picture as the published model. The number of values it produces can be anything.

The input must match exactly. The model receives a 144 × 36 grayscale strip spanning both eyes, as a float32 tensor of shape [1, 36, 144, 1] with values from 0 to 255. saccade.js builds that strip in one fixed way, in this order:

  1. crop around fixed MediaPipe face landmarks,
  2. convert to grayscale (BT.601),
  3. resize with OpenCV-compatible bilinear interpolation,
  4. equalize contrast with CLAHE (clip limit 2.0, 8 × 8 tiles).

A model that expects a different shape will fail to load, which is easy to notice. A model that expects the same shape but was trained on pictures prepared differently will load and run and produce nonsense, and saccade.js has no way to detect that. Check your training preprocessing against the list above.

The outputs are flexible. The first output is the embedding. It can be any length, as long as it is the same length on every frame of a session. saccade.js reads the length from the model.

A second output is optional. If your model has an output named cal_weight with shape [1, 1], saccade.js reads it as a quality score for each frame between 0 and 1, and uses it to weight both the averaging of frames and the calibration. Without it, every frame counts equally.

What saccade.js checks. Three problems stop it when the model loads, before any participant data is collected:

  • an input shape it cannot provide,
  • an embedding whose length changes during a session,
  • a cal_weight outside the range 0 to 1.

Two things it cannot check. It cannot tell how your training pictures were prepared. And it does not compare the contract number shown in the release table with the one it implements; that number is recorded, not enforced.