psychopy 2025.1.0__py3-none-any.whl → 2025.2.1__py3-none-any.whl

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.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (226) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/colors.py +10 -5
  113. psychopy/data/experiment.py +133 -23
  114. psychopy/data/routine.py +12 -0
  115. psychopy/data/staircase.py +42 -20
  116. psychopy/data/trial.py +20 -12
  117. psychopy/data/utils.py +43 -3
  118. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  120. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  121. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  122. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  123. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  124. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  125. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  126. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  127. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  128. psychopy/event.py +20 -15
  129. psychopy/experiment/_experiment.py +86 -10
  130. psychopy/experiment/components/__init__.py +3 -10
  131. psychopy/experiment/components/_base.py +9 -20
  132. psychopy/experiment/components/button/__init__.py +1 -1
  133. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  134. psychopy/experiment/components/camera/__init__.py +137 -359
  135. psychopy/experiment/components/keyboard/__init__.py +17 -24
  136. psychopy/experiment/components/microphone/__init__.py +61 -110
  137. psychopy/experiment/components/movie/__init__.py +2 -3
  138. psychopy/experiment/components/serialOut/__init__.py +192 -93
  139. psychopy/experiment/components/settings/__init__.py +45 -27
  140. psychopy/experiment/components/sound/__init__.py +82 -73
  141. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  142. psychopy/experiment/devices.py +303 -0
  143. psychopy/experiment/exports.py +20 -18
  144. psychopy/experiment/flow.py +7 -0
  145. psychopy/experiment/loops.py +47 -29
  146. psychopy/experiment/monitor.py +74 -0
  147. psychopy/experiment/params.py +48 -10
  148. psychopy/experiment/plugins.py +28 -108
  149. psychopy/experiment/py2js_transpiler.py +1 -1
  150. psychopy/experiment/routines/__init__.py +1 -1
  151. psychopy/experiment/routines/_base.py +59 -24
  152. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  153. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  154. psychopy/hardware/__init__.py +20 -57
  155. psychopy/hardware/button.py +15 -2
  156. psychopy/hardware/camera/__init__.py +2237 -1394
  157. psychopy/hardware/joystick/__init__.py +1 -1
  158. psychopy/hardware/keyboard.py +5 -8
  159. psychopy/hardware/listener.py +4 -1
  160. psychopy/hardware/manager.py +75 -35
  161. psychopy/hardware/microphone.py +53 -7
  162. psychopy/hardware/monitor.py +144 -0
  163. psychopy/hardware/photometer/__init__.py +156 -117
  164. psychopy/hardware/serialdevice.py +16 -2
  165. psychopy/hardware/soundsensor.py +4 -1
  166. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  167. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  168. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  169. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  170. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  171. psychopy/iohub/util/__init__.py +7 -8
  172. psychopy/localization/generateTranslationTemplate.py +208 -116
  173. psychopy/localization/messages.pot +4305 -3502
  174. psychopy/monitors/MonitorCenter.py +174 -74
  175. psychopy/plugins/__init__.py +6 -4
  176. psychopy/preferences/devices.py +80 -0
  177. psychopy/preferences/generateHints.py +2 -1
  178. psychopy/preferences/preferences.py +35 -11
  179. psychopy/scripts/psychopy-pkgutil.py +969 -0
  180. psychopy/scripts/psyexpCompile.py +1 -1
  181. psychopy/session.py +34 -38
  182. psychopy/sound/__init__.py +6 -260
  183. psychopy/sound/audioclip.py +164 -0
  184. psychopy/sound/backend_ptb.py +8 -0
  185. psychopy/sound/backend_pygame.py +10 -0
  186. psychopy/sound/backend_pysound.py +9 -0
  187. psychopy/sound/backends/__init__.py +0 -0
  188. psychopy/sound/microphone.py +3 -0
  189. psychopy/sound/sound.py +58 -0
  190. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  191. psychopy/tests/data/duplicateHeaders.csv +2 -0
  192. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  193. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  194. psychopy/tests/test_data/test_utils.py +5 -1
  195. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  196. psychopy/tests/test_hardware/test_ports.py +0 -12
  197. psychopy/tests/test_tools/test_stringtools.py +1 -1
  198. psychopy/tools/attributetools.py +12 -5
  199. psychopy/tools/fontmanager.py +17 -14
  200. psychopy/tools/gltools.py +4 -2
  201. psychopy/tools/movietools.py +43 -2
  202. psychopy/tools/stringtools.py +33 -8
  203. psychopy/tools/versionchooser.py +1 -1
  204. psychopy/validation/audio.py +5 -1
  205. psychopy/validation/visual.py +5 -1
  206. psychopy/visual/basevisual.py +8 -7
  207. psychopy/visual/circle.py +2 -2
  208. psychopy/visual/helpers.py +3 -1
  209. psychopy/visual/image.py +29 -109
  210. psychopy/visual/movies/__init__.py +1800 -313
  211. psychopy/visual/polygon.py +4 -0
  212. psychopy/visual/shape.py +2 -2
  213. psychopy/visual/window.py +35 -12
  214. psychopy/voicekey/__init__.py +41 -669
  215. psychopy/voicekey/labjack_vks.py +7 -48
  216. psychopy/voicekey/parallel_vks.py +7 -42
  217. psychopy/voicekey/vk_tools.py +114 -263
  218. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
  219. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
  220. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  221. psychopy/visual/movies/players/__init__.py +0 -62
  222. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  223. psychopy/voicekey/demo_vks.py +0 -12
  224. psychopy/voicekey/signal.py +0 -42
  225. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  226. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
1
  <?xml version="1.0" ?>
2
- <PsychoPy2experiment encoding="utf-8" version="2025.1.0">
2
+ <PsychoPy2experiment encoding="utf-8" version="2025.2.0">
3
3
  <Settings>
4
- <Param val="use prefs" valType="str" updates="None" name="Audio latency priority"/>
5
4
  <Param val="use prefs" valType="str" updates="None" name="Audio lib"/>
6
5
  <Param val="" valType="str" updates="None" name="Completed URL"/>
7
6
  <Param val="auto" valType="str" updates="None" name="Data file delimiter"/>
@@ -168,17 +167,12 @@
168
167
  <Param val="[[0, 0.5],[0, -0.5],[1, -0.5],[1, 0.5]]" valType="list" updates="constant" name="vertices"/>
169
168
  </PolygonComponent>
170
169
  <MicrophoneComponent name="mic" plugin="None">
171
- <Param val="auto" valType="str" updates="None" name="channels"/>
172
- <Param val="default" valType="str" updates="None" name="device"/>
173
- <Param val="" valType="str" updates="None" name="deviceLabel"/>
170
+ <Param val="demoMicrophone" valType="device" updates="None" name="deviceLabel"/>
174
171
  <Param val="False" valType="bool" updates="None" name="disabled"/>
175
172
  <Param val="" valType="code" updates="None" name="durationEstim"/>
176
- <Param val="False" valType="code" updates="None" name="exclusive"/>
177
- <Param val="24000" valType="num" updates="None" name="maxSize"/>
178
173
  <Param val="mic" valType="code" updates="None" name="name"/>
179
174
  <Param val="default" valType="code" updates="None" name="outputType"/>
180
175
  <Param val="warn" valType="str" updates="set every repeat" name="policyWhenFull"/>
181
- <Param val="DVD Audio (48kHz)" valType="num" updates="None" name="sampleRate"/>
182
176
  <Param val="True" valType="bool" updates="None" name="saveStartStop"/>
183
177
  <Param val="True" valType="bool" updates="None" name="speakTimes"/>
184
178
  <Param val="" valType="code" updates="None" name="startEstim"/>
@@ -258,7 +252,6 @@
258
252
  <KeyboardComponent name="key_resp" plugin="None">
259
253
  <Param val="'space'" valType="list" updates="constant" name="allowedKeys"/>
260
254
  <Param val="" valType="str" updates="constant" name="correctAns"/>
261
- <Param val="" valType="str" updates="None" name="deviceLabel"/>
262
255
  <Param val="False" valType="bool" updates="None" name="disabled"/>
263
256
  <Param val="True" valType="bool" updates="constant" name="discard previous"/>
264
257
  <Param val="" valType="code" updates="None" name="durationEstim"/>
@@ -336,17 +329,12 @@
336
329
  <Param val="" valType="code" updates="None" name="validator"/>
337
330
  </TextboxComponent>
338
331
  <MicrophoneComponent name="mic_2" plugin="None">
339
- <Param val="auto" valType="str" updates="None" name="channels"/>
340
- <Param val="None" valType="str" updates="None" name="device"/>
341
- <Param val="" valType="str" updates="None" name="deviceLabel"/>
332
+ <Param val="demoMicrophone" valType="device" updates="None" name="deviceLabel"/>
342
333
  <Param val="False" valType="bool" updates="None" name="disabled"/>
343
334
  <Param val="" valType="code" updates="None" name="durationEstim"/>
344
- <Param val="False" valType="code" updates="None" name="exclusive"/>
345
- <Param val="24000" valType="num" updates="None" name="maxSize"/>
346
335
  <Param val="mic_2" valType="code" updates="None" name="name"/>
347
336
  <Param val="default" valType="code" updates="None" name="outputType"/>
348
337
  <Param val="warn" valType="str" updates="set every repeat" name="policyWhenFull"/>
349
- <Param val="DVD Audio (48kHz)" valType="num" updates="None" name="sampleRate"/>
350
338
  <Param val="True" valType="bool" updates="None" name="saveStartStop"/>
351
339
  <Param val="True" valType="bool" updates="None" name="speakTimes"/>
352
340
  <Param val="" valType="code" updates="None" name="startEstim"/>
@@ -456,7 +444,6 @@
456
444
  <KeyboardComponent name="key_resp_2" plugin="None">
457
445
  <Param val="'space'" valType="list" updates="constant" name="allowedKeys"/>
458
446
  <Param val="" valType="str" updates="constant" name="correctAns"/>
459
- <Param val="" valType="str" updates="None" name="deviceLabel"/>
460
447
  <Param val="False" valType="bool" updates="None" name="disabled"/>
461
448
  <Param val="True" valType="bool" updates="constant" name="discard previous"/>
462
449
  <Param val="" valType="code" updates="None" name="durationEstim"/>
@@ -0,0 +1,133 @@
1
+ """
2
+ This script reads an HDF5 eye-tracking data file, identifies trials based on specified
3
+ start and end markers in the experiment messages, extracts gaze data for each trial,
4
+ and animates gaze movement over time in a 2D space.
5
+
6
+ The user can customize:
7
+ - The path to the HDF5 file
8
+ - The marker text used to indicate the start and end of trials
9
+
10
+ ⚠️ If you get an error saying a package (like h5py, numpy, matplotlib) is missing:
11
+ In PsychoPy, go to:
12
+ Tools > Plugins/Packages Manager > Packages
13
+ Then search for and install the missing package (e.g., "h5py", "matplotlib", "numpy").
14
+ """
15
+
16
+ """
17
+ This script reads an HDF5 eye-tracking data file, identifies trials based on specified
18
+ start and end markers in the experiment messages, extracts gaze data for each trial,
19
+ and animates gaze movement over time in a 2D space.
20
+
21
+ The user can customize:
22
+ - The path to the HDF5 file
23
+ - The marker text used to indicate the start and end of trials
24
+ """
25
+
26
+ # ==== USER INPUTS ====
27
+ HDF5_FILE = '823238_eyetracking_youtube_2025-05-01_10h34.25.036.hdf5' # Path to HDF5 file
28
+ TRIAL_START_MARKER = 'BEGIN_SEQUENCE 3'#'trial_start' # Marker text for start of trial
29
+ TRIAL_END_MARKER = 'DONE_SEQUENCE 3'#'trial_end' # Marker text for end of trial
30
+ # ======================
31
+
32
+ import h5py
33
+ import numpy as np
34
+ import matplotlib.pyplot as plt
35
+ import matplotlib.animation as animation
36
+
37
+ def extract_eye_data_in_trials(hdf5_path, start_marker, end_marker):
38
+ """
39
+ Extract gaze data for each trial based on custom trial start/end markers.
40
+
41
+ Parameters:
42
+ hdf5_path (str): Path to HDF5 file.
43
+ start_marker (str): String identifying trial start in the message events.
44
+ end_marker (str): String identifying trial end in the message events.
45
+
46
+ Returns:
47
+ List of dicts: Each dict contains time, gaze_x, and gaze_y arrays for one trial.
48
+ """
49
+ with h5py.File(hdf5_path, 'r') as f:
50
+ # Dataset paths
51
+ msg_path = 'data_collection/events/experiment/MessageEvent'
52
+ eye_path = 'data_collection/events/eyetracker/MonocularEyeSampleEvent'
53
+
54
+ # Load message times and text
55
+ messages = f[msg_path]
56
+ msg_times = messages['time'][:]
57
+ msg_texts = messages['text'][:].astype(str)
58
+
59
+ # Find trial start/end times using the provided marker strings
60
+ trial_starts = msg_times[np.char.find(msg_texts, start_marker) != -1]
61
+ trial_ends = msg_times[np.char.find(msg_texts, end_marker) != -1]
62
+
63
+ if len(trial_starts) != len(trial_ends):
64
+ raise ValueError("Mismatched number of trial start and end events")
65
+
66
+ # Load eye tracking data
67
+ eye_data = f[eye_path]
68
+ eye_times = eye_data['time'][:]
69
+ gaze_x = eye_data['gaze_x'][:]
70
+ gaze_y = eye_data['gaze_y'][:]
71
+
72
+ # Extract data between trial start and end times
73
+ trials_gaze = []
74
+ for start, end in zip(trial_starts, trial_ends):
75
+ mask = (eye_times >= start) & (eye_times <= end)
76
+ trial_gaze = {
77
+ 'start_time': start,
78
+ 'end_time': end,
79
+ 'time': eye_times[mask],
80
+ 'gaze_x': gaze_x[mask],
81
+ 'gaze_y': gaze_y[mask]
82
+ }
83
+ trials_gaze.append(trial_gaze)
84
+
85
+ return trials_gaze
86
+
87
+ def animate_gaze(trial_data, trial_num, save_to_file=False):
88
+ """
89
+ Animate gaze positions over time for a single trial.
90
+
91
+ Parameters:
92
+ trial_data (dict): Dictionary with 'time', 'gaze_x', and 'gaze_y' keys.
93
+ trial_num (int): Index of the trial (for labeling).
94
+ save_to_file (bool): Whether to save the animation as an .mp4 video file.
95
+ """
96
+ x = trial_data['gaze_x']
97
+ y = trial_data['gaze_y']
98
+ t = trial_data['time']
99
+
100
+ fig, ax = plt.subplots(figsize=(8, 6))
101
+ ax.set_xlim(-1, 1)
102
+ ax.set_ylim(-0.5, 0.5)
103
+ ax.set_title(f"Gaze Animation - Trial {trial_num}")
104
+ ax.set_xlabel("Gaze X")
105
+ ax.set_ylabel("Gaze Y")
106
+
107
+ point, = ax.plot([], [], 'ro', markersize=5) # Red dot to show gaze
108
+
109
+ def init():
110
+ point.set_data([], [])
111
+ return point,
112
+
113
+ def update(frame):
114
+ point.set_data(x[frame], y[frame])
115
+ return point,
116
+
117
+ ani = animation.FuncAnimation(
118
+ fig, update, frames=len(x),
119
+ init_func=init, blit=True, interval=10
120
+ )
121
+
122
+ if save_to_file:
123
+ ani.save(f'gaze_trial_{trial_num}.mp4', fps=60, extra_args=['-vcodec', 'libx264'])
124
+
125
+ plt.show()
126
+
127
+ # ====== MAIN EXECUTION ======
128
+ if __name__ == '__main__':
129
+ result = extract_eye_data_in_trials(HDF5_FILE, TRIAL_START_MARKER, TRIAL_END_MARKER)
130
+ if not result:
131
+ print("No valid trials found.")
132
+ else:
133
+ animate_gaze(result[0], trial_num=1, save_to_file=False)
psychopy/event.py CHANGED
@@ -297,7 +297,7 @@ def _onPygletMouseRelease(x, y, button, modifiers, emulated=False):
297
297
 
298
298
  def _onPygletMouseWheel(x, y, scroll_x, scroll_y):
299
299
  global mouseWheelRel
300
- mouseWheelRel = mouseWheelRel + numpy.array([scroll_x, scroll_y])
300
+ mouseWheelRel += numpy.array([scroll_x, scroll_y])
301
301
  msg = "Mouse: wheel shift=(%i,%i), pos=(%i,%i)"
302
302
  logging.data(msg % (scroll_x, scroll_y, x, y))
303
303
 
@@ -874,23 +874,28 @@ class Mouse:
874
874
 
875
875
  """
876
876
  global mouseButtons, mouseTimes
877
- if usePygame:
878
- return mouse.get_pressed()
879
- else:
877
+
878
+ if self.win is None: # no backend specified
879
+ return None
880
+
881
+ if havePyglet and self.win.winType == 'pyglet':
880
882
  # for each (pyglet) window, dispatch its events before checking
881
883
  # event buffer
882
- if havePyglet:
883
- for win in pyglet.app.windows:
884
- win.dispatch_events() # pump events on pyglet windows
885
-
886
- if haveGLFW:
887
- glfw.poll_events()
884
+ for win in pyglet.app.windows:
885
+ win.dispatch_events() # pump events on pyglet windows
886
+ elif haveGLFW and self.win.winType == 'glfw':
887
+ glfw.poll_events()
888
+ elif havePygame and self.win.winType == 'pygame':
889
+ return mouse.get_pressed()
890
+ else:
891
+ raise RuntimeError(
892
+ "Mouse.getPressed() is only supported for the pyglet, "
893
+ "pygame and glfw backends.")
888
894
 
889
- # else:
890
- if not getTime:
891
- return copy.copy(mouseButtons)
892
- else:
893
- return copy.copy(mouseButtons), copy.copy(mouseTimes)
895
+ if not getTime:
896
+ return copy.copy(mouseButtons)
897
+ else:
898
+ return copy.copy(mouseButtons), copy.copy(mouseTimes)
894
899
 
895
900
  def isPressedIn(self, shape, buttons=(0, 1, 2)):
896
901
  """Returns `True` if the mouse is currently inside the shape and
@@ -117,6 +117,7 @@ class Experiment:
117
117
  Routine. The Flow controls how Routines are organised
118
118
  e.g. the nature of repeats and branching of an experiment.
119
119
  """
120
+
120
121
 
121
122
  def __init__(self, prefs=None):
122
123
  super(Experiment, self).__init__()
@@ -165,6 +166,11 @@ class Experiment:
165
166
  self._expHandler = TrialHandler(exp=self, name='thisExp')
166
167
  self._expHandler.type = 'ExperimentHandler' # true at run-time
167
168
 
169
+ # get a local reference of all Components and Routines (refreshed on loading a new file)
170
+ self.allCompons = getAllComponents(
171
+ self.prefsBuilder['componentsFolders'], fetchIcons=False)
172
+ self.allRoutines = getAllStandaloneRoutines(fetchIcons=False)
173
+
168
174
  def __eq__(self, other):
169
175
  if isinstance(other, Experiment):
170
176
  # if another experiment, compare filenames
@@ -577,6 +583,14 @@ class Experiment:
577
583
  name = paramNode.get('name')
578
584
  valType = paramNode.get('valType')
579
585
  val = paramNode.get('val')
586
+ #
587
+ # get knowwn legacy params for the current Component
588
+ componentLegacyParams = []
589
+ if componentNode is not None:
590
+ if componentNode.tag in self.allCompons:
591
+ componentLegacyParams = self.allCompons[componentNode.tag].legacyParams
592
+ if componentNode.tag in self.allRoutines:
593
+ componentLegacyParams = self.allRoutines[componentNode.tag].legacyParams
580
594
  # many components need web char newline replacement
581
595
  if not name == 'advancedParams':
582
596
  val = val.replace("&#10;", "\n")
@@ -727,6 +741,10 @@ class Experiment:
727
741
  else:
728
742
  if name in params:
729
743
  params[name].val = val
744
+ elif name in legacyParams + componentLegacyParams:
745
+ # don't warn people if we know it's OK (e.g. for params
746
+ # that have been removed
747
+ return recognised
730
748
  else:
731
749
  # we found an unknown parameter (probably from the future)
732
750
  params[name] = Param(
@@ -740,11 +758,7 @@ class Experiment:
740
758
  params[name].allowedTypes = paramNode.get('allowedTypes')
741
759
  if params[name].allowedTypes is None:
742
760
  params[name].allowedTypes = []
743
- if name in legacyParams + ['JS libs', 'OSF Project ID']:
744
- # don't warn people if we know it's OK (e.g. for params
745
- # that have been removed
746
- pass
747
- elif componentNode is not None and componentNode.get("plugin", False) not in (False, "", "None", None):
761
+ if componentNode is not None and componentNode.get("plugin", False) not in (False, "", "None", None):
748
762
  # is param unrecognised because it's from a plugin?
749
763
  params[name].categ = "Plugin"
750
764
  params[name].plugin = componentNode.get("plugin", False)
@@ -757,17 +771,24 @@ class Experiment:
757
771
 
758
772
  # get the value type and update rate
759
773
  if 'valType' in list(paramNode.keys()):
760
- params[name].valType = paramNode.get('valType')
774
+ valType = paramNode.get('valType')
775
+ setValType = True
761
776
  # compatibility checks:
762
777
  if name in ['allowedKeys'] and paramNode.get('valType') == 'str':
763
778
  # these components were changed in v1.70.00
764
- params[name].valType = 'code'
779
+ valType = 'code'
765
780
  elif name == 'Selected rows':
766
781
  # changed in 1.81.00 from 'code' to 'str': allow string or var
767
- params[name].valType = 'str'
782
+ valType = 'str'
768
783
  # conversions based on valType
769
784
  if params[name].valType == 'bool':
770
785
  params[name].val = eval("%s" % params[name].val)
786
+ # "device" valType was introduced in 2025.2.0 and should always override saved valType
787
+ if params[name].valType == "device":
788
+ setValType = False
789
+ # do actual setting
790
+ if setValType:
791
+ params[name].valType = valType
771
792
  if 'updates' in list(paramNode.keys()):
772
793
  params[name].updates = paramNode.get('updates')
773
794
 
@@ -873,9 +894,9 @@ class Experiment:
873
894
  self.setExpName(shortName)
874
895
  # fetch routines
875
896
  routinesNode = root.find('Routines')
876
- allCompons = getAllComponents(
897
+ self.allCompons = allCompons = getAllComponents(
877
898
  self.prefsBuilder['componentsFolders'], fetchIcons=False)
878
- allRoutines = getAllStandaloneRoutines(fetchIcons=False)
899
+ self.allRoutines = allRoutines = getAllStandaloneRoutines(fetchIcons=False)
879
900
  # get each routine node from the list of routines
880
901
  for routineNode in routinesNode:
881
902
  if routineNode.tag == "Routine":
@@ -959,6 +980,9 @@ class Experiment:
959
980
  if paramNode.tag == "Param":
960
981
  for key, val in paramNode.items():
961
982
  name = paramNode.get("name")
983
+ # "device" valType was introduced in 2025.2.0 and should always override saved valType
984
+ if key == "valType" and routine.params[name].valType == "device":
985
+ continue
962
986
  if name in routine.params:
963
987
  setattr(routine.params[name], key, val)
964
988
  # Add routine to experiment
@@ -1134,6 +1158,58 @@ class Experiment:
1134
1158
  def htmlFolder(self):
1135
1159
  return self.settings.params['HTML path'].val
1136
1160
 
1161
+ def getRequiredDeviceNames(self):
1162
+ """
1163
+ Get the device names which need to be defined for this experiment to run, along with a list
1164
+ of possible types for each one.
1165
+
1166
+ Returns
1167
+ -------
1168
+ dict[str: list[str]]
1169
+ Device names and a list of possible types for each one
1170
+ """
1171
+ # dict in which to store usages
1172
+ usages = {}
1173
+
1174
+ def _process(emt):
1175
+ """
1176
+ Process an element (Component or Routine) for device names and append them to the
1177
+ usages dict.
1178
+
1179
+ Parameters
1180
+ ----------
1181
+ emt : Component or Routine
1182
+ Element to process
1183
+ """
1184
+ # iterate through param's inita values
1185
+ for param in getInitVals(emt.params).values():
1186
+ # if it's a device...
1187
+ if param.valType == "device":
1188
+ # get value
1189
+ deviceName = param.val
1190
+ # make sure device name is in usages dict
1191
+ if deviceName not in usages:
1192
+ usages[deviceName] = []
1193
+ # add any new usages
1194
+ for cls in getattr(emt, "deviceClasses", []):
1195
+ if cls not in usages[deviceName]:
1196
+ usages[deviceName].append(cls)
1197
+
1198
+ # iterate through routines
1199
+ for rt in self.routines.values():
1200
+ if isinstance(rt, BaseStandaloneRoutine):
1201
+ # for standalone routines, get device names from params
1202
+ _process(rt)
1203
+ else:
1204
+ # for regular routines, get device names from each component
1205
+ for comp in rt:
1206
+ _process(comp)
1207
+ # process settings
1208
+ _process(self.settings)
1209
+
1210
+ return usages
1211
+
1212
+
1137
1213
  def getComponentFromName(self, name):
1138
1214
  """Searches all the Routines in the Experiment for a matching Comp name
1139
1215
 
@@ -25,7 +25,6 @@ excludeComponents = [
25
25
  'BaseComponent',
26
26
  'BaseVisualComponent',
27
27
  'BaseDeviceComponent',
28
- 'BaseStandaloneRoutine' # templates only
29
28
  ] # this one isn't ready yet
30
29
 
31
30
  # Plugin components are added dynamically at runtime, usually from plugin
@@ -288,14 +287,6 @@ def getInitVals(params, target="PsychoPy"):
288
287
  .format(inits[name]))
289
288
  inits[name].valType = 'code'
290
289
 
291
- if name == "deviceLabel":
292
- if "name" in inits and not params[name]:
293
- # if deviceName exists but is blank, use component name
294
- inits[name].val = inits['name'].val
295
- # make a code version of device name
296
- inits['deviceLabelCode'] = copy.copy(inits[name])
297
- inits['deviceLabelCode'].valType = "code"
298
-
299
290
  if not hasattr(inits[name], 'updates'): # might be settings parameter instead
300
291
  continue
301
292
 
@@ -305,7 +296,7 @@ def getInitVals(params, target="PsychoPy"):
305
296
  inits[name].val = None
306
297
  inits[name].valType = 'extendedStr'
307
298
  else:
308
- inits[name].val = 'None'
299
+ inits[name].val = None
309
300
  inits[name].valType = 'code'
310
301
 
311
302
  # is constant so don't touch the parameter value
@@ -393,6 +384,8 @@ def getInitVals(params, target="PsychoPy"):
393
384
  elif name == 'allowedKeys':
394
385
  inits[name].val = "[]"
395
386
  inits[name].valType = 'code'
387
+ elif name == "deviceLabel":
388
+ inits[name].valType = "device"
396
389
  else:
397
390
  # if not explicitly handled, default to None
398
391
  inits[name].val = "None"
@@ -13,6 +13,7 @@ from xml.etree.ElementTree import Element
13
13
 
14
14
  from psychopy import prefs
15
15
  from psychopy.constants import FOREVER
16
+ from psychopy.experiment.devices import DeviceMixin
16
17
  from ..params import Param
17
18
  from psychopy.experiment.utils import canBeNumeric
18
19
  from psychopy.experiment.utils import CodeGenerationException
@@ -42,6 +43,9 @@ class BaseComponent:
42
43
  validatorClasses = []
43
44
  # hide this Component in Builder view?
44
45
  hidden = False
46
+ # are there any known legacy params for this Component?
47
+ # these will be removed & warnings ignored on experiment load
48
+ legacyParams = []
45
49
 
46
50
  def __init__(self, exp, parentName, name='',
47
51
  startType='time (s)', startVal='',
@@ -67,7 +71,7 @@ class BaseComponent:
67
71
  msg = _translate(
68
72
  "Name of this Component (alphanumeric or _, no spaces)")
69
73
  self.params['name'] = Param(name,
70
- valType='code', inputType="single", categ='Basic',
74
+ valType='code', inputType="name", categ='Basic',
71
75
  hint=msg,
72
76
  label=_translate("Name"))
73
77
 
@@ -1373,12 +1377,10 @@ class BaseComponent:
1373
1377
  return "thisExp"
1374
1378
 
1375
1379
 
1376
- class BaseDeviceComponent(BaseComponent):
1380
+ class BaseDeviceComponent(BaseComponent, DeviceMixin):
1377
1381
  """
1378
1382
  Base class for most components which interface with a hardware device.
1379
1383
  """
1380
- # list of class strings (readable by DeviceManager) which this component's device could be
1381
- deviceClasses = []
1382
1384
 
1383
1385
  def __init__(
1384
1386
  self, exp, parentName,
@@ -1404,22 +1406,9 @@ class BaseDeviceComponent(BaseComponent):
1404
1406
  saveStartStop=saveStartStop, syncScreenRefresh=syncScreenRefresh,
1405
1407
  disabled=disabled
1406
1408
  )
1407
- # require hardware
1408
- self.exp.requirePsychopyLibs(
1409
- ['hardware']
1410
- )
1411
- # --- Device params ---
1412
- self.order += [
1413
- "deviceLabel"
1414
- ]
1415
- # label to refer to device by
1416
- self.params['deviceLabel'] = Param(
1417
- deviceLabel, valType="str", inputType="single", categ="Device",
1418
- label=_translate("Device label"),
1419
- hint=_translate(
1420
- "A label to refer to this Component's associated hardware device by. If using the "
1421
- "same device for multiple components, be sure to use the same label here."
1422
- )
1409
+ # add device stuff
1410
+ self.addDeviceParams(
1411
+ defaultLabel=deviceLabel
1423
1412
  )
1424
1413
 
1425
1414
 
@@ -87,7 +87,7 @@ class ButtonComponent(BaseVisualComponent):
87
87
  label=_translate("Run once per click")
88
88
  )
89
89
  self.params['callback'] = Param(
90
- callback, valType='extendedCode', inputType="multi", allowedTypes=[], categ='Basic',
90
+ callback, valType='extendedCode', inputType="code", allowedTypes=[], categ='Basic',
91
91
  updates='constant',
92
92
  hint=_translate("Code to run when button is clicked"),
93
93
  label=_translate("Callback function"))