scanpath-studio 0.44.0__tar.gz → 0.45.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.44.0/scanpath_studio.egg-info → scanpath_studio-0.45.0}/PKG-INFO +11 -11
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/pyproject.toml +12 -11
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/__init__.py +3 -2
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/aggregation.py +1 -1
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/api.py +2 -1
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/app.py +9 -13
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/cli.py +70 -2
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/code_snippet.py +59 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/column_names.py +8 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/controls.py +6 -4
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/data.py +20 -10
- scanpath_studio-0.45.0/scanpath_studio/datasets.py +2936 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/export.py +2 -2
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/plots.py +95 -16
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/styles.py +17 -5
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/tabs.py +56 -25
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/tour.py +1 -1
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/ui_mode.py +11 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/url_state.py +22 -9
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/utils.py +2 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard.py +647 -9
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0/scanpath_studio.egg-info}/PKG-INFO +11 -11
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/requires.txt +10 -10
- scanpath_studio-0.44.0/scanpath_studio/datasets.py +0 -1233
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/LICENSE +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/MANIFEST.in +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/NOTICE +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/README.md +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/__main__.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/alignment.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/analysis_recipe.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/animation_export.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/annotations.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/assets/scanpath_studio_title_logo.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/assets/scanpath_studio_title_logo_dark.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/authoring.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/authoring_component.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/build_info.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/citation.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/cite_button.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/color_picker_commit.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/compare_source.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/computations.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/constants.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/crash_report.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/data_health.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/dataset_table.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/debug_log.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/desktop_update.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/easter_egg.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/experimental_setup.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/export_status.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/feedback.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/fields.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/file_citation.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/html_embed.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/illustration.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/loading.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/measures.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/menu.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/metadata.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/model_scanpaths.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/multipart.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/onestop_shard.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/persistence.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/plot_config.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/preprocessing.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/progress.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/fixations.csv +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/fixations.parquet +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/ia.csv +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/ia.parquet +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_a.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_b.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_c.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_d.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__paragraph.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__question.png +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/manifest.csv +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/raw_gaze.csv +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/raw_gaze.parquet +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/save_dialog.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/session_keys.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/similarity.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/streamlit_entry.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/synthetic.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/truncation_tooltip.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/update_sample_data.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/updates.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard_shell.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard_watch.py +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/SOURCES.txt +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/dependency_links.txt +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/entry_points.txt +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/top_level.txt +0 -0
- {scanpath_studio-0.44.0 → scanpath_studio-0.45.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scanpath-studio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.45.0
|
|
4
4
|
Summary: Interactive visualization of eye-tracking data from reading studies: draw, replay and compare scanpaths, fixations, saccades and gaze heatmaps over the text, and export figures and tables.
|
|
5
5
|
Author: Keren Gruteke Klein, Maya Grossman, Ella Lion, Deborah N. Jakobi, David R. Reich, Lena Jäger, Yevgeni Berzak
|
|
6
6
|
Author-email: Omer Shubi <lacclab.technion@gmail.com>
|
|
@@ -24,20 +24,20 @@ Requires-Python: >=3.11
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
License-File: NOTICE
|
|
27
|
-
Requires-Dist: streamlit[performance]>=1.
|
|
27
|
+
Requires-Dist: streamlit[performance]>=1.67.0
|
|
28
28
|
Requires-Dist: packaging>=26.3
|
|
29
|
-
Requires-Dist: pandas>=3.
|
|
29
|
+
Requires-Dist: pandas>=3.1
|
|
30
30
|
Requires-Dist: plotly>=7.1
|
|
31
31
|
Requires-Dist: numpy>=2.4
|
|
32
32
|
Requires-Dist: scipy>=1.17
|
|
33
|
-
Requires-Dist: pyarrow>=
|
|
34
|
-
Requires-Dist: kaleido>=1.
|
|
35
|
-
Requires-Dist: watchdog>=
|
|
33
|
+
Requires-Dist: pyarrow>=26.0
|
|
34
|
+
Requires-Dist: kaleido>=1.5
|
|
35
|
+
Requires-Dist: watchdog>=7.1.0
|
|
36
36
|
Requires-Dist: streamlit-sortables>=0.3.1
|
|
37
|
-
Requires-Dist: pillow>=
|
|
38
|
-
Requires-Dist: imageio[ffmpeg]>=2.
|
|
37
|
+
Requires-Dist: pillow>=13.0
|
|
38
|
+
Requires-Dist: imageio[ffmpeg]>=2.38
|
|
39
39
|
Requires-Dist: openpyxl>=3.1.5
|
|
40
|
-
Requires-Dist: truststore>=0.10
|
|
40
|
+
Requires-Dist: truststore>=0.10.5
|
|
41
41
|
Requires-Dist: xlrd>=2.0.2
|
|
42
42
|
Provides-Extra: test
|
|
43
43
|
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
@@ -45,7 +45,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
|
45
45
|
Requires-Dist: pytest-xdist>=3.0.0; extra == "test"
|
|
46
46
|
Requires-Dist: pytest-timeout>=2.1.0; extra == "test"
|
|
47
47
|
Provides-Extra: lint
|
|
48
|
-
Requires-Dist: ruff==0.
|
|
48
|
+
Requires-Dist: ruff==0.17.4; extra == "lint"
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs-material[imaging]>=9.7.7; extra == "docs"
|
|
51
51
|
Requires-Dist: mkdocstrings[python]>=1.0.6; extra == "docs"
|
|
@@ -53,7 +53,7 @@ Requires-Dist: markdown-exec>=1.12.3; extra == "docs"
|
|
|
53
53
|
Requires-Dist: mkdocs-redirects>=1.2.3; extra == "docs"
|
|
54
54
|
Requires-Dist: mkdocs-git-revision-date-localized-plugin>=1.6.0; extra == "docs"
|
|
55
55
|
Requires-Dist: mkdocs-llmstxt>=0.5.0; extra == "docs"
|
|
56
|
-
Requires-Dist: ruff==0.
|
|
56
|
+
Requires-Dist: ruff==0.17.4; extra == "docs"
|
|
57
57
|
Dynamic: license-file
|
|
58
58
|
|
|
59
59
|
# Scanpath Studio
|
|
@@ -38,34 +38,35 @@ classifiers = [
|
|
|
38
38
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
39
39
|
]
|
|
40
40
|
dependencies = [
|
|
41
|
-
"streamlit[performance]>=1.
|
|
41
|
+
"streamlit[performance]>=1.67.0",
|
|
42
42
|
# Build versions sort and compare as PEP 440 (`build_info.py`, `updates.py`).
|
|
43
43
|
"packaging>=26.3",
|
|
44
|
-
"pandas>=3.
|
|
44
|
+
"pandas>=3.1",
|
|
45
45
|
"plotly>=7.1",
|
|
46
46
|
"numpy>=2.4",
|
|
47
47
|
# The drift-correction port (`alignment.py`); BSD-3, MIT-compatible.
|
|
48
48
|
"scipy>=1.17",
|
|
49
|
-
"pyarrow>=
|
|
50
|
-
"kaleido>=1.
|
|
49
|
+
"pyarrow>=26.0",
|
|
50
|
+
"kaleido>=1.5",
|
|
51
51
|
# Optional Streamlit runtime dep: enables fast event-based file-watching
|
|
52
52
|
# (hot reload) for `streamlit run`. Not imported by app code — Streamlit
|
|
53
53
|
# auto-detects it and falls back to polling (+ an "install Watchdog" log) if
|
|
54
54
|
# absent.
|
|
55
|
-
"watchdog>=
|
|
55
|
+
"watchdog>=7.1.0",
|
|
56
56
|
# Drag-to-reorder the trial chip fields (the chips popover).
|
|
57
57
|
"streamlit-sortables>=0.3.1",
|
|
58
58
|
# Rasterized animation export (GIF/MP4): Pillow encodes GIF; imageio's ffmpeg
|
|
59
59
|
# extra bundles an ffmpeg binary for MP4 (no system package needed).
|
|
60
|
-
"pillow>=
|
|
61
|
-
"imageio[ffmpeg]>=2.
|
|
60
|
+
"pillow>=13.0",
|
|
61
|
+
"imageio[ffmpeg]>=2.38",
|
|
62
62
|
# pandas' .xlsx engine. Not optional in practice: the upload path reads
|
|
63
|
-
# spreadsheets (`data._read_one`)
|
|
63
|
+
# spreadsheets (`data._read_one`), and MultiplEYE's comprehension questions
|
|
64
|
+
# come as a workbook.
|
|
64
65
|
"openpyxl>=3.1.5",
|
|
65
66
|
# Dataset downloads verify HTTPS against the OS certificate store, so a
|
|
66
67
|
# python.org Python without its CA bundle, or a TLS-inspecting network,
|
|
67
68
|
# can still fetch OneStop / PoTeC (`datasets._open_url`).
|
|
68
|
-
"truststore>=0.10",
|
|
69
|
+
"truststore>=0.10.5",
|
|
69
70
|
# The Excel 97–2003 (.xls) engine, for the same readers.
|
|
70
71
|
"xlrd>=2.0.2",
|
|
71
72
|
]
|
|
@@ -84,7 +85,7 @@ test = [
|
|
|
84
85
|
# unpinned ruff means a local run and CI can disagree — a new release lands new
|
|
85
86
|
# rules and reds a branch that changed nothing. Bump both together.
|
|
86
87
|
lint = [
|
|
87
|
-
"ruff==0.
|
|
88
|
+
"ruff==0.17.4",
|
|
88
89
|
]
|
|
89
90
|
# Build the documentation site (MkDocs Material + API autodoc from docstrings).
|
|
90
91
|
# Material caps MkDocs below 2.0, which has no plugin system; every plugin here
|
|
@@ -101,7 +102,7 @@ docs = [
|
|
|
101
102
|
"mkdocs-llmstxt>=0.5.0",
|
|
102
103
|
# mkdocstrings wraps the API reference's signatures with it. Pinned with
|
|
103
104
|
# the `lint` extra's, so the two extras resolve together.
|
|
104
|
-
"ruff==0.
|
|
105
|
+
"ruff==0.17.4",
|
|
105
106
|
]
|
|
106
107
|
|
|
107
108
|
[project.urls]
|
|
@@ -19,6 +19,7 @@ __all__ = [
|
|
|
19
19
|
"list_parts",
|
|
20
20
|
"list_trials",
|
|
21
21
|
"load_authored_scanpath",
|
|
22
|
+
"load_multipleye",
|
|
22
23
|
"load_onestop",
|
|
23
24
|
"load_participant_metadata",
|
|
24
25
|
"load_potec",
|
|
@@ -42,7 +43,7 @@ __all__ = [
|
|
|
42
43
|
# against. `__version__` is the exact build, worked out from it lazily
|
|
43
44
|
# (build_info.py) — "0.35.0" at the release itself,
|
|
44
45
|
# "0.35.0.post3+g8f18219" three commits after it.
|
|
45
|
-
__release__ = "0.
|
|
46
|
+
__release__ = "0.45.0"
|
|
46
47
|
|
|
47
48
|
# Public headless API (see api.py / datasets.py). Resolved lazily so
|
|
48
49
|
# `import scanpath_studio` stays cheap and doesn't pull in pandas/plotly/
|
|
@@ -60,7 +61,7 @@ _HELD_BACK_EXPORTS = frozenset(
|
|
|
60
61
|
"trial_summary",
|
|
61
62
|
}
|
|
62
63
|
)
|
|
63
|
-
_DATASET_EXPORTS = frozenset({"load_potec", "load_onestop"})
|
|
64
|
+
_DATASET_EXPORTS = frozenset({"load_potec", "load_multipleye", "load_onestop"})
|
|
64
65
|
_API_EXPORTS = (
|
|
65
66
|
(frozenset(__all__) | _HELD_BACK_EXPORTS)
|
|
66
67
|
- {"__version__", "main"}
|
|
@@ -363,7 +363,7 @@ def text_screen_options(frame: pd.DataFrame, text_col: str, text_id) -> list[str
|
|
|
363
363
|
|
|
364
364
|
Empty for a single-screen dataset — which is every corpus without
|
|
365
365
|
screen identity, so callers can treat "no options" as "there is nothing to
|
|
366
|
-
pick". Ordered by ``screen_index`` when present (
|
|
366
|
+
pick". Ordered by ``screen_index`` when present (MultiplEYE ranks it by first
|
|
367
367
|
fixation onset, i.e. the order the reader actually went through the pages),
|
|
368
368
|
else by first appearance.
|
|
369
369
|
"""
|
|
@@ -3974,7 +3974,8 @@ def figure_code(
|
|
|
3974
3974
|
kind: ``"static"``, ``"animation"`` or ``"comparison"``: the builder the snippet
|
|
3975
3975
|
calls.
|
|
3976
3976
|
source: How the data is loaded: ``"demo"``, ``"synthetic"``, ``"files"``,
|
|
3977
|
-
``"potec"``, ``"onestop"``, ``"author"``, ``"raw_gaze"``
|
|
3977
|
+
``"potec"``, ``"onestop"``, ``"multipleye"``, ``"author"``, ``"raw_gaze"``
|
|
3978
|
+
(a dataset recorded
|
|
3978
3979
|
as raw gaze alone, whose samples at ``source_options["raw_gaze"]`` are the
|
|
3979
3980
|
data), or ``"unknown"`` for data a snippet can't name.
|
|
3980
3981
|
source_options: That loader's arguments: ``{"root": …}``, ``{"words": [...],
|
|
@@ -6321,7 +6321,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6321
6321
|
|
|
6322
6322
|
Raised once by ``main`` on the run after ✅ Add dataset, once the new
|
|
6323
6323
|
dataset's frames are loaded, so ``message`` carries its counts. Closing it
|
|
6324
|
-
keeps the user on 🗂️ Data Management, where **Open in Scanpath** under the
|
|
6324
|
+
keeps the user on 🗂️ Data Management, where **Open in Scanpath view** under the
|
|
6325
6325
|
list of datasets does the same later. Buttons are handled by their return
|
|
6326
6326
|
value, never ``on_click`` — a dialog body is a fragment (see
|
|
6327
6327
|
``_leave_dataset_editor_dialog``).
|
|
@@ -6329,7 +6329,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6329
6329
|
st.markdown(message)
|
|
6330
6330
|
open_col, again_col = st.columns(2, gap="small")
|
|
6331
6331
|
if open_col.button(
|
|
6332
|
-
f"{ICONS['view_scanpath']} Open in Scanpath",
|
|
6332
|
+
f"{ICONS['view_scanpath']} Open in Scanpath view",
|
|
6333
6333
|
key="dataset_added_open",
|
|
6334
6334
|
type="primary",
|
|
6335
6335
|
width="stretch",
|
|
@@ -6349,7 +6349,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6349
6349
|
st.rerun(scope="app")
|
|
6350
6350
|
st.caption(
|
|
6351
6351
|
f"Close this to stay on {ICONS['view_data']} Data Management. "
|
|
6352
|
-
"**Open in Scanpath**, under the list of datasets, opens it later."
|
|
6352
|
+
"**Open in Scanpath view**, under the list of datasets, opens it later."
|
|
6353
6353
|
)
|
|
6354
6354
|
|
|
6355
6355
|
|
|
@@ -9071,12 +9071,8 @@ def _render_backup_reminder(host, active_view: str) -> None:
|
|
|
9071
9071
|
return
|
|
9072
9072
|
box = host.container(key="sps_backup_reminder", border=True)
|
|
9073
9073
|
box.markdown(
|
|
9074
|
-
f"{ICONS['warning']} **This server saves nothing.**
|
|
9075
|
-
"
|
|
9076
|
-
"your annotations and designs. Keep the files you uploaded, and export your "
|
|
9077
|
-
f"annotations from {ICONS['view_data']} **Data Management → Annotations** and each "
|
|
9078
|
-
"dataset's mapping from "
|
|
9079
|
-
f"{ICONS['edit']} **Edit dataset → Download setup file**. "
|
|
9074
|
+
f"{ICONS['warning']} **This server saves nothing.** Refreshing or "
|
|
9075
|
+
"closing the tab loses your datasets, annotations and designs. "
|
|
9080
9076
|
f"[How to keep copies of your work ↗]({BACKUP_GUIDE_URL})"
|
|
9081
9077
|
)
|
|
9082
9078
|
row = box.container(horizontal=True, gap="small")
|
|
@@ -9788,7 +9784,7 @@ def _run_app() -> None:
|
|
|
9788
9784
|
dataset_table_slot = setup_source_slot.container(key="tutorial_available_datasets")
|
|
9789
9785
|
# The two ways to make a dataset are one **+ Add
|
|
9790
9786
|
# dataset** menu (the Scanpath picker's + with its name spelled out), under
|
|
9791
|
-
# the list it adds to, beside **Open in Scanpath** for the dataset the list
|
|
9787
|
+
# the list it adds to, beside **Open in Scanpath view** for the dataset the list
|
|
9792
9788
|
# marks Current. Filled once `data_choice` is known.
|
|
9793
9789
|
add_dataset_slot = (
|
|
9794
9790
|
setup_source_slot.container(key="data_page_add", horizontal=True)
|
|
@@ -9843,7 +9839,7 @@ def _run_app() -> None:
|
|
|
9843
9839
|
# picked from the list: the view switch is a request, since this is
|
|
9844
9840
|
# an `on_click` callback (see `url_state._go_scanpath`).
|
|
9845
9841
|
add_dataset_slot.button(
|
|
9846
|
-
"Open in Scanpath",
|
|
9842
|
+
"Open in Scanpath view",
|
|
9847
9843
|
icon=ICONS["view_scanpath"],
|
|
9848
9844
|
key="data_open_in_scanpath",
|
|
9849
9845
|
on_click=_go_scanpath,
|
|
@@ -10362,10 +10358,10 @@ def _run_app() -> None:
|
|
|
10362
10358
|
st.session_state[TRIAL_IDENTITY_CHECK_KEY] = asked_by
|
|
10363
10359
|
if added:
|
|
10364
10360
|
# Say what ✅ Add dataset brought in, and offer the next step: a
|
|
10365
|
-
# dialog with Open in Scanpath / Add another. The Trial ID verdict
|
|
10361
|
+
# dialog with Open in Scanpath view / Add another. The Trial ID verdict
|
|
10366
10362
|
# takes the run's one dialog when it has something to say, and any
|
|
10367
10363
|
# other dialog already open wins too; the counts then go to a toast,
|
|
10368
|
-
# and **Open in Scanpath** under the list is the way on.
|
|
10364
|
+
# and **Open in Scanpath view** under the list is the way on.
|
|
10369
10365
|
message = dataset_added_message(str(added), words_all, fixations_all)
|
|
10370
10366
|
offered = False
|
|
10371
10367
|
if not identity_modal:
|
|
@@ -30,6 +30,7 @@ from . import column_names as _cn
|
|
|
30
30
|
from .code_snippet import (
|
|
31
31
|
SOURCE_AUTHOR,
|
|
32
32
|
SOURCE_DEMO,
|
|
33
|
+
SOURCE_MULTIPLEYE,
|
|
33
34
|
SOURCE_ONESTOP,
|
|
34
35
|
SOURCE_POTEC,
|
|
35
36
|
SnippetSource,
|
|
@@ -595,6 +596,17 @@ def _render_parser() -> argparse.ArgumentParser:
|
|
|
595
596
|
"lacclab, a local lab-processed export with no download.",
|
|
596
597
|
)
|
|
597
598
|
|
|
599
|
+
src.add_argument(
|
|
600
|
+
"--multipleye",
|
|
601
|
+
metavar="DIR",
|
|
602
|
+
help="Load a MultiplEYE session set from DIR, the folder holding its "
|
|
603
|
+
"scanpaths/ or fixations/ folder and its stimuli_*/ folder. A participant "
|
|
604
|
+
"is a session (e.g. 001_ZH_CH_1_ET1) and a trial is one reading of one "
|
|
605
|
+
"stimulus (e.g. 001_ZH_CH_1_ET1_Lit_Alchemist_4), with its pages and "
|
|
606
|
+
"comprehension questions as screens. The screen comes from the session "
|
|
607
|
+
"set's own config; "
|
|
608
|
+
"set the font with --font-size and --font-family.",
|
|
609
|
+
)
|
|
598
610
|
src.add_argument(
|
|
599
611
|
"--participant-metadata",
|
|
600
612
|
metavar="FILE",
|
|
@@ -1113,7 +1125,8 @@ def _render_parser() -> argparse.ArgumentParser:
|
|
|
1113
1125
|
metavar="WxH",
|
|
1114
1126
|
help="Screen size in px, e.g. 2560x1440 (default: 2560x1440 for "
|
|
1115
1127
|
"--sample and --onestop, 1680x1050 for --potec, 1200x800 for "
|
|
1116
|
-
"--authoring,
|
|
1128
|
+
"--authoring, the session set's own monitor for --multipleye, "
|
|
1129
|
+
"otherwise estimated from the data).",
|
|
1117
1130
|
)
|
|
1118
1131
|
viz.add_argument(
|
|
1119
1132
|
"--coordinate-grid",
|
|
@@ -2377,6 +2390,12 @@ def _snippet_data_source(args) -> SnippetSource:
|
|
|
2377
2390
|
"parts": list(args.onestop_part or ["Paragraph"]),
|
|
2378
2391
|
},
|
|
2379
2392
|
)
|
|
2393
|
+
if args.multipleye:
|
|
2394
|
+
return cs.SnippetSource(
|
|
2395
|
+
kind=cs.SOURCE_MULTIPLEYE,
|
|
2396
|
+
label="MultiplEYE",
|
|
2397
|
+
options={"root": args.multipleye},
|
|
2398
|
+
)
|
|
2380
2399
|
if args.words or args.fixations:
|
|
2381
2400
|
options = {
|
|
2382
2401
|
"words": list(args.words or []),
|
|
@@ -2574,6 +2593,36 @@ def _parse_xy(value: str | None, flag: str = "--stimulus-image-origin") -> tuple
|
|
|
2574
2593
|
return (x, y)
|
|
2575
2594
|
|
|
2576
2595
|
|
|
2596
|
+
def _load_multipleye_render(root: str, participant: str | None):
|
|
2597
|
+
"""`render --multipleye`: the session set, normalized like the app's.
|
|
2598
|
+
|
|
2599
|
+
``participant`` is a session id, matched case-insensitively; given one, only
|
|
2600
|
+
that session is read. Returns ``(data, participant)`` — the loaded frames
|
|
2601
|
+
and the session id as the export spells it.
|
|
2602
|
+
"""
|
|
2603
|
+
from .datasets import load_multipleye, multipleye_inventory
|
|
2604
|
+
|
|
2605
|
+
sessions, _ = multipleye_inventory(root)
|
|
2606
|
+
if not sessions:
|
|
2607
|
+
raise FileNotFoundError(
|
|
2608
|
+
f"No MultiplEYE sessions under {root}: expected a scanpaths/ or "
|
|
2609
|
+
"fixations/ folder holding one folder per session."
|
|
2610
|
+
)
|
|
2611
|
+
session = None
|
|
2612
|
+
if participant is not None:
|
|
2613
|
+
want = str(participant).strip().lower()
|
|
2614
|
+
session = next((s for s in sessions if s.lower() == want), None)
|
|
2615
|
+
if session is None:
|
|
2616
|
+
raise ValueError(
|
|
2617
|
+
f"No MultiplEYE session {participant!r} under {root} "
|
|
2618
|
+
f"(sessions: {', '.join(sessions)})."
|
|
2619
|
+
)
|
|
2620
|
+
data = load_multipleye(
|
|
2621
|
+
root, sessions=[session] if session else None, names="canonical"
|
|
2622
|
+
)
|
|
2623
|
+
return data, session
|
|
2624
|
+
|
|
2625
|
+
|
|
2577
2626
|
def _apply_shared_colorbar_flags(args: argparse.Namespace) -> None:
|
|
2578
2627
|
"""Hand v0.33.0's shared ``--colorbar-*`` values to each bar that was not
|
|
2579
2628
|
given its own; a bar's own flag wins, wherever it sits on the line.
|
|
@@ -2628,6 +2677,7 @@ def render(argv: list[str]) -> None:
|
|
|
2628
2677
|
bool(args.authoring),
|
|
2629
2678
|
bool(args.potec),
|
|
2630
2679
|
bool(args.onestop),
|
|
2680
|
+
bool(args.multipleye),
|
|
2631
2681
|
]
|
|
2632
2682
|
# Your own tables are words and/or fixations — or raw gaze alone, for a
|
|
2633
2683
|
# dataset recorded as samples only. Beside any other input, --raw-gaze is
|
|
@@ -2636,7 +2686,13 @@ def render(argv: list[str]) -> None:
|
|
|
2636
2686
|
bool(args.raw_gaze) and not any(corpus_inputs)
|
|
2637
2687
|
)
|
|
2638
2688
|
if sum([*corpus_inputs, own_tables]) != 1:
|
|
2639
|
-
inputs = [
|
|
2689
|
+
inputs = [
|
|
2690
|
+
"--sample",
|
|
2691
|
+
"--authoring PATH",
|
|
2692
|
+
"--potec DIR",
|
|
2693
|
+
"--onestop DIR",
|
|
2694
|
+
"--multipleye DIR",
|
|
2695
|
+
]
|
|
2640
2696
|
raise SystemExit(
|
|
2641
2697
|
f"Provide exactly one input: {', '.join(inputs)}, or your own tables "
|
|
2642
2698
|
"(--words and/or --fixations; one of them is enough for "
|
|
@@ -2831,6 +2887,18 @@ def render(argv: list[str]) -> None:
|
|
|
2831
2887
|
words, fixations = data
|
|
2832
2888
|
column_names = dict(data.column_names)
|
|
2833
2889
|
canvas = canvas or source_canvas(SOURCE_ONESTOP)
|
|
2890
|
+
elif args.multipleye:
|
|
2891
|
+
try:
|
|
2892
|
+
data, args.participant = _load_multipleye_render(
|
|
2893
|
+
args.multipleye, args.participant
|
|
2894
|
+
)
|
|
2895
|
+
except (ValueError, FileNotFoundError, OSError) as exc:
|
|
2896
|
+
raise SystemExit(str(exc))
|
|
2897
|
+
words, fixations = data
|
|
2898
|
+
column_names = dict(data.column_names)
|
|
2899
|
+
# Each fixation carries the session set's monitor (`canvas_width`), so
|
|
2900
|
+
# the screen is read off the data.
|
|
2901
|
+
canvas = canvas or source_canvas(SOURCE_MULTIPLEYE)
|
|
2834
2902
|
else:
|
|
2835
2903
|
manifest = None
|
|
2836
2904
|
if args.trial_parts_manifest:
|
|
@@ -87,6 +87,7 @@ SOURCE_FILES = "files"
|
|
|
87
87
|
SOURCE_AUTHOR = "author"
|
|
88
88
|
SOURCE_POTEC = "potec"
|
|
89
89
|
SOURCE_ONESTOP = "onestop"
|
|
90
|
+
SOURCE_MULTIPLEYE = "multipleye"
|
|
90
91
|
#: A dataset recorded as raw gaze alone — no words or fixations table,
|
|
91
92
|
#: so its data half is the samples (``options["raw_gaze"]``, the paths) and the
|
|
92
93
|
#: builder is handed ``None`` for the other two.
|
|
@@ -155,6 +156,8 @@ def source_canvas(kind: str) -> tuple[int, int] | None:
|
|
|
155
156
|
return (1680, 1050) # PoTeC monitor (DELL P2210)
|
|
156
157
|
if kind == SOURCE_AUTHOR:
|
|
157
158
|
return (1200, 800)
|
|
159
|
+
# MultiplEYE stamps each session set's own monitor on its fixations, so it
|
|
160
|
+
# is read off the data like any other dataset's.
|
|
158
161
|
return None
|
|
159
162
|
|
|
160
163
|
|
|
@@ -306,6 +309,19 @@ def _onestop_python(source: SnippetSource) -> list[str]:
|
|
|
306
309
|
return lines
|
|
307
310
|
|
|
308
311
|
|
|
312
|
+
#: The folder a MultiplEYE snippet loads when the dataset was uploaded, and so
|
|
313
|
+
#: has no folder the server could quote.
|
|
314
|
+
MULTIPLEYE_ROOT_PLACEHOLDER = "path/to/MultiplEYE_session_set"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _multipleye_python(source: SnippetSource) -> list[str]:
|
|
318
|
+
return [
|
|
319
|
+
"words, fixations = sps.load_multipleye(",
|
|
320
|
+
f" {_py(_root(source, MULTIPLEYE_ROOT_PLACEHOLDER))},",
|
|
321
|
+
")",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
|
|
309
325
|
def _unknown_python(source: SnippetSource) -> list[str]:
|
|
310
326
|
if source.options.get("keep_columns"):
|
|
311
327
|
return [
|
|
@@ -356,6 +372,10 @@ def _onestop_cli(source: SnippetSource) -> list[str]:
|
|
|
356
372
|
return argv
|
|
357
373
|
|
|
358
374
|
|
|
375
|
+
def _multipleye_cli(source: SnippetSource) -> list[str]:
|
|
376
|
+
return ["--multipleye", _root(source, MULTIPLEYE_ROOT_PLACEHOLDER)]
|
|
377
|
+
|
|
378
|
+
|
|
359
379
|
def _unknown_cli(source: SnippetSource) -> list[str]:
|
|
360
380
|
return ["--words", "words.csv", "--fixations", "fixations.csv", *_keep_cli(source)]
|
|
361
381
|
|
|
@@ -392,6 +412,8 @@ def upload_source(
|
|
|
392
412
|
placeholders: an upload has no path the server could quote.
|
|
393
413
|
"""
|
|
394
414
|
recipe = recipe if isinstance(recipe, dict) else {}
|
|
415
|
+
if isinstance(multipleye := recipe.get("multipleye"), dict):
|
|
416
|
+
return multipleye_source(label, multipleye, edited=bool(recipe.get("schemas")))
|
|
395
417
|
schemas = recipe.get("schemas") if isinstance(recipe.get("schemas"), dict) else {}
|
|
396
418
|
steps = [str(s) for s in recipe.get("steps") or ()]
|
|
397
419
|
notes = [
|
|
@@ -452,6 +474,42 @@ def upload_source(
|
|
|
452
474
|
)
|
|
453
475
|
|
|
454
476
|
|
|
477
|
+
def multipleye_source(
|
|
478
|
+
label: str, recipe: dict, *, edited: bool = False
|
|
479
|
+
) -> SnippetSource:
|
|
480
|
+
"""The data half for a MultiplEYE session set added through the app.
|
|
481
|
+
|
|
482
|
+
``recipe`` is the dataset's ``source_recipe["multipleye"]``: the ``root``
|
|
483
|
+
folder it was read from, ``None`` for an upload. ``load_multipleye`` reads
|
|
484
|
+
the same files the app did, so a folder-added dataset's snippet runs as it
|
|
485
|
+
is; an uploaded
|
|
486
|
+
one names a placeholder folder instead, and the whole session set there may
|
|
487
|
+
hold more than the files uploaded. ``edited`` says ✏️ Edit dataset changed
|
|
488
|
+
the mapping since, which the loader does not replay."""
|
|
489
|
+
root = recipe.get("root")
|
|
490
|
+
notes = []
|
|
491
|
+
if not root:
|
|
492
|
+
notes.append(
|
|
493
|
+
"This dataset was added from uploaded files, so the snippet can't "
|
|
494
|
+
f"name their folder. Replace `{MULTIPLEYE_ROOT_PLACEHOLDER}` with the "
|
|
495
|
+
"MultiplEYE session set they came from. The loader reads the whole "
|
|
496
|
+
"session set, so it can include trials, question screens, reading "
|
|
497
|
+
"measures and page images that the upload left out."
|
|
498
|
+
)
|
|
499
|
+
if edited:
|
|
500
|
+
notes.append(
|
|
501
|
+
"Its mapping was changed in Edit dataset after it was added. The "
|
|
502
|
+
"loader reads the session set as it was added, so make the same "
|
|
503
|
+
"change in your script."
|
|
504
|
+
)
|
|
505
|
+
return SnippetSource(
|
|
506
|
+
kind=SOURCE_MULTIPLEYE,
|
|
507
|
+
label=label,
|
|
508
|
+
options={"root": str(root) if root else None},
|
|
509
|
+
note=" ".join(notes),
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
455
513
|
def _compact_schema(schema: dict) -> dict:
|
|
456
514
|
"""A mapping without its unmapped fields, so the snippet stays readable.
|
|
457
515
|
|
|
@@ -551,6 +609,7 @@ _SOURCE_WRITERS: dict[str, tuple[Any, Any]] = {
|
|
|
551
609
|
SOURCE_AUTHOR: (_author_python, _author_cli),
|
|
552
610
|
SOURCE_POTEC: (_potec_python, _potec_cli),
|
|
553
611
|
SOURCE_ONESTOP: (_onestop_python, _onestop_cli),
|
|
612
|
+
SOURCE_MULTIPLEYE: (_multipleye_python, _multipleye_cli),
|
|
554
613
|
SOURCE_RAW_GAZE: (_raw_gaze_only_python, _raw_gaze_only_cli),
|
|
555
614
|
SOURCE_UPLOAD: (_upload_python, _upload_cli),
|
|
556
615
|
SOURCE_UNKNOWN: (_unknown_python, _unknown_cli),
|
|
@@ -1012,6 +1012,14 @@ def stored_source_recipe(stored: Mapping) -> dict:
|
|
|
1012
1012
|
return {"schemas": schemas, "unresolved": unresolved}
|
|
1013
1013
|
|
|
1014
1014
|
|
|
1015
|
+
def is_multipleye_entry(stored) -> bool:
|
|
1016
|
+
"""Whether a stored dataset was added as a MultiplEYE session set: its
|
|
1017
|
+
``source_recipe`` records the session set (``"multipleye"``), not a
|
|
1018
|
+
mapping of the user's files."""
|
|
1019
|
+
recipe = stored.get("source_recipe") if isinstance(stored, Mapping) else None
|
|
1020
|
+
return isinstance(recipe, Mapping) and isinstance(recipe.get("multipleye"), Mapping)
|
|
1021
|
+
|
|
1022
|
+
|
|
1015
1023
|
# --- A frame that carries its own names ---------------------------------------
|
|
1016
1024
|
|
|
1017
1025
|
#: The `DataFrame.attrs` key a frame under the dataset's own names carries its
|
|
@@ -8142,8 +8142,8 @@ def render_plot_controls(
|
|
|
8142
8142
|
label_visibility="collapsed",
|
|
8143
8143
|
)
|
|
8144
8144
|
|
|
8145
|
-
# A stimulus image can come from the dataset (a
|
|
8146
|
-
# `image_path`) OR be uploaded here for any dataset (a
|
|
8145
|
+
# A stimulus image can come from the dataset (MultiplEYE stamps a
|
|
8146
|
+
# per-trial `image_path`) OR be uploaded here for any dataset (a
|
|
8147
8147
|
# full-monitor screenshot of the reading screen). The upload's `data:`
|
|
8148
8148
|
# URI is stashed in session for the tab to place + the (pure) collector
|
|
8149
8149
|
# to read; the switch is enabled whenever either source exists.
|
|
@@ -9319,7 +9319,7 @@ _DEFAULT_FILTER_FIELDS = [
|
|
|
9319
9319
|
# on every export) and worth filtering on — it is how you exclude the start
|
|
9320
9320
|
# or the tail of a session when you suspect practice or fatigue effects.
|
|
9321
9321
|
"TRIAL_INDEX",
|
|
9322
|
-
#
|
|
9322
|
+
# MultiplEYE facets (present only when that corpus is loaded).
|
|
9323
9323
|
"genre",
|
|
9324
9324
|
"session",
|
|
9325
9325
|
"is_practice",
|
|
@@ -9693,9 +9693,11 @@ _CHIP_DEFAULT_CONDITIONS = [
|
|
|
9693
9693
|
"question_preview",
|
|
9694
9694
|
"repeated_reading_trial",
|
|
9695
9695
|
"is_correct",
|
|
9696
|
-
#
|
|
9696
|
+
# MultiplEYE facets + reader metadata (present only for that corpus).
|
|
9697
9697
|
"genre",
|
|
9698
9698
|
"session",
|
|
9699
|
+
"pp_age",
|
|
9700
|
+
"pp_gender",
|
|
9699
9701
|
]
|
|
9700
9702
|
# Virtual chip fields → label. These are computed per trial (not data columns)
|
|
9701
9703
|
# and always trial-level.
|
|
@@ -3813,7 +3813,7 @@ def source_labels(paths: Sequence[str]) -> list[str]:
|
|
|
3813
3813
|
def source_file_name(label: str) -> str:
|
|
3814
3814
|
"""The file stem inside a :func:`source_labels` label — without the folders
|
|
3815
3815
|
that qualify it, its ``#n`` occurrence number or a kept extension — for
|
|
3816
|
-
code that parses identity out of a file's name."""
|
|
3816
|
+
code that parses identity out of a file's name (MultiplEYE uploads)."""
|
|
3817
3817
|
name = re.sub(r"#\d+$", "", str(label).rsplit("/", 1)[-1])
|
|
3818
3818
|
stem = Path(name).stem
|
|
3819
3819
|
return stem if Path(name).suffix.lower().lstrip(".") in UPLOAD_FILE_TYPES else name
|
|
@@ -5744,7 +5744,7 @@ def detect_word_id_offset(words: pd.DataFrame, fixations: pd.DataFrame) -> int:
|
|
|
5744
5744
|
.groupby(keys, sort=False)["_id"]
|
|
5745
5745
|
.agg(["min", "max"])
|
|
5746
5746
|
)
|
|
5747
|
-
joined = w_stats.join(f_stats, how="inner",
|
|
5747
|
+
joined = w_stats.join(f_stats, how="inner", suffixes=("_w", "_f"))
|
|
5748
5748
|
if joined.empty:
|
|
5749
5749
|
return 0
|
|
5750
5750
|
zero_based = joined["min_w"] == 0
|
|
@@ -6245,9 +6245,9 @@ WORD_OPTIONAL_FIELDS = [
|
|
|
6245
6245
|
("dspan_ind_end", "dspan_ind_end", "passthrough", "meta"),
|
|
6246
6246
|
("is_in_aspan", "is_in_aspan", "boolean", "meta"),
|
|
6247
6247
|
("is_in_dspan", "is_in_dspan", "boolean", "meta"),
|
|
6248
|
-
#
|
|
6249
|
-
# JSON + the per-trial stimulus-image path + the genre facet, kept
|
|
6250
|
-
# panels / image layer can read them off the word frame too.
|
|
6248
|
+
# MultiplEYE side-data (also see FIX_OPTIONAL_FIELDS): the comprehension
|
|
6249
|
+
# questions JSON + the per-trial stimulus-image path + the genre facet, kept
|
|
6250
|
+
# so the panels / image layer can read them off the word frame too.
|
|
6251
6251
|
("comprehension_questions", "comprehension_questions", "passthrough", "meta"),
|
|
6252
6252
|
("image_path", "image_path", "passthrough", "meta"),
|
|
6253
6253
|
("image_x", "image_x", "numeric", "meta"),
|
|
@@ -6257,9 +6257,12 @@ WORD_OPTIONAL_FIELDS = [
|
|
|
6257
6257
|
("stimulus_font_px", "stimulus_font_px", "numeric", "meta"),
|
|
6258
6258
|
("stimulus_font_family", "stimulus_font_family", "passthrough", "meta"),
|
|
6259
6259
|
("genre", "genre", "string", "meta"),
|
|
6260
|
-
# Multipart screens: what a screen *is* (
|
|
6261
|
-
# trial
|
|
6260
|
+
# Multipart screens: what a screen *is* (`reading` / `question` on
|
|
6261
|
+
# MultiplEYE — a trial mixes both, and their fixations have different
|
|
6262
|
+
# provenance), and which block of a question screen a word belongs to
|
|
6263
|
+
# (`stem` / `target` / `distractor_a`… — a per-word facet).
|
|
6262
6264
|
("screen_kind", "screen_kind", "passthrough", "meta"),
|
|
6265
|
+
("aoi_block", "aoi_block", "passthrough", "meta"),
|
|
6263
6266
|
]
|
|
6264
6267
|
|
|
6265
6268
|
FIX_OPTIONAL_FIELDS = [
|
|
@@ -6303,9 +6306,10 @@ FIX_OPTIONAL_FIELDS = [
|
|
|
6303
6306
|
("blink", "is_blink", "boolean", "fixation"),
|
|
6304
6307
|
("blink_flag", "is_blink", "boolean", "fixation"),
|
|
6305
6308
|
("BLINK", "is_blink", "boolean", "fixation"),
|
|
6306
|
-
#
|
|
6307
|
-
# comprehension panel / the stimulus-image layer.
|
|
6308
|
-
#
|
|
6309
|
+
# MultiplEYE trial-level facets + side-data → Trial Info chips / filter
|
|
6310
|
+
# facets / the comprehension panel / the stimulus-image layer. All are
|
|
6311
|
+
# MultiplEYE-specific source names (carried only when the loader emits them),
|
|
6312
|
+
# so they're inert for other corpora.
|
|
6309
6313
|
("genre", "genre", "string", "meta"),
|
|
6310
6314
|
("session", "session", "string", "meta"),
|
|
6311
6315
|
("participant", "participant", "string", "meta"),
|
|
@@ -6317,6 +6321,12 @@ FIX_OPTIONAL_FIELDS = [
|
|
|
6317
6321
|
("image_y", "image_y", "numeric", "meta"),
|
|
6318
6322
|
("stimulus_font_px", "stimulus_font_px", "numeric", "meta"),
|
|
6319
6323
|
("stimulus_font_family", "stimulus_font_family", "passthrough", "meta"),
|
|
6324
|
+
# Reader metadata merged from participant_data.csv (namespaced pp_*).
|
|
6325
|
+
("pp_age", "pp_age", "numeric", "meta"),
|
|
6326
|
+
("pp_gender", "pp_gender", "string", "meta"),
|
|
6327
|
+
("pp_native_language", "pp_native_language", "string", "meta"),
|
|
6328
|
+
("pp_years_education", "pp_years_education", "numeric", "meta"),
|
|
6329
|
+
("pp_education_level", "pp_education_level", "string", "meta"),
|
|
6320
6330
|
# Which kind of screen a fixation happened on (see the word table).
|
|
6321
6331
|
("screen_kind", "screen_kind", "passthrough", "meta"),
|
|
6322
6332
|
]
|