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.
- psychopy/VERSION +1 -1
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/_psychopyApp.py +35 -13
- psychopy/app/builder/builder.py +88 -35
- psychopy/app/builder/dialogs/__init__.py +69 -220
- psychopy/app/builder/dialogs/dlgsCode.py +29 -8
- psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
- psychopy/app/builder/validators.py +25 -17
- psychopy/app/coder/coder.py +12 -1
- psychopy/app/coder/repl.py +5 -2
- psychopy/app/colorpicker/__init__.py +1 -1
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/idle.py +7 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
- psychopy/app/plugin_manager/dialog.py +12 -3
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +203 -63
- psychopy/app/plugin_manager/plugins.py +120 -240
- psychopy/app/preferencesDlg.py +6 -1
- psychopy/app/psychopyApp.py +16 -4
- psychopy/app/runner/runner.py +10 -2
- psychopy/app/runner/scriptProcess.py +8 -3
- psychopy/app/stdout/stdOutRich.py +11 -4
- psychopy/app/themes/icons.py +3 -0
- psychopy/app/utils.py +61 -0
- psychopy/colors.py +10 -5
- psychopy/data/experiment.py +133 -23
- psychopy/data/routine.py +12 -0
- psychopy/data/staircase.py +42 -20
- psychopy/data/trial.py +20 -12
- psychopy/data/utils.py +43 -3
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
- psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/event.py +20 -15
- psychopy/experiment/_experiment.py +86 -10
- psychopy/experiment/components/__init__.py +3 -10
- psychopy/experiment/components/_base.py +9 -20
- psychopy/experiment/components/button/__init__.py +1 -1
- psychopy/experiment/components/buttonBox/__init__.py +50 -54
- psychopy/experiment/components/camera/__init__.py +137 -359
- psychopy/experiment/components/keyboard/__init__.py +17 -24
- psychopy/experiment/components/microphone/__init__.py +61 -110
- psychopy/experiment/components/movie/__init__.py +2 -3
- psychopy/experiment/components/serialOut/__init__.py +192 -93
- psychopy/experiment/components/settings/__init__.py +45 -27
- psychopy/experiment/components/sound/__init__.py +82 -73
- psychopy/experiment/components/soundsensor/__init__.py +43 -80
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/exports.py +20 -18
- psychopy/experiment/flow.py +7 -0
- psychopy/experiment/loops.py +47 -29
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +48 -10
- psychopy/experiment/plugins.py +28 -108
- psychopy/experiment/py2js_transpiler.py +1 -1
- psychopy/experiment/routines/__init__.py +1 -1
- psychopy/experiment/routines/_base.py +59 -24
- psychopy/experiment/routines/audioValidator/__init__.py +19 -155
- psychopy/experiment/routines/visualValidator/__init__.py +25 -25
- psychopy/hardware/__init__.py +20 -57
- psychopy/hardware/button.py +15 -2
- psychopy/hardware/camera/__init__.py +2237 -1394
- psychopy/hardware/joystick/__init__.py +1 -1
- psychopy/hardware/keyboard.py +5 -8
- psychopy/hardware/listener.py +4 -1
- psychopy/hardware/manager.py +75 -35
- psychopy/hardware/microphone.py +53 -7
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/photometer/__init__.py +156 -117
- psychopy/hardware/serialdevice.py +16 -2
- psychopy/hardware/soundsensor.py +4 -1
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
- psychopy/iohub/devices/keyboard/darwin.py +8 -5
- psychopy/iohub/util/__init__.py +7 -8
- psychopy/localization/generateTranslationTemplate.py +208 -116
- psychopy/localization/messages.pot +4305 -3502
- psychopy/monitors/MonitorCenter.py +174 -74
- psychopy/plugins/__init__.py +6 -4
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/preferences.py +35 -11
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +1 -1
- psychopy/session.py +34 -38
- psychopy/sound/__init__.py +6 -260
- psychopy/sound/audioclip.py +164 -0
- psychopy/sound/backend_ptb.py +8 -0
- psychopy/sound/backend_pygame.py +10 -0
- psychopy/sound/backend_pysound.py +9 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/microphone.py +3 -0
- psychopy/sound/sound.py +58 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
- psychopy/tests/test_data/test_utils.py +5 -1
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
- psychopy/tests/test_hardware/test_ports.py +0 -12
- psychopy/tests/test_tools/test_stringtools.py +1 -1
- psychopy/tools/attributetools.py +12 -5
- psychopy/tools/fontmanager.py +17 -14
- psychopy/tools/gltools.py +4 -2
- psychopy/tools/movietools.py +43 -2
- psychopy/tools/stringtools.py +33 -8
- psychopy/tools/versionchooser.py +1 -1
- psychopy/validation/audio.py +5 -1
- psychopy/validation/visual.py +5 -1
- psychopy/visual/basevisual.py +8 -7
- psychopy/visual/circle.py +2 -2
- psychopy/visual/helpers.py +3 -1
- psychopy/visual/image.py +29 -109
- psychopy/visual/movies/__init__.py +1800 -313
- psychopy/visual/polygon.py +4 -0
- psychopy/visual/shape.py +2 -2
- psychopy/visual/window.py +35 -12
- psychopy/voicekey/__init__.py +41 -669
- psychopy/voicekey/labjack_vks.py +7 -48
- psychopy/voicekey/parallel_vks.py +7 -42
- psychopy/voicekey/vk_tools.py +114 -263
- {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
- {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
- {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
- psychopy/visual/movies/players/__init__.py +0 -62
- psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
- psychopy/voicekey/demo_vks.py +0 -12
- psychopy/voicekey/signal.py +0 -42
- {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
- {psychopy-2025.1.0.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.
|
|
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
|
-
"
|
|
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="
|
|
318
|
-
|
|
128
|
+
micDeviceLabel, valType="device", inputType="device", categ="Device",
|
|
129
|
+
allowedVals=getMicValues,
|
|
130
|
+
allowedLabels=getMicLabels,
|
|
131
|
+
label=_translate("Microphone device"),
|
|
319
132
|
hint=_translate(
|
|
320
|
-
"
|
|
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
|
-
"#
|
|
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
|
-
#
|
|
173
|
+
# if specified, get camera from device manager
|
|
465
174
|
code = (
|
|
466
|
-
"
|
|
467
|
-
"
|
|
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')
|
|
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
|
-
#
|
|
207
|
+
# start webcam at component start
|
|
496
208
|
indented = self.writeStartTestCode(buff)
|
|
497
209
|
if indented:
|
|
498
210
|
code = (
|
|
499
|
-
"#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
227
|
+
# stop webcam at component stop
|
|
510
228
|
indented = self.writeStopTestCode(buff)
|
|
511
229
|
if indented:
|
|
512
230
|
code = (
|
|
513
|
-
"#
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 = (
|
|
399
|
-
|
|
400
|
-
|
|
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'],
|