scanpath-studio 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scanpath_studio-0.14.0/LICENSE +21 -0
- scanpath_studio-0.14.0/MANIFEST.in +5 -0
- scanpath_studio-0.14.0/PKG-INFO +229 -0
- scanpath_studio-0.14.0/README.md +174 -0
- scanpath_studio-0.14.0/pyproject.toml +66 -0
- scanpath_studio-0.14.0/scanpath_studio/__init__.py +13 -0
- scanpath_studio-0.14.0/scanpath_studio/__main__.py +18 -0
- scanpath_studio-0.14.0/scanpath_studio/annotations.py +320 -0
- scanpath_studio-0.14.0/scanpath_studio/app.py +752 -0
- scanpath_studio-0.14.0/scanpath_studio/constants.py +83 -0
- scanpath_studio-0.14.0/scanpath_studio/controls.py +467 -0
- scanpath_studio-0.14.0/scanpath_studio/data.py +961 -0
- scanpath_studio-0.14.0/scanpath_studio/export.py +510 -0
- scanpath_studio-0.14.0/scanpath_studio/measures.py +505 -0
- scanpath_studio-0.14.0/scanpath_studio/onestop_shard.py +139 -0
- scanpath_studio-0.14.0/scanpath_studio/plots.py +2186 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/fixations.csv +3210 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/fixations.parquet +0 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/ia.csv +3923 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/ia.parquet +0 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/raw_gaze.csv +2234 -0
- scanpath_studio-0.14.0/scanpath_studio/sample_data/raw_gaze.parquet +0 -0
- scanpath_studio-0.14.0/scanpath_studio/styles.py +72 -0
- scanpath_studio-0.14.0/scanpath_studio/synthetic.py +133 -0
- scanpath_studio-0.14.0/scanpath_studio/tabs.py +1916 -0
- scanpath_studio-0.14.0/scanpath_studio/update_sample_data.py +500 -0
- scanpath_studio-0.14.0/scanpath_studio/utils.py +556 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/PKG-INFO +229 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/SOURCES.txt +65 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/dependency_links.txt +1 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/entry_points.txt +2 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/requires.txt +11 -0
- scanpath_studio-0.14.0/scanpath_studio.egg-info/top_level.txt +1 -0
- scanpath_studio-0.14.0/setup.cfg +4 -0
- scanpath_studio-0.14.0/tests/test_annotations.py +134 -0
- scanpath_studio-0.14.0/tests/test_app.py +213 -0
- scanpath_studio-0.14.0/tests/test_apptest.py +53 -0
- scanpath_studio-0.14.0/tests/test_data.py +405 -0
- scanpath_studio-0.14.0/tests/test_export.py +219 -0
- scanpath_studio-0.14.0/tests/test_filters.py +76 -0
- scanpath_studio-0.14.0/tests/test_measures.py +201 -0
- scanpath_studio-0.14.0/tests/test_plots.py +870 -0
- scanpath_studio-0.14.0/tests/test_raw_gaze_sample.py +85 -0
- scanpath_studio-0.14.0/tests/test_smoke.py +284 -0
- scanpath_studio-0.14.0/tests/test_synthetic.py +103 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Scanpath Studio Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scanpath-studio
|
|
3
|
+
Version: 0.14.0
|
|
4
|
+
Summary: Interactive Streamlit workbench for visualizing eye-tracking-while-reading scanpaths, computing reading measures, and exporting figures and tabular data.
|
|
5
|
+
Author-email: "Omer Shubi (LACC Lab, Technion)" <lacclab.technion@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Scanpath Studio Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Repository, https://github.com/lacclab/scanpath-studio
|
|
29
|
+
Project-URL: Issues, https://github.com/lacclab/scanpath-studio/issues
|
|
30
|
+
Keywords: eye-tracking,scanpath,visualization,streamlit,reading,psycholinguistics
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Intended Audience :: Science/Research
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: streamlit>=1.52.2
|
|
45
|
+
Requires-Dist: pandas>=2.3.3
|
|
46
|
+
Requires-Dist: plotly>=6.5.0
|
|
47
|
+
Requires-Dist: numpy>=2.3.5
|
|
48
|
+
Requires-Dist: pyarrow>=22.0.0
|
|
49
|
+
Requires-Dist: kaleido>=1.0
|
|
50
|
+
Requires-Dist: watchdog>=4.0.0
|
|
51
|
+
Provides-Extra: test
|
|
52
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
53
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# Scanpath Studio
|
|
57
|
+
|
|
58
|
+
[](https://pypi.org/project/scanpath-studio/)
|
|
59
|
+
[](https://scanpath-studio.streamlit.app)
|
|
60
|
+
[](https://github.com/lacclab/scanpath-studio/actions/workflows/ci.yml)
|
|
61
|
+
[](LICENSE)
|
|
62
|
+
|
|
63
|
+
An interactive workbench for visualizing **eye-tracking-while-reading** data.
|
|
64
|
+
Drop in a trial and see the scanpath the way the reader saw it: words at their
|
|
65
|
+
true on-screen positions, fixations and saccades layered on top, a density
|
|
66
|
+
heatmap, side-by-side trial comparisons, and animated replay — all tunable, and
|
|
67
|
+
all exportable as publication-ready figures.
|
|
68
|
+
|
|
69
|
+
It is **dataset-agnostic** (auto-detects EyeLink / Gazepoint / snake-case
|
|
70
|
+
columns) and ships with a small [OneStop][onestop-paper] demo so you can try it
|
|
71
|
+
with zero setup.
|
|
72
|
+
|
|
73
|
+
> **Authors:** Omer Shubi, Keren Gruteke Klein, and others (TBD) — LACC Lab, Technion.
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
*A scanpath replayed fixation by fixation over the text the reader saw (bundled OneStop demo).*
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Try it
|
|
82
|
+
|
|
83
|
+
**Live demo (zero install):** <https://scanpath-studio.streamlit.app>
|
|
84
|
+
|
|
85
|
+
**Or run locally:**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install scanpath-studio
|
|
89
|
+
scanpath-studio # launches the app in your browser
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## What you can visualize
|
|
95
|
+
|
|
96
|
+
The plot is built from layers you can toggle independently:
|
|
97
|
+
|
|
98
|
+
- **Text** — every word drawn at the exact pixel coordinates the participant saw.
|
|
99
|
+
- **Fixations** — where the eye paused, sized and **colored by any column** in your data (duration, GPT-2 surprisal, word frequency, …).
|
|
100
|
+
- **Saccades** — the jumps between fixations; backward jumps (regressions) stand out.
|
|
101
|
+
- **Areas of interest** — word bounding boxes that tie each fixation to a word.
|
|
102
|
+
- **Heatmap** — the trial aggregated into a word-level measure (total fixation duration, fixation count, …).
|
|
103
|
+
|
|
104
|
+
On top of the layered view:
|
|
105
|
+
|
|
106
|
+
- **Animated replay** — watch the scanpath unfold fixation by fixation, at real or scaled speed.
|
|
107
|
+
- **Compare two trials** — overlaid on one canvas or side-by-side (e.g. ordinary vs. information-seeking reading, first vs. repeated reading, L1 vs. L2).
|
|
108
|
+
- **Critical-span highlight** — mark a region of interest (e.g. an answer span) by color or border to see at a glance whether it was read.
|
|
109
|
+
- **Out-of-text & by-line** — flag fixations that land outside every word box, or color fixations by the text line they fall on.
|
|
110
|
+
- **Fully customizable** — map any field to color, size, or axes; set the plot background (white or a neutral gray); every toggle, palette, and scale is independent.
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
|
|
114
|
+
*Overlay a second reading to compare two readers of the same text on a shared real-time clock.*
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## The four tabs
|
|
119
|
+
|
|
120
|
+
| Tab | What's there |
|
|
121
|
+
|-----|--------------|
|
|
122
|
+
| **Interactive Plot** | The layered scanpath view, trial picker (by trial / text / participant), trial metadata, and two-trial comparison. |
|
|
123
|
+
| **Animated Scanpath** | Frame-by-frame replay; each frame lasts the actual fixation duration ÷ playback speed. |
|
|
124
|
+
| **Raw Data** | Paginated word, fixation, and raw-gaze tables, each with CSV + Parquet download. |
|
|
125
|
+
| **Data Statistics** | Summary stats (mean fixation duration, saccade amplitude, regression rate, reading speed), a fixation-duration distribution, and a per-word reading-measure bar plot. |
|
|
126
|
+
|
|
127
|
+

|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Reading measures from raw fixations
|
|
132
|
+
|
|
133
|
+
If your data only carries raw fixations, the app computes the canonical
|
|
134
|
+
per-word measures itself (pre-aggregated EyeLink columns, if present, take
|
|
135
|
+
precedence):
|
|
136
|
+
|
|
137
|
+
| Measure | Definition |
|
|
138
|
+
|---------|------------|
|
|
139
|
+
| **FFD** — first fixation duration | duration of the first fixation to land on the word |
|
|
140
|
+
| **FPRT** / gaze duration | sum of fixations from first entry until the eye first leaves |
|
|
141
|
+
| **RPD** / go-past time | sum of fixations from first entry until the eye first moves past the word |
|
|
142
|
+
| **TFD** / dwell | sum of all fixations on the word |
|
|
143
|
+
| fixation count, skip, regression in/out, saccade amplitude | standard reading-research flags and counts |
|
|
144
|
+
|
|
145
|
+
Definitions follow Rayner (1998) and Inhoff & Radach (1998).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Triage your trials
|
|
150
|
+
|
|
151
|
+
Filter the trial pool by **condition** — information-seeking *Hunting* vs.
|
|
152
|
+
ordinary *Gathering* reading, difficulty, first vs. repeated reading, answer
|
|
153
|
+
correctness — or by your own annotations. **Star** favorites, **tag** trials
|
|
154
|
+
(e.g. "To exclude"), and jot per-trial **notes**; download everything as a JSON
|
|
155
|
+
sidecar and restore it in a later session.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Your data
|
|
160
|
+
|
|
161
|
+
Upload **CSV, Parquet, or Feather** tables for words/AoIs, fixations, and
|
|
162
|
+
(optionally) raw gaze. Columns are auto-detected from common EyeLink,
|
|
163
|
+
Gazepoint, and snake-case conventions; a sidebar **Column mapping** panel lets
|
|
164
|
+
you override any guess.
|
|
165
|
+
|
|
166
|
+
**Areas of interest** come straight from your word boxes — given as
|
|
167
|
+
`(x, y, width, height)` or EyeLink's `IA_LEFT/RIGHT/TOP/BOTTOM` — the app never
|
|
168
|
+
invents them. Fixations are tied to words by bounding-box containment (with a
|
|
169
|
+
small nearest-word fallback); fixations that miss every box are flagged
|
|
170
|
+
*out-of-text*.
|
|
171
|
+
|
|
172
|
+
## Bulk export
|
|
173
|
+
|
|
174
|
+
One panel exports artifacts for **every filtered trial** into a single zip —
|
|
175
|
+
per-trial PNG + SVG figures, the exact plot settings (`plot_config.json`),
|
|
176
|
+
fixations, and per-word measures, plus aggregated tables across trials. Ideal
|
|
177
|
+
for paper figures or building an image dataset of scanpaths for vision models.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Run from source
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git clone https://github.com/lacclab/scanpath-studio.git
|
|
185
|
+
cd scanpath-studio
|
|
186
|
+
pip install -e ".[test]" # or: uv sync
|
|
187
|
+
streamlit run streamlit_app.py
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Tested on Python 3.11–3.13. Run the tests with `pytest`; lint with
|
|
191
|
+
`ruff check --exclude other_vis .`. See [AGENTS.md](AGENTS.md) for an
|
|
192
|
+
architectural overview.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Citation
|
|
197
|
+
|
|
198
|
+
A system-demo paper is in preparation — **citation TBD**. Until then, cite the
|
|
199
|
+
software via GitHub's **"Cite this repository"** button (generated from
|
|
200
|
+
[`CITATION.cff`](CITATION.cff)).
|
|
201
|
+
|
|
202
|
+
If you use the bundled demo data, please cite the OneStop corpus:
|
|
203
|
+
|
|
204
|
+
```bibtex
|
|
205
|
+
@article{berzak2025onestop,
|
|
206
|
+
title = {{OneStop}: A 360-Participant {E}nglish Eye Tracking Dataset
|
|
207
|
+
with Different Reading Regimes},
|
|
208
|
+
author = {Berzak, Yevgeni and Malmaud, Jonathan and Shubi, Omer
|
|
209
|
+
and Meiri, Yoav and Lion, Ella and Levy, Roger},
|
|
210
|
+
journal = {Scientific Data},
|
|
211
|
+
year = {2025},
|
|
212
|
+
publisher = {Nature Publishing Group},
|
|
213
|
+
doi = {10.1038/s41597-025-06272-2},
|
|
214
|
+
url = {https://www.nature.com/articles/s41597-025-06272-2},
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The bundled demo is a subset of [OneStop Eye Movements][onestop-corpus], used
|
|
219
|
+
under its original license ([docs][onestop-docs]).
|
|
220
|
+
|
|
221
|
+
[onestop-paper]: https://www.nature.com/articles/s41597-025-06272-2
|
|
222
|
+
[onestop-corpus]: https://github.com/lacclab/OneStop-Eye-Movements
|
|
223
|
+
[onestop-docs]: https://lacclab.github.io/OneStop-Eye-Movements/
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Scanpath Studio
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/scanpath-studio/)
|
|
4
|
+
[](https://scanpath-studio.streamlit.app)
|
|
5
|
+
[](https://github.com/lacclab/scanpath-studio/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
An interactive workbench for visualizing **eye-tracking-while-reading** data.
|
|
9
|
+
Drop in a trial and see the scanpath the way the reader saw it: words at their
|
|
10
|
+
true on-screen positions, fixations and saccades layered on top, a density
|
|
11
|
+
heatmap, side-by-side trial comparisons, and animated replay — all tunable, and
|
|
12
|
+
all exportable as publication-ready figures.
|
|
13
|
+
|
|
14
|
+
It is **dataset-agnostic** (auto-detects EyeLink / Gazepoint / snake-case
|
|
15
|
+
columns) and ships with a small [OneStop][onestop-paper] demo so you can try it
|
|
16
|
+
with zero setup.
|
|
17
|
+
|
|
18
|
+
> **Authors:** Omer Shubi, Keren Gruteke Klein, and others (TBD) — LACC Lab, Technion.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
*A scanpath replayed fixation by fixation over the text the reader saw (bundled OneStop demo).*
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Try it
|
|
27
|
+
|
|
28
|
+
**Live demo (zero install):** <https://scanpath-studio.streamlit.app>
|
|
29
|
+
|
|
30
|
+
**Or run locally:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install scanpath-studio
|
|
34
|
+
scanpath-studio # launches the app in your browser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What you can visualize
|
|
40
|
+
|
|
41
|
+
The plot is built from layers you can toggle independently:
|
|
42
|
+
|
|
43
|
+
- **Text** — every word drawn at the exact pixel coordinates the participant saw.
|
|
44
|
+
- **Fixations** — where the eye paused, sized and **colored by any column** in your data (duration, GPT-2 surprisal, word frequency, …).
|
|
45
|
+
- **Saccades** — the jumps between fixations; backward jumps (regressions) stand out.
|
|
46
|
+
- **Areas of interest** — word bounding boxes that tie each fixation to a word.
|
|
47
|
+
- **Heatmap** — the trial aggregated into a word-level measure (total fixation duration, fixation count, …).
|
|
48
|
+
|
|
49
|
+
On top of the layered view:
|
|
50
|
+
|
|
51
|
+
- **Animated replay** — watch the scanpath unfold fixation by fixation, at real or scaled speed.
|
|
52
|
+
- **Compare two trials** — overlaid on one canvas or side-by-side (e.g. ordinary vs. information-seeking reading, first vs. repeated reading, L1 vs. L2).
|
|
53
|
+
- **Critical-span highlight** — mark a region of interest (e.g. an answer span) by color or border to see at a glance whether it was read.
|
|
54
|
+
- **Out-of-text & by-line** — flag fixations that land outside every word box, or color fixations by the text line they fall on.
|
|
55
|
+
- **Fully customizable** — map any field to color, size, or axes; set the plot background (white or a neutral gray); every toggle, palette, and scale is independent.
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
*Overlay a second reading to compare two readers of the same text on a shared real-time clock.*
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## The four tabs
|
|
64
|
+
|
|
65
|
+
| Tab | What's there |
|
|
66
|
+
|-----|--------------|
|
|
67
|
+
| **Interactive Plot** | The layered scanpath view, trial picker (by trial / text / participant), trial metadata, and two-trial comparison. |
|
|
68
|
+
| **Animated Scanpath** | Frame-by-frame replay; each frame lasts the actual fixation duration ÷ playback speed. |
|
|
69
|
+
| **Raw Data** | Paginated word, fixation, and raw-gaze tables, each with CSV + Parquet download. |
|
|
70
|
+
| **Data Statistics** | Summary stats (mean fixation duration, saccade amplitude, regression rate, reading speed), a fixation-duration distribution, and a per-word reading-measure bar plot. |
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Reading measures from raw fixations
|
|
77
|
+
|
|
78
|
+
If your data only carries raw fixations, the app computes the canonical
|
|
79
|
+
per-word measures itself (pre-aggregated EyeLink columns, if present, take
|
|
80
|
+
precedence):
|
|
81
|
+
|
|
82
|
+
| Measure | Definition |
|
|
83
|
+
|---------|------------|
|
|
84
|
+
| **FFD** — first fixation duration | duration of the first fixation to land on the word |
|
|
85
|
+
| **FPRT** / gaze duration | sum of fixations from first entry until the eye first leaves |
|
|
86
|
+
| **RPD** / go-past time | sum of fixations from first entry until the eye first moves past the word |
|
|
87
|
+
| **TFD** / dwell | sum of all fixations on the word |
|
|
88
|
+
| fixation count, skip, regression in/out, saccade amplitude | standard reading-research flags and counts |
|
|
89
|
+
|
|
90
|
+
Definitions follow Rayner (1998) and Inhoff & Radach (1998).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Triage your trials
|
|
95
|
+
|
|
96
|
+
Filter the trial pool by **condition** — information-seeking *Hunting* vs.
|
|
97
|
+
ordinary *Gathering* reading, difficulty, first vs. repeated reading, answer
|
|
98
|
+
correctness — or by your own annotations. **Star** favorites, **tag** trials
|
|
99
|
+
(e.g. "To exclude"), and jot per-trial **notes**; download everything as a JSON
|
|
100
|
+
sidecar and restore it in a later session.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Your data
|
|
105
|
+
|
|
106
|
+
Upload **CSV, Parquet, or Feather** tables for words/AoIs, fixations, and
|
|
107
|
+
(optionally) raw gaze. Columns are auto-detected from common EyeLink,
|
|
108
|
+
Gazepoint, and snake-case conventions; a sidebar **Column mapping** panel lets
|
|
109
|
+
you override any guess.
|
|
110
|
+
|
|
111
|
+
**Areas of interest** come straight from your word boxes — given as
|
|
112
|
+
`(x, y, width, height)` or EyeLink's `IA_LEFT/RIGHT/TOP/BOTTOM` — the app never
|
|
113
|
+
invents them. Fixations are tied to words by bounding-box containment (with a
|
|
114
|
+
small nearest-word fallback); fixations that miss every box are flagged
|
|
115
|
+
*out-of-text*.
|
|
116
|
+
|
|
117
|
+
## Bulk export
|
|
118
|
+
|
|
119
|
+
One panel exports artifacts for **every filtered trial** into a single zip —
|
|
120
|
+
per-trial PNG + SVG figures, the exact plot settings (`plot_config.json`),
|
|
121
|
+
fixations, and per-word measures, plus aggregated tables across trials. Ideal
|
|
122
|
+
for paper figures or building an image dataset of scanpaths for vision models.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Run from source
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/lacclab/scanpath-studio.git
|
|
130
|
+
cd scanpath-studio
|
|
131
|
+
pip install -e ".[test]" # or: uv sync
|
|
132
|
+
streamlit run streamlit_app.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Tested on Python 3.11–3.13. Run the tests with `pytest`; lint with
|
|
136
|
+
`ruff check --exclude other_vis .`. See [AGENTS.md](AGENTS.md) for an
|
|
137
|
+
architectural overview.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Citation
|
|
142
|
+
|
|
143
|
+
A system-demo paper is in preparation — **citation TBD**. Until then, cite the
|
|
144
|
+
software via GitHub's **"Cite this repository"** button (generated from
|
|
145
|
+
[`CITATION.cff`](CITATION.cff)).
|
|
146
|
+
|
|
147
|
+
If you use the bundled demo data, please cite the OneStop corpus:
|
|
148
|
+
|
|
149
|
+
```bibtex
|
|
150
|
+
@article{berzak2025onestop,
|
|
151
|
+
title = {{OneStop}: A 360-Participant {E}nglish Eye Tracking Dataset
|
|
152
|
+
with Different Reading Regimes},
|
|
153
|
+
author = {Berzak, Yevgeni and Malmaud, Jonathan and Shubi, Omer
|
|
154
|
+
and Meiri, Yoav and Lion, Ella and Levy, Roger},
|
|
155
|
+
journal = {Scientific Data},
|
|
156
|
+
year = {2025},
|
|
157
|
+
publisher = {Nature Publishing Group},
|
|
158
|
+
doi = {10.1038/s41597-025-06272-2},
|
|
159
|
+
url = {https://www.nature.com/articles/s41597-025-06272-2},
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The bundled demo is a subset of [OneStop Eye Movements][onestop-corpus], used
|
|
164
|
+
under its original license ([docs][onestop-docs]).
|
|
165
|
+
|
|
166
|
+
[onestop-paper]: https://www.nature.com/articles/s41597-025-06272-2
|
|
167
|
+
[onestop-corpus]: https://github.com/lacclab/OneStop-Eye-Movements
|
|
168
|
+
[onestop-docs]: https://lacclab.github.io/OneStop-Eye-Movements/
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scanpath-studio"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Interactive Streamlit workbench for visualizing eye-tracking-while-reading scanpaths, computing reading measures, and exporting figures and tabular data."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [{ name = "Omer Shubi (LACC Lab, Technion)", email = "lacclab.technion@gmail.com" }]
|
|
12
|
+
license = { file = "LICENSE" }
|
|
13
|
+
keywords = ["eye-tracking", "scanpath", "visualization", "streamlit", "reading", "psycholinguistics"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"streamlit>=1.52.2",
|
|
28
|
+
"pandas>=2.3.3",
|
|
29
|
+
"plotly>=6.5.0",
|
|
30
|
+
"numpy>=2.3.5",
|
|
31
|
+
"pyarrow>=22.0.0",
|
|
32
|
+
"kaleido>=1.0",
|
|
33
|
+
"watchdog>=4.0.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
test = [
|
|
38
|
+
"pytest>=8.0.0",
|
|
39
|
+
"pytest-cov>=4.0.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Repository = "https://github.com/lacclab/scanpath-studio"
|
|
44
|
+
Issues = "https://github.com/lacclab/scanpath-studio/issues"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
scanpath-studio = "scanpath_studio.__main__:main"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.dynamic]
|
|
50
|
+
# Single source of truth for the version: scanpath_studio/__version__
|
|
51
|
+
version = { attr = "scanpath_studio.__version__" }
|
|
52
|
+
|
|
53
|
+
[tool.setuptools]
|
|
54
|
+
include-package-data = true
|
|
55
|
+
package-dir = {"" = "."}
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["."]
|
|
59
|
+
include = ["scanpath_studio"]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.package-data]
|
|
62
|
+
"scanpath_studio" = [
|
|
63
|
+
"sample_data/*.csv",
|
|
64
|
+
"sample_data/*.parquet",
|
|
65
|
+
"sample_data/*.feather",
|
|
66
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Streamlit workbench for scanpath visualization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = ["__version__", "main"]
|
|
6
|
+
__version__ = "0.14.0"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
"""Programmatic entry point — `from scanpath_studio import main`."""
|
|
11
|
+
from .app import main as _main
|
|
12
|
+
|
|
13
|
+
_main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import importlib.resources as resources
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from streamlit.web import cli as stcli
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main(argv: Optional[list[str]] = None) -> None:
|
|
9
|
+
"""Entrypoint that launches the Streamlit app via ``streamlit run``."""
|
|
10
|
+
extra_args = list(argv) if argv is not None else sys.argv[1:]
|
|
11
|
+
app_resource = resources.files(__package__).joinpath("app.py")
|
|
12
|
+
with resources.as_file(app_resource) as app_path:
|
|
13
|
+
sys.argv = ["streamlit", "run", str(app_path), *extra_args]
|
|
14
|
+
sys.exit(stcli.main())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|