HeatMatch
Visualize and compare very dense saccade patterns from long free-viewing eye-tracking data.
HeatMatch turns hundreds or thousands of saccades into continuous orientation fields: at every location it estimates saccade density, mean orientation, and how consistently the saccades agree in orientation. From these fields it draws orientation-coded heatmaps that show where gaze moves and in which direction. It also computes a similarity score for comparing saccade patterns across participants, stimuli, or conditions, without AOIs, scanpath alignment, or manual segmentation.
It was developed for 60-second viewing of paintings (empirical aesthetics, art history) and works for any static stimulus. Python implementation accompanying the paper:
Xingyu Long, Jozsef Arato, Sophia Kury, Anna Miscena, and Raphael Rosenberg. 2026. |
|
Overview
- OOI-coded heatmaps — orientation fields visualized with researcher-defined color anchors and confidence-weighted opacity.
- HeatMatch similarity — comparison based on saccade density and orientation.
Per-participant (columns 1–5) and aggregated (column 6) heatmaps for three paintings. Hue = mean saccade orientation relative to OOI; opacity = local confidence. Stimulus images: public domain via Wikimedia Commons — see paper for details.
See the paper for methodology.
When to use it
- Long, dense viewing. Scanpath comparison methods such as MultiMatch or ScanMatch align scanpaths sequence by sequence. With hundreds of saccades per trial and strongly diverging paths, they become slow and hard to interpret. HeatMatch aggregates instead of aligning, so it scales to arbitrarily dense data.
- Orientation matters. Fixation heatmaps and density-based metrics ignore saccade direction. HeatMatch keeps orientation, so you can ask whether gaze follows horizontal, vertical, diagonal, or symmetric structure in an image, e.g. composition lines in a painting.
- Group, stimulus, and individual comparisons. In the paper, the similarity score recovers the abstract vs. still-life distinction and differences in within-painting agreement. It also reveals stable individual "saccadic signatures" that hold across paintings.
It is not a replacement for sequence-based methods. HeatMatch is permutation-invariant (it ignores saccade order) and treats orientations as unsigned (left-to-right = right-to-left). Use it alongside MultiMatch, ScanMatch, or RQA when order or direction matters.
Installation
pip install heatmatch
Dependencies: numpy, matplotlib, Pillow. For development, clone the repository and run pip install -e ..
Quick Start
import matplotlib.pyplot as plt
from heatmatch import make_reference_grid, make_orientation_field, Heatmap, compute_similarity
W, H = 2880, 2160
onset = ... # (J, 2) saccade start coordinates
offset = ... # (J, 2) saccade end coordinates
pts, xx, yy = make_reference_grid(W, H, grid_resolution=200)
field = make_orientation_field(pts, onset, offset, sigma=50.0, grid_shape=yy.shape)
fig, ax = plt.subplots()
Heatmap(field, W, H).draw(ax, ooi=0.0) # ooi=0 → horizontal, ooi=90 → vertical
plt.show()
# Compare two patterns (fields built on the same grid, e.g. two participants)
result = compute_similarity(field_a, field_b)
s = (result.s_loc + result.s_dir) / 2 # composite HeatMatch score S in [0, 1]
print(result.s_loc, result.s_dir, s)
A full worked example is in demo.ipynb. The dataset is not included — download data_anonymized.csv from osf.io/f2xhj and place it in tests/.
API
heatmatch.fields
make_reference_grid(w, h, grid_resolution) |
Returns (pts, xx, yy); int → square grid, (ny, nx) tuple → rectangular |
make_orientation_field(pts, onset, offset, sigma, grid_shape, ...) |
Returns OrientationField with omega_mean, R, rho |
heatmatch.heatmapping
Heatmap(field, w, h, image=None) |
Caches colormap, opacity, and (optionally) grayscale background image |
Heatmap.draw(ax, ooi, opacity_density_weight, base_opacity, cmap, ...) |
Renders onto ax |
OOI in degrees: 0 = east, 90 = north, 180 = west, 270 = south. Unsigned, so ooi and ooi ± 180 are equivalent.
heatmatch.matching
compute_similarity(field_a, field_b, density_coherence_tradeoff) |
Returns SimilarityResult(s_loc, s_dir) |
Key Parameters
| Parameter | Default | Notes |
|---|---|---|
sigma |
50.0 |
Gaussian bandwidth in px. Paper evaluates {50, 100, 150}. |
grid_resolution |
200 |
Int or (ny, nx). Paper uses 200. |
ooi |
0.0 |
Orientation of Interest in degrees. |
opacity_density_weight |
1.0 |
Density vs. coherence for opacity. Recommended 0.5–1.0. |
base_opacity |
0.85 |
Global opacity ceiling; useful with a background image. |
density_coherence_tradeoff |
0.5 |
Density vs. coherence for S_dir. At 1.0, angular information is discarded. |
Citation
@article{long2026heatmatch,
author = {Long, Xingyu and Arato, Jozsef and Kury, Sophia and Miscena, Anna and Rosenberg, Raphael},
title = {HeatMatch: Orientation-Aware Visualization and Comparison of Very Dense Saccade Patterns},
journal = {Proc. ACM Comput. Graph. Interact. Tech.},
year = {2026},
volume = {9},
number = {2},
articleno = {18},
doi = {10.1145/3803539},
}
Metadata
Release files for heatmatch 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| heatmatch-1.0.0.tar.gz | 14.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| heatmatch-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.5 kB
Release files / heatmatch-1.0.0.tar.gz
| Download URL | heatmatch-1.0.0.tar.gz |
|---|---|
| Size | 14.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
753b324c2abe0096360935867b098a8c1ebc397efd0a697b41f115c77822a407
|
|
BLAKE2b-256 checksum How to use checksums |
497ba48f28dbd1078b5f8c1275446d79391cb0dd3c25cf2e8689d0ef00c3ff11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.2
|
Release files / heatmatch-1.0.0-py3-none-any.whl
| Download URL | heatmatch-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
80d206daab3663353fa673000d0d0d1a4a3cb77c6ae94e010e0e608304247ff4
|
|
BLAKE2b-256 checksum How to use checksums |
934966fd5041bdc0acae6b3546b6bd4bbda3e7ddc34436b86407d7666795c182
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.2
|