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
@@ -8,6 +8,9 @@ from psychopy import logging
8
8
  from psychopy.experiment.components import (
9
9
  BaseComponent, BaseDeviceComponent, Param, _translate, getInitVals
10
10
  )
11
+ from psychopy.preferences import prefs
12
+ from psychopy.experiment.components.microphone import MicrophoneDeviceBackend
13
+ from psychopy.experiment.devices import DeviceBackend
11
14
  from psychopy.tools import stringtools as st, systemtools as syst, audiotools as at
12
15
 
13
16
 
@@ -31,7 +34,21 @@ class CameraComponent(BaseDeviceComponent):
31
34
  iconFile = Path(__file__).parent / 'webcam.png'
32
35
  tooltip = _translate('Webcam: Record video from a webcam.')
33
36
  beta = False
34
- deviceClasses = ["psychopy.hardware.camera.Camera"]
37
+ deviceClasses = ["psychopy.hardware.camera.CameraDevice"]
38
+ legacyParams = [
39
+ # old device setup params, no longer needed as this is handled by DeviceManager
40
+ "cameraLib",
41
+ "device",
42
+ "deviceManual",
43
+ "frameRate",
44
+ "frameRateManual",
45
+ "mic",
46
+ "micChannels",
47
+ "micMaxRecSize",
48
+ "micSampleRate",
49
+ "resolution",
50
+ "resolutionManual"
51
+ ]
35
52
 
36
53
  def __init__(
37
54
  # Basic
@@ -41,25 +58,27 @@ class CameraComponent(BaseDeviceComponent):
41
58
  stopType='duration (s)', stopVal='', durationEstim='',
42
59
  # Device
43
60
  deviceLabel="",
44
- cameraLib="ffpyplayer",
45
- device="default",
46
- resolution="",
47
- frameRate="",
48
- deviceManual="",
49
- resolutionManual="",
50
- frameRateManual="",
51
61
  # audio
52
62
  micDeviceLabel="",
53
- mic=None,
54
- channels='auto',
55
- sampleRate='DVD Audio (48kHz)',
56
- maxSize=24000,
57
63
  # Data
58
64
  saveFile=True,
59
- outputFileType="mp4", codec="h263",
60
65
  saveStartStop=True, syncScreenRefresh=False,
61
66
  # Testing
62
67
  disabled=False,
68
+ # legacy
69
+ outputFileType="mp4",
70
+ codec="h263",
71
+ mic=None,
72
+ channels='auto',
73
+ sampleRate='DVD Audio (48kHz)',
74
+ maxSize=24000,
75
+ cameraLib="ffpyplayer",
76
+ device="default",
77
+ resolution="",
78
+ frameRate="",
79
+ deviceManual="",
80
+ resolutionManual="",
81
+ frameRateManual="",
63
82
  ):
64
83
  # Initialise superclass
65
84
  super(CameraComponent, self).__init__(
@@ -82,300 +101,39 @@ class CameraComponent(BaseDeviceComponent):
82
101
  # Add requirement
83
102
  self.exp.requireImport(importName="camera", importFrom="psychopy.hardware")
84
103
  self.exp.requireImport(importName="microphone", importFrom="psychopy.sound")
85
-
86
- # Define some functions for live populating listCtrls
87
- def getResolutionsForDevice(cameraLib, deviceName):
88
- """
89
- Get a list of resolutions available for the given device.
90
-
91
- Parameters
92
- ----------
93
- cameraLib : Param
94
- Param object containing name of backend library
95
- deviceName : Param
96
- Param object containing device name/index
97
-
98
- Returns
99
- -------
100
- list
101
- List of resolutions, specified as strings in the format `(width, height)`
102
- """
103
- if cameraLib == "opencv":
104
- return [""]
105
- try:
106
- from psychopy.hardware.camera import Camera
107
- # get all devices
108
- if isinstance(cameraLib, Param):
109
- cameraLib = cameraLib.val
110
- connectedCameras = Camera.getCameras(cameraLib=cameraLib)
111
- # if device is a param, get its val
112
- if isinstance(deviceName, Param):
113
- deviceName = deviceName.val
114
- # get first device if default
115
- if deviceName in (None, "", "default") and len(connectedCameras):
116
- deviceName = list(connectedCameras)[0]
117
- # get formats for this device
118
- formats = connectedCameras.get(deviceName, [])
119
- # extract resolutions
120
- formats = [_format.frameSize for _format in formats]
121
- # remove duplicates and sort
122
- formats = list(set(formats))
123
- formats.sort(key=lambda res: res[0], reverse=True)
124
-
125
- return [""] + formats
126
- except:
127
- return [""]
128
-
129
- def getFrameRatesForDevice(cameraLib, deviceName, resolution=None):
130
- """
131
- Get a list of frame rates available for the given device.
132
-
133
- Parameters
134
- ----------
135
- cameraLib : Param
136
- Param object containing name of backend library
137
- deviceName : Param
138
- Param object containing device name/index
139
-
140
- Returns
141
- -------
142
- list
143
- List of frame rates
144
- """
145
- if cameraLib == "opencv":
146
- return [""]
147
- try:
148
- from psychopy.hardware.camera import Camera
149
- # get all devices
150
- if isinstance(cameraLib, Param):
151
- cameraLib = cameraLib.val
152
- connectedCameras = Camera.getCameras(cameraLib=cameraLib)
153
- # if device is a param, get its val
154
- if isinstance(deviceName, Param):
155
- deviceName = deviceName.val
156
- # get first device if default
157
- if deviceName in (None, "", "default") and len(connectedCameras):
158
- deviceName = list(connectedCameras)[0]
159
- # get formats for this device
160
- formats = connectedCameras.get(deviceName, [])
161
- # if frameRate is a param, get its val
162
- if isinstance(resolution, Param):
163
- resolution = resolution.val
164
- # filter for current frame rate
165
- if resolution not in (None, "", "default"):
166
- formats = [f for f in formats if f.frameSize == resolution]
167
- # extract resolutions
168
- formats = [_format.frameRate for _format in formats]
169
- # remove duplicates and sort
170
- formats = list(set(formats))
171
- formats.sort(reverse=True)
172
-
173
- return [""] + formats
174
- except:
175
- return [""]
176
-
177
- # --- Device params ---
178
- self.order += [
179
- "cameraLib",
180
- "device",
181
- "deviceManual",
182
- "resolution",
183
- "resolutionManual",
184
- "frameRate",
185
- "frameRateManual",
186
- ]
187
- self.params['cameraLib'] = Param(
188
- cameraLib, valType='str', inputType="choice", categ="Device",
189
- allowedVals=["ffpyplayer", "opencv"], allowedLabels=["FFPyPlayer", "OpenCV"],
190
- hint=_translate("Python package to use behind the scenes."),
191
- label=_translate("Backend")
192
- )
193
- msg = _translate(
194
- "What device would you like to use to record video? This will only affect local "
195
- "experiments - online experiments ask the participant which device to use."
196
- )
197
-
198
- def getCameraNames():
199
- """
200
- Similar to getCameraDescriptions, only returns camera names
201
- as a list of strings.
202
-
203
- Returns
204
- -------
205
- list
206
- Array of camera device names, preceeded by "default"
207
- """
208
- if self.params['cameraLib'] == "opencv":
209
- return ["default"]
210
- # enter a try statement in case ffpyplayer isn't installed
211
- try:
212
- # import
213
- from psychopy.hardware.camera import Camera
214
- connectedCameras = Camera.getCameras(cameraLib=self.params['cameraLib'].val)
215
-
216
- return ["default"] + list(connectedCameras)
217
- except:
218
- return ["default"]
219
-
220
- self.params['device'] = Param(
221
- device, valType='str', inputType="choice", categ="Device",
222
- allowedVals=getCameraNames, allowedLabels=getCameraNames,
223
- hint=msg,
224
- label=_translate("Video device")
225
- )
226
- self.depends.append({
227
- "dependsOn": 'cameraLib', # if...
228
- "condition": "", # meets...
229
- "param": 'device', # then...
230
- "true": "populate", # should...
231
- "false": "populate", # otherwise...
232
- })
233
- self.params['deviceManual'] = Param(
234
- deviceManual, valType='code', inputType="single", categ="Device",
235
- hint=msg,
236
- label=_translate("Video device")
237
- )
238
- msg = _translate("Resolution (w x h) to record to, leave blank to use device default.")
239
- conf = functools.partial(getResolutionsForDevice, self.params['cameraLib'], self.params['device'])
240
- self.params['resolution'] = Param(
241
- resolution, valType='list', inputType="choice", categ="Device",
242
- allowedVals=conf, allowedLabels=conf,
243
- hint=msg,
244
- label=_translate("Resolution")
245
- )
246
- self.depends.append({
247
- "dependsOn": 'device', # if...
248
- "condition": "", # meets...
249
- "param": 'resolution', # then...
250
- "true": "populate", # should...
251
- "false": "populate", # otherwise...
252
- })
253
- self.params['resolutionManual'] = Param(
254
- resolutionManual, valType='list', inputType="single", categ="Device",
255
- hint=msg,
256
- label=_translate("Resolution")
257
- )
258
- msg = _translate("Frame rate (frames per second) to record at, leave "
259
- "blank to use device default.")
260
- conf = functools.partial(
261
- getFrameRatesForDevice,
262
- self.params['cameraLib'],
263
- self.params['device'],
264
- self.params['resolution'])
265
- self.params['frameRate'] = Param(
266
- frameRate, valType='int', inputType="choice", categ="Device",
267
- allowedVals=conf, allowedLabels=conf,
268
- hint=msg,
269
- label=_translate("Frame rate")
270
- )
271
- self.depends.append({
272
- "dependsOn": 'device', # if...
273
- "condition": "", # meets...
274
- "param": 'frameRate', # then...
275
- "true": "populate", # should...
276
- "false": "populate", # otherwise...
277
- })
278
-
279
- msg += _translate(
280
- " For some cameras, you may need to use "
281
- "`camera.CAMERA_FRAMERATE_NTSC` or "
282
- "`camera.CAMERA_FRAMERATE_NTSC / 2`.")
283
- self.params['frameRateManual'] = Param(
284
- frameRateManual, valType='int', inputType="single", categ="Device",
285
- hint=msg,
286
- label=_translate("Frame rate")
287
- )
288
-
289
- # add dependencies for manual spec under open cv
290
- for param in ("device", "resolution", "frameRate"):
291
- # hide the choice ctrl
292
- self.depends.append({
293
- "dependsOn": 'cameraLib', # if...
294
- "condition": "=='opencv'", # meets...
295
- "param": param, # then...
296
- "true": "hide", # should...
297
- "false": "show", # otherwise...
298
- })
299
- # show to manual ctrl
300
- self.depends.append({
301
- "dependsOn": 'cameraLib', # if...
302
- "condition": "=='opencv'", # meets...
303
- "param": param + "Manual", # then...
304
- "true": "show", # should...
305
- "false": "hide", # otherwise...
306
- })
307
-
104
+
308
105
  # --- Audio params ---
106
+ # --- Device params ---
309
107
  self.order += [
310
- "micDeviceLabel",
311
- "mic",
312
- "micChannels",
313
- "micSampleRate",
314
- "micMaxRecSize"
108
+ "deviceLabel"
315
109
  ]
110
+ # functions for getting device labels
111
+ def getMicDevices():
112
+ # start with default
113
+ devices = [("", _translate("Default"))]
114
+ # iterate through saved devices
115
+ for name, device in prefs.devices.items():
116
+ # if device is a microphone, include it
117
+ if isinstance(device, MicrophoneDeviceBackend):
118
+ devices.append(
119
+ (name, name)
120
+ )
121
+ return devices
122
+ def getMicLabels():
123
+ return [device[1] for device in getMicDevices()]
124
+ def getMicValues():
125
+ return [device[0] for device in getMicDevices()]
126
+ # label to refer to device by
316
127
  self.params['micDeviceLabel'] = Param(
317
- micDeviceLabel, valType="str", inputType="single", categ="Audio",
318
- label=_translate("Microphone device label"),
128
+ micDeviceLabel, valType="device", inputType="device", categ="Device",
129
+ allowedVals=getMicValues,
130
+ allowedLabels=getMicLabels,
131
+ label=_translate("Microphone device"),
319
132
  hint=_translate(
320
- "A label to refer to this Component's associated microphone device by. If using "
321
- "the same device for multiple components, be sure to use the same label here."
133
+ "The named device from Device Manager to use for this Component."
322
134
  )
323
135
  )
324
136
 
325
- def getMicDeviceIndices():
326
- from psychopy.hardware.microphone import MicrophoneDevice
327
- profiles = MicrophoneDevice.getAvailableDevices()
328
-
329
- return [None] + [profile['index'] for profile in profiles]
330
-
331
- def getMicDeviceNames():
332
- from psychopy.hardware.microphone import MicrophoneDevice
333
- profiles = MicrophoneDevice.getAvailableDevices()
334
-
335
- return ["default"] + [profile['deviceName'] for profile in profiles]
336
-
337
- msg = _translate(
338
- "What microphone device would you like the use to record? This "
339
- "will only affect local experiments - online experiments ask the "
340
- "participant which mic to use.")
341
- self.params['mic'] = Param(
342
- mic, valType='str', inputType="choice", categ="Audio",
343
- allowedVals=getMicDeviceIndices,
344
- allowedLabels=getMicDeviceNames,
345
- hint=msg,
346
- label=_translate("Microphone")
347
- )
348
- msg = _translate(
349
- "Record two channels (stereo) or one (mono, smaller file). Select "
350
- "'auto' to use as many channels as the selected device allows.")
351
-
352
- self.params['micChannels'] = Param(
353
- channels, valType='str', inputType="choice", categ='Audio',
354
- allowedVals=['auto', 'mono', 'stereo'],
355
- hint=msg,
356
- label=_translate("Channels"))
357
-
358
- def getSampleRates():
359
- return [r[0] for r in at.sampleRateQualityLevels.values()]
360
- def getSampleRateLabels():
361
- return [r[1] for r in at.sampleRateQualityLevels.values()]
362
- msg = _translate(
363
- "How many samples per second (Hz) to record at")
364
- self.params['micSampleRate'] = Param(
365
- sampleRate, valType='num', inputType="choice", categ='Audio',
366
- allowedVals=getSampleRates,
367
- allowedLabels=getSampleRateLabels,
368
- hint=msg, direct=False,
369
- label=_translate("Sample rate (hz)"))
370
-
371
- msg = _translate(
372
- "To avoid excessively large output files, what is the biggest file "
373
- "size you are likely to expect?")
374
- self.params['micMaxRecSize'] = Param(
375
- maxSize, valType='num', inputType="single", categ='Audio',
376
- hint=msg,
377
- label=_translate("Max recording size (kb)"))
378
-
379
137
  # --- Data params ---
380
138
  msg = _translate("Save webcam output to a file?")
381
139
  self.params['saveFile'] = Param(
@@ -395,55 +153,6 @@ class CameraComponent(BaseDeviceComponent):
395
153
  inits['micDeviceLabelCode'] = copy.copy(inits['micDeviceLabel'])
396
154
  inits['micDeviceLabelCode'].valType = "code"
397
155
 
398
- def writeDeviceCode(self, buff):
399
- """
400
- Code to setup the CameraDevice for this component.
401
-
402
- Parameters
403
- ----------
404
- buff : io.StringIO
405
- Text buffer to write code to.
406
- """
407
- inits = getInitVals(self.params)
408
- self.setupMicNameInInits(inits)
409
- # --- setup mic ---
410
- # make sure mic sample rate is numeric
411
- if inits['micSampleRate'].val in at.sampleRateLabels:
412
- inits['micSampleRate'].val = at.sampleRateLabels[inits['micSampleRate'].val]
413
- # substitute channel value for numeric equivalent
414
- inits['micChannels'] = {'mono': 1, 'stereo': 2, 'auto': None}[self.params['micChannels'].val]
415
- # initialise mic device
416
- code = (
417
- "# initialise microphone\n"
418
- "deviceManager.addDevice(\n"
419
- " deviceClass='psychopy.hardware.microphone.MicrophoneDevice',\n"
420
- " deviceName=%(micDeviceLabel)s,\n"
421
- " index=%(mic)s,\n"
422
- " channels=%(micChannels)s, \n"
423
- " sampleRateHz=%(micSampleRate)s, \n"
424
- " maxRecordingSize=%(micMaxRecSize)s\n"
425
- ")\n"
426
- )
427
- buff.writeOnceIndentedLines(code % inits)
428
-
429
- # --- setup camera ---
430
- # initialise camera device
431
- code = (
432
- "# initialise camera\n"
433
- "cam = deviceManager.addDevice(\n"
434
- " deviceClass='psychopy.hardware.camera.Camera',\n"
435
- " deviceName=%(deviceLabel)s,\n"
436
- " cameraLib=%(cameraLib)s, \n"
437
- " device=%(device)s, \n"
438
- " mic=%(micDeviceLabel)s, \n"
439
- " frameRate=%(frameRate)s, \n"
440
- " frameSize=%(resolution)s\n"
441
- ")\n"
442
- "cam.open()\n"
443
- "\n"
444
- )
445
- buff.writeOnceIndentedLines(code % inits)
446
-
447
156
  def writeRoutineStartCode(self, buff):
448
157
  pass
449
158
 
@@ -451,7 +160,7 @@ class CameraComponent(BaseDeviceComponent):
451
160
  inits = getInitVals(self.params)
452
161
  # Use filename with a suffix to store recordings
453
162
  code = (
454
- "# Make folder to store recordings from %(name)s\n"
163
+ "# make folder to store recordings from %(name)s\n"
455
164
  "%(name)sRecFolder = filename + '_%(name)s_recorded'\n"
456
165
  "if not os.path.isdir(%(name)sRecFolder):\n"
457
166
  " os.mkdir(%(name)sRecFolder)\n"
@@ -461,16 +170,19 @@ class CameraComponent(BaseDeviceComponent):
461
170
  def writeInitCode(self, buff):
462
171
  inits = getInitVals(self.params, "PsychoPy")
463
172
 
464
- # Create Microphone object
173
+ # if specified, get camera from device manager
465
174
  code = (
466
- "# get camera object\n"
467
- "%(name)s = deviceManager.getDevice(%(deviceLabel)s)\n"
175
+ "%(name)s = camera.Camera(\n"
176
+ " win=win,\n"
177
+ " device=%(deviceLabel)s,\n"
178
+ " mic=%(micDeviceLabel)s,\n"
179
+ ")"
468
180
  )
469
181
  buff.writeIndentedLines(code % inits)
470
182
  if self.params['saveFile']:
471
183
  code = (
472
184
  "# connect camera save method to experiment handler so it's called when data saves\n"
473
- "thisExp.connectSaveMethod(%(name)s.save, os.path.join(%(name)sRecFolder, '_recovered.mp4'), encoderLib='ffpyplayer')\n"
185
+ "thisExp.connectSaveMethod(%(name)s.save, os.path.join(%(name)sRecFolder, '_recovered.mp4'))\n"
474
186
  )
475
187
  buff.writeIndentedLines(code % inits)
476
188
 
@@ -492,25 +204,31 @@ class CameraComponent(BaseDeviceComponent):
492
204
  buff.writeIndentedLines(code % inits)
493
205
 
494
206
  def writeFrameCode(self, buff):
495
- # Start webcam at component start
207
+ # start webcam at component start
496
208
  indented = self.writeStartTestCode(buff)
497
209
  if indented:
498
210
  code = (
499
- "# Start %(name)s recording\n"
211
+ "# start %(name)s recording\n"
500
212
  "%(name)s.record()\n"
501
213
  )
502
214
  buff.writeIndentedLines(code % self.params)
503
215
  buff.setIndentLevel(-indented, relative=True)
504
216
 
505
- # Update any params while active
217
+ # update any params while active
506
218
  indented = self.writeActiveTestCode(buff)
219
+ if indented:
220
+ code = (
221
+ "# get current frame data from camera\n"
222
+ "%(name)s.poll()\n"
223
+ )
224
+ buff.writeIndentedLines(code % self.params)
507
225
  buff.setIndentLevel(-indented, relative=True)
508
226
 
509
- # Stop webcam at component stop
227
+ # stop webcam at component stop
510
228
  indented = self.writeStopTestCode(buff)
511
229
  if indented:
512
230
  code = (
513
- "# Stop %(name)s recording\n"
231
+ "# stop %(name)s recording\n"
514
232
  "%(name)s.stop()\n"
515
233
  )
516
234
  buff.writeIndentedLines(code % self.params)
@@ -557,7 +275,7 @@ class CameraComponent(BaseDeviceComponent):
557
275
  " %(name)sRecFolder, \n"
558
276
  " 'recording_%(name)s_%%s.mp4' %% data.utils.getDateStr()\n"
559
277
  ")\n"
560
- "%(name)s.save(%(name)sFilename, encoderLib='ffpyplayer')\n"
278
+ "%(name)s.save(%(name)sFilename)\n"
561
279
  "thisExp.currentLoop.addData('%(name)s.clip', %(name)sFilename)\n"
562
280
  )
563
281
  buff.writeIndentedLines(code % self.params)
@@ -599,5 +317,65 @@ class CameraComponent(BaseDeviceComponent):
599
317
  buff.writeIndentedLines(code % self.params)
600
318
 
601
319
 
320
+ class CameraDeviceBackend(DeviceBackend):
321
+ # name of this backend to display in Device Manager
322
+ backendLabel = "Camera"
323
+ # class of the device which this backend corresponds to
324
+ deviceClass = "psychopy.hardware.camera.CameraDevice"
325
+ # icon to show in device manager
326
+ icon = "light/webcam.png"
327
+
328
+ def writeDeviceCode(self, buff):
329
+ # write base setup
330
+ self.writeBaseDeviceCode(buff, close=False)
331
+ # add params
332
+ code = (
333
+ " frameRate=%(frameRate)s,\n"
334
+ " frameSize=%(frameSize)s\n"
335
+ ")"
336
+ )
337
+ buff.writeIndentedLines(code % self.params)
338
+
339
+ def getParams(self):
340
+ from psychopy.hardware.camera import CameraDevice
341
+
342
+ # get supported resolutions and framerates
343
+ resolutions = set()
344
+ frameRates = set()
345
+ for profile in CameraDevice.getAvailableDevices(best=False):
346
+ if profile['deviceName'] == self.profile['deviceName']:
347
+ resolutions.add(profile['frameSize'])
348
+ frameRates.add(profile['frameRate'])
349
+
350
+ order = [
351
+ 'frameSize',
352
+ 'frameRate',
353
+ ]
354
+ params = {}
355
+
356
+ self.params['frameSize'] = Param(
357
+ "", valType='list', inputType="choice",
358
+ allowedVals=[""] + list(sorted(resolutions)), allowedLabels=["Default"] + list(sorted(resolutions)),
359
+ hint=_translate(
360
+ "Resolution (w x h) to record to, leave blank to use device default."
361
+ ),
362
+ label=_translate("Resolution")
363
+ )
364
+ params['frameRate'] = Param(
365
+ None, valType='int', inputType="choice",
366
+ allowedVals=[""] + list(frameRates), allowedLabels=["Default"] + list(frameRates),
367
+ hint=_translate(
368
+ "Frame rate (frames per second) to record at, leave blank to use device default."
369
+ ),
370
+ label=_translate("Frame rate")
371
+ )
372
+
373
+ return params, order
374
+
375
+
376
+ # register backend with Component
377
+ CameraComponent.registerBackend(CameraDeviceBackend)
378
+
379
+
602
380
  if __name__ == "__main__":
603
381
  pass
@@ -8,7 +8,7 @@
8
8
  from pathlib import Path
9
9
 
10
10
  from psychopy.alerts._alerts import alert
11
- from psychopy.experiment.components import BaseDeviceComponent, Param, _translate, getInitVals
11
+ from psychopy.experiment.components import BaseComponent, Param, _translate, getInitVals
12
12
  from psychopy.experiment import CodeGenerationException, valid_var_re
13
13
  from pkgutil import find_loader
14
14
 
@@ -16,14 +16,17 @@ from pkgutil import find_loader
16
16
  havePTB = find_loader('psychtoolbox') is not None
17
17
 
18
18
 
19
- class KeyboardComponent(BaseDeviceComponent):
19
+ class KeyboardComponent(BaseComponent):
20
20
  """An event class for checking the keyboard at given timepoints"""
21
21
  # an attribute of the class, determines the section in components panel
22
22
  categories = ['Responses']
23
23
  targets = ['PsychoPy', 'PsychoJS']
24
24
  iconFile = Path(__file__).parent / 'keyboard.png'
25
25
  tooltip = _translate('Keyboard: check and record keypresses')
26
- deviceClasses = ["psychopy.hardware.keyboard.KeyboardDevice"]
26
+ legacyParams = [
27
+ # as there's only ever 1 keyboard, it shouldn't interact with device manager
28
+ "deviceLabel"
29
+ ]
27
30
 
28
31
  def __init__(self, exp, parentName, name='key_resp', deviceLabel="",
29
32
  allowedKeys="'y','n','left','right','space'", registerOn="press",
@@ -34,12 +37,11 @@ class KeyboardComponent(BaseDeviceComponent):
34
37
  startEstim='', durationEstim='',
35
38
  syncScreenRefresh=True,
36
39
  disabled=False):
37
- BaseDeviceComponent.__init__(
40
+ BaseComponent.__init__(
38
41
  self, exp, parentName, name,
39
42
  startType=startType, startVal=startVal,
40
43
  stopType=stopType, stopVal=stopVal,
41
44
  startEstim=startEstim, durationEstim=durationEstim,
42
- deviceLabel=deviceLabel,
43
45
  disabled=disabled
44
46
  )
45
47
 
@@ -142,27 +144,13 @@ class KeyboardComponent(BaseDeviceComponent):
142
144
  updates='constant',
143
145
  hint=msg,
144
146
  label=_translate("Sync timing with screen"))
145
-
146
- def writeDeviceCode(self, buff):
147
- # get inits
148
- inits = getInitVals(self.params)
149
- # write device creation code
150
- code = (
151
- "if deviceManager.getDevice(%(deviceLabel)s) is None:\n"
152
- " # initialise %(deviceLabelCode)s\n"
153
- " %(deviceLabelCode)s = deviceManager.addDevice(\n"
154
- " deviceClass='keyboard',\n"
155
- " deviceName=%(deviceLabel)s,\n"
156
- " )\n"
157
- )
158
- buff.writeOnceIndentedLines(code % inits)
159
-
147
+
160
148
  def writeInitCode(self, buff):
161
149
  # get inits
162
150
  inits = getInitVals(self.params)
163
151
  # make Keyboard object
164
152
  code = (
165
- "%(name)s = keyboard.Keyboard(deviceName=%(deviceLabel)s)\n"
153
+ "%(name)s = keyboard.Keyboard(deviceName='defaultKeyboard')\n"
166
154
  )
167
155
  buff.writeIndentedLines(code % inits)
168
156
 
@@ -395,9 +383,14 @@ class KeyboardComponent(BaseDeviceComponent):
395
383
  waitRelease = "false"
396
384
  if self.params['registerOn'] == "release":
397
385
  waitRelease = "true"
398
- code = ("let theseKeys = {name}.getKeys({{keyList: {keyStr}, waitRelease: {waitRelease}}});\n"
399
- "_{name}_allKeys = _{name}_allKeys.concat(theseKeys);\n"
400
- "if (_{name}_allKeys.length > 0) {{\n")
386
+ code = (
387
+ "let theseKeys = {name}.getKeys({{\n"
388
+ " keyList: typeof {keyStr} === 'string' ? [{keyStr}] : {keyStr}, \n"
389
+ " waitRelease: {waitRelease}\n"
390
+ "}});\n"
391
+ "_{name}_allKeys = _{name}_allKeys.concat(theseKeys);\n"
392
+ "if (_{name}_allKeys.length > 0) {{\n"
393
+ )
401
394
  buff.writeIndentedLines(
402
395
  code.format(
403
396
  name=self.params['name'],