scanpath-studio 0.43.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.43.0/scanpath_studio.egg-info → scanpath_studio-0.45.0}/PKG-INFO +11 -11
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/pyproject.toml +12 -11
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/__init__.py +3 -2
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/aggregation.py +1 -1
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/api.py +12 -3
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/app.py +79 -31
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/cli.py +70 -2
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/code_snippet.py +59 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/column_names.py +33 -2
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/computations.py +40 -5
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/constants.py +12 -3
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/controls.py +208 -81
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/data.py +522 -101
- scanpath_studio-0.45.0/scanpath_studio/datasets.py +2936 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/export.py +2 -2
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/plots.py +150 -27
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/styles.py +55 -9
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/tabs.py +80 -28
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/tour.py +1 -1
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/ui_mode.py +11 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/url_state.py +22 -9
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/utils.py +2 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard.py +673 -9
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0/scanpath_studio.egg-info}/PKG-INFO +11 -11
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/requires.txt +10 -10
- scanpath_studio-0.43.0/scanpath_studio/datasets.py +0 -1233
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/LICENSE +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/MANIFEST.in +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/NOTICE +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/README.md +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/__main__.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/alignment.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/analysis_recipe.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/animation_export.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/annotations.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/assets/scanpath_studio_title_logo.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/assets/scanpath_studio_title_logo_dark.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/authoring.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/authoring_component.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/build_info.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/citation.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/cite_button.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/color_picker_commit.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/compare_source.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/crash_report.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/data_health.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/dataset_table.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/debug_log.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/desktop_update.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/easter_egg.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/experimental_setup.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/export_status.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/feedback.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/fields.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/file_citation.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/html_embed.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/illustration.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/loading.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/measures.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/menu.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/metadata.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/model_scanpaths.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/multipart.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/onestop_shard.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/persistence.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/plot_config.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/preprocessing.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/progress.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/fixations.csv +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/fixations.parquet +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/ia.csv +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/ia.parquet +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_1_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_2_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_3_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_4_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_5_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_1_6_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_1_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_2_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_3_Adv__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_4_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_5_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_a.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_b.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_c.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_d.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__paragraph.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/2_2_6_Ele__question.png +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/images/manifest.csv +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/raw_gaze.csv +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/sample_data/raw_gaze.parquet +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/save_dialog.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/session_keys.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/similarity.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/streamlit_entry.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/synthetic.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/truncation_tooltip.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/update_sample_data.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/updates.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard_shell.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio/wizard_watch.py +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/SOURCES.txt +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/dependency_links.txt +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/entry_points.txt +0 -0
- {scanpath_studio-0.43.0 → scanpath_studio-0.45.0}/scanpath_studio.egg-info/top_level.txt +0 -0
- {scanpath_studio-0.43.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
|
"""
|
|
@@ -840,6 +840,13 @@ def load_scanpath_data(
|
|
|
840
840
|
Trials that match none raise a ``data.StimulusJoinWarning`` (a ``UserWarning``).
|
|
841
841
|
- **Fixations without x/y** but with a word ID are placed at the centers of their
|
|
842
842
|
word boxes.
|
|
843
|
+
- **Text word IDs.** A word ID column that holds text and no numbers (``w1``,
|
|
844
|
+
``Hello_3``) is read as keys, kept in a ``word_key`` column (under the column's
|
|
845
|
+
own name with ``names="source"``). Each word's ``word_id`` is then its position
|
|
846
|
+
in its text, 1, 2, … in the Words table's row order (the order of the reading
|
|
847
|
+
that lists the most of the text), one number for every reader, and a
|
|
848
|
+
fixation's is the number of the word with the same text ID in its trial —
|
|
849
|
+
exactly the same text, else none. A text ID never matches a number.
|
|
843
850
|
- **Bookkeeping columns.** Columns named in ``data.INTERNAL_COLUMNS`` are the
|
|
844
851
|
pipeline's own; ``data.drop_internal_columns`` removes them.
|
|
845
852
|
|
|
@@ -887,8 +894,9 @@ def load_scanpath_data(
|
|
|
887
894
|
data.StimulusJoinError: A stimulus-level Words table shares neither a trial ID
|
|
888
895
|
nor a ``text_id`` with any trial (or, multipart, with every screen a trial
|
|
889
896
|
has fixations on). A ``ValueError``.
|
|
890
|
-
data.UnplacedFixationsError: The fixations
|
|
891
|
-
|
|
897
|
+
data.UnplacedFixationsError: The fixations have no x/y, and not one of their
|
|
898
|
+
word IDs names a word: text IDs the Words table does not have, or numbers
|
|
899
|
+
against a Words table of text IDs. A ``ValueError``.
|
|
892
900
|
"""
|
|
893
901
|
_check_names_choice(names)
|
|
894
902
|
if words is None and fixations is None:
|
|
@@ -3966,7 +3974,8 @@ def figure_code(
|
|
|
3966
3974
|
kind: ``"static"``, ``"animation"`` or ``"comparison"``: the builder the snippet
|
|
3967
3975
|
calls.
|
|
3968
3976
|
source: How the data is loaded: ``"demo"``, ``"synthetic"``, ``"files"``,
|
|
3969
|
-
``"potec"``, ``"onestop"``, ``"author"``, ``"raw_gaze"``
|
|
3977
|
+
``"potec"``, ``"onestop"``, ``"multipleye"``, ``"author"``, ``"raw_gaze"``
|
|
3978
|
+
(a dataset recorded
|
|
3970
3979
|
as raw gaze alone, whose samples at ``source_options["raw_gaze"]`` are the
|
|
3971
3980
|
data), or ``"unknown"`` for data a snippet can't name.
|
|
3972
3981
|
source_options: That loader's arguments: ``{"root": …}``, ``{"words": [...],
|
|
@@ -158,6 +158,7 @@ from scanpath_studio.controls import (
|
|
|
158
158
|
background_swatch,
|
|
159
159
|
clear_trial_filter,
|
|
160
160
|
clear_trial_filters,
|
|
161
|
+
column_mapping_from_state,
|
|
161
162
|
column_mapping_ui,
|
|
162
163
|
has_active_trial_filters,
|
|
163
164
|
read_trial_filters,
|
|
@@ -304,7 +305,7 @@ from scanpath_studio.session_keys import (
|
|
|
304
305
|
PARAM_CORPUS,
|
|
305
306
|
PARAM_DATASET,
|
|
306
307
|
)
|
|
307
|
-
from scanpath_studio.styles import get_app_css, widen_menu
|
|
308
|
+
from scanpath_studio.styles import compact_css, get_app_css, widen_menu
|
|
308
309
|
from scanpath_studio.tabs import (
|
|
309
310
|
_EDITOR_KEY_NOISE,
|
|
310
311
|
_REMAP_DIRTY_KEY,
|
|
@@ -730,7 +731,7 @@ def configure_page() -> None:
|
|
|
730
731
|
page_icon="👀",
|
|
731
732
|
layout="wide",
|
|
732
733
|
)
|
|
733
|
-
st.
|
|
734
|
+
st.html(_app_stylesheet())
|
|
734
735
|
# Seeded here, not later in the run: the stylesheet below hides one mode's
|
|
735
736
|
# containers, so it needs the mode on the very first run.
|
|
736
737
|
ui_mode.seed_mode(st.session_state, st.query_params, _cookies())
|
|
@@ -752,6 +753,19 @@ LOGO_PATH = Path(__file__).parent / "assets" / "scanpath_studio_title_logo.png"
|
|
|
752
753
|
LOGO_DARK_PATH = LOGO_PATH.with_name("scanpath_studio_title_logo_dark.png")
|
|
753
754
|
|
|
754
755
|
|
|
756
|
+
@functools.cache
|
|
757
|
+
def _app_stylesheet() -> str:
|
|
758
|
+
"""The app stylesheet as sent: compacted, for a style-only `st.html`.
|
|
759
|
+
|
|
760
|
+
Not a markdown block: Streamlit applies a markdown block's raw HTML only
|
|
761
|
+
once its own HTML renderer has loaded and parsed the whole block, which on
|
|
762
|
+
a slow connection left the first screen unstyled for seconds. Built once
|
|
763
|
+
per process — the sheet depends on nothing but module constants, and
|
|
764
|
+
compacting it costs ~10 ms, on every rerun otherwise.
|
|
765
|
+
"""
|
|
766
|
+
return compact_css(get_app_css())
|
|
767
|
+
|
|
768
|
+
|
|
755
769
|
@functools.cache
|
|
756
770
|
def _logo_theme_css() -> str:
|
|
757
771
|
"""CSS that swaps the header wordmark for its dark variant in dark mode.
|
|
@@ -3775,7 +3789,8 @@ def reset_column_mapping() -> None:
|
|
|
3775
3789
|
#: ✕ Cancel puts the keys back.
|
|
3776
3790
|
BUILTIN_MAPPING_HELD_KEY = "_builtin_mapping_held"
|
|
3777
3791
|
#: The panels' current picks, ``{"words": schema, "fixations": schema}``,
|
|
3778
|
-
#: written by `prepare_data` on every run that
|
|
3792
|
+
#: written by `prepare_data` on every run of a source that has them, drawn or
|
|
3793
|
+
#: read from their keys.
|
|
3779
3794
|
BUILTIN_MAPPING_PENDING_KEY = "_builtin_mapping_pending"
|
|
3780
3795
|
#: Set by ✅ Save changes for the success line on the screen it returns to.
|
|
3781
3796
|
BUILTIN_MAPPING_SAVED_KEY = "_builtin_mapping_saved"
|
|
@@ -4040,12 +4055,18 @@ def prepare_data(
|
|
|
4040
4055
|
declared_fix_schema: dict | None = None,
|
|
4041
4056
|
mapping_dataset: object = None,
|
|
4042
4057
|
held_schemas: dict | None = None,
|
|
4058
|
+
render_mapping: bool = True,
|
|
4043
4059
|
) -> tuple[pd.DataFrame, pd.DataFrame, list]:
|
|
4044
4060
|
"""Infer schemas and normalize incoming dataframes to canonical column names.
|
|
4045
4061
|
|
|
4046
|
-
When ``allow_override`` is True,
|
|
4047
|
-
|
|
4048
|
-
|
|
4062
|
+
When ``allow_override`` is True, the user's picks in the mapping panels
|
|
4063
|
+
(pre-filled with auto-detection) decide each field. Otherwise just
|
|
4064
|
+
auto-detect.
|
|
4065
|
+
|
|
4066
|
+
``render_mapping`` says whether those panels are drawn this run. They are
|
|
4067
|
+
only on screen while ✏️ Edit dataset is open, so every other run reads the
|
|
4068
|
+
same picks from session state (`controls.column_mapping_from_state`)
|
|
4069
|
+
instead of building sixty-odd hidden selectboxes.
|
|
4049
4070
|
|
|
4050
4071
|
Returns ``(words_norm, fixations_norm, problems)``. ``problems`` is a list
|
|
4051
4072
|
of human-readable strings; when it's non-empty the column mapping isn't
|
|
@@ -4066,8 +4087,9 @@ def prepare_data(
|
|
|
4066
4087
|
happen to match (``controls.forget_mapping_for_other_table``).
|
|
4067
4088
|
|
|
4068
4089
|
``held_schemas`` (``{"words": …, "fixations": …}``) is the mapping the
|
|
4069
|
-
dataset keeps while ✏️ Edit dataset is open: the panels
|
|
4070
|
-
their
|
|
4090
|
+
dataset keeps while ✏️ Edit dataset is open: the panels' picks (drawn, or
|
|
4091
|
+
read from their keys on another view) are published as the draft
|
|
4092
|
+
(``BUILTIN_MAPPING_PENDING_KEY``),
|
|
4071
4093
|
but the frames are normalized under the held mapping until ✅ Save changes.
|
|
4072
4094
|
"""
|
|
4073
4095
|
has_words = not words_df.empty
|
|
@@ -4081,7 +4103,15 @@ def prepare_data(
|
|
|
4081
4103
|
word_proposed = _apply_declared_schema(
|
|
4082
4104
|
_proposed_schema("words", words_df), declared_word_schema
|
|
4083
4105
|
)
|
|
4084
|
-
if allow_override:
|
|
4106
|
+
if allow_override and not render_mapping:
|
|
4107
|
+
word_schema = column_mapping_from_state(
|
|
4108
|
+
words_df,
|
|
4109
|
+
"col_map_words",
|
|
4110
|
+
WORD_FIELD_SPECS,
|
|
4111
|
+
word_proposed,
|
|
4112
|
+
dataset=mapping_dataset,
|
|
4113
|
+
)
|
|
4114
|
+
elif allow_override:
|
|
4085
4115
|
word_schema = column_mapping_ui(
|
|
4086
4116
|
words_df,
|
|
4087
4117
|
table_label="Words (interest areas)",
|
|
@@ -4099,6 +4129,7 @@ def prepare_data(
|
|
|
4099
4129
|
stack_labels=True,
|
|
4100
4130
|
dataset=mapping_dataset,
|
|
4101
4131
|
)
|
|
4132
|
+
if allow_override:
|
|
4102
4133
|
pending["words"] = word_schema
|
|
4103
4134
|
if held_schemas and held_schemas.get("words") is not None:
|
|
4104
4135
|
word_schema = held_schemas["words"]
|
|
@@ -4112,7 +4143,15 @@ def prepare_data(
|
|
|
4112
4143
|
fix_proposed = _apply_declared_schema(
|
|
4113
4144
|
_proposed_schema("fixations", fixations_df), declared_fix_schema
|
|
4114
4145
|
)
|
|
4115
|
-
if allow_override:
|
|
4146
|
+
if allow_override and not render_mapping:
|
|
4147
|
+
fix_schema = column_mapping_from_state(
|
|
4148
|
+
fixations_df,
|
|
4149
|
+
"col_map_fix",
|
|
4150
|
+
FIX_FIELD_SPECS,
|
|
4151
|
+
fix_proposed,
|
|
4152
|
+
dataset=mapping_dataset,
|
|
4153
|
+
)
|
|
4154
|
+
elif allow_override:
|
|
4116
4155
|
fix_schema = column_mapping_ui(
|
|
4117
4156
|
fixations_df,
|
|
4118
4157
|
table_label="Fixations",
|
|
@@ -4126,6 +4165,7 @@ def prepare_data(
|
|
|
4126
4165
|
stack_labels=True,
|
|
4127
4166
|
dataset=mapping_dataset,
|
|
4128
4167
|
)
|
|
4168
|
+
if allow_override:
|
|
4129
4169
|
pending["fixations"] = fix_schema
|
|
4130
4170
|
if held_schemas and held_schemas.get("fixations") is not None:
|
|
4131
4171
|
fix_schema = held_schemas["fixations"]
|
|
@@ -6281,7 +6321,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6281
6321
|
|
|
6282
6322
|
Raised once by ``main`` on the run after ✅ Add dataset, once the new
|
|
6283
6323
|
dataset's frames are loaded, so ``message`` carries its counts. Closing it
|
|
6284
|
-
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
|
|
6285
6325
|
list of datasets does the same later. Buttons are handled by their return
|
|
6286
6326
|
value, never ``on_click`` — a dialog body is a fragment (see
|
|
6287
6327
|
``_leave_dataset_editor_dialog``).
|
|
@@ -6289,7 +6329,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6289
6329
|
st.markdown(message)
|
|
6290
6330
|
open_col, again_col = st.columns(2, gap="small")
|
|
6291
6331
|
if open_col.button(
|
|
6292
|
-
f"{ICONS['view_scanpath']} Open in Scanpath",
|
|
6332
|
+
f"{ICONS['view_scanpath']} Open in Scanpath view",
|
|
6293
6333
|
key="dataset_added_open",
|
|
6294
6334
|
type="primary",
|
|
6295
6335
|
width="stretch",
|
|
@@ -6309,7 +6349,7 @@ def _dataset_added_dialog(message: str) -> None:
|
|
|
6309
6349
|
st.rerun(scope="app")
|
|
6310
6350
|
st.caption(
|
|
6311
6351
|
f"Close this to stay on {ICONS['view_data']} Data Management. "
|
|
6312
|
-
"**Open in Scanpath**, under the list of datasets, opens it later."
|
|
6352
|
+
"**Open in Scanpath view**, under the list of datasets, opens it later."
|
|
6313
6353
|
)
|
|
6314
6354
|
|
|
6315
6355
|
|
|
@@ -9031,12 +9071,8 @@ def _render_backup_reminder(host, active_view: str) -> None:
|
|
|
9031
9071
|
return
|
|
9032
9072
|
box = host.container(key="sps_backup_reminder", border=True)
|
|
9033
9073
|
box.markdown(
|
|
9034
|
-
f"{ICONS['warning']} **This server saves nothing.**
|
|
9035
|
-
"
|
|
9036
|
-
"your annotations and designs. Keep the files you uploaded, and export your "
|
|
9037
|
-
f"annotations from {ICONS['view_data']} **Data Management → Annotations** and each "
|
|
9038
|
-
"dataset's mapping from "
|
|
9039
|
-
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. "
|
|
9040
9076
|
f"[How to keep copies of your work ↗]({BACKUP_GUIDE_URL})"
|
|
9041
9077
|
)
|
|
9042
9078
|
row = box.container(horizontal=True, gap="small")
|
|
@@ -9553,11 +9589,14 @@ def _run_app() -> None:
|
|
|
9553
9589
|
# The page is built on EVERY run and merely hidden when another view is
|
|
9554
9590
|
# active (`DATA_PAGE_OFFSCREEN_KEY` → `display: none` in styles.py). That is
|
|
9555
9591
|
# not laziness: the slots below are filled by the loaders' directory input,
|
|
9556
|
-
# ⬇ Download button
|
|
9557
|
-
#
|
|
9558
|
-
#
|
|
9559
|
-
#
|
|
9560
|
-
#
|
|
9592
|
+
# ⬇ Download button and source options, all of which *drive* the load on
|
|
9593
|
+
# every rerun — and Streamlit drops the key of a widget that did not
|
|
9594
|
+
# render. Rendering-then-hiding keeps every loader's every-run semantics
|
|
9595
|
+
# without splitting each into a render/resolve pair. The one exception is
|
|
9596
|
+
# the built-in sources' column-mapping panels, sixty-odd selectboxes and a
|
|
9597
|
+
# sixth of every rerun's bytes: they are split that way
|
|
9598
|
+
# (`controls.column_mapping_from_state`) and drawn only while ✏️ Edit
|
|
9599
|
+
# dataset shows them.
|
|
9561
9600
|
#
|
|
9562
9601
|
# The page has two *parts*, both built every run for the same reason the
|
|
9563
9602
|
# page itself is (above): the overview always shows, and the editor opens
|
|
@@ -9745,7 +9784,7 @@ def _run_app() -> None:
|
|
|
9745
9784
|
dataset_table_slot = setup_source_slot.container(key="tutorial_available_datasets")
|
|
9746
9785
|
# The two ways to make a dataset are one **+ Add
|
|
9747
9786
|
# dataset** menu (the Scanpath picker's + with its name spelled out), under
|
|
9748
|
-
# 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
|
|
9749
9788
|
# marks Current. Filled once `data_choice` is known.
|
|
9750
9789
|
add_dataset_slot = (
|
|
9751
9790
|
setup_source_slot.container(key="data_page_add", horizontal=True)
|
|
@@ -9800,7 +9839,7 @@ def _run_app() -> None:
|
|
|
9800
9839
|
# picked from the list: the view switch is a request, since this is
|
|
9801
9840
|
# an `on_click` callback (see `url_state._go_scanpath`).
|
|
9802
9841
|
add_dataset_slot.button(
|
|
9803
|
-
"Open in Scanpath",
|
|
9842
|
+
"Open in Scanpath view",
|
|
9804
9843
|
icon=ICONS["view_scanpath"],
|
|
9805
9844
|
key="data_open_in_scanpath",
|
|
9806
9845
|
on_click=_go_scanpath,
|
|
@@ -9949,8 +9988,9 @@ def _run_app() -> None:
|
|
|
9949
9988
|
else:
|
|
9950
9989
|
deep_link_pid = None
|
|
9951
9990
|
raw_gaze_df: pd.DataFrame | None = None
|
|
9952
|
-
#
|
|
9953
|
-
#
|
|
9991
|
+
# Is the open source one whose mapping the editable pre-normalization
|
|
9992
|
+
# panels decide, in the page's Column mapping section? (Mode A — see the
|
|
9993
|
+
# dispatch below. They are drawn only while ✏️ Edit dataset is open.)
|
|
9954
9994
|
mapping_editor_rendered = False
|
|
9955
9995
|
# Start each load with a clean column-mapping stash; each branch below
|
|
9956
9996
|
# records the schema it used for the Data page's mapping section.
|
|
@@ -10107,8 +10147,11 @@ def _run_app() -> None:
|
|
|
10107
10147
|
# default first-load source; pre-filled with auto-detection, so an
|
|
10108
10148
|
# untouched mapping normalizes identically.
|
|
10109
10149
|
allow_override=mapping_editor_rendered,
|
|
10110
|
-
# Mode A of the Data page's one Column mapping section
|
|
10150
|
+
# Mode A of the Data page's one Column mapping section, drawn only
|
|
10151
|
+
# while ✏️ Edit dataset shows it; every other run reads the same
|
|
10152
|
+
# picks from their keys.
|
|
10111
10153
|
mapping_host=mapping_body_slot,
|
|
10154
|
+
render_mapping=data_view and editing,
|
|
10112
10155
|
# A corpus that publishes its schema starts from it, not from
|
|
10113
10156
|
# auto-detection. The panels stay editable — this only changes what
|
|
10114
10157
|
# they start at.
|
|
@@ -10154,6 +10197,11 @@ def _run_app() -> None:
|
|
|
10154
10197
|
_render_dataset_load_failure(
|
|
10155
10198
|
_dataset_display_name(_dataset_owner), mapping_problems
|
|
10156
10199
|
)
|
|
10200
|
+
# The mapping panels on ✏️ Edit dataset are a draft until ✅ Save
|
|
10201
|
+
# changes, and the dataset keeps the mapping that cannot load — so the
|
|
10202
|
+
# editor needs its Save here too, or a field fixed there never applies.
|
|
10203
|
+
if data_view and editing and mapping_editor_rendered:
|
|
10204
|
+
_render_builtin_editor_footer(editor_footer_slot)
|
|
10157
10205
|
_render_offpage_setup_notice(data_view)
|
|
10158
10206
|
_finish_page()
|
|
10159
10207
|
_render_datasets_table(None, None, None)
|
|
@@ -10310,10 +10358,10 @@ def _run_app() -> None:
|
|
|
10310
10358
|
st.session_state[TRIAL_IDENTITY_CHECK_KEY] = asked_by
|
|
10311
10359
|
if added:
|
|
10312
10360
|
# Say what ✅ Add dataset brought in, and offer the next step: a
|
|
10313
|
-
# dialog with Open in Scanpath / Add another. The Trial ID verdict
|
|
10361
|
+
# dialog with Open in Scanpath view / Add another. The Trial ID verdict
|
|
10314
10362
|
# takes the run's one dialog when it has something to say, and any
|
|
10315
10363
|
# other dialog already open wins too; the counts then go to a toast,
|
|
10316
|
-
# and **Open in Scanpath** under the list is the way on.
|
|
10364
|
+
# and **Open in Scanpath view** under the list is the way on.
|
|
10317
10365
|
message = dataset_added_message(str(added), words_all, fixations_all)
|
|
10318
10366
|
offered = False
|
|
10319
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:
|