psychopy 2025.1.1__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 (220) 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/data/experiment.py +133 -23
  113. psychopy/data/routine.py +12 -0
  114. psychopy/data/staircase.py +42 -20
  115. psychopy/data/trial.py +20 -12
  116. psychopy/data/utils.py +42 -2
  117. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  118. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  119. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  120. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  121. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  122. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  123. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  124. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  125. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  126. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  127. psychopy/event.py +20 -15
  128. psychopy/experiment/_experiment.py +86 -10
  129. psychopy/experiment/components/__init__.py +3 -10
  130. psychopy/experiment/components/_base.py +9 -20
  131. psychopy/experiment/components/button/__init__.py +1 -1
  132. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  133. psychopy/experiment/components/camera/__init__.py +137 -359
  134. psychopy/experiment/components/keyboard/__init__.py +17 -24
  135. psychopy/experiment/components/microphone/__init__.py +61 -110
  136. psychopy/experiment/components/movie/__init__.py +2 -3
  137. psychopy/experiment/components/serialOut/__init__.py +192 -93
  138. psychopy/experiment/components/settings/__init__.py +45 -27
  139. psychopy/experiment/components/sound/__init__.py +82 -73
  140. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  141. psychopy/experiment/devices.py +303 -0
  142. psychopy/experiment/exports.py +20 -18
  143. psychopy/experiment/flow.py +7 -0
  144. psychopy/experiment/loops.py +47 -29
  145. psychopy/experiment/monitor.py +74 -0
  146. psychopy/experiment/params.py +48 -10
  147. psychopy/experiment/plugins.py +28 -108
  148. psychopy/experiment/py2js_transpiler.py +1 -1
  149. psychopy/experiment/routines/__init__.py +1 -1
  150. psychopy/experiment/routines/_base.py +59 -24
  151. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  152. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  153. psychopy/hardware/__init__.py +20 -57
  154. psychopy/hardware/button.py +15 -2
  155. psychopy/hardware/camera/__init__.py +2237 -1394
  156. psychopy/hardware/joystick/__init__.py +1 -1
  157. psychopy/hardware/keyboard.py +5 -8
  158. psychopy/hardware/listener.py +4 -1
  159. psychopy/hardware/manager.py +75 -35
  160. psychopy/hardware/microphone.py +52 -6
  161. psychopy/hardware/monitor.py +144 -0
  162. psychopy/hardware/photometer/__init__.py +156 -117
  163. psychopy/hardware/serialdevice.py +16 -2
  164. psychopy/hardware/soundsensor.py +4 -1
  165. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  166. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  167. psychopy/iohub/util/__init__.py +7 -8
  168. psychopy/localization/generateTranslationTemplate.py +208 -116
  169. psychopy/localization/messages.pot +4305 -3502
  170. psychopy/monitors/MonitorCenter.py +174 -74
  171. psychopy/plugins/__init__.py +6 -4
  172. psychopy/preferences/devices.py +80 -0
  173. psychopy/preferences/generateHints.py +2 -1
  174. psychopy/preferences/preferences.py +35 -11
  175. psychopy/scripts/psychopy-pkgutil.py +969 -0
  176. psychopy/scripts/psyexpCompile.py +1 -1
  177. psychopy/session.py +34 -38
  178. psychopy/sound/__init__.py +6 -260
  179. psychopy/sound/audioclip.py +164 -0
  180. psychopy/sound/backend_ptb.py +8 -0
  181. psychopy/sound/backend_pygame.py +10 -0
  182. psychopy/sound/backend_pysound.py +9 -0
  183. psychopy/sound/backends/__init__.py +0 -0
  184. psychopy/sound/microphone.py +3 -0
  185. psychopy/sound/sound.py +58 -0
  186. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  187. psychopy/tests/data/duplicateHeaders.csv +2 -0
  188. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  189. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  190. psychopy/tests/test_data/test_utils.py +5 -1
  191. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  192. psychopy/tests/test_hardware/test_ports.py +0 -12
  193. psychopy/tests/test_tools/test_stringtools.py +1 -1
  194. psychopy/tools/attributetools.py +12 -5
  195. psychopy/tools/fontmanager.py +17 -14
  196. psychopy/tools/movietools.py +43 -2
  197. psychopy/tools/stringtools.py +33 -8
  198. psychopy/tools/versionchooser.py +1 -1
  199. psychopy/validation/audio.py +5 -1
  200. psychopy/validation/visual.py +5 -1
  201. psychopy/visual/basevisual.py +8 -7
  202. psychopy/visual/circle.py +2 -2
  203. psychopy/visual/image.py +29 -109
  204. psychopy/visual/movies/__init__.py +1800 -313
  205. psychopy/visual/polygon.py +4 -0
  206. psychopy/visual/shape.py +2 -2
  207. psychopy/visual/window.py +34 -11
  208. psychopy/voicekey/__init__.py +41 -669
  209. psychopy/voicekey/labjack_vks.py +7 -48
  210. psychopy/voicekey/parallel_vks.py +7 -42
  211. psychopy/voicekey/vk_tools.py +114 -263
  212. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/METADATA +17 -11
  213. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/RECORD +216 -184
  214. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  215. psychopy/visual/movies/players/__init__.py +0 -62
  216. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  217. psychopy/voicekey/demo_vks.py +0 -12
  218. psychopy/voicekey/signal.py +0 -42
  219. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  220. {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,303 @@
1
+ import inspect
2
+ from pathlib import Path
3
+ from psychopy import logging
4
+ from psychopy.experiment.params import Param
5
+ from psychopy.localization import _translate
6
+
7
+
8
+ class DeviceMixin:
9
+ """
10
+ Mixin for base Component/Routine classes which adds necessary params and attributes to
11
+ interact with device manager. This shouldn't need to be mixed in directly - instead just use
12
+ BaseDeviceComponent or BaseDeviceRoutine which include this mixin.
13
+ """
14
+
15
+ def __init_subclass__(cls):
16
+ cls.backends = set()
17
+
18
+ def addDeviceParams(self, defaultLabel=""):
19
+ from psychopy.preferences import prefs
20
+
21
+ # require hardware
22
+ self.exp.requirePsychopyLibs(
23
+ ['hardware']
24
+ )
25
+
26
+ # --- Device params ---
27
+ self.order += [
28
+ "deviceLabel"
29
+ ]
30
+ # functions for getting device labels
31
+ def getDevices():
32
+ # start with default
33
+ devices = [("", _translate("Default"))]
34
+ # iterate through saved devices
35
+ for name, device in prefs.devices.items():
36
+ # iterate through backends for this Component
37
+ for backend in self.backends:
38
+ # if device is the correct type, include it
39
+ if isinstance(device, backend):
40
+ devices.append(
41
+ (name, name)
42
+ )
43
+ return devices
44
+ def getLabels():
45
+ return [device[1] for device in getDevices()]
46
+ def getValues():
47
+ return [device[0] for device in getDevices()]
48
+ # label to refer to device by
49
+ self.params['deviceLabel'] = Param(
50
+ defaultLabel, valType="device", inputType="device", categ="Device",
51
+ allowedVals=getValues,
52
+ allowedLabels=getLabels,
53
+ label=_translate("Device"),
54
+ hint=_translate(
55
+ "The named device from Device Manager to use for this Component."
56
+ )
57
+ )
58
+
59
+ @classmethod
60
+ def registerBackend(cls, backend):
61
+ """
62
+ Register a device backend as relevant to this Component.
63
+
64
+ Parameters
65
+ ----------
66
+ backend : type
67
+ Subclass of `psychopy.experiment.devices.DeviceBackend` to associate with this
68
+ Component.
69
+ """
70
+ if not hasattr(cls, "backends"):
71
+ cls.backends = set()
72
+
73
+ cls.backends.add(backend)
74
+
75
+
76
+ class DeviceBackend:
77
+ """
78
+ Representation of a hardware class in Builder.
79
+ """
80
+ # name of this backend to display in Device Manager
81
+ backendLabel = None
82
+ # icon to use for this backend (relative to current file path, leave as None for no icon)
83
+ icon = None
84
+ # class of the device which this backend corresponds to
85
+ deviceClass = "psychopy.hardware.base.BaseDevice"
86
+
87
+ def __init__(self, profile):
88
+ # store device profile
89
+ self.profile = profile
90
+ # initialise params and order arrays
91
+ self.params = {}
92
+ self.order = []
93
+ # add a param for the device label to all backends
94
+ self.params['deviceLabel'] = Param(
95
+ "", valType="str", inputType="name",
96
+ label=_translate("Device label"),
97
+ hint=_translate(
98
+ "A name to refer to this device by in Device Manager."
99
+ )
100
+ )
101
+ # device label always first
102
+ self.order += [
103
+ "deviceLabel"
104
+ ]
105
+ # get further params from subclass method
106
+ params, order = self.getParams()
107
+ self.params.update(params)
108
+ self.order += order
109
+
110
+ def __repr__(self):
111
+ return (
112
+ f"<{type(self).__name__}: name={self.name}>"
113
+ )
114
+
115
+ def getParams(self):
116
+ """
117
+ Get parameters from this backend to add to each new device from this backend.
118
+
119
+ Returns
120
+ -------
121
+ dict[str:Param]
122
+ Dict of Param objects for controlling devices in this backend
123
+ list[str]
124
+ List of param names, defining the order in which params should appear
125
+ """
126
+ return {}, []
127
+
128
+ def writeDeviceCode(self, buff):
129
+ """
130
+ Write the code to create a device for this backend. This method must be overloaded by device backend subclasses.
131
+
132
+ To write the basics of device initialisation, you can do: ::
133
+ # this opens a call to DeviceManager.addDevice with the basic necessary arguments included, and does not close the brackets so you can add more
134
+ self.writeBaseDeviceCode(buff, close=False)
135
+ code = (
136
+ # write any param-specific inits here (e.g. "threshold=%(threshold)s,\n")
137
+ ")\n"
138
+ )
139
+ buff.writeIndentedLines
140
+
141
+ To use just the basic device initialisation code, you can just do: ::
142
+ return self.writeBaseDeviceCode(buff, close=True)
143
+ """
144
+
145
+ raise NotImplementedError()
146
+
147
+ @classmethod
148
+ def getIconFile(cls):
149
+ """
150
+ Get the file for this backend's icon as a Path, if there is one
151
+
152
+ Returns
153
+ -------
154
+ Path or None
155
+ File path for this backend's icon, or None if there is no icon
156
+ """
157
+ # return None if no icon
158
+ if cls.icon is None:
159
+ return
160
+ # make sure it's a Path
161
+ icon = cls.icon
162
+ if isinstance(icon, str):
163
+ icon = Path(icon)
164
+ # get folder containing class def file
165
+ folder = Path(inspect.getfile(cls)).parent
166
+ # make absolute
167
+ file = (folder / icon).resolve()
168
+
169
+ return file
170
+
171
+ @classmethod
172
+ def fromJSON(cls, data):
173
+ """
174
+ Initialise an instance of this class from a JSON dict.
175
+
176
+ Parameters
177
+ ----------
178
+ data : dict
179
+ JSON data to initialise from
180
+ """
181
+ # initialise
182
+ device = cls(
183
+ profile=data['profile']
184
+ )
185
+ # apply param vals
186
+ device.applyJSON(data)
187
+
188
+ return device
189
+
190
+ def applyJSON(self, data):
191
+ """
192
+ Apply data from a JSON dict to this object.
193
+
194
+ Parameters
195
+ ----------
196
+ data : dict
197
+ JSON data to apply
198
+ """
199
+ # get profile
200
+ self.profile = data['profile']
201
+ # apply param vals
202
+ for name, val in data['params'].items():
203
+ self.params[name].applyJSON(val)
204
+
205
+ def toJSON(self):
206
+ """
207
+ Get this object as a JSON dict.
208
+
209
+ Returns
210
+ -------
211
+ dict
212
+ JSON dict representing this object, will be in the form:..
213
+
214
+ {
215
+ '__cls__': <import string for this class>,
216
+ 'profile': <dict from DeviceManager.getAvailableDevices>,
217
+ 'params': <dict of Param JSON objects>,
218
+ }
219
+ """
220
+ # create dict
221
+ data = {
222
+ '__cls__': f"{type(self).__module__}.{type(self).__name__}",
223
+ 'profile': self.profile,
224
+ 'params': {}
225
+ }
226
+ # add params
227
+ for key, param in self.params.items():
228
+ data['params'][key] = param.toJSON()
229
+
230
+ return data
231
+
232
+ @staticmethod
233
+ def getAllBackends():
234
+ """
235
+ Get all backends known to the current PsychoPy session.
236
+
237
+ Returns
238
+ -------
239
+ list[type]
240
+ List of backend classes
241
+ """
242
+ from psychopy.experiment import getAllElements
243
+ from psychopy.experiment.monitor import BasePhotometerDeviceBackend, ScreenBufferPhotometerDeviceBackend
244
+ allBackends = []
245
+ # look for device backends associated with all known Components and Routines
246
+ for emt in getAllElements(fetchIcons=False).values():
247
+ if hasattr(emt, "backends"):
248
+ for backend in emt.backends:
249
+ # check that each backend is a DeviceBackend
250
+ if issubclass(backend, DeviceBackend) and backend not in allBackends:
251
+ # append if so
252
+ allBackends.append(backend)
253
+ # add subclasses of BasePhotometerBackend as it doesn't come from any Component
254
+ for cls in BasePhotometerDeviceBackend.__subclasses__():
255
+ if cls not in allBackends:
256
+ allBackends.append(cls)
257
+
258
+ return allBackends
259
+
260
+
261
+ def writeBaseDeviceCode(self, buff, close=False):
262
+ """
263
+ Write the basic device code
264
+
265
+ Parameters
266
+ ----------
267
+ buff : io.StringIO
268
+ Buffer to write to
269
+ close : bool, optional
270
+ If False (default), won't close the `addDevice` call (so you need to write the closing
271
+ bracket yourself)
272
+ """
273
+ # write init call with device label
274
+ code = (
275
+ "# initialize %(deviceLabel)s\n"
276
+ "deviceManager.addDevice(\n"
277
+ " deviceName=%(deviceLabel)s,\n"
278
+ )
279
+ buff.writeIndentedLines(code % self.params)
280
+ # add options from profile
281
+ code = ""
282
+ for key, value in self.profile.items():
283
+ # skip attributes already covered by a param
284
+ if key in self.params or key in ("deviceName", ):
285
+ continue
286
+ code += f" {key}={repr(value)},\n"
287
+ buff.writeIndentedLines(code)
288
+ # if close requested, add closing bracket
289
+ if close:
290
+ code = (
291
+ ")\n"
292
+ )
293
+ buff.writeIndentedLines(code)
294
+
295
+ @property
296
+ def name(self):
297
+ return self.params['deviceLabel'].val
298
+
299
+ @name.setter
300
+ def name(self, value):
301
+ # update param value
302
+ self.params['deviceLabel'].val = value
303
+
@@ -127,30 +127,31 @@ class NameSpace:
127
127
  2011 Jeremy Gray
128
128
  """
129
129
 
130
+ # numpy imports
131
+ numpy = _numpyImports + _numpyRandomImports + ['np']
132
+ # core python keywords
133
+ keywords = keyword.kwlist + dir(__builtins__) + ['self']
134
+ # builder stuff
135
+ builder = [
136
+ 'KeyResponse', 'keyboard', 'buttons', 'continueRoutine', 'expInfo', 'expName', 'thisExp',
137
+ 'filename', 'logFile', 'paramName', 't', 'frameN', 'currentLoop', 'dlg', '_thisDir',
138
+ 'endExpNow', 'globalClock', 'routineTimer', 'frameDur', 'theseKeys', 'win', 'x', 'y',
139
+ 'level', 'component', 'thisComponent'
140
+ ]
141
+ # PsychoPy constants
142
+ constants = dir(constants)
143
+ # PsychoPy modules
144
+ psychopy = psychopy.__all__ + ['psychopy', 'os']
145
+ # all non-user builder stuff
146
+ nonUserBuilder = numpy + keywords + psychopy + constants
147
+
130
148
  def __init__(self, exp):
131
149
  """Set-up an experiment's namespace: reserved words and user space
132
150
  """
133
151
  super(NameSpace, self).__init__()
134
152
  self.exp = exp
135
- # deepcopy fails if you pre-compile regular expressions and stash here
136
-
137
- self.numpy = _numpyImports + _numpyRandomImports + ['np']
138
- # noinspection PyUnresolvedReferences
139
- self.keywords = keyword.kwlist + dir(__builtins__) + ['self']
140
- # these are based on a partial test, known to be incomplete:
141
- self.psychopy = psychopy.__all__ + ['psychopy', 'os']
142
- self.constants = dir(constants)
143
- self.builder = ['KeyResponse', 'keyboard', 'buttons',
144
- 'continueRoutine', 'expInfo', 'expName', 'thisExp',
145
- 'filename', 'logFile', 'paramName',
146
- 't', 'frameN', 'currentLoop', 'dlg', '_thisDir',
147
- 'endExpNow',
148
- 'globalClock', 'routineTimer', 'frameDur',
149
- 'theseKeys', 'win', 'x', 'y', 'level', 'component',
150
- 'thisComponent']
151
153
  # user-entered, from Builder dialog or conditions file:
152
154
  self.user = []
153
- self.nonUserBuilder = self.numpy + self.keywords + self.psychopy
154
155
 
155
156
  def __str__(self, numpy_count_only=True):
156
157
  varibs = self.user + self.builder + self.psychopy
@@ -232,7 +233,8 @@ class NameSpace:
232
233
  if i < len(su) - 1 and su[i + 1] == var]
233
234
  return duplicates or None
234
235
 
235
- def isValid(self, name):
236
+ @staticmethod
237
+ def isValid(name):
236
238
  """var-name compatible? return True if string name is
237
239
  alphanumeric + underscore only, with non-digit first
238
240
  """
@@ -274,6 +274,11 @@ class Flow(list):
274
274
  code = (
275
275
  "# mark experiment as started\n"
276
276
  "thisExp.status = STARTED\n"
277
+ "# update experiment info\n"
278
+ "expInfo['date'] = data.getDateStr()\n"
279
+ "expInfo['expName'] = expName\n"
280
+ "expInfo['expVersion'] = expVersion\n"
281
+ "expInfo['psychopyVersion'] = psychopyVersion\n"
277
282
  "# make sure window is set to foreground to prevent losing focus\n"
278
283
  "win.winHandle.activate()\n"
279
284
  "# make sure variables created by exec are available globally\n"
@@ -336,6 +341,8 @@ class Flow(list):
336
341
  "if ioServer is not None:\n"
337
342
  " ioServer.syncClock(globalClock)\n"
338
343
  "logging.setDefaultClock(globalClock)\n"
344
+ "if eyetracker is not None:\n"
345
+ " eyetracker.enableEventReporting()\n"
339
346
  "# routine timer to track time remaining of each (possibly non-slip) routine\n"
340
347
  "routineTimer = core.Clock()\n"
341
348
  "win.flip() # flip window to reset last flip timer\n"
@@ -93,7 +93,7 @@ class TrialHandler(_BaseLoopHandler):
93
93
  self.order = ['name'] # make name come first (others don't matter)
94
94
  self.params = {}
95
95
  self.params['name'] = Param(
96
- name, valType='code', inputType="single", updates=None, allowedUpdates=None,
96
+ name, valType='code', inputType="name", updates=None, allowedUpdates=None,
97
97
  label=_translate('Name'),
98
98
  hint=_translate("Name of this loop"))
99
99
  self.params['nReps'] = Param(
@@ -107,7 +107,7 @@ class TrialHandler(_BaseLoopHandler):
107
107
  hint=_translate("A list of dictionaries describing the "
108
108
  "parameters in each condition"))
109
109
  self.params['conditionsFile'] = Param(
110
- conditionsFile, valType='file', inputType="table", updates=None, allowedUpdates=None,
110
+ conditionsFile, valType='file', inputType="conditions", updates=None, allowedUpdates=None,
111
111
  label=_translate('Conditions'),
112
112
  hint=_translate("Name of a file specifying the parameters for "
113
113
  "each condition (.csv, .xlsx, or .pkl). Browse "
@@ -189,6 +189,7 @@ class TrialHandler(_BaseLoopHandler):
189
189
  " originPath=-1, \n"
190
190
  " trialList=%(trialList)s, \n"
191
191
  " seed=%(random seed)s, \n"
192
+ " isTrials=%(isTrials)s, \n"
192
193
  ")\n"
193
194
  )
194
195
  buff.writeIndentedLines(code % inits)
@@ -478,7 +479,7 @@ class StairHandler(_BaseLoopHandler):
478
479
  self.children = []
479
480
  self.params = {}
480
481
  self.params['name'] = Param(
481
- name, valType='code',
482
+ name, valType='code', inputType="name",
482
483
  hint=_translate("Name of this loop"),
483
484
  label=_translate('Name'))
484
485
  self.params['nReps'] = Param(
@@ -560,15 +561,27 @@ class StairHandler(_BaseLoopHandler):
560
561
  if self.params['N reversals'].val in ("", None, 'None'):
561
562
  self.params['N reversals'].val = '0'
562
563
  # write the code
563
- code = ('\n# --------Prepare to start Staircase "%(name)s" --------\n'
564
- "# set up handler to look after next chosen value etc\n"
565
- "%(name)s = data.StairHandler(startVal=%(start value)s, extraInfo=expInfo,\n"
566
- " stepSizes=%(step sizes)s, stepType=%(step type)s,\n"
567
- " nReversals=%(N reversals)s, nTrials=%(nReps)s, \n"
568
- " nUp=%(N up)s, nDown=%(N down)s,\n"
569
- " minVal=%(min value)s, maxVal=%(max value)s,\n"
570
- " originPath=-1, name='%(name)s')\n"
571
- "thisExp.addLoop(%(name)s) # add the loop to the experiment")
564
+ code = (
565
+ "\n"
566
+ "# --- Prepare to start Staircase '%(name)s' --- \n"
567
+ "# set up handler to look after next chosen value etc\n"
568
+ "%(name)s = data.StairHandler(\n"
569
+ " startVal=%(start value)s, \n"
570
+ " extraInfo=expInfo,\n"
571
+ " stepSizes=%(step sizes)s, \n"
572
+ " stepType=%(step type)s,\n"
573
+ " nReversals=%(N reversals)s, \n"
574
+ " nTrials=%(nReps)s, \n"
575
+ " nUp=%(N up)s, \n"
576
+ " nDown=%(N down)s,\n"
577
+ " minVal=%(min value)s, \n"
578
+ " maxVal=%(max value)s,\n"
579
+ " originPath=-1, \n"
580
+ " name='%(name)s',\n"
581
+ " isTrials=%(isTrials)s, \n"
582
+ ")\n"
583
+ "thisExp.addLoop(%(name)s) # add the loop to the experiment"
584
+ )
572
585
  buff.writeIndentedLines(code % self.params)
573
586
  code = "level = %s = %s # initialise some vals\n"
574
587
  buff.writeIndented(code % (self.thisName, self.params['start value']))
@@ -633,7 +646,7 @@ class MultiStairHandler(_BaseLoopHandler):
633
646
  self.order = ['name'] # make name come first
634
647
  self.params = {}
635
648
  self.params['name'] = Param(
636
- name, valType='code', inputType='single',
649
+ name, valType='code', inputType='name',
637
650
  label=_translate('Name'),
638
651
  hint=_translate("Name of this loop"))
639
652
  self.params['nReps'] = Param(
@@ -664,7 +677,7 @@ class MultiStairHandler(_BaseLoopHandler):
664
677
  hint=_translate('Where to loop from and to (see values currently'
665
678
  ' shown in the flow view)'))
666
679
  self.params['conditions'] = Param(
667
- list(conditions), valType='list', inputType='single',
680
+ list(conditions), valType='list', inputType='conditions',
668
681
  updates=None, allowedUpdates=None,
669
682
  label=_translate('Conditions'),
670
683
  hint=_translate("A list of dictionaries describing the "
@@ -692,7 +705,7 @@ class MultiStairHandler(_BaseLoopHandler):
692
705
  return root / "staircaseTemplate.xltx"
693
706
 
694
707
  self.params['conditionsFile'] = Param(
695
- conditionsFile, valType='file', inputType='table', updates=None, allowedUpdates=None,
708
+ conditionsFile, valType='file', inputType='conditions', updates=None, allowedUpdates=None,
696
709
  label=_translate('Conditions'),
697
710
  hint=_translate("An xlsx or csv file specifying the parameters "
698
711
  "for each condition"),
@@ -718,20 +731,25 @@ class MultiStairHandler(_BaseLoopHandler):
718
731
  makeLoopIndex(self.params['name'].val)
719
732
  self.thisName = "condition"
720
733
  # create the MultistairHander
721
- code = ("\n# set up handler to look after randomisation of trials etc\n"
722
- "conditions = data.importConditions(%(conditionsFile)s)\n"
723
- "%(name)s = data.MultiStairHandler(stairType=%(stairType)s, "
724
- "name='%(name)s',\n"
725
- " nTrials=%(nReps)s,\n"
726
- " conditions=conditions,\n"
727
- " method=%(switchMethod)s,\n"
728
- " originPath=-1)\n"
729
- "thisExp.addLoop(%(name)s) # add the loop to the experiment\n"
730
- "# initialise values for first condition\n"
731
- "level = %(name)s._nextIntensity # initialise some vals\n"
732
- "condition = %(name)s.currentStaircase.condition\n"
733
- # start the loop:
734
- "\nfor level, condition in %(name)s:\n")
734
+ code = (
735
+ "\n# set up handler to look after randomisation of trials etc\n"
736
+ "conditions = data.importConditions(%(conditionsFile)s)\n"
737
+ "%(name)s = data.MultiStairHandler(\n"
738
+ " stairType=%(stairType)s, "
739
+ " name='%(name)s',\n"
740
+ " nTrials=%(nReps)s,\n"
741
+ " conditions=conditions,\n"
742
+ " method=%(switchMethod)s,\n"
743
+ " originPath=-1,\n"
744
+ " isTrials=%(isTrials)s"
745
+ ")\n"
746
+ "thisExp.addLoop(%(name)s) # add the loop to the experiment\n"
747
+ "# initialise values for first condition\n"
748
+ "level = %(name)s._nextIntensity # initialise some vals\n"
749
+ "condition = %(name)s.currentStaircase.condition\n"
750
+ # start the loop:
751
+ "\nfor level, condition in %(name)s:\n"
752
+ )
735
753
  buff.writeIndentedLines(code % self.params)
736
754
 
737
755
  buff.setIndentLevel(1, relative=True)
@@ -0,0 +1,74 @@
1
+ from psychopy.experiment.devices import DeviceBackend
2
+ from psychopy.experiment.params import Param
3
+ from psychopy.experiment import getInitVals
4
+ from psychopy.localization import _translate
5
+
6
+
7
+ class BasePhotometerDeviceBackend(DeviceBackend):
8
+ """
9
+ Subclass common to all Photometer Device Backend classes, so they can be identified in the
10
+ absence of a common Component.
11
+ """
12
+ icon = "../app/Resources/light/photometer.png"
13
+
14
+
15
+ class ScreenBufferPhotometerDeviceBackend(BasePhotometerDeviceBackend):
16
+ """
17
+ Represents an emulator photometer, which just returns the pixel values from the screen for
18
+ teaching / sanity checking purposes only.
19
+ """
20
+ backendLabel = "Photometer Emulator (debug)"
21
+ deviceClass = "psychopy.hardware.photometer.ScreenBufferPhotometerDevice"
22
+
23
+ def getParams(self):
24
+ params = {}
25
+ order = [
26
+ "pos",
27
+ "size",
28
+ "units"
29
+ ]
30
+
31
+ params['pos'] = Param(
32
+ (0, 0), valType="list", inputType="single",
33
+ label=_translate("Position (x, y)"),
34
+ hint=_translate(
35
+ "Position of the patch of pixels to pretend there is a photometer looking at"
36
+ )
37
+ )
38
+ params['size'] = Param(
39
+ (16, 16), valType="list", inputType="single",
40
+ label=_translate("Size (w, h)"),
41
+ hint=_translate(
42
+ "Size of the patch of pixels to pretend there is a photometer looking at"
43
+ )
44
+ )
45
+ params['units'] = Param(
46
+ "pix", valType="str", inputType="choice",
47
+ allowedVals=[
48
+ "from exp settings", "deg", "cm", "pix", "norm", "height", "degFlatPos", "degFlat"
49
+ ],
50
+ label=_translate("Spatial units"),
51
+ hint=_translate(
52
+ "Spatial units in which to interpret size and position"
53
+ )
54
+ )
55
+
56
+ return params, order
57
+
58
+ def writeDeviceCode(self, buff):
59
+ # write core code
60
+ DeviceBackend.writeBaseDeviceCode(self, buff, close=False)
61
+ # get inits
62
+ inits = getInitVals(self.params)
63
+ # add params
64
+ code = (
65
+ " win=win,\n"
66
+ " size=%(size)s,\n"
67
+ " pos=%(pos)s,\n"
68
+ " units=%(units)s\n"
69
+ ")\n"
70
+ )
71
+ buff.writeIndentedLines(code % inits)
72
+
73
+
74
+