| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
In Russian: docs/README-rus.md
A library for the event detection and analysis of eye and gaze movements: extraction of gaze shifts, fixations and integral characteristics of a recording, following the author's methodology. The computation of the thresholds builds on a model of the saccadic main sequence and takes the sampling rate into account.
If you use the library in research, cite the article whose result is built into the threshold computation — see How to cite.
Scope of applicability: eye and gaze data.
This library was created from mathematical and physiological considerations about the quality and the nature of the data obtained with various eye trackers and oculographs.
The main distinction adopted in the library is that it works separately with data about gaze, which may be computed inside proprietary or open systems or obtained by the author's methods, and with data about eye movement, which some systems also provide. The position of the eye — in the general case the position of the pupil — may be passed to the library as a displacement in millimetres. For that case the library contains a function converting the eye position into two angles. For working with images of the eye and with coordinates in pixels it is recommended to use other solutions beforehand.
The library assumes that the input data are calibrated and not heavily noisy. It does not set out to assess the quality of the data or the level of noise: that assessment remains with the user.
Why does the library distinguish gaze, gaze movement and eye movement? This can be illustrated by the simplest situation: a person looks at one point and turns their head at the same time. At that moment gaze remains motionless, yet the eye moves relative to the head. Hence eye movement is not the same as gaze movement, and stabilization of gaze is not the same as stabilization of the eye.
The following distinction of concepts is therefore adopted in the library. When the position of the point a person is looking at is meant, in a motionless frame of reference, the concept of gaze is used. The concept of a fixation applies to gaze only. The concept of a gaze shift, which may be fast, is used in the same way; suitable approximations may be applied to describe it.
Gaze may be built from data combined or averaged over the two eyes, for example by a proprietary system, or from the data of a single eye. If a single gaze signal is passed in, it is assumed to be exactly that — gaze data formed with the movement of the eye relative to the head and the movement of the head itself taken into account.
Eye movement is analysed relative to the head and has a value of its own in studies where what matters is precisely the movement produced by the oculomotor apparatus. Eye movement is made up of several components, in particular movements related to stabilization and voluntary movements. The concept of a saccade likewise applies to eye movement: within this library a saccade means a movement of the eyeball produced by the oculomotor muscles. The corresponding models apply to that movement.
In some kinds of task these distinctions may be immaterial, and using the same concepts for different kinds of movement leads to no substantial consequences. Nevertheless the library uses the distinction described here, so as to simplify the choice of the processing methods that are needed and applicable.
The distinction governs the whole terminology of the API:
| Concept | Description | Related event |
|---|---|---|
| Eye movement | movement of the eye relative to the head | saccade, rest |
| Gaze | computed relative to the surrounding space | (fast) gaze shift, fixation |
Distinction by coordinate frame. If a quantity is given in coordinates attached to the head or to a head-mounted eye tracker, it refers to the eye — no matter how insistently the device calls it gaze. It becomes gaze only after the position of the head in motionless space has been accounted for.
Example: Pupil Invisible records a single eye, and its azimuth/elevation are given in the frame of the scene camera, rigidly attached to the head — this is eye movement, not gaze.
Gaze is computed relative to the surrounding space. It may be built from data combined over the two eyes or from the data of a single eye.
In gaze markup a relatively fast movement is called a gaze shift and not a saccade: a change of gaze includes head movement as well. The saccade model does not apply to the general case of a gaze shift.
Between gaze shifts the following are distinguished:
The concept of a fixation applies only to gaze. If the object moves, the correct term appears to be tracking: studying it requires additional information and is not supported in the current version of the library.
Series over the whole recording, sample for sample: eccentricity_deg (the angle to the neutral direction), azimuth_deg (the direction of the deviation), speed_deg_s (the full angular velocity).
The table of gaze shifts gaze_shifts:
| Column | Units | Meaning |
|---|---|---|
| t_start_s, t_end_s, duration_s, i_start, i_end | s, samples | the boundaries of the event and its duration |
| peak_speed_deg_s | deg/s | peak velocity: the events are selected by it |
| amplitude_deg | deg | amplitude, a great-circle angle |
| eccentricity_start_deg, eccentricity_end_deg | deg | from where and to where gaze was shifted |
| azimuth_start_deg, azimuth_end_deg | deg | the direction of the deviation at the boundaries |
The table of fixations fixations — position (t_start_s, t_end_s, duration_s, mean_horizontal_deg, mean_vertical_deg, mean_eccentricity_deg, mean_speed_deg_s, n_valid), spread (spread_std_deg, spread_p95_deg, spread_peak_deg, spread_deg, spread_limit_deg, spread_valid, cov_fro_norm_deg2), the areas of the swept region (bcea_deg2, hull_area_deg2), drift (slope_horizontal_deg_s, slope_vertical_deg_s, p_horizontal, p_vertical, has_trend, duration_valid) and the provenance of the record when splitting (heterogeneous_parent, split_score, split_pvalue, split_depth).
Integral characteristics of the recording: the gaze stabilization coefficient ksv (the fraction of time with a velocity below the threshold), the trajectory length tr_length (deg), the standard deviation of an arbitrary characteristic over an interval find_std_fx, and the rate of gaze shifts together with the fraction of time occupied by fixations — describe_markup (also the .summary() method of the markup result).
The conditions of the computation params_used — which thresholds were applied and where they came from; report.describe(...) reconstructs the text of the report from them.
The areas are computed for gaze only: an area is meaningful as a projection onto motionless space.
The data must be calibrated and not heavily noisy: the library does not take the assessment of quality and of the level of noise upon itself.
| Function | What it does |
|---|---|
| gaze_direction | the orientation of the head and of the eye (vector, quaternion, rotation vector) → the gaze direction |
| mark_gaze_events_direction | markup by direction: gaze shifts and fixations |
| mark_gaze_events_1d | the same for a one-dimensional angular recording |
| ksv | the gaze stabilization coefficient |
| describe_markup | the rate of events and the fraction of time occupied by fixations |
| tr_length, find_std_fx | the trajectory length and the standard deviation over an interval |
| analyze_stable_intervals | statistics of the intervals between events |
| bcea_deg2, convex_hull_area_deg2 | the areas of the swept region |
| angles_from_orientation, angular_distance_deg, rotate_points | orientation of the head: quaternions → angles |
| find_blinks_by_gaps, find_blinks_by_threshold, refine_blink_bounds, blink_mask | blinks |
Eye movement is movement of the eye relative to the head. It has a value of its own where what matters is precisely the oculomotor apparatus, and it is made up of several components: movements related to stabilization and voluntary ones.
A saccade applies precisely to eye movement: by it is meant a movement of the eyeball produced by the oculomotor muscles. The models of the main sequence and of the saccade trajectory apply to such movement.
Rest is an interval over which the eye is motionless relative to the head. This state is not a fixation: gaze may be motionless while the eye moves, and the other way round. During a head turn with compensation there is a fixation but no rest; with a motionless eye and a turned head there is rest but no fixation.
Three quantities between saccades, by three different functions:
| quantity | function | checks |
|---|---|---|
| an interval of rest | mark_eye_rest_from_angles | velocity, homogeneity, spread, duration |
| an interval between saccades | mark_eye_data_with_full_angle_from_angles | homogeneity, spread, duration |
| a stable interval | stable_intervals_by_velocity | velocity only: consecutive samples below the threshold |
Typical eye movements: saccade, drift, tremor.
A series over the whole recording: alpha_deg — the angle of deviation from the direction (0, 0).
The table of saccades — the same columns as for gaze shifts: the boundaries, the peak velocity, the amplitude, the eccentricity at the boundaries.
The table of intervals — of rest or between saccades, depending on the function: position, spread, drift, the provenance of the record when splitting. The schema coincides with the table of fixations except for the areas: there is no bcea_deg2 and no hull_area_deg2 here — an area is meaningful as a projection onto motionless space, and eye movement relative to the head has no such projection.
The thresholds at a given sampling rate — min_saccade_minima_by_sampling returns the minimum amplitude, the duration, the peak velocity, the velocity threshold of rest, the admissible spread and the flags of which of them have hit the physiological limit.
The shape of a saccade — match_saccade_templates gives the fitted amplitude and duration of the model curve, r2 and the standard deviation of the residual.
By default the input is angular, in degrees: yaw/pitch angles of the eye or of gaze in the frame attached to the head, or a single angular coordinate. Such an input goes straight into the markup functions — the thresholds and all returned quantities are in degrees as well.
A metric input is brought to the angular form: millimetres → conversion to angles → then the standard path. The conversion is done by make_okulogram: atan2(displacement − neutral, r_eye) with r_eye = 12 mm; the preparation is smooth_eye_position_mm and find_central_position, in millimetres as well. After the conversion the recording is marked up by the same functions as an angular one.
The units of a metric input are not free: they are tied together by the radius. Metres give a silently wrong angle, understated by about a factor of a thousand. For images of the eye and coordinates in pixels it is recommended to use other solutions beforehand: the conversion from pixels to millimetres is not part of the library.
Binocular recordings. For devices such as the Vive Pro Eye, which report the position of each eye separately, the functions working in millimetres are applied to each eye separately: smoothing → its own neutral → its own angles. The coordinates of the eyes must not be averaged before the conversion to angles — the interpupillary distance would enter the result as a constant offset.
| Function | What it does |
|---|---|
| mark_eye_rest_from_angles | saccades and intervals of rest |
| mark_eye_data_with_full_angle_from_angles | saccades and intervals between saccades |
| stable_intervals_by_velocity | stable intervals, by velocity only |
| make_okulogram | mm → degrees, the oculogram |
| find_central_position | the neutral position as the median |
| star_eye_smoothing, smooth_eye_position_mm | smoothing that does not blur fast movements |
| angular_velocity_at_rate | velocity brought to a chosen sampling rate |
| min_saccade_minima_by_sampling, approx_params_by_fs | the thresholds and the parameters of the main sequence |
| SaccadeModel, switch_times, trajectory, saccade_template, match_saccade_templates | the saccade trajectory model |
Requirements that are the same for both branches; the specifics of the input are in the "Required input data" sections above.
The resolving power of a recording determines which saccades can be detected at all. All the thresholds are therefore computed by the model from the actual rate rather than given as constants:
| fs, Hz | min. amplitude, deg | min. duration, ms | peak threshold, deg/s | fixation threshold, deg/s |
|---|---|---|---|---|
| 60 | 8.55 | 50.0 | 243.6 | 171.0 |
| 90 | 1.87 | 33.3 | 94.1 | 37.4 |
| 250 | 0.25 | 18.4 | 28.2 | 5.0 |
| 1250 | 0.25 | 20.0 | 29.2 | 5.0 |
From below the thresholds are bounded by physiology: the velocity of an ideal foveal fixation is 5 deg/s, its spatial spread 5 deg, the minimum duration 50 ms. Without these bounds the model at high rates gave a peak saccade velocity of 0.32 deg/s — a physically impossible value.
A consequence worth knowing: metric values are comparable only between recordings of the same sampling rate, or after the recordings have been brought to a common rate.
import pandas as pd
from eyetracking_analytics import markup
from eyetracking_analytics.report import enable_report_logging
enable_report_logging() # to see the values everything was computed with
data = pd.read_csv("record.csv") # columns: T_s (seconds!), gaze_yaw, gaze_pitch
result = markup.mark_gaze_events_direction(
data,
col_horizontal="gaze_yaw",
col_vertical="gaze_pitch",
col_time="T_s",
geometry="angles", # yaw/pitch on the unit sphere
)
print(len(result.gaze_shifts), "gaze shifts")
print(len(result.fixations), "fixations")
print(result.fixations[["t_start_s", "duration_s", "mean_eccentricity_deg", "spread_deg"]])No thresholds are given here — they are derived from the sampling rate. The log shows which values were applied and where each of them came from:
[eyetracking_analytics] разметка переводов взора по направлению запись ............................ 19.99 с, 1800 отсчётов (из данных) частота записи .................... 90 Гц (из данных: дрожание шага 0.0 %) параметризация .................... angles (пользователь: yaw/pitch на сфере) порог пика скорости ............... 94.1437 град/с (модель) порог скорости на фиксации ........ 37.3693 град/с (модель) минимальная амплитуда ............. 1.8685 град (модель) найдено ........................... 14 переводов взора, 29 фиксаций (вычислено)
The computation log is emitted in Russian: it is produced by the code, and translating it would change the output that existing projects and reference fixtures rely on.
| Module | Purpose |
|---|---|
| markup | detection of gaze shifts and fixations: one-dimensional recording and direction recording |
| constants | the fixed constants: one declaration per quantity, imported by every module |
| direction | input: the orientation of the head and of the eye → a single gaze direction |
| models | model of the saccadic main sequence, all thresholds by sampling rate |
| sampling | rate estimation, time-unit checking, decimation |
| for_raw_data | raw eye data: oculogram, neutral position, smoothing, velocity |
| eyetracking_funs | metrics: gaze stabilization coefficient, trajectory length, interval statistics |
| gaze_mark | preprocessing and computational kernels of the markup |
| orientation | orientation of the head and gaze: quaternions → angles, vector rotation |
| blinks | blinks: detection by quality flag or by signal, boundary refinement, mask |
| saccade_model | saccade trajectory model: a family of curves and the search for similar ones in a recording |
| report | computation log: which parameters were applied and where they came from |
Layout: the package lives in src/eyetracking_analytics/, the tests in tests/.
A detailed description of functions, units and table schemas is in docs/api_reference.md.
This is research code whose results go into publications, so reproducibility is placed above convenience:
The library is installed as an ordinary package.
pip install -e . # the library
pip install -e ".[fast]" # + numba (acceleration, no effect on the numbers)
pip install -e ".[viz]" # + plotly, matplotlib, seaborn
pip install -e ".[dev]" # + pytest, ruffFrom a project where the library is a submodule:
git submodule update --init eyetracking_analytics
pip install -e ./eyetracking_analyticsOr straight from the repository, without a submodule:
pip install "git+https://github.com/ankorm/eyetracking_analytics.git"
pip install "git+https://github.com/ankorm/eyetracking_analytics.git@v1.0.0" # pinned versionImportant for older projects. It used to be enough to put the submodule into the project directory: the modules lay in its root and were found through sys.path. Now the package lives in src/, so having the submodule directory on the path is not enough — from eyetracking_analytics import eyetracking_funs gives ImportError: ... (unknown location). It suffices to run pip install -e ./eyetracking_analytics once; the imports in the notebooks themselves need no change, they keep working as before.
The public API is listed in eyetracking_analytics.__all__ — 99 names; everything else is an implementation detail.
The local environment (the same one CI installs):
python3.11 -m venv .venv
.venv/bin/pip install -e ".[dev,fast]" # without numba: ".[dev]".venv/bin/pytest # the whole suite
.venv/bin/pytest tests/test_golden.py # only the regression fixtures
.venv/bin/ruff check . # the lint, mandatoryMore on the discipline of changing the reference is in tests/README.md.
Required: numpy, pandas, scipy. Optional (extras): fast — numba (acceleration, no effect on the numbers), viz — plotly for debug plots and matplotlib with seaborn for BilinearRegression.plot_fit, dev — pytest.
The library is open, version 1.0.0. The API is used in the author's projects and has been checked on real recordings.
From version 1.0.0 a compatibility promise applies: an incompatible change of the public API or of the numerical results is released only with a major version bump and an explicit note in CHANGELOG.md. Criteria that affect the set of fixations are therefore switched on explicitly: on spread the intervals are only flagged by default, and dropping them is requested with the drop_invalid_spread flag.
All changes, marked with whether they alter numerical results, are in CHANGELOG.md. The release DOI is issued by Zenodo; until it appears the repository itself can be cited, see How to cite.
The library is not a self-contained tool: a research result is built into it. The main-sequence parameters (T0_ms, m, V0, n), from which all detection thresholds are derived, are taken from Table 4 of the article and recomputed for the sampling rate. This is why the citation must be to the article:
Kruchinina A., Xu X. Analysis of saccadic main sequence relationship from a time-optimal control perspective // Russian Journal of Biomechanics. — 2026. — Vol. 30, no. 1. — P. 108–114. — DOI 10.15593/RJBiomech/2026.1.10
This is the citation for the whole markup: the thresholds are computed by the model in every scenario of using the library.
Separately — the saccade trajectory model (saccade_model): a second-order element with relay control and the switching moments following from the condition of hitting the target. If saccade_template, saccade_template_family or match_saccade_templates were used, add:
Kruchinina A., Yakushev A. G. Parametrization of Saccade Trajectories // Moscow University Mechanics Bulletin. — 2018. — Vol. 73, no. 4. — P. 97–100. — DOI 10.3103/S0027133018340052
Kruchinina A. Optimal time task in saccadic eye movement // Russian Journal of Biomechanics. — 2020. — Vol. 24, no. 1. — P. 33–39. — DOI 10.15593/RJBiomech/2020.1.04
Separately — the gaze stabilization coefficient (ksv): the criterion of the quality of visual tracking is not ours, it was proposed in the work below. The citation is needed only if a value of ksv is reported; it does not extend to the rest of the markup:
Shtefanova O. Yu., Yakushev A. G. A quality criterion for visual tracking during nystagmus // Moscow University Mechanics Bulletin. — 2008. — Vol. 63, no. 4. — P. 100–102. — DOI 10.3103/S0027133008040043
The primary citation is the code itself: DOI 10.5281/zenodo.22096316 — it points at all versions and resolves to the latest. The DOI of a particular version is issued separately (1.0.0 — 10.5281/zenodo.22096317), but the first one is what should be cited. The works above are added to this citation according to what exactly was used; one does not replace the other. The machine-readable form of all references is in CITATION.cff: the library itself is described by the top-level fields and all the works are collected in references; there is deliberately no preferred-citation field, so that tools cite the program. On GitHub this file provides the "Cite this repository" button, and the release DOI is issued by Zenodo.
In the text of a paper:
Kruchinina A. eyetracking_analytics: a library for detection and analysis of eye and gaze movements. Version 1.0.0. 2026. DOI: 10.5281/zenodo.22096316
BibTeX:
@software{kruchinina_eyetracking_analytics,
author = {Kruchinina, Anna},
title = {eyetracking_analytics: detection and analysis of eye and gaze movements},
version = {1.0.0},
year = {2026},
publisher = {Zenodo},
doi = {10.5281/zenodo.22096316},
url = {https://github.com/ankorm/eyetracking_analytics}
}What to add to this citation depends on what exactly was used:
| What was used | What to add |
|---|---|
| any markup: the thresholds are always computed by the model | Kruchinina, Xu, 2026 |
| the saccade trajectory model (saccade_model) | Kruchinina, Yakushev, 2018 and Kruchinina, 2020 |
| the gaze stabilization coefficient (ksv) | Shtefanova, Yakushev, 2008 |
| the area of the swept region (bcea_deg2) | Kruchinina, Polikanova, 2025 |
The bibliographic data of these works are above in this section and in CITATION.cff.
The methodology on real material — biathletes shooting from the prone position compared with novices: markup of eye and head movements, integral characteristics of the recording.
Kruchinina A., Polikanova I. S. Analysis of Eye and Head Tracking Movements during Shooting from the Prone Position in Biathletes Compared to Novices // Psychology. Journal of the Higher School of Economics. — 2025. — Vol. 22, no. 3. — P. 473–488. — DOI 10.17323/1813-8918-2025-3-473-488
This is an application of the methodology and not its model: there is no need to cite the library through this work — the references to the models are collected in the How to cite section.
BSD 3-Clause — the same licence as numpy, scipy and pandas. Use, including commercial use, is free; the text of the licence and the attribution must be retained, and the author's name may not be used to promote derived products.
The licence permits use, but does not cancel the scientific norm of citing the source of the methodology: legal permission and academic attribution are different things.
The primary version of each file is the Russian one; the English translation lies next to it.
| File | About |
|---|---|
| docs/api_reference.md | functions, parameters, units, table schemas |
| tests/README.md | the reference of the methodology, the levels of comparison, the CI matrix |
| docs/Sci_base.md | the concepts of the methodology |
| CHANGELOG.md | all changes, marked with whether they alter numerical results |
| CITATION.cff | references to the article and to the code in machine-readable form |
| LICENSE | BSD 3-Clause |
| Back | FazBrowse Home | New Git URL |