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
psychopy/experiment/params.py
CHANGED
|
@@ -16,12 +16,13 @@ The code that writes out a *_lastrun.py experiment file is (in order):
|
|
|
16
16
|
settings.SettingsComponent.writeEndCode()
|
|
17
17
|
"""
|
|
18
18
|
import functools
|
|
19
|
+
import json
|
|
19
20
|
from xml.etree.ElementTree import Element
|
|
20
21
|
|
|
21
22
|
import re
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
|
|
24
|
-
from psychopy import logging
|
|
25
|
+
from psychopy import data, logging
|
|
25
26
|
from . import utils
|
|
26
27
|
from . import py2js
|
|
27
28
|
|
|
@@ -55,6 +56,8 @@ inputDefaults = {
|
|
|
55
56
|
# these are parameters which once existed but are no longer needed, so inclusion in this list will
|
|
56
57
|
# silence any "future version" warnings
|
|
57
58
|
legacyParams = [
|
|
59
|
+
# settings params from the early days of PsychoJS
|
|
60
|
+
"JS libs", "OSF Project ID"
|
|
58
61
|
# in 2021.1, we standardised colorSpace to be object-wide rather than param-specific
|
|
59
62
|
"lineColorSpace", "borderColorSpace", "fillColorSpace", "foreColorSpace",
|
|
60
63
|
# in 2024.2.0, we removed some superfluous params from the pupil labs backend
|
|
@@ -230,7 +233,7 @@ class Param():
|
|
|
230
233
|
return "%i" % self.val # int and float -> str(int)
|
|
231
234
|
except TypeError:
|
|
232
235
|
return "%s" % self.val # try array of float instead?
|
|
233
|
-
elif self.valType in ['extendedStr','str', 'file', 'table']:
|
|
236
|
+
elif self.valType in ['extendedStr', 'str', 'file', 'table', 'device']:
|
|
234
237
|
# at least 1 non-escaped '$' anywhere --> code wanted
|
|
235
238
|
# return str if code wanted
|
|
236
239
|
# return repr if str wanted; this neatly handles "it's" and 'He
|
|
@@ -300,13 +303,19 @@ class Param():
|
|
|
300
303
|
# Otherwise, treat as string
|
|
301
304
|
return repr(val)
|
|
302
305
|
elif self.valType == 'list':
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
if self.inputType == "fileList":
|
|
307
|
+
# treat each item as a string-type param
|
|
308
|
+
output = []
|
|
309
|
+
for item in data.utils.listFromString(self.val):
|
|
310
|
+
item = str(Param(item, "file"))
|
|
311
|
+
output.append(item)
|
|
312
|
+
return "[{}]".format(",".join(output))
|
|
313
|
+
else:
|
|
314
|
+
valid, val = self.dollarSyntax()
|
|
315
|
+
val = toList(val)
|
|
316
|
+
return "{}".format(val)
|
|
306
317
|
elif self.valType == 'fixedList':
|
|
307
318
|
return "{}".format(self.val)
|
|
308
|
-
elif self.valType == 'fileList':
|
|
309
|
-
return "{}".format(self.val)
|
|
310
319
|
elif self.valType == 'bool':
|
|
311
320
|
if utils.scriptTarget == "PsychoJS":
|
|
312
321
|
return ("%s" % self.val).lower() # make True -> "true"
|
|
@@ -372,12 +381,41 @@ class Param():
|
|
|
372
381
|
allowedLabels=self.allowedLabels,
|
|
373
382
|
direct=self.direct,
|
|
374
383
|
canBePath=self.canBePath,
|
|
375
|
-
categ=self.categ
|
|
384
|
+
categ=self.categ,
|
|
385
|
+
ctrlParams=self.ctrlParams
|
|
376
386
|
)
|
|
377
387
|
|
|
378
388
|
def __deepcopy__(self, memo):
|
|
379
389
|
return self.copy()
|
|
380
|
-
|
|
390
|
+
|
|
391
|
+
@classmethod
|
|
392
|
+
def fromJSON(cls, data):
|
|
393
|
+
# initialise
|
|
394
|
+
param = Param(
|
|
395
|
+
"",
|
|
396
|
+
"code",
|
|
397
|
+
)
|
|
398
|
+
# apply
|
|
399
|
+
param.applyJSON(data)
|
|
400
|
+
|
|
401
|
+
def applyJSON(self, data):
|
|
402
|
+
if "val" in data:
|
|
403
|
+
self.val = data['val']
|
|
404
|
+
if "valType" in data:
|
|
405
|
+
self.valType = data['valType']
|
|
406
|
+
if "updates" in data:
|
|
407
|
+
self.updates = "{}".format(data['updates'])
|
|
408
|
+
if "plugin" in data:
|
|
409
|
+
self.plugin = "{}".format(data['plugin'])
|
|
410
|
+
|
|
411
|
+
def toJSON(self):
|
|
412
|
+
return {
|
|
413
|
+
'val': self.val,
|
|
414
|
+
'valType': self.valType,
|
|
415
|
+
'updates': "{}".format(self.updates),
|
|
416
|
+
'plugin': "{}".format(self.plugin)
|
|
417
|
+
}
|
|
418
|
+
|
|
381
419
|
@property
|
|
382
420
|
def _xml(self):
|
|
383
421
|
# Make root element
|
|
@@ -425,7 +463,7 @@ class Param():
|
|
|
425
463
|
return True, val
|
|
426
464
|
else:
|
|
427
465
|
# If value does not begin with an unescaped $, treat it as a string
|
|
428
|
-
if not re.findall(r"(?<!\\)\$", val):
|
|
466
|
+
if not re.findall(r"(?<!\\)\$", str(val)):
|
|
429
467
|
# Return if all $ are escaped (\$)
|
|
430
468
|
return True, val
|
|
431
469
|
else:
|
psychopy/experiment/plugins.py
CHANGED
|
@@ -3,120 +3,40 @@ from psychopy import logging
|
|
|
3
3
|
|
|
4
4
|
class PluginDevicesMixin:
|
|
5
5
|
"""
|
|
6
|
-
|
|
7
|
-
plugins and use them to create different devices for different plugin backends.
|
|
6
|
+
Legacy placeholder class - used to handle device backends added by plugins before Builder had a dedicated device manager.
|
|
8
7
|
"""
|
|
9
|
-
|
|
10
|
-
def __init_subclass__(cls):
|
|
11
|
-
# list of backends for this component - each should be a subclass of DeviceBackend
|
|
12
|
-
cls.backends = []
|
|
13
|
-
|
|
14
|
-
def loadBackends(self):
|
|
15
|
-
# add params from backends
|
|
16
|
-
for backend in self.backends:
|
|
17
|
-
# get params using backend's method
|
|
18
|
-
params, order = backend.getParams(self)
|
|
19
|
-
# get reference to package
|
|
20
|
-
plugin = None
|
|
21
|
-
if hasattr(backend, "__module__") and backend.__module__:
|
|
22
|
-
pkg = backend.__module__.split(".")[0]
|
|
23
|
-
if pkg != "psychopy":
|
|
24
|
-
plugin = pkg
|
|
25
|
-
# add order
|
|
26
|
-
self.order.extend(order)
|
|
27
|
-
# add any params
|
|
28
|
-
for key, param in params.items():
|
|
29
|
-
if key in self.params:
|
|
30
|
-
# if this param already exists (i.e. from saved data), get the saved val
|
|
31
|
-
param.val = self.params[key].val
|
|
32
|
-
param.updates = self.params[key].updates
|
|
33
|
-
# add param
|
|
34
|
-
self.params[key] = param
|
|
35
|
-
# store plugin reference
|
|
36
|
-
self.params[key].plugin = plugin
|
|
37
|
-
|
|
38
|
-
# add dependencies so that backend params are only shown for this backend
|
|
39
|
-
for name in params:
|
|
40
|
-
self.depends.append(
|
|
41
|
-
{
|
|
42
|
-
"dependsOn": "deviceBackend", # if...
|
|
43
|
-
"condition": f"== '{backend.key}'", # meets...
|
|
44
|
-
"param": name, # then...
|
|
45
|
-
"true": "show", # should...
|
|
46
|
-
"false": "hide", # otherwise...
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
# add requirements
|
|
50
|
-
backend.addRequirements(self)
|
|
51
|
-
|
|
52
|
-
def getBackendKeys(self):
|
|
53
|
-
keys = []
|
|
54
|
-
for backend in self.backends:
|
|
55
|
-
keys.append(backend.key)
|
|
56
|
-
|
|
57
|
-
return keys
|
|
58
|
-
|
|
59
|
-
def getBackendLabels(self):
|
|
60
|
-
labels = []
|
|
61
|
-
for backend in self.backends:
|
|
62
|
-
labels.append(backend.label)
|
|
63
|
-
|
|
64
|
-
return labels
|
|
65
|
-
|
|
66
|
-
def writeDeviceCode(self, buff):
|
|
67
|
-
# write init code from backend
|
|
68
|
-
for backend in self.backends:
|
|
69
|
-
if backend.key == self.params['deviceBackend']:
|
|
70
|
-
backend.writeDeviceCode(self, buff)
|
|
8
|
+
pass
|
|
71
9
|
|
|
72
10
|
|
|
73
|
-
|
|
74
|
-
|
|
11
|
+
from .devices import DeviceBackend
|
|
12
|
+
class DeviceBackend(DeviceBackend):
|
|
13
|
+
"""
|
|
14
|
+
Legacy wrapper to convert plugins.DeviceBackend classes to the newer devices.DeviceBackend class.
|
|
15
|
+
"""
|
|
16
|
+
key = "microphone"
|
|
17
|
+
label = ""
|
|
18
|
+
component = None
|
|
19
|
+
deviceClasses = ["psychopy.hardware.soundsensor.MicrophoneSoundSensor"]
|
|
75
20
|
component = PluginDevicesMixin
|
|
76
|
-
# what value should Builder use for this backend?
|
|
77
21
|
key = ""
|
|
78
|
-
# what label should be displayed by Builder for this backend?
|
|
79
22
|
label = ""
|
|
80
|
-
# values to append to the component's deviceClasses array
|
|
81
23
|
deviceClasses = []
|
|
82
24
|
|
|
83
|
-
def __init_subclass__(cls):
|
|
84
|
-
logging.debug(
|
|
85
|
-
f"Registered backend for {cls.component.__name__}: {cls.label} ({cls.key}) from "
|
|
86
|
-
f"{cls.__module__}:{cls.__name__}"
|
|
87
|
-
)
|
|
88
|
-
# add class to list of backends for ButtonBoxComponent
|
|
89
|
-
cls.component.backends = cls.component.backends.copy()
|
|
90
|
-
cls.component.backends.append(cls)
|
|
91
|
-
# add device classes to component
|
|
92
|
-
if cls is not PluginDevicesMixin and hasattr(cls.component, "deviceClasses"):
|
|
93
|
-
for deviceClass in cls.deviceClasses:
|
|
94
|
-
if deviceClass not in cls.component.deviceClasses:
|
|
95
|
-
cls.component.deviceClasses = cls.component.deviceClasses.copy()
|
|
96
|
-
cls.component.deviceClasses.append(deviceClass)
|
|
97
|
-
|
|
98
|
-
def getParams(self):
|
|
99
|
-
"""
|
|
100
|
-
Get parameters from this backend to add to each new instance of ButtonBoxComponent
|
|
101
|
-
|
|
102
|
-
Returns
|
|
103
|
-
-------
|
|
104
|
-
dict[str:Param]
|
|
105
|
-
Dict of Param objects, which will be added to any Button Box Component's params, along
|
|
106
|
-
with a dependency to only show them when this backend is selected
|
|
107
|
-
list[str]
|
|
108
|
-
List of param names, defining the order in which params should appear
|
|
109
|
-
"""
|
|
110
|
-
raise NotImplementedError()
|
|
111
25
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
26
|
+
def __init_subclass__(cls):
|
|
27
|
+
# if component was specified by class attribute, register it
|
|
28
|
+
if cls.component is not PluginDevicesMixin:
|
|
29
|
+
cls.component.registerBackend(cls)
|
|
30
|
+
# if we can get params without initialising, add them to the Component's legacy list
|
|
31
|
+
# (as they're now parameters of the device rather than the Component)
|
|
32
|
+
try:
|
|
33
|
+
for name in cls.getParams(None)[0]:
|
|
34
|
+
cls.component.legacyParams.append(name)
|
|
35
|
+
except AttributeError:
|
|
36
|
+
pass
|
|
37
|
+
# placeholder value for icon
|
|
38
|
+
cls.icon = None
|
|
39
|
+
# use label for backendLabel
|
|
40
|
+
cls.backendLabel = cls.label
|
|
41
|
+
# a backend only has 1 device now
|
|
42
|
+
cls.deviceClass = cls.deviceClasses[0]
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# Distributed under the terms of the GNU General Public License (GPL).
|
|
9
9
|
|
|
10
10
|
from importlib import import_module
|
|
11
|
-
from ._base import BaseStandaloneRoutine, BaseValidatorRoutine, Routine
|
|
11
|
+
from ._base import BaseStandaloneRoutine, BaseDeviceRoutine, BaseValidatorRoutine, Routine
|
|
12
12
|
from .unknown import UnknownRoutine
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from psychopy import logging
|
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
from psychopy.experiment.components.static import StaticComponent
|
|
18
18
|
from psychopy.experiment.components.routineSettings import RoutineSettingsComponent
|
|
19
|
+
from psychopy.experiment.devices import DeviceMixin
|
|
19
20
|
from psychopy.localization import _translate
|
|
20
21
|
from psychopy.experiment import Param
|
|
21
22
|
|
|
@@ -32,6 +33,9 @@ class BaseStandaloneRoutine:
|
|
|
32
33
|
beta = False
|
|
33
34
|
# hide this Component in Builder view?
|
|
34
35
|
hidden = False
|
|
36
|
+
# are there any known legacy params for this Routine?
|
|
37
|
+
# these will be removed & warnings ignored on experiment load
|
|
38
|
+
legacyParams = []
|
|
35
39
|
|
|
36
40
|
def __init__(self, exp, name='',
|
|
37
41
|
stopType='duration (s)', stopVal='',
|
|
@@ -47,7 +51,7 @@ class BaseStandaloneRoutine:
|
|
|
47
51
|
msg = _translate(
|
|
48
52
|
"Name of this Routine (alphanumeric or _, no spaces)")
|
|
49
53
|
self.params['name'] = Param(name,
|
|
50
|
-
valType='code', inputType="
|
|
54
|
+
valType='code', inputType="name", categ='Basic',
|
|
51
55
|
hint=msg,
|
|
52
56
|
label=_translate('Name'))
|
|
53
57
|
|
|
@@ -374,14 +378,41 @@ class BaseStandaloneRoutine:
|
|
|
374
378
|
self.params['disabled'].val = value
|
|
375
379
|
|
|
376
380
|
|
|
377
|
-
class
|
|
381
|
+
class BaseDeviceRoutine(BaseStandaloneRoutine, DeviceMixin):
|
|
382
|
+
"""
|
|
383
|
+
Base class for most routines which interface with a hardware device.
|
|
384
|
+
"""
|
|
385
|
+
def __init__(
|
|
386
|
+
self, exp,
|
|
387
|
+
# basic
|
|
388
|
+
name='',
|
|
389
|
+
stopType='duration (s)', stopVal='',
|
|
390
|
+
# device
|
|
391
|
+
deviceLabel="",
|
|
392
|
+
# testing
|
|
393
|
+
disabled=False
|
|
394
|
+
):
|
|
395
|
+
# initialise base component
|
|
396
|
+
BaseStandaloneRoutine.__init__(
|
|
397
|
+
self, exp,
|
|
398
|
+
# basic
|
|
399
|
+
name=name,
|
|
400
|
+
stopType=stopType, stopVal=stopVal,
|
|
401
|
+
# testing
|
|
402
|
+
disabled=disabled
|
|
403
|
+
)
|
|
404
|
+
# add device stuff
|
|
405
|
+
self.addDeviceParams(
|
|
406
|
+
defaultLabel=deviceLabel
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class BaseValidatorRoutine(BaseDeviceRoutine):
|
|
378
411
|
"""
|
|
379
412
|
Subcategory of Standalone Routine, which sets up a "validator" - an object which is linked to in the Testing tab
|
|
380
413
|
of another Component and validates that the component behaved as expected. Any validator Routines should subclass
|
|
381
414
|
this rather than BaseStandaloneRoutine.
|
|
382
415
|
"""
|
|
383
|
-
# list of class strings (readable by DeviceManager) which this component's device could be
|
|
384
|
-
deviceClasses = []
|
|
385
416
|
|
|
386
417
|
def writeRoutineStartValidationCode(self, buff, stim):
|
|
387
418
|
"""
|
|
@@ -667,19 +698,23 @@ class Routine(list):
|
|
|
667
698
|
)
|
|
668
699
|
buff.writeIndentedLines(code % self.params)
|
|
669
700
|
|
|
670
|
-
code = (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
701
|
+
code = (
|
|
702
|
+
"for thisComponent in {name}.components:\n"
|
|
703
|
+
" thisComponent.tStart = None\n"
|
|
704
|
+
" thisComponent.tStop = None\n"
|
|
705
|
+
" thisComponent.tStartRefresh = None\n"
|
|
706
|
+
" thisComponent.tStopRefresh = None\n"
|
|
707
|
+
" if hasattr(thisComponent, 'status'):\n"
|
|
708
|
+
" thisComponent.status = NOT_STARTED\n"
|
|
709
|
+
"# reset timers\n"
|
|
710
|
+
't = 0\n'
|
|
711
|
+
'_timeToFirstFrame = win.getFutureFlipTime(clock="now")\n'
|
|
712
|
+
# '{clockName}.reset(-_timeToFirstFrame) # t0 is time of first possible flip\n'
|
|
713
|
+
'frameN = -1\n'
|
|
714
|
+
'\n'
|
|
715
|
+
'# --- Run Routine "{name}" ---\n'
|
|
716
|
+
'thisExp.currentRoutine = {name}\n'
|
|
717
|
+
)
|
|
683
718
|
buff.writeIndentedLines(code.format(name=self.name,
|
|
684
719
|
clockName=self._clockName))
|
|
685
720
|
|
|
@@ -760,16 +795,16 @@ class Routine(list):
|
|
|
760
795
|
# are we done yet?
|
|
761
796
|
code = (
|
|
762
797
|
'\n'
|
|
763
|
-
'#
|
|
764
|
-
'if not continueRoutine
|
|
765
|
-
'forced-end of Routine\n'
|
|
798
|
+
'# has a Component requested the Routine to end?\n'
|
|
799
|
+
'if not continueRoutine:\n'
|
|
766
800
|
' %(name)s.forceEnded = routineForceEnded = True\n'
|
|
801
|
+
'# has the Routine been forcibly ended?\n'
|
|
802
|
+
'if %(name)s.forceEnded or routineForceEnded:\n'
|
|
767
803
|
' break\n'
|
|
768
|
-
'
|
|
769
|
-
'
|
|
804
|
+
'# has every Component finished?\n'
|
|
805
|
+
'continueRoutine = False\n'
|
|
770
806
|
'for thisComponent in %(name)s.components:\n'
|
|
771
|
-
' if hasattr(thisComponent, "status") and '
|
|
772
|
-
'thisComponent.status != FINISHED:\n'
|
|
807
|
+
' if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:\n'
|
|
773
808
|
' continueRoutine = True\n'
|
|
774
809
|
' break # at least one component has not yet finished\n')
|
|
775
810
|
buff.writeIndentedLines(code % self.params)
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from psychopy.preferences import prefs
|
|
5
6
|
from psychopy.alerts._alerts import alert
|
|
6
7
|
from psychopy.experiment import Param
|
|
7
|
-
from psychopy.experiment.plugins import
|
|
8
|
+
from psychopy.experiment.plugins import DeviceBackend
|
|
8
9
|
from psychopy.experiment.components import getInitVals
|
|
9
|
-
from psychopy.experiment.routines import Routine,
|
|
10
|
+
from psychopy.experiment.routines import Routine, BaseDeviceRoutine
|
|
10
11
|
from psychopy.localization import _translate
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class AudioValidatorRoutine(
|
|
14
|
+
class AudioValidatorRoutine(BaseDeviceRoutine):
|
|
14
15
|
"""
|
|
15
16
|
Use a sound sensor (voicekey or microphone) to confirm that audio stimuli are presented when they should be.
|
|
16
17
|
"""
|
|
@@ -22,16 +23,24 @@ class AudioValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
22
23
|
"Use a sound sensor to confirm that audio stimuli are presented when they should "
|
|
23
24
|
"be."
|
|
24
25
|
)
|
|
25
|
-
deviceClasses = ["psychopy.validation.voicekey.AudioValidator"]
|
|
26
26
|
version = "2025.1.0"
|
|
27
|
+
legacyParams = [
|
|
28
|
+
# old device setup params, no longer needed as this is handled by DeviceManager
|
|
29
|
+
"deviceBackend",
|
|
30
|
+
"meMicrophone",
|
|
31
|
+
"meThreshold",
|
|
32
|
+
"meRange",
|
|
33
|
+
"meSamplingWindow",
|
|
34
|
+
]
|
|
27
35
|
|
|
28
36
|
def __init__(
|
|
29
37
|
self,
|
|
30
38
|
# basic
|
|
31
39
|
exp, name='audioVal',
|
|
32
|
-
threshold=0.5,
|
|
33
40
|
# device
|
|
34
|
-
deviceLabel="",
|
|
41
|
+
deviceLabel="", channel="0",
|
|
42
|
+
# legacy
|
|
43
|
+
threshold=0.5, deviceBackend="microphone",
|
|
35
44
|
):
|
|
36
45
|
|
|
37
46
|
self.exp = exp # so we can access the experiment if necess
|
|
@@ -43,44 +52,13 @@ class AudioValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
43
52
|
|
|
44
53
|
exp.requirePsychopyLibs(["validation"])
|
|
45
54
|
|
|
46
|
-
# --- Basic ---
|
|
47
|
-
self.order += [
|
|
48
|
-
"threshold",
|
|
49
|
-
]
|
|
50
|
-
self.params['threshold'] = Param(
|
|
51
|
-
threshold, valType="code", inputType="single", categ="Basic",
|
|
52
|
-
label=_translate("Threshold"),
|
|
53
|
-
hint=_translate(
|
|
54
|
-
"Arbitrary volume threshold at which the sound sensor should register a positive, units go from 0 (least volume) to 1 (most volume)."
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
55
|
del self.params['stopType']
|
|
58
56
|
del self.params['stopVal']
|
|
59
57
|
|
|
60
58
|
# --- Device ---
|
|
61
59
|
self.order += [
|
|
62
|
-
"deviceLabel",
|
|
63
|
-
"deviceBackend",
|
|
64
60
|
"channel",
|
|
65
61
|
]
|
|
66
|
-
self.params['deviceLabel'] = Param(
|
|
67
|
-
deviceLabel, valType="str", inputType="single", categ="Device",
|
|
68
|
-
label=_translate("Device name"),
|
|
69
|
-
hint=_translate(
|
|
70
|
-
"A name to refer to this Component's associated hardware device by. If using the "
|
|
71
|
-
"same device for multiple components, be sure to use the same name here."
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
|
-
self.params['deviceBackend'] = Param(
|
|
75
|
-
deviceBackend, valType="code", inputType="choice", categ="Device",
|
|
76
|
-
allowedVals=self.getBackendKeys,
|
|
77
|
-
allowedLabels=self.getBackendLabels,
|
|
78
|
-
label=_translate("Sound sensor type"),
|
|
79
|
-
hint=_translate(
|
|
80
|
-
"Type of sound sensor to use."
|
|
81
|
-
),
|
|
82
|
-
direct=False
|
|
83
|
-
)
|
|
84
62
|
self.params['channel'] = Param(
|
|
85
63
|
channel, valType="code", inputType="single", categ="Device",
|
|
86
64
|
label=_translate("Sound sensor channel"),
|
|
@@ -91,47 +69,14 @@ class AudioValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
91
69
|
)
|
|
92
70
|
)
|
|
93
71
|
|
|
94
|
-
self.loadBackends()
|
|
95
|
-
|
|
96
|
-
def writeDeviceCode(self, buff):
|
|
97
|
-
"""
|
|
98
|
-
Code to setup the CameraDevice for this component.
|
|
99
|
-
|
|
100
|
-
Parameters
|
|
101
|
-
----------
|
|
102
|
-
buff : io.StringIO
|
|
103
|
-
Text buffer to write code to.
|
|
104
|
-
"""
|
|
105
|
-
# do usual backend-specific device code writing
|
|
106
|
-
PluginDevicesMixin.writeDeviceCode(self, buff)
|
|
107
|
-
# get inits
|
|
108
|
-
inits = getInitVals(self.params)
|
|
109
|
-
# get device handle
|
|
110
|
-
code = (
|
|
111
|
-
"%(deviceLabelCode)s = deviceManager.getDevice(%(deviceLabel)s)\n"
|
|
112
|
-
"%(deviceLabelCode)s.setThreshold(%(threshold)s, channel=%(channel)s)\n"
|
|
113
|
-
)
|
|
114
|
-
buff.writeOnceIndentedLines(code % inits)
|
|
115
|
-
|
|
116
72
|
def writeMainCode(self, buff):
|
|
117
73
|
inits = getInitVals(self.params)
|
|
118
|
-
# get diode
|
|
119
|
-
code = (
|
|
120
|
-
"# diode object for %(name)s\n"
|
|
121
|
-
"%(name)sDevice = deviceManager.getDevice(%(deviceLabel)s)\n"
|
|
122
|
-
)
|
|
123
|
-
buff.writeIndentedLines(code % inits)
|
|
124
|
-
|
|
125
|
-
if self.params['threshold']:
|
|
126
|
-
code = (
|
|
127
|
-
"%(name)sDevice.setThreshold(%(threshold)s, channel=%(channel)s)\n"
|
|
128
|
-
)
|
|
129
|
-
buff.writeIndentedLines(code % inits)
|
|
130
74
|
# create validator object
|
|
131
75
|
code = (
|
|
132
76
|
"# validator object for %(name)s\n"
|
|
133
77
|
"%(name)s = validation.AudioValidator(\n"
|
|
134
|
-
" %(
|
|
78
|
+
" deviceManager.getDevice(%(deviceLabel)s), \n"
|
|
79
|
+
" %(channel)s,\n"
|
|
135
80
|
")\n"
|
|
136
81
|
)
|
|
137
82
|
buff.writeIndentedLines(code % inits)
|
|
@@ -258,86 +203,5 @@ class AudioValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
258
203
|
return stims
|
|
259
204
|
|
|
260
205
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Adds a microphone sound sensor emulation backend for AudioValidator, as well as acting as an
|
|
264
|
-
example for implementing other sound sensor device backends.
|
|
265
|
-
"""
|
|
266
|
-
|
|
267
|
-
key = "microphone"
|
|
268
|
-
label = _translate("Microphone")
|
|
269
|
-
component = AudioValidatorRoutine
|
|
270
|
-
deviceClasses = ["psychopy.hardware.soundsensor.MicrophoneSoundSensor"]
|
|
271
|
-
|
|
272
|
-
def getParams(self: AudioValidatorRoutine):
|
|
273
|
-
# define order
|
|
274
|
-
order = [
|
|
275
|
-
'microphone',
|
|
276
|
-
'dbRange',
|
|
277
|
-
'samplingWindow'
|
|
278
|
-
]
|
|
279
|
-
# define params
|
|
280
|
-
params = {}
|
|
281
|
-
def getDeviceIndices():
|
|
282
|
-
from psychopy.hardware.microphone import MicrophoneDevice
|
|
283
|
-
profiles = MicrophoneDevice.getAvailableDevices()
|
|
284
|
-
|
|
285
|
-
return [None] + [profile['index'] for profile in profiles]
|
|
286
|
-
|
|
287
|
-
def getDeviceNames():
|
|
288
|
-
from psychopy.hardware.microphone import MicrophoneDevice
|
|
289
|
-
profiles = MicrophoneDevice.getAvailableDevices()
|
|
290
|
-
|
|
291
|
-
return ["default"] + [profile['deviceName'] for profile in profiles]
|
|
292
|
-
|
|
293
|
-
params['microphone'] = Param(
|
|
294
|
-
None, valType='str', inputType="choice", categ="Device",
|
|
295
|
-
allowedVals=getDeviceIndices,
|
|
296
|
-
allowedLabels=getDeviceNames,
|
|
297
|
-
label=_translate("Microphone"),
|
|
298
|
-
hint=_translate(
|
|
299
|
-
"What microphone device to use?"
|
|
300
|
-
)
|
|
301
|
-
)
|
|
302
|
-
params['dbRange'] = Param(
|
|
303
|
-
(0, 1), valType="list", inputType="single", categ="Device",
|
|
304
|
-
label=_translate("Decibel range"),
|
|
305
|
-
hint=_translate(
|
|
306
|
-
"Range of possible decibels to expect mic responses to be in, by default (0, 1)"
|
|
307
|
-
)
|
|
308
|
-
)
|
|
309
|
-
params['samplingWindow'] = Param(
|
|
310
|
-
0.03, valType="code", inputType="single", categ="Device",
|
|
311
|
-
label=_translate("Sampling window"),
|
|
312
|
-
hint=_translate(
|
|
313
|
-
"How long (s) to average samples from the microphone across? Larger sampling "
|
|
314
|
-
"windows reduce the chance of random spikes, but also reduce sensitivity."
|
|
315
|
-
)
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
return params, order
|
|
319
|
-
|
|
320
|
-
def addRequirements(self):
|
|
321
|
-
# needs microphone
|
|
322
|
-
self.exp.requireImport(
|
|
323
|
-
importName="MicrophoneDevice",
|
|
324
|
-
importFrom="psychopy.hardware.microphone"
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
def writeDeviceCode(self: AudioValidatorRoutine, buff):
|
|
328
|
-
# get inits
|
|
329
|
-
inits = getInitVals(self.params)
|
|
330
|
-
# make MicrophoneVoiceKey object
|
|
331
|
-
code = (
|
|
332
|
-
"%(name)sDevice = MicrophoneDevice(\n"
|
|
333
|
-
" index=%(microphone)s\n"
|
|
334
|
-
")\n"
|
|
335
|
-
"deviceManager.addDevice(\n"
|
|
336
|
-
" deviceClass='psychopy.hardware.soundsensor.MicrophoneSoundSensor',\n"
|
|
337
|
-
" deviceName=%(deviceLabel)s,\n"
|
|
338
|
-
" device=%(name)sDevice, \n"
|
|
339
|
-
" dbRange=%(dbRange)s, \n"
|
|
340
|
-
" samplingWindow=%(samplingWindow)s, \n"
|
|
341
|
-
")\n"
|
|
342
|
-
)
|
|
343
|
-
buff.writeOnceIndentedLines(code % inits)
|
|
206
|
+
from ...components.soundsensor import MicrophoneSoundSensorBackend
|
|
207
|
+
AudioValidatorRoutine.registerBackend(MicrophoneSoundSensorBackend)
|