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