psychopy 2024.1.3__py3-none-any.whl → 2024.2.0__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/.DS_Store +0 -0
- psychopy/CHANGELOG.txt +206 -0
- psychopy/GIT_SHA +1 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +77 -15
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/__init__.py +76 -2
- psychopy/app/_psychopyApp.py +126 -101
- psychopy/app/builder/builder.py +14 -10
- psychopy/app/builder/dialogs/__init__.py +8 -8
- psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
- psychopy/app/builder/dialogs/paramCtrls.py +24 -57
- psychopy/app/builder/localizedStrings.py +11 -9
- psychopy/app/builder/validators.py +2 -2
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +4 -4
- psychopy/app/connections/sendusage.py +2 -2
- psychopy/app/connections/updates.py +9 -9
- psychopy/app/dialogs.py +34 -2
- psychopy/app/idle.py +31 -0
- psychopy/app/jobs.py +21 -3
- psychopy/app/linuxconfig/__init__.py +9 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1258 -1176
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
- psychopy/app/pavlovia_ui/_base.py +33 -3
- psychopy/app/pavlovia_ui/search.py +0 -1
- psychopy/app/plugin_manager/dialog.py +104 -51
- psychopy/app/plugin_manager/packages.py +5 -0
- psychopy/app/plugin_manager/plugins.py +152 -67
- psychopy/app/preferencesDlg.py +8 -8
- psychopy/app/psychopyApp.py +11 -5
- psychopy/app/ribbon.py +124 -14
- psychopy/app/runner/runner.py +6 -1
- psychopy/app/stdout/stdOutRich.py +27 -11
- psychopy/app/themes/icons.py +52 -2
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +2 -2
- psychopy/colors.py +2 -1
- psychopy/compatibility.py +53 -1
- psychopy/contrib/.DS_Store +0 -0
- psychopy/contrib/configobj/__init__.py +10 -8
- psychopy/data/__init__.py +3 -2
- psychopy/data/base.py +5 -5
- psychopy/data/experiment.py +130 -4
- psychopy/data/routine.py +56 -0
- psychopy/data/staircase.py +2 -2
- psychopy/data/trial.py +559 -97
- psychopy/data/utils.py +56 -21
- psychopy/demos/.DS_Store +0 -0
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +4 -4
- psychopy/demos/builder/Hardware/.DS_Store +0 -0
- psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
- psychopy/demos/coder/iohub/.DS_Store +0 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/event.py +30 -29
- psychopy/experiment/.DS_Store +0 -0
- psychopy/experiment/_experiment.py +6 -6
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/__init__.py +6 -3
- psychopy/experiment/components/_base.py +286 -131
- psychopy/experiment/components/aperture/.DS_Store +0 -0
- psychopy/experiment/components/brush/.DS_Store +0 -0
- psychopy/experiment/components/button/.DS_Store +0 -0
- psychopy/experiment/components/button/__init__.py +5 -1
- psychopy/experiment/components/buttonBox/.DS_Store +0 -0
- psychopy/experiment/components/buttonBox/__init__.py +21 -12
- psychopy/experiment/components/camera/.DS_Store +0 -0
- psychopy/experiment/components/code/.DS_Store +0 -0
- psychopy/experiment/components/dots/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/form/__init__.py +6 -2
- psychopy/experiment/components/grating/.DS_Store +0 -0
- psychopy/experiment/components/grating/__init__.py +14 -3
- psychopy/experiment/components/image/.DS_Store +0 -0
- psychopy/experiment/components/image/__init__.py +14 -3
- psychopy/experiment/components/joyButtons/.DS_Store +0 -0
- psychopy/experiment/components/joystick/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/__init__.py +22 -10
- psychopy/experiment/components/microphone/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +59 -39
- psychopy/experiment/components/mouse/.DS_Store +0 -0
- psychopy/experiment/components/mouse/__init__.py +44 -29
- psychopy/experiment/components/movie/.DS_Store +0 -0
- psychopy/experiment/components/movie/__init__.py +1 -1
- psychopy/experiment/components/panorama/.DS_Store +0 -0
- psychopy/experiment/components/parallelOut/.DS_Store +0 -0
- psychopy/experiment/components/patch/.DS_Store +0 -0
- psychopy/experiment/components/polygon/.DS_Store +0 -0
- psychopy/experiment/components/polygon/__init__.py +26 -6
- psychopy/experiment/components/progress/.DS_Store +0 -0
- psychopy/experiment/components/progress/__init__.py +1 -1
- psychopy/experiment/components/ratingScale/.DS_Store +0 -0
- psychopy/experiment/components/resourceManager/.DS_Store +0 -0
- psychopy/experiment/components/roi/.DS_Store +0 -0
- psychopy/experiment/components/roi/__init__.py +5 -0
- psychopy/experiment/components/routineSettings/.DS_Store +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +57 -10
- psychopy/experiment/components/serialOut/.DS_Store +0 -0
- psychopy/experiment/components/settings/.DS_Store +0 -0
- psychopy/experiment/components/settings/__init__.py +117 -42
- psychopy/experiment/components/slider/.DS_Store +0 -0
- psychopy/experiment/components/sound/.DS_Store +0 -0
- psychopy/experiment/components/sound/__init__.py +54 -19
- psychopy/experiment/components/static/.DS_Store +0 -0
- psychopy/experiment/components/static/__init__.py +1 -1
- psychopy/experiment/components/text/.DS_Store +0 -0
- psychopy/experiment/components/text/__init__.py +28 -3
- psychopy/experiment/components/textbox/.DS_Store +0 -0
- psychopy/experiment/components/textbox/__init__.py +12 -2
- psychopy/experiment/components/unknown/.DS_Store +0 -0
- psychopy/experiment/components/unknown/__init__.py +1 -2
- psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
- psychopy/experiment/components/variable/.DS_Store +0 -0
- psychopy/experiment/flow.py +11 -4
- psychopy/experiment/loops.py +85 -37
- psychopy/experiment/params.py +74 -32
- psychopy/experiment/py2js_transpiler.py +8 -1
- psychopy/experiment/routines/.DS_Store +0 -0
- psychopy/experiment/routines/_base.py +102 -22
- psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +5 -1
- psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
- psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
- psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/__init__.py +7 -6
- psychopy/experiment/routines/unknown/.DS_Store +0 -0
- psychopy/gui/wxgui.py +4 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/__init__.py +1 -1
- psychopy/hardware/base.py +12 -0
- psychopy/hardware/camera/__init__.py +1 -15
- psychopy/hardware/cedrus.py +10 -11
- psychopy/hardware/crs/colorcal.py +13 -22
- psychopy/hardware/crs/optical.py +10 -20
- psychopy/hardware/emulator.py +17 -14
- psychopy/hardware/eyetracker.py +42 -118
- psychopy/hardware/gammasci.py +4 -15
- psychopy/hardware/keyboard.py +102 -11
- psychopy/hardware/listener.py +3 -0
- psychopy/hardware/microphone.py +148 -18
- psychopy/hardware/minolta.py +8 -15
- psychopy/hardware/photodiode.py +191 -16
- psychopy/hardware/photometer/__init__.py +11 -19
- psychopy/hardware/pr.py +8 -15
- psychopy/hardware/speaker.py +39 -4
- psychopy/info.py +0 -71
- psychopy/iohub/.DS_Store +0 -0
- psychopy/iohub/__init__.py +1 -1
- psychopy/iohub/client/__init__.py +30 -20
- psychopy/iohub/client/keyboard.py +24 -24
- psychopy/iohub/datastore/__init__.py +2 -2
- psychopy/iohub/datastore/util.py +2 -2
- psychopy/iohub/default_config.yaml +1 -1
- psychopy/iohub/devices/.DS_Store +0 -0
- psychopy/iohub/devices/__init__.py +112 -25
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
- psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
- psychopy/iohub/devices/eyetracker/__init__.py +46 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
- psychopy/iohub/server.py +2 -2
- psychopy/iohub/start_iohub_process.py +3 -0
- psychopy/iohub/util/__init__.py +62 -70
- psychopy/layout.py +5 -5
- psychopy/logging.py +8 -1
- psychopy/microphone.py +10 -37
- psychopy/platform_specific/__init__.py +0 -2
- psychopy/platform_specific/darwin.py +1 -3
- psychopy/platform_specific/linux.py +31 -33
- psychopy/platform_specific/win32.py +38 -13
- psychopy/plugins/__init__.py +148 -116
- psychopy/plugins/util.py +39 -0
- psychopy/preferences/Darwin.spec +4 -2
- psychopy/preferences/FreeBSD.spec +4 -2
- psychopy/preferences/Linux.spec +4 -2
- psychopy/preferences/Windows.spec +4 -2
- psychopy/preferences/baseNoArch.spec +4 -2
- psychopy/preferences/preferences.py +47 -24
- psychopy/projects/pavlovia.py +47 -4
- psychopy/scripts/psyexpCompile.py +0 -4
- psychopy/session.py +153 -21
- psychopy/sound/__init__.py +31 -21
- psychopy/sound/_base.py +20 -3
- psychopy/sound/audioclip.py +320 -33
- psychopy/sound/backend_ptb.py +47 -58
- psychopy/sound/backend_pygame.py +1 -1
- psychopy/sound/backend_pysound.py +6 -15
- psychopy/sound/transcribe.py +53 -0
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_session/.DS_Store +0 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/conftest.py +2 -2
- psychopy/tests/test_app/test_speed.py +4 -1
- psychopy/tests/test_data/test_TrialHandler2.py +146 -1
- psychopy/tests/test_experiment/.DS_Store +0 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
- psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
- psychopy/tests/test_experiment/test_py2js.py +1 -1
- psychopy/tests/test_hardware/test_keyboard.py +184 -16
- psychopy/tests/test_hardware/test_ports.py +1 -11
- psychopy/tests/test_liaison/test_Liaison.py +47 -0
- psychopy/tests/test_misc/test_core.py +5 -0
- psychopy/tests/test_session/test_Session.py +5 -1
- psychopy/tests/test_tools/test_versionchooser.py +39 -8
- psychopy/tests/test_visual/test_all_stimuli.py +0 -97
- psychopy/tests/test_visual/test_image.py +6 -5
- psychopy/tests/test_visual/test_textbox.py +36 -0
- psychopy/tests/utils.py +4 -0
- psychopy/tools/filetools.py +1 -1
- psychopy/tools/pkgtools.py +160 -137
- psychopy/tools/versionchooser.py +10 -10
- psychopy/tools/wizard.py +3 -3
- psychopy/visual/.DS_Store +0 -0
- psychopy/visual/backends/pygletbackend.py +24 -13
- psychopy/visual/basevisual.py +5 -11
- psychopy/visual/button.py +2 -14
- psychopy/visual/helpers.py +5 -5
- psychopy/visual/line.py +1 -2
- psychopy/visual/movie2.py +7 -816
- psychopy/visual/movie3.py +7 -589
- psychopy/visual/movies/__init__.py +8 -11
- psychopy/visual/movies/frame.py +5 -2
- psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
- psychopy/visual/noise.py +8 -7
- psychopy/visual/patch.py +7 -16
- psychopy/visual/progress.py +1 -1
- psychopy/visual/radial.py +9 -7
- psychopy/visual/ratingscale.py +8 -1415
- psychopy/visual/secondorder.py +10 -9
- psychopy/visual/shape.py +7 -2
- psychopy/visual/text.py +1 -1
- psychopy/visual/textbox2/textbox2.py +28 -5
- psychopy/web.py +5 -2
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/RECORD +313 -219
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
- psychopy/app/Resources/click.png +0 -0
- psychopy/app/Resources/next.png +0 -0
- psychopy/experiment/components/patch/__init__.py +0 -121
- psychopy/experiment/components/patch/classic/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
- psychopy/experiment/components/patch/light/patch.png +0 -0
- psychopy/experiment/components/patch/light/patch@2x.png +0 -0
- psychopy/experiment/components/ratingScale/__init__.py +0 -337
- psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
- psychopy/platform_specific/posix.py +0 -16
- psychopy/tests/test_sound/test_microphone.py +0 -217
- psychopy/tests/test_visual/test_ratingScale.py +0 -299
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
- /psychopy/{app/Resources → assets}/USB-C.png +0 -0
- /psychopy/{app/Resources → assets}/USB.png +0 -0
- /psychopy/{app/Resources → assets}/creditCard.png +0 -0
- /psychopy/{app/Resources → assets}/default.mp3 +0 -0
- /psychopy/{app/Resources → assets}/default.mp4 +0 -0
- /psychopy/{app/Resources → assets}/default.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
psychopy/gui/wxgui.py
CHANGED
|
@@ -13,11 +13,11 @@ import wx
|
|
|
13
13
|
import numpy
|
|
14
14
|
import os
|
|
15
15
|
from psychopy.localization import _translate
|
|
16
|
-
from
|
|
16
|
+
from packaging.version import Version
|
|
17
17
|
|
|
18
18
|
OK = wx.ID_OK
|
|
19
19
|
|
|
20
|
-
thisVer =
|
|
20
|
+
thisVer = Version(wx.__version__)
|
|
21
21
|
|
|
22
22
|
def ensureWxApp():
|
|
23
23
|
# make sure there's a wxApp prior to showing a gui, e.g., for expInfo
|
|
@@ -26,9 +26,9 @@ def ensureWxApp():
|
|
|
26
26
|
wx.Dialog(None, -1) # not shown; FileDialog gives same exception
|
|
27
27
|
return True
|
|
28
28
|
except wx._core.PyNoAppError:
|
|
29
|
-
if thisVer <
|
|
29
|
+
if thisVer < Version('2.9'):
|
|
30
30
|
return wx.PySimpleApp()
|
|
31
|
-
elif thisVer >=
|
|
31
|
+
elif thisVer >= Version('4.0') and thisVer < Version('4.1'):
|
|
32
32
|
raise Exception(
|
|
33
33
|
"wx>=4.0 clashes with pyglet and making it unsafe "
|
|
34
34
|
"as a PsychoPy gui helper. Please install PyQt (4 or 5)"
|
|
Binary file
|
psychopy/hardware/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ from itertools import chain
|
|
|
7
7
|
from psychopy import logging
|
|
8
8
|
from . import eyetracker, listener
|
|
9
9
|
from .manager import DeviceManager, deviceManager
|
|
10
|
-
from .base import BaseDevice
|
|
10
|
+
from .base import BaseDevice, BaseResponse, BaseResponseDevice
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
from collections.abc import Iterable
|
psychopy/hardware/base.py
CHANGED
|
@@ -14,6 +14,7 @@ __all__ = [
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
import inspect
|
|
17
|
+
import time
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class BaseResponse:
|
|
@@ -170,6 +171,17 @@ class BaseResponseDevice(BaseDevice):
|
|
|
170
171
|
"""
|
|
171
172
|
pass
|
|
172
173
|
|
|
174
|
+
def hasUnfinishedMessage(self):
|
|
175
|
+
"""
|
|
176
|
+
If there is a message which have been partially received but not finished (e.g.
|
|
177
|
+
getting the start of a message from a serial device but no end of line character
|
|
178
|
+
yet), this will return True.
|
|
179
|
+
|
|
180
|
+
If not implemented or not relevant on a given device (e.g. Keyboard, which only
|
|
181
|
+
sends full messages), this will always return False.
|
|
182
|
+
"""
|
|
183
|
+
return False
|
|
184
|
+
|
|
173
185
|
def parseMessage(self, message):
|
|
174
186
|
raise NotImplementedError(
|
|
175
187
|
"All subclasses of BaseDevice must implement the method `parseMessage`"
|
|
@@ -1725,7 +1725,7 @@ class Camera:
|
|
|
1725
1725
|
|
|
1726
1726
|
# current camera frame since the start of recording
|
|
1727
1727
|
self._player = None # media player instance
|
|
1728
|
-
self.
|
|
1728
|
+
self.status = NOT_STARTED
|
|
1729
1729
|
self._isRecording = False
|
|
1730
1730
|
self._bufferSecs = float(bufferSecs)
|
|
1731
1731
|
self._lastFrame = None # use None to avoid imports for ImageStim
|
|
@@ -1943,20 +1943,6 @@ class Camera:
|
|
|
1943
1943
|
"""
|
|
1944
1944
|
return getCameraDescriptions(collapse=collapse)
|
|
1945
1945
|
|
|
1946
|
-
@property
|
|
1947
|
-
def status(self):
|
|
1948
|
-
"""Status flag for the camera (`int`).
|
|
1949
|
-
|
|
1950
|
-
Can be either `RECORDING`, `STOPPED`, `STOPPING`, or `NOT_STARTED`. This
|
|
1951
|
-
property used in Builder output scripts and does not update on its own.
|
|
1952
|
-
|
|
1953
|
-
"""
|
|
1954
|
-
return self._status
|
|
1955
|
-
|
|
1956
|
-
@status.setter
|
|
1957
|
-
def status(self, value):
|
|
1958
|
-
self._status = value
|
|
1959
|
-
|
|
1960
1946
|
@property
|
|
1961
1947
|
def device(self):
|
|
1962
1948
|
"""Camera to use (`str` or `None`).
|
psychopy/hardware/cedrus.py
CHANGED
|
@@ -19,18 +19,17 @@ instead (bundled with Standalone PsychoPy)::
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import psychopy.logging as logging
|
|
22
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RB730(
|
|
26
|
+
PluginStub,
|
|
27
|
+
plugin="psychopy-cedrus",
|
|
28
|
+
doclink="https://psychopy.github.io/psychopy-cedrus/coder/RB730"
|
|
29
|
+
):
|
|
30
|
+
pass
|
|
31
|
+
|
|
22
32
|
|
|
23
|
-
try:
|
|
24
|
-
from psychopy_cedrus import RB730
|
|
25
|
-
except (ModuleNotFoundError, ImportError):
|
|
26
|
-
logging.error(
|
|
27
|
-
"Support for Cedrus Corporation hardware is not available this "
|
|
28
|
-
"session. Please install `psychopy-cedrus` and restart the session "
|
|
29
|
-
"to enable support.")
|
|
30
|
-
except Exception as e:
|
|
31
|
-
logging.error(
|
|
32
|
-
"Error encountered while loading `psychopy-cedrus`. Check logs for "
|
|
33
|
-
"more information.")
|
|
34
33
|
|
|
35
34
|
if __name__ == "__main__":
|
|
36
35
|
pass
|
|
@@ -5,26 +5,17 @@
|
|
|
5
5
|
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
|
|
6
6
|
# Distributed under the terms of the GNU General Public License (GPL).
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
logging.error(
|
|
14
|
-
"Support for Cambridge Research Systems ColorCAL is not available this "
|
|
15
|
-
"session. Please install `psychopy-crs` and restart the session to "
|
|
16
|
-
"enable support.")
|
|
17
|
-
except Exception as e:
|
|
18
|
-
logging.error(
|
|
19
|
-
"Error encountered while loading `psychopy-crs`. Check logs for more "
|
|
20
|
-
"information.")
|
|
21
|
-
else:
|
|
22
|
-
# Monkey-patch our metadata into CRS class if missing required attributes
|
|
23
|
-
if not hasattr(ColorCAL, "longName"):
|
|
24
|
-
setattr(ColorCAL, "longName", "CRS ColorCAL")
|
|
25
|
-
|
|
26
|
-
if not hasattr(ColorCAL, "driverFor"):
|
|
27
|
-
setattr(ColorCAL, "driverFor", ["colorcal"])
|
|
28
|
-
|
|
29
|
-
if __name__ == "__main__":
|
|
8
|
+
|
|
9
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ColorCAL(PluginStub, plugin="psychopy-crs", doclink="https://psychopy.github.io/psychopy-crs/coder/ColorCAL"):
|
|
30
13
|
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Monkey-patch our metadata into CRS class if missing required attributes
|
|
17
|
+
if not hasattr(ColorCAL, "longName"):
|
|
18
|
+
setattr(ColorCAL, "longName", "CRS ColorCAL")
|
|
19
|
+
|
|
20
|
+
if not hasattr(ColorCAL, "driverFor"):
|
|
21
|
+
setattr(ColorCAL, "driverFor", ["colorcal"])
|
psychopy/hardware/crs/optical.py
CHANGED
|
@@ -21,26 +21,16 @@
|
|
|
21
21
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
22
|
# THE SOFTWARE.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
25
25
|
|
|
26
|
-
try:
|
|
27
|
-
from psychopy_crs.optical import OptiCAL
|
|
28
|
-
except (ModuleNotFoundError, ImportError):
|
|
29
|
-
logging.error(
|
|
30
|
-
"Support for Cambridge Research Systems OptiCAL is not available this "
|
|
31
|
-
"session. Please install `psychopy-crs` and restart the session to "
|
|
32
|
-
"enable support.")
|
|
33
|
-
except Exception as e:
|
|
34
|
-
logging.error(
|
|
35
|
-
"Error encountered while loading `psychopy-crs`. Check logs for more "
|
|
36
|
-
"information.")
|
|
37
|
-
else:
|
|
38
|
-
# Monkey-patch our metadata into CRS class if missing required attributes
|
|
39
|
-
if not hasattr(OptiCAL, "longName"):
|
|
40
|
-
setattr(OptiCAL, "longName", "CRS OptiCal")
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
setattr(OptiCAL, "driverFor", ["optical"])
|
|
44
|
-
|
|
45
|
-
if __name__ == "__main__":
|
|
27
|
+
class OptiCAL(PluginStub, plugin="psychopy-crs", doclink="https://psychopy.github.io/psychopy-crs/coder/OptiCAL"):
|
|
46
28
|
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Monkey-patch our metadata into CRS class if missing required attributes
|
|
32
|
+
if not hasattr(OptiCAL, "longName"):
|
|
33
|
+
setattr(OptiCAL, "longName", "CRS OptiCal")
|
|
34
|
+
|
|
35
|
+
if not hasattr(OptiCAL, "driverFor"):
|
|
36
|
+
setattr(OptiCAL, "driverFor", ["optical"])
|
psychopy/hardware/emulator.py
CHANGED
|
@@ -18,20 +18,23 @@ These are optional components that can be obtained by installing the
|
|
|
18
18
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
|
|
22
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SyncGenerator(
|
|
26
|
+
PluginStub,
|
|
27
|
+
plugin="psychopy-mri-emulator"
|
|
28
|
+
):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResponseEmulator(
|
|
33
|
+
PluginStub,
|
|
34
|
+
plugin="psychopy-mri-emulator"
|
|
35
|
+
):
|
|
36
|
+
pass
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
if __name__ == "__main__":
|
|
37
40
|
pass
|
psychopy/hardware/eyetracker.py
CHANGED
|
@@ -1,48 +1,46 @@
|
|
|
1
1
|
from psychopy.constants import STARTED, NOT_STARTED, PAUSED, STOPPED, FINISHED
|
|
2
2
|
from psychopy.alerts import alert
|
|
3
3
|
from psychopy import logging
|
|
4
|
+
from psychopy.iohub.devices import importDeviceModule
|
|
4
5
|
from psychopy.tools.attributetools import AttributeGetSetMixin
|
|
5
6
|
from copy import copy
|
|
7
|
+
import importlib
|
|
6
8
|
import sys
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class EyetrackerControl(AttributeGetSetMixin):
|
|
10
|
-
currentlyRecording = False
|
|
11
12
|
|
|
12
13
|
def __init__(self, tracker, actionType="Start and Stop"):
|
|
13
14
|
self.tracker = tracker
|
|
14
15
|
self.actionType = actionType
|
|
15
|
-
self.
|
|
16
|
+
self.status = NOT_STARTED
|
|
17
|
+
|
|
18
|
+
def start(self):
|
|
19
|
+
"""
|
|
20
|
+
Start recording
|
|
21
|
+
"""
|
|
22
|
+
# if previously at a full stop, clear events
|
|
23
|
+
if not self.tracker.isRecordingEnabled():
|
|
24
|
+
logging.exp("eyetracker.clearEvents()")
|
|
25
|
+
self.tracker.clearEvents()
|
|
26
|
+
# start recording
|
|
27
|
+
self.tracker.setRecordingState(True)
|
|
28
|
+
logging.exp("eyetracker.setRecordingState(True)")
|
|
29
|
+
|
|
30
|
+
def stop(self):
|
|
31
|
+
"""
|
|
32
|
+
Stop recording
|
|
33
|
+
"""
|
|
34
|
+
self.tracker.setRecordingState(False)
|
|
35
|
+
logging.exp("eyetracker.setRecordingState(False)")
|
|
16
36
|
|
|
17
37
|
@property
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
new = self._status = value
|
|
25
|
-
# Skip if there's no change
|
|
26
|
-
if new == old:
|
|
27
|
-
return
|
|
28
|
-
# Start recording if set to STARTED
|
|
29
|
-
if new in (STARTED,):
|
|
30
|
-
if old in (NOT_STARTED, STOPPED, FINISHED):
|
|
31
|
-
# If was previously at a full stop, clear events before starting again
|
|
32
|
-
if self.actionType.find('Start') >= 0 and EyetrackerControl.currentlyRecording is False:
|
|
33
|
-
logging.exp("eyetracker.clearEvents()")
|
|
34
|
-
self.tracker.clearEvents()
|
|
35
|
-
# Start recording
|
|
36
|
-
if self.actionType.find('Start') >= 0 and not EyetrackerControl.currentlyRecording:
|
|
37
|
-
self.tracker.setRecordingState(True)
|
|
38
|
-
logging.exp("eyetracker.setRecordingState(True)")
|
|
39
|
-
EyetrackerControl.currentlyRecording = True
|
|
40
|
-
# Stop recording if set to any stop constants
|
|
41
|
-
if new in (NOT_STARTED, PAUSED, STOPPED, FINISHED):
|
|
42
|
-
if self.actionType.find('Stop') >= 0 and EyetrackerControl.currentlyRecording:
|
|
43
|
-
self.tracker.setRecordingState(False)
|
|
44
|
-
logging.exp("eyetracker.setRecordingState(False)")
|
|
45
|
-
EyetrackerControl.currentlyRecording = False
|
|
38
|
+
def currentlyRecording(self):
|
|
39
|
+
"""
|
|
40
|
+
Check if the eyetracker is currently recording
|
|
41
|
+
added for backwards compatibility, should be removed in future
|
|
42
|
+
"""
|
|
43
|
+
return self.tracker.isRecordingEnabled()
|
|
46
44
|
|
|
47
45
|
@property
|
|
48
46
|
def pos(self):
|
|
@@ -84,94 +82,20 @@ class EyetrackerCalibration:
|
|
|
84
82
|
def __iter__(self):
|
|
85
83
|
"""Overload dict() method to return in ioHub format"""
|
|
86
84
|
tracker = self.eyetracker.getIOHubDeviceClass(full=True)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if tracker == 'eyetracker.hw.sr_research.eyelink.EyeTracker':
|
|
103
|
-
# As EyeLink
|
|
104
|
-
asDict = {
|
|
105
|
-
'target_attributes': dict(target),
|
|
106
|
-
'type': self.targetLayout,
|
|
107
|
-
'auto_pace': self.progressMode == "time",
|
|
108
|
-
'pacing_speed': self.targetDelay,
|
|
109
|
-
'randomize': self.randomisePos,
|
|
110
|
-
'text_color': textColor,
|
|
111
|
-
'screen_background_color': getattr(self.win._color, self.colorSpace)
|
|
112
|
-
}
|
|
113
|
-
elif tracker == 'eyetracker.hw.tobii.EyeTracker':
|
|
114
|
-
# As Tobii
|
|
115
|
-
targetAttrs = dict(target)
|
|
116
|
-
targetAttrs['animate'] = {
|
|
117
|
-
'enable': self.movementAnimation,
|
|
118
|
-
'expansion_ratio': self.expandScale,
|
|
119
|
-
'contract_only': self.expandScale == 1
|
|
120
|
-
}
|
|
121
|
-
asDict = {
|
|
122
|
-
'target_attributes': targetAttrs,
|
|
123
|
-
'type': self.targetLayout,
|
|
124
|
-
'randomize': self.randomisePos,
|
|
125
|
-
'auto_pace': self.progressMode == "time",
|
|
126
|
-
'target_delay': self.targetDelay,
|
|
127
|
-
'target_duration': self.targetDur,
|
|
128
|
-
'unit_type': self.units,
|
|
129
|
-
'color_type': self.colorSpace,
|
|
130
|
-
'text_color': textColor,
|
|
131
|
-
'screen_background_color': getattr(self.win._color, self.colorSpace),
|
|
132
|
-
}
|
|
133
|
-
elif tracker == 'eyetracker.hw.gazepoint.gp3.EyeTracker':
|
|
134
|
-
# As GazePoint
|
|
135
|
-
targetAttrs = dict(target)
|
|
136
|
-
targetAttrs['animate'] = {
|
|
137
|
-
'enable': self.movementAnimation,
|
|
138
|
-
'expansion_ratio': self.expandScale,
|
|
139
|
-
'contract_only': self.expandScale == 1
|
|
140
|
-
}
|
|
141
|
-
asDict = {
|
|
142
|
-
'use_builtin': False,
|
|
143
|
-
'target_delay': self.targetDelay,
|
|
144
|
-
'target_duration': self.targetDur,
|
|
145
|
-
'target_attributes': targetAttrs,
|
|
146
|
-
'type': self.targetLayout,
|
|
147
|
-
'randomize': self.randomisePos,
|
|
148
|
-
'unit_type': self.units,
|
|
149
|
-
'color_type': self.colorSpace,
|
|
150
|
-
'text_color': textColor,
|
|
151
|
-
'screen_background_color': getattr(self.win._color, self.colorSpace),
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
elif tracker == 'eyetracker.hw.mouse.EyeTracker':
|
|
155
|
-
# As MouseGaze
|
|
156
|
-
targetAttrs = dict(target)
|
|
157
|
-
targetAttrs['animate'] = {
|
|
158
|
-
'enable': self.movementAnimation,
|
|
159
|
-
'expansion_ratio': self.expandScale,
|
|
160
|
-
'contract_only': self.expandScale == 1
|
|
161
|
-
}
|
|
162
|
-
# Run as MouseGaze
|
|
163
|
-
asDict = {
|
|
164
|
-
'target_attributes': targetAttrs,
|
|
165
|
-
'type': self.targetLayout,
|
|
166
|
-
'randomize': self.randomisePos,
|
|
167
|
-
'auto_pace': self.progressMode == "time",
|
|
168
|
-
'pacing_speed': self.targetDelay,
|
|
169
|
-
'unit_type': self.units,
|
|
170
|
-
'color_type': self.colorSpace,
|
|
171
|
-
'text_color': textColor,
|
|
172
|
-
'screen_background_color': getattr(self.win._color, self.colorSpace),
|
|
173
|
-
}
|
|
174
|
-
# Return
|
|
85
|
+
# split into package and class name
|
|
86
|
+
pkgName = ".".join(tracker.split(".")[:-1])
|
|
87
|
+
clsName = tracker.split(".")[-1]
|
|
88
|
+
# make sure pkgName is fully qualified
|
|
89
|
+
if not pkgName.startswith("psychopy.iohub.devices."):
|
|
90
|
+
pkgName = "psychopy.iohub.devices." + pkgName
|
|
91
|
+
# import package
|
|
92
|
+
pkg = importDeviceModule(pkgName)
|
|
93
|
+
# get tracker class
|
|
94
|
+
trackerCls = getattr(pkg, clsName)
|
|
95
|
+
# get self as dict
|
|
96
|
+
asDict = trackerCls.getCalibrationDict(self)
|
|
97
|
+
|
|
98
|
+
# return
|
|
175
99
|
for key, value in asDict.items():
|
|
176
100
|
yield key, value
|
|
177
101
|
|
psychopy/hardware/gammasci.py
CHANGED
|
@@ -14,19 +14,8 @@ These are optional components that can be obtained by installing the
|
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
except (ModuleNotFoundError, ImportError):
|
|
22
|
-
logging.error(
|
|
23
|
-
"Support for Gamma-Scientific Inc. hardware is not available this "
|
|
24
|
-
"session. Please install `psychopy-gammasci` and restart the session "
|
|
25
|
-
"to enable support.")
|
|
26
|
-
except Exception as e:
|
|
27
|
-
logging.error(
|
|
28
|
-
"Error encountered while loading `psychopy-gammasci`. Check logs for "
|
|
29
|
-
"more information.")
|
|
30
|
-
|
|
31
|
-
if __name__ == "__main__":
|
|
17
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class S470(PluginStub, plugin="psychopy-gammasci", doclink="https://psychopy.github.io/psychopy-gammasci/coder/S470"):
|
|
32
21
|
pass
|
psychopy/hardware/keyboard.py
CHANGED
|
@@ -67,6 +67,7 @@ import psychopy.clock
|
|
|
67
67
|
from psychopy import logging
|
|
68
68
|
from psychopy.constants import NOT_STARTED
|
|
69
69
|
import time
|
|
70
|
+
import numpy as np
|
|
70
71
|
|
|
71
72
|
from psychopy.hardware.base import BaseResponseDevice, BaseResponse
|
|
72
73
|
from psychopy.hardware import DeviceManager
|
|
@@ -231,6 +232,25 @@ class Keyboard(AttributeGetSetMixin):
|
|
|
231
232
|
keyList=keyList, ignoreKeys=ignoreKeys, waitRelease=waitRelease, clear=clear
|
|
232
233
|
)
|
|
233
234
|
|
|
235
|
+
def getState(self, keys):
|
|
236
|
+
"""
|
|
237
|
+
Get the current state of a key or set of keys
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
keys : str or list[str]
|
|
242
|
+
Either the code for a single key, or a list of key codes.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
keys : bool or list[bool]
|
|
247
|
+
True if pressed, False if not. Will be a single value if given a
|
|
248
|
+
single key, or a list of bools if given a list of keys.
|
|
249
|
+
"""
|
|
250
|
+
return self.device.getState(
|
|
251
|
+
keys=keys
|
|
252
|
+
)
|
|
253
|
+
|
|
234
254
|
def waitKeys(self, maxWait=float('inf'), keyList=None, waitRelease=True,
|
|
235
255
|
clear=True):
|
|
236
256
|
return self.device.waitKeys(
|
|
@@ -493,7 +513,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
493
513
|
if resp.value in ignoreKeys:
|
|
494
514
|
wanted = False
|
|
495
515
|
# if we got this far and the key is still wanted and not present, add it to output
|
|
496
|
-
if wanted and resp
|
|
516
|
+
if wanted and not any(k is resp for k in keys):
|
|
497
517
|
keys.append(resp)
|
|
498
518
|
# if clear=True, mark wanted responses as toClear
|
|
499
519
|
if wanted and clear:
|
|
@@ -504,6 +524,70 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
504
524
|
|
|
505
525
|
return keys
|
|
506
526
|
|
|
527
|
+
def getState(self, keys):
|
|
528
|
+
"""
|
|
529
|
+
Get the current state of a key or set of keys
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
keys : str or list[str]
|
|
534
|
+
Either the code for a single key, or a list of key codes.
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
keys : bool or list[bool]
|
|
539
|
+
True if pressed, False if not. Will be a single value if given a
|
|
540
|
+
single key, or a list of bools if given a list of keys.
|
|
541
|
+
"""
|
|
542
|
+
# if given a string, convert to a list
|
|
543
|
+
if isinstance(keys, str):
|
|
544
|
+
keys = [keys]
|
|
545
|
+
# start off False
|
|
546
|
+
state = [False] * len(keys)
|
|
547
|
+
|
|
548
|
+
if KeyboardDevice._backend == 'ptb':
|
|
549
|
+
# use ptb.Keyboard.check if backend is ptb
|
|
550
|
+
for buffer in self._buffers.values():
|
|
551
|
+
# get output from ptb
|
|
552
|
+
anyPressed, t, mat = buffer.dev.check()
|
|
553
|
+
# if got any key...
|
|
554
|
+
if mat.any():
|
|
555
|
+
# convert each key index to a key name
|
|
556
|
+
for i in np.where(mat.flatten())[0]:
|
|
557
|
+
# account for ptb's 1-based indexing
|
|
558
|
+
i = int(i) + 1
|
|
559
|
+
# get key name from index (or None if not applicable)
|
|
560
|
+
name = keyNames.get(i, None)
|
|
561
|
+
# check if it's on our list
|
|
562
|
+
if name in keys:
|
|
563
|
+
state[keys.index(name)] = True
|
|
564
|
+
elif KeyboardDevice._backend == 'iohub':
|
|
565
|
+
# get current state of ioHub keyboard
|
|
566
|
+
ioHubState = KeyboardDevice._iohubKeyboard.getCurrentDeviceState()
|
|
567
|
+
# iterate through pressed keys
|
|
568
|
+
for i in ioHubState.get("pressed_keys", {}):
|
|
569
|
+
# iohub returns strings - integerise
|
|
570
|
+
i = int(i)
|
|
571
|
+
# get key name from index (or None if not applicable)
|
|
572
|
+
name = keyNames.get(i, None)
|
|
573
|
+
# check if it's on our list
|
|
574
|
+
if name in keys:
|
|
575
|
+
state[keys.index(name)] = True
|
|
576
|
+
else:
|
|
577
|
+
# make a key state handler
|
|
578
|
+
handler = event.pyglet.window.key.KeyStateHandler()
|
|
579
|
+
# iterate through our list of keys
|
|
580
|
+
for i, key in enumerate(keys):
|
|
581
|
+
# if handler has an entry for the given key, it's pressed
|
|
582
|
+
state[i] = handler[getattr(event.pyglet.window.key, key.upper())]
|
|
583
|
+
|
|
584
|
+
# if state is a single value, remove list wrapper
|
|
585
|
+
if len(state) == 1:
|
|
586
|
+
state = state[0]
|
|
587
|
+
|
|
588
|
+
return state
|
|
589
|
+
|
|
590
|
+
|
|
507
591
|
def dispatchMessages(self):
|
|
508
592
|
if KeyboardDevice._backend == 'ptb':
|
|
509
593
|
for buffer in self._buffers.values():
|
|
@@ -521,7 +605,6 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
521
605
|
elif KeyboardDevice._backend == 'iohub':
|
|
522
606
|
# get events from backend (need to reverse order)
|
|
523
607
|
key_events = KeyboardDevice._iohubKeyboard.getKeys(clear=True)
|
|
524
|
-
key_events.reverse()
|
|
525
608
|
# parse and receive each event
|
|
526
609
|
for k in key_events:
|
|
527
610
|
kpress = self.parseMessage(k)
|
|
@@ -552,10 +635,13 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
552
635
|
response = None
|
|
553
636
|
|
|
554
637
|
if KeyboardDevice._backend == 'ptb':
|
|
555
|
-
message['time'] -= self.clock.getLastResetTime()
|
|
556
638
|
if message['down']:
|
|
557
639
|
# if message is from a key down event, make a new response
|
|
558
|
-
response = KeyPress(
|
|
640
|
+
response = KeyPress(
|
|
641
|
+
code=message['keycode'],
|
|
642
|
+
tDown=message['time'] - logging.defaultClock.getLastResetTime()
|
|
643
|
+
)
|
|
644
|
+
response.rt = message['time'] - self.clock.getLastResetTime()
|
|
559
645
|
self._keysStillDown.append(response)
|
|
560
646
|
else:
|
|
561
647
|
# if message is from a key up event, alter existing response
|
|
@@ -563,7 +649,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
563
649
|
if key.code == message['keycode']:
|
|
564
650
|
response = key
|
|
565
651
|
# calculate duration
|
|
566
|
-
key.duration = message['time'] - key.tDown
|
|
652
|
+
key.duration = message['time'] - key.tDown - logging.defaultClock.getLastResetTime()
|
|
567
653
|
# remove key from stillDown
|
|
568
654
|
self._keysStillDown.remove(key)
|
|
569
655
|
# stop processing keys as we're done
|
|
@@ -573,7 +659,8 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
573
659
|
if message.type == "KEYBOARD_PRESS":
|
|
574
660
|
# if message is from a key down event, make a new response
|
|
575
661
|
response = KeyPress(code=message.char, tDown=message.time, name=message.key)
|
|
576
|
-
response.rt = response.tDown -
|
|
662
|
+
response.rt = response.tDown - (
|
|
663
|
+
self.clock.getLastResetTime() - self._iohubKeyboard.clock.getLastResetTime())
|
|
577
664
|
self._keysStillDown.append(response)
|
|
578
665
|
else:
|
|
579
666
|
# if message is from a key up event, alter existing response
|
|
@@ -600,9 +687,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
600
687
|
|
|
601
688
|
def waitKeys(self, maxWait=float('inf'), keyList=None, waitRelease=True,
|
|
602
689
|
clear=True):
|
|
603
|
-
"""Same as `~psychopy.hardware.keyboard.Keyboard.getKeys`,
|
|
690
|
+
"""Same as `~psychopy.hardware.keyboard.Keyboard.getKeys`,
|
|
604
691
|
but halts everything (including drawing) while awaiting keyboard input.
|
|
605
|
-
|
|
692
|
+
|
|
606
693
|
:Parameters:
|
|
607
694
|
maxWait : any numeric value.
|
|
608
695
|
Maximum number of seconds period and which keys to wait for.
|
|
@@ -622,9 +709,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
622
709
|
clear : **True** or False
|
|
623
710
|
Whether to clear the keyboard event buffer (and discard preceding
|
|
624
711
|
keypresses) before starting to monitor for new keypresses.
|
|
625
|
-
|
|
712
|
+
|
|
626
713
|
Returns None if times out.
|
|
627
|
-
|
|
714
|
+
|
|
628
715
|
"""
|
|
629
716
|
timer = psychopy.clock.Clock()
|
|
630
717
|
|
|
@@ -643,6 +730,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
643
730
|
|
|
644
731
|
def clearEvents(self, eventType=None):
|
|
645
732
|
"""Clear the events from the Keyboard such as previous key presses"""
|
|
733
|
+
# clear backend buffers
|
|
646
734
|
if KeyboardDevice._backend == 'ptb':
|
|
647
735
|
for buffer in self._buffers.values():
|
|
648
736
|
buffer.flush() # flush the device events to the soft buffer
|
|
@@ -654,6 +742,9 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
654
742
|
else:
|
|
655
743
|
global event
|
|
656
744
|
event.clearEvents(eventType)
|
|
745
|
+
# clear dispatched responses
|
|
746
|
+
self.responses = []
|
|
747
|
+
|
|
657
748
|
logging.info("Keyboard events cleared", obj=self)
|
|
658
749
|
|
|
659
750
|
|
|
@@ -745,7 +836,7 @@ class _KeyBuffer(object):
|
|
|
745
836
|
if not keyList and not waitRelease:
|
|
746
837
|
keyPresses = list(self._keysStillDown)
|
|
747
838
|
for k in list(self._keys):
|
|
748
|
-
if not any(x.name == k.name and x.tDown == k.tDown
|
|
839
|
+
if not any(x.name == k.name and x.tDown == k.tDown for x in keyPresses):
|
|
749
840
|
keyPresses.append(k)
|
|
750
841
|
if clear:
|
|
751
842
|
self._keys = deque()
|
psychopy/hardware/listener.py
CHANGED
|
@@ -135,6 +135,9 @@ class ListenerLoop(threading.Thread):
|
|
|
135
135
|
# dispatch messages from devices
|
|
136
136
|
for device in self.devices:
|
|
137
137
|
device.dispatchMessages()
|
|
138
|
+
# if there are no more devices attached, stop
|
|
139
|
+
if not len(self.devices):
|
|
140
|
+
self._active = False
|
|
138
141
|
# sleep for 10ms
|
|
139
142
|
time.sleep(self.refreshRate)
|
|
140
143
|
|