scanpath-studio 0.43.0__tar.gz → 0.44.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.
Files changed (161) hide show
  1. {scanpath_studio-0.43.0/scanpath_studio.egg-info → scanpath_studio-0.44.0}/PKG-INFO +1 -1
  2. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/__init__.py +1 -1
  3. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/api.py +10 -2
  4. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/app.py +70 -18
  5. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/column_names.py +25 -2
  6. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/computations.py +40 -5
  7. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/constants.py +12 -3
  8. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/controls.py +202 -77
  9. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/data.py +502 -91
  10. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/plots.py +55 -11
  11. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/styles.py +38 -4
  12. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/tabs.py +24 -3
  13. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/wizard.py +26 -0
  14. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0/scanpath_studio.egg-info}/PKG-INFO +1 -1
  15. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/LICENSE +0 -0
  16. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/MANIFEST.in +0 -0
  17. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/NOTICE +0 -0
  18. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/README.md +0 -0
  19. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/pyproject.toml +0 -0
  20. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/__main__.py +0 -0
  21. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/aggregation.py +0 -0
  22. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/alignment.py +0 -0
  23. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/analysis_recipe.py +0 -0
  24. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/animation_export.py +0 -0
  25. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/annotations.py +0 -0
  26. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/assets/scanpath_studio_title_logo.png +0 -0
  27. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/assets/scanpath_studio_title_logo_dark.png +0 -0
  28. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/authoring.py +0 -0
  29. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/authoring_component.py +0 -0
  30. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/build_info.py +0 -0
  31. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/citation.py +0 -0
  32. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/cite_button.py +0 -0
  33. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/cli.py +0 -0
  34. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/code_snippet.py +0 -0
  35. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/color_picker_commit.py +0 -0
  36. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/compare_source.py +0 -0
  37. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/crash_report.py +0 -0
  38. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/data_health.py +0 -0
  39. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/dataset_table.py +0 -0
  40. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/datasets.py +0 -0
  41. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/debug_log.py +0 -0
  42. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/desktop_update.py +0 -0
  43. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/easter_egg.py +0 -0
  44. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/experimental_setup.py +0 -0
  45. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/export.py +0 -0
  46. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/export_status.py +0 -0
  47. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/feedback.py +0 -0
  48. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/fields.py +0 -0
  49. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/file_citation.py +0 -0
  50. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/html_embed.py +0 -0
  51. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/illustration.py +0 -0
  52. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/loading.py +0 -0
  53. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/measures.py +0 -0
  54. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/menu.py +0 -0
  55. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/metadata.py +0 -0
  56. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/model_scanpaths.py +0 -0
  57. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/multipart.py +0 -0
  58. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/onestop_shard.py +0 -0
  59. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/persistence.py +0 -0
  60. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/plot_config.py +0 -0
  61. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/preprocessing.py +0 -0
  62. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/progress.py +0 -0
  63. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/fixations.csv +0 -0
  64. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/fixations.parquet +0 -0
  65. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/ia.csv +0 -0
  66. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/ia.parquet +0 -0
  67. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_a.png +0 -0
  68. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_b.png +0 -0
  69. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_c.png +0 -0
  70. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__answer_d.png +0 -0
  71. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__paragraph.png +0 -0
  72. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_1_Ele__question.png +0 -0
  73. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_a.png +0 -0
  74. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_b.png +0 -0
  75. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_c.png +0 -0
  76. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__answer_d.png +0 -0
  77. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__paragraph.png +0 -0
  78. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_2_Ele__question.png +0 -0
  79. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_a.png +0 -0
  80. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_b.png +0 -0
  81. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_c.png +0 -0
  82. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__answer_d.png +0 -0
  83. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__paragraph.png +0 -0
  84. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_3_Adv__question.png +0 -0
  85. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_a.png +0 -0
  86. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_b.png +0 -0
  87. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_c.png +0 -0
  88. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__answer_d.png +0 -0
  89. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__paragraph.png +0 -0
  90. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_4_Adv__question.png +0 -0
  91. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_a.png +0 -0
  92. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_b.png +0 -0
  93. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_c.png +0 -0
  94. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__answer_d.png +0 -0
  95. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__paragraph.png +0 -0
  96. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_5_Adv__question.png +0 -0
  97. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_a.png +0 -0
  98. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_b.png +0 -0
  99. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_c.png +0 -0
  100. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__answer_d.png +0 -0
  101. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__paragraph.png +0 -0
  102. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_1_6_Ele__question.png +0 -0
  103. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_a.png +0 -0
  104. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_b.png +0 -0
  105. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_c.png +0 -0
  106. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__answer_d.png +0 -0
  107. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__paragraph.png +0 -0
  108. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_1_Adv__question.png +0 -0
  109. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_a.png +0 -0
  110. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_b.png +0 -0
  111. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_c.png +0 -0
  112. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__answer_d.png +0 -0
  113. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__paragraph.png +0 -0
  114. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_2_Adv__question.png +0 -0
  115. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_a.png +0 -0
  116. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_b.png +0 -0
  117. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_c.png +0 -0
  118. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__answer_d.png +0 -0
  119. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__paragraph.png +0 -0
  120. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_3_Adv__question.png +0 -0
  121. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_a.png +0 -0
  122. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_b.png +0 -0
  123. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_c.png +0 -0
  124. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__answer_d.png +0 -0
  125. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__paragraph.png +0 -0
  126. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_4_Ele__question.png +0 -0
  127. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_a.png +0 -0
  128. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_b.png +0 -0
  129. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_c.png +0 -0
  130. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__answer_d.png +0 -0
  131. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__paragraph.png +0 -0
  132. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_5_Ele__question.png +0 -0
  133. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_a.png +0 -0
  134. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_b.png +0 -0
  135. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_c.png +0 -0
  136. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__answer_d.png +0 -0
  137. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__paragraph.png +0 -0
  138. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/2_2_6_Ele__question.png +0 -0
  139. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/images/manifest.csv +0 -0
  140. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/raw_gaze.csv +0 -0
  141. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/sample_data/raw_gaze.parquet +0 -0
  142. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/save_dialog.py +0 -0
  143. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/session_keys.py +0 -0
  144. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/similarity.py +0 -0
  145. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/streamlit_entry.py +0 -0
  146. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/synthetic.py +0 -0
  147. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/tour.py +0 -0
  148. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/truncation_tooltip.py +0 -0
  149. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/ui_mode.py +0 -0
  150. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/update_sample_data.py +0 -0
  151. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/updates.py +0 -0
  152. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/url_state.py +0 -0
  153. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/utils.py +0 -0
  154. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/wizard_shell.py +0 -0
  155. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio/wizard_watch.py +0 -0
  156. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio.egg-info/SOURCES.txt +0 -0
  157. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio.egg-info/dependency_links.txt +0 -0
  158. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio.egg-info/entry_points.txt +0 -0
  159. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio.egg-info/requires.txt +0 -0
  160. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/scanpath_studio.egg-info/top_level.txt +0 -0
  161. {scanpath_studio-0.43.0 → scanpath_studio-0.44.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scanpath-studio
3
- Version: 0.43.0
3
+ Version: 0.44.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>
@@ -42,7 +42,7 @@ __all__ = [
42
42
  # against. `__version__` is the exact build, worked out from it lazily
43
43
  # (build_info.py) — "0.35.0" at the release itself,
44
44
  # "0.35.0.post3+g8f18219" three commits after it.
45
- __release__ = "0.43.0"
45
+ __release__ = "0.44.0"
46
46
 
47
47
  # Public headless API (see api.py / datasets.py). Resolved lazily so
48
48
  # `import scanpath_studio` stays cheap and doesn't pull in pandas/plotly/
@@ -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 map no x/y and their word ID column
891
- holds no numbers, so none could be placed. A ``ValueError``.
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:
@@ -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.markdown(get_app_css(), unsafe_allow_html=True)
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 draws them.
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, render the mapping panels that let the
4047
- user pick the exact column names for each field (pre-filled with auto-detection).
4048
- Otherwise just auto-detect.
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 still render and
4070
- their picks are published as the draft (``BUILTIN_MAPPING_PENDING_KEY``),
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"]
@@ -9553,11 +9593,14 @@ def _run_app() -> None:
9553
9593
  # The page is built on EVERY run and merely hidden when another view is
9554
9594
  # active (`DATA_PAGE_OFFSCREEN_KEY` → `display: none` in styles.py). That is
9555
9595
  # not laziness: the slots below are filled by the loaders' directory input,
9556
- # ⬇ Download button, source options and column-mapping selectboxes, all of
9557
- # which *drive* `prepare_data` on every rerun — and Streamlit drops the key
9558
- # of a widget that did not render. Rendering-then-hiding keeps every
9559
- # loader's every-run semantics without splitting each into a render/resolve
9560
- # pair.
9596
+ # ⬇ Download button and source options, all of which *drive* the load on
9597
+ # every rerun — and Streamlit drops the key of a widget that did not
9598
+ # render. Rendering-then-hiding keeps every loader's every-run semantics
9599
+ # without splitting each into a render/resolve pair. The one exception is
9600
+ # the built-in sources' column-mapping panels, sixty-odd selectboxes and a
9601
+ # sixth of every rerun's bytes: they are split that way
9602
+ # (`controls.column_mapping_from_state`) and drawn only while ✏️ Edit
9603
+ # dataset shows them.
9561
9604
  #
9562
9605
  # The page has two *parts*, both built every run for the same reason the
9563
9606
  # page itself is (above): the overview always shows, and the editor opens
@@ -9949,8 +9992,9 @@ def _run_app() -> None:
9949
9992
  else:
9950
9993
  deep_link_pid = None
9951
9994
  raw_gaze_df: pd.DataFrame | None = None
9952
- # Did this load already draw the editable pre-normalization mapping panels
9953
- # into the page's Column mapping section? (Mode A — see the dispatch below.)
9995
+ # Is the open source one whose mapping the editable pre-normalization
9996
+ # panels decide, in the page's Column mapping section? (Mode A — see the
9997
+ # dispatch below. They are drawn only while ✏️ Edit dataset is open.)
9954
9998
  mapping_editor_rendered = False
9955
9999
  # Start each load with a clean column-mapping stash; each branch below
9956
10000
  # records the schema it used for the Data page's mapping section.
@@ -10107,8 +10151,11 @@ def _run_app() -> None:
10107
10151
  # default first-load source; pre-filled with auto-detection, so an
10108
10152
  # untouched mapping normalizes identically.
10109
10153
  allow_override=mapping_editor_rendered,
10110
- # Mode A of the Data page's one Column mapping section.
10154
+ # Mode A of the Data page's one Column mapping section, drawn only
10155
+ # while ✏️ Edit dataset shows it; every other run reads the same
10156
+ # picks from their keys.
10111
10157
  mapping_host=mapping_body_slot,
10158
+ render_mapping=data_view and editing,
10112
10159
  # A corpus that publishes its schema starts from it, not from
10113
10160
  # auto-detection. The panels stay editable — this only changes what
10114
10161
  # they start at.
@@ -10154,6 +10201,11 @@ def _run_app() -> None:
10154
10201
  _render_dataset_load_failure(
10155
10202
  _dataset_display_name(_dataset_owner), mapping_problems
10156
10203
  )
10204
+ # The mapping panels on ✏️ Edit dataset are a draft until ✅ Save
10205
+ # changes, and the dataset keeps the mapping that cannot load — so the
10206
+ # editor needs its Save here too, or a field fixed there never applies.
10207
+ if data_view and editing and mapping_editor_rendered:
10208
+ _render_builtin_editor_footer(editor_footer_slot)
10157
10209
  _render_offpage_setup_notice(data_view)
10158
10210
  _finish_page()
10159
10211
  _render_datasets_table(None, None, None)
@@ -19,7 +19,7 @@ import json
19
19
  from collections.abc import Iterable, Mapping
20
20
  from dataclasses import dataclass, field
21
21
 
22
- from .constants import SAMPLE_INDEX
22
+ from .constants import SAMPLE_INDEX, WORD_KEY
23
23
 
24
24
  #: How a canonical column came to be.
25
25
  MAPPED = "mapped" # read from one source column, unchanged
@@ -104,6 +104,7 @@ _CANONICAL_LABELS: dict[str, str] = {
104
104
  "line_idx": "Line",
105
105
  "text": "Word",
106
106
  "word_id": "Word #",
107
+ WORD_KEY: "Word ID",
107
108
  "x": "X",
108
109
  "y": "Y",
109
110
  "saccade_amplitude": "Saccade amplitude (px)",
@@ -147,6 +148,7 @@ ROLE_LABELS: dict[str, str] = {
147
148
  "unique_text_id": "Text",
148
149
  "screen_id": "Screen",
149
150
  "word_id": "Word #",
151
+ WORD_KEY: "Word ID",
150
152
  "text": "Word",
151
153
  "line_idx": "Line",
152
154
  "x": "X",
@@ -205,10 +207,23 @@ IDENTITY_COLUMNS = frozenset(
205
207
  "unique_text_id",
206
208
  "screen_id",
207
209
  "word_id",
210
+ WORD_KEY,
208
211
  }
209
212
  )
210
213
 
211
214
 
215
+ def _text_word_id_entries(entry: SourceName | None, how: str) -> dict:
216
+ """The entries for a Word ID read as text (:meth:`ColumnNames.with_rewrites`),
217
+ from the ``word_id`` entry the schema implied; none unless that names the
218
+ file's column."""
219
+ if entry is None or entry.kind != MAPPED or len(entry.sources) != 1:
220
+ return {}
221
+ return {
222
+ WORD_KEY: SourceName(entry.sources, MAPPED),
223
+ "word_id": SourceName((), GENERATED, entry.display + how),
224
+ }
225
+
226
+
212
227
  def canonical_label(column) -> str:
213
228
  """A readable label for a column the app made (a measure, a run, an angle …)."""
214
229
  from .data import READING_MEASURE_FIELDS
@@ -446,9 +461,17 @@ class ColumnNames:
446
461
  ``table``) marked converted: a padded id, a shifted word id, positions
447
462
  filled from word boxes are no longer what the file held under its name,
448
463
  so the column is labelled "<source><how>" and exported under its
449
- internal name."""
464
+ internal name.
465
+
466
+ A rewrite of :data:`~scanpath_studio.constants.WORD_KEY` is a Word ID
467
+ read as text: the text is the file's column, so ``word_key`` takes
468
+ the Word ID's source name, and ``word_id`` — the number the app gave
469
+ each text id — becomes generated, "<source><how>" its note."""
450
470
  changed = dict(self.entries)
451
471
  for rewritten_table, column, how in rewrites or ():
472
+ if rewritten_table == table and column == WORD_KEY:
473
+ changed.update(_text_word_id_entries(changed.get("word_id"), how))
474
+ continue
452
475
  entry = changed.get(column)
453
476
  if rewritten_table != table or entry is None or entry.kind != MAPPED:
454
477
  continue
@@ -267,15 +267,50 @@ REGISTER: tuple[Computation, ...] = (
267
267
  unit="px",
268
268
  precedence="Only when x/y are absent; recorded coordinates always win.",
269
269
  missing=(
270
- "No matching word box, or no word id (blank, or not a number) ⇒ the "
271
- "fixation keeps no coordinates — a missing id never matches a box "
272
- "with none. No x/y mapped and a Word ID column with no numbers "
273
- "at all ⇒ UnplacedFixationsError, never an empty figure."
270
+ "No matching word box, or no word id (blank, or a text id that names "
271
+ "no word) ⇒ the fixation keeps no coordinates — a missing id never "
272
+ "matches a box with none. No x/y, text Word IDs involved "
273
+ "(`norm.text_word_ids`) and not one fixation placed ⇒ "
274
+ "UnplacedFixationsError, never an empty figure."
274
275
  ),
275
276
  tiers="A, C",
276
277
  status=STATUS_VERIFIED,
277
278
  consumers=(_UI, _API, _CLI),
278
- tests=("tests/test_data.py",),
279
+ tests=("tests/test_data.py", "tests/test_text_word_ids.py"),
280
+ ),
281
+ Computation(
282
+ id="norm.text_word_ids",
283
+ name="Text word IDs",
284
+ category=CATEGORY_NORMALIZATION,
285
+ summary="Read a Word ID column of text (w1, Hello_3) as keys, and number them.",
286
+ formula=(
287
+ "A Word ID column with filled cells and not one number is text: "
288
+ "each cell, stripped, is kept in `word_key`. Words: `word_id` = 1, "
289
+ "2, … per text (`text_id`, else the trial; and screen), one number "
290
+ "for every reader: the keys of the reading that lists the most of "
291
+ "them in its row order, then any other reading's keys in the order "
292
+ "the table first lists them. Fixations: `word_id` = the `word_id` "
293
+ "of the word whose key equals the fixation's exactly, in the same "
294
+ "trial."
295
+ ),
296
+ code="scanpath_studio/data.py:match_text_word_ids",
297
+ output="word_id, word_key",
298
+ grouping="Words: (text_id[, screen_id]); fixations: (participant_id, trial_id[, screen_id])",
299
+ precedence=(
300
+ "Only for a column with no number in it: a column with any number is "
301
+ "read as numbers, its other cells as blanks."
302
+ ),
303
+ missing=(
304
+ "A blank cell or `.` is no key, so no number. A fixation key its "
305
+ "trial does not list ⇒ no word, warned with the count. A text id "
306
+ "never matches a number: numeric fixation ids against text word ids "
307
+ "are set aside (warned), and with x/y the word is then assigned by "
308
+ "geometry. A key a trial lists twice is one word."
309
+ ),
310
+ tiers="A, C",
311
+ status=STATUS_VERIFIED,
312
+ consumers=(_UI, _API, _CLI, _EXPORT),
313
+ tests=("tests/test_text_word_ids.py",),
279
314
  ),
280
315
  Computation(
281
316
  id="norm.participant_metadata",
@@ -13,6 +13,14 @@ PACKAGE_NAME = "scanpath_studio"
13
13
  #: number, so the plot can colour by order without calling it time.
14
14
  SAMPLE_INDEX = "sample_index"
15
15
 
16
+ #: A Word ID the data spells as text (``w1``, ``Hello_3``) rather than as a
17
+ #: number, as the file spells it. The numeric ``word_id`` every consumer joins
18
+ #: on is then the word's position in its text, one number for every reader
19
+ #: (`data.normalize_words`), and a fixation's is looked up through this key
20
+ #: (`data.match_text_word_ids`). A
21
+ #: column the user sees and exports, under the Word ID column's own name.
22
+ WORD_KEY = "word_key"
23
+
16
24
 
17
25
  # --- Experimental features ----------------------------------------------------
18
26
  # Features that are built but not yet validated or fully integrated are off
@@ -849,7 +857,8 @@ RAW_GAZE_LINK_FOR_KEY = "_raw_gaze_link_for"
849
857
  #: metadata tables, preprocessing). Both are built every run and switched by key,
850
858
  #: for exactly the reason the page itself is (see below): the editor is nothing
851
859
  #: but widgets that drive `prepare_data`, and Streamlit drops the key of a widget
852
- #: that did not render.
860
+ #: that did not render. The built-in sources' column-mapping panels are the
861
+ #: exception, drawn only while it is open (`controls.column_mapping_from_state`).
853
862
  DATASET_EDITOR_OPEN_KEY = "_dataset_editor_open"
854
863
  DATA_OVERVIEW_KEY = "data_overview"
855
864
  DATA_EDITOR_KEY = "data_dataset_editor"
@@ -859,8 +868,8 @@ DATA_EDITOR_OFFSCREEN_KEY = "data_dataset_editor_offscreen"
859
868
  # that view is active, off-screen otherwise.
860
869
  #
861
870
  # The setup widgets (the loaders' directory input and ⬇ Download button, the
862
- # source options, the column-mapping selectboxes) *drive* `prepare_data` on
863
- # every rerun, and Streamlit drops the key of a widget that did not render. A
871
+ # source options) *drive* the load on every rerun, and Streamlit drops the key
872
+ # of a widget that did not render. A
864
873
  # page body would only execute while its view is selected, so the page is
865
874
  # rendered every run either way and simply hidden — `styles.py` gives the
866
875
  # off-screen key `display: none` — rather than splitting every loader into a