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| sha256 | 5a1a111e37f97bd50fcffccbf6498bf377700d27cd4e3ea5bc5d237541a3c73a |
|---|---|
| Size | 20.6 MB |
| Preprocessing contract | 1 |
| Input | eye_image [1,36,144,1] float32, range uint8_0_255 |
| Output | embedding [1,128] float32 |
| Calibration weight | cal_weight [1,1] float32, one per frame, in [0, 1] |
| ONNX opset | 17 |
| Trained checkpoint | vassar-cogsci-lab/eye-tracking-multiframe/kwppnkuj |
| Exported | 2026-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:
| Value | What it means |
|---|---|
eye-embedding@1.0.0 | The file matched this published release exactly. |
sha256:f4669a8398d9 | The file is not a published release. The value is the start of its fingerprint, so you can still identify it. |
unverified | The 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:
- crop around fixed MediaPipe face landmarks,
- convert to grayscale (BT.601),
- resize with OpenCV-compatible bilinear interpolation,
- 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_weightoutside 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.