psychopy 2024.1.4__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/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 +1011 -942
- 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 +145 -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/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/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/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 +6 -5
- 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 -10
- 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 +31 -0
- 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/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-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
- {psychopy-2024.1.4.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.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
psychopy/hardware/microphone.py
CHANGED
|
@@ -6,7 +6,7 @@ from psychtoolbox import audio as audio
|
|
|
6
6
|
from psychopy import logging as logging, prefs
|
|
7
7
|
from psychopy.localization import _translate
|
|
8
8
|
from psychopy.constants import NOT_STARTED
|
|
9
|
-
from psychopy.hardware import BaseDevice
|
|
9
|
+
from psychopy.hardware import BaseDevice, BaseResponse, BaseResponseDevice
|
|
10
10
|
from psychopy.sound.audiodevice import AudioDeviceInfo, AudioDeviceStatus
|
|
11
11
|
from psychopy.sound.audioclip import AudioClip
|
|
12
12
|
from psychopy.sound.exceptions import AudioInvalidCaptureDeviceError, AudioInvalidDeviceError, \
|
|
@@ -27,6 +27,10 @@ except (ImportError, ModuleNotFoundError):
|
|
|
27
27
|
_hasPTB = False
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
class MicrophoneResponse(BaseResponse):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
30
34
|
class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
31
35
|
"""Class for recording audio from a microphone or input stream.
|
|
32
36
|
|
|
@@ -144,11 +148,42 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
144
148
|
# if there are none, error
|
|
145
149
|
if not len(_devices):
|
|
146
150
|
raise AudioInvalidCaptureDeviceError(_translate(
|
|
147
|
-
"Could not choose default recording device as no recording
|
|
148
|
-
"connected."
|
|
151
|
+
"Could not choose default recording device as no recording "
|
|
152
|
+
"devices are connected."
|
|
149
153
|
))
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
|
|
155
|
+
# Try and get the best match which are compatible with the user's
|
|
156
|
+
# specified settings.
|
|
157
|
+
if sampleRateHz is not None or channels is not None:
|
|
158
|
+
self._device = self.findBestDevice(
|
|
159
|
+
index=_devices[0].deviceIndex, # use first that shows up
|
|
160
|
+
sampleRateHz=sampleRateHz,
|
|
161
|
+
channels=channels
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
self._device = _devices[0]
|
|
165
|
+
|
|
166
|
+
# Check if the default device settings are differnt than the ones
|
|
167
|
+
# specified by the user, if so, warn them that the default device
|
|
168
|
+
# settings are overwriting their settings.
|
|
169
|
+
if channels is None:
|
|
170
|
+
channels = self._device.inputChannels
|
|
171
|
+
elif channels != self._device.inputChannels:
|
|
172
|
+
logging.warning(
|
|
173
|
+
"Number of channels specified ({}) does not match the "
|
|
174
|
+
"default device's number of input channels ({}).".format(
|
|
175
|
+
channels, self._device.inputChannels))
|
|
176
|
+
channels = self._device.inputChannels
|
|
177
|
+
|
|
178
|
+
if sampleRateHz is None:
|
|
179
|
+
sampleRateHz = self._device.defaultSampleRate
|
|
180
|
+
elif sampleRateHz != self._device.defaultSampleRate:
|
|
181
|
+
logging.warning(
|
|
182
|
+
"Sample rate specified ({}) does not match the default "
|
|
183
|
+
"device's sample rate ({}).".format(
|
|
184
|
+
sampleRateHz, self._device.defaultSampleRate))
|
|
185
|
+
sampleRateHz = self._device.defaultSampleRate
|
|
186
|
+
|
|
152
187
|
elif isinstance(index, str):
|
|
153
188
|
# if given a str that's a name from DeviceManager, get info from device
|
|
154
189
|
device = DeviceManager.getDevice(index)
|
|
@@ -167,8 +202,13 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
167
202
|
channels=channels
|
|
168
203
|
)
|
|
169
204
|
|
|
170
|
-
|
|
171
|
-
|
|
205
|
+
devInfoText = ('Using audio device #{} ({}) for audio capture. '
|
|
206
|
+
'Full spec: {}').format(
|
|
207
|
+
self._device.deviceIndex,
|
|
208
|
+
self._device.deviceName,
|
|
209
|
+
self._device)
|
|
210
|
+
|
|
211
|
+
logging.info(devInfoText)
|
|
172
212
|
|
|
173
213
|
# error if specified device is not suitable for capture
|
|
174
214
|
if not self._device.isCapture:
|
|
@@ -176,11 +216,12 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
176
216
|
'Specified audio device not suitable for audio recording. '
|
|
177
217
|
'Has no input channels.')
|
|
178
218
|
|
|
179
|
-
# get the
|
|
180
|
-
self.
|
|
181
|
-
|
|
182
|
-
|
|
219
|
+
# get these values from the configured device
|
|
220
|
+
self._channels = self._device.inputChannels
|
|
221
|
+
logging.debug('Set recording channels to {} ({})'.format(
|
|
222
|
+
self._channels, 'stereo' if self._channels > 1 else 'mono'))
|
|
183
223
|
|
|
224
|
+
self._sampleRateHz = self._device.defaultSampleRate
|
|
184
225
|
logging.debug('Set stream sample rate to {} Hz'.format(
|
|
185
226
|
self._sampleRateHz))
|
|
186
227
|
|
|
@@ -195,13 +236,6 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
195
236
|
|
|
196
237
|
assert 0 <= self._audioLatencyMode <= 4 # sanity check for pref
|
|
197
238
|
|
|
198
|
-
# set the number of recording channels
|
|
199
|
-
self._channels = \
|
|
200
|
-
self._device.inputChannels if channels is None else int(channels)
|
|
201
|
-
|
|
202
|
-
logging.debug('Set recording channels to {} ({})'.format(
|
|
203
|
-
self._channels, 'stereo' if self._channels > 1 else 'mono'))
|
|
204
|
-
|
|
205
239
|
# internal recording buffer size in seconds
|
|
206
240
|
assert isinstance(streamBufferSecs, (float, int))
|
|
207
241
|
self._streamBufferSecs = float(streamBufferSecs)
|
|
@@ -268,6 +302,9 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
268
302
|
logging.debug('Audio capture device #{} ready'.format(
|
|
269
303
|
self._device.deviceIndex))
|
|
270
304
|
|
|
305
|
+
# list to store listeners in
|
|
306
|
+
self.listeners = []
|
|
307
|
+
|
|
271
308
|
def findBestDevice(self, index, sampleRateHz, channels):
|
|
272
309
|
"""
|
|
273
310
|
Find the closest match among the microphone profiles listed by psychtoolbox as valid.
|
|
@@ -797,6 +834,99 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
|
|
|
797
834
|
|
|
798
835
|
return self._recording.getSegment() # full recording
|
|
799
836
|
|
|
837
|
+
def getCurrentVolume(self, timeframe=0.2):
|
|
838
|
+
"""
|
|
839
|
+
Get the current volume measured by the mic.
|
|
840
|
+
|
|
841
|
+
Parameters
|
|
842
|
+
----------
|
|
843
|
+
timeframe : float
|
|
844
|
+
Time frame (s) over which to take samples from. Default is 0.1s.
|
|
845
|
+
|
|
846
|
+
Returns
|
|
847
|
+
-------
|
|
848
|
+
float
|
|
849
|
+
Current volume registered by the mic, will depend on relative volume of the mic but
|
|
850
|
+
should mostly be between 0 (total silence) and 1 (very loud).
|
|
851
|
+
"""
|
|
852
|
+
# if mic hasn't started yet, return 0 as it's recorded nothing
|
|
853
|
+
if not self.isStarted:
|
|
854
|
+
return 0
|
|
855
|
+
# poll most recent samples
|
|
856
|
+
self.poll()
|
|
857
|
+
# get last 0.1sas a clip
|
|
858
|
+
clip = self._recording.getSegment(
|
|
859
|
+
max(self._recording.lastSample / self._sampleRateHz - timeframe, 0)
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
return clip.rms() * 10
|
|
863
|
+
|
|
864
|
+
def addListener(self, listener, startLoop=False):
|
|
865
|
+
"""
|
|
866
|
+
Add a listener, which will receive all the same messages as this device.
|
|
867
|
+
|
|
868
|
+
Parameters
|
|
869
|
+
----------
|
|
870
|
+
listener : str or psychopy.hardware.listener.BaseListener
|
|
871
|
+
Either a Listener object, or use one of the following strings to create one:
|
|
872
|
+
- "liaison": Create a LiaisonListener with DeviceManager.liaison as the server
|
|
873
|
+
- "print": Create a PrintListener with default settings
|
|
874
|
+
- "log": Create a LoggingListener with default settings
|
|
875
|
+
startLoop : bool
|
|
876
|
+
If True, then upon adding the listener, start up an asynchronous loop to dispatch messages.
|
|
877
|
+
"""
|
|
878
|
+
# add listener as normal
|
|
879
|
+
BaseResponseDevice.addListener(self, listener, startLoop=startLoop)
|
|
880
|
+
# if we're starting a listener loop, start recording
|
|
881
|
+
if startLoop:
|
|
882
|
+
self.start()
|
|
883
|
+
|
|
884
|
+
def clearListeners(self):
|
|
885
|
+
"""
|
|
886
|
+
Remove any listeners from this device.
|
|
887
|
+
|
|
888
|
+
Returns
|
|
889
|
+
-------
|
|
890
|
+
bool
|
|
891
|
+
True if completed successfully
|
|
892
|
+
"""
|
|
893
|
+
# clear listeners as normal
|
|
894
|
+
BaseResponseDevice.clearListeners(self)
|
|
895
|
+
# stop recording
|
|
896
|
+
self.stop()
|
|
897
|
+
|
|
898
|
+
def dispatchMessages(self, clear=True):
|
|
899
|
+
"""
|
|
900
|
+
Dispatch current volume as a MicrophoneResponse object to any attached listeners.
|
|
901
|
+
|
|
902
|
+
Parameters
|
|
903
|
+
----------
|
|
904
|
+
clear : bool
|
|
905
|
+
If True, will clear the recording up until now after dispatching the volume. This is
|
|
906
|
+
useful if you're just sampling volume and aren't wanting to store the recording.
|
|
907
|
+
"""
|
|
908
|
+
# create a response object
|
|
909
|
+
message = MicrophoneResponse(
|
|
910
|
+
logging.defaultClock.getTime(),
|
|
911
|
+
self.getCurrentVolume()
|
|
912
|
+
)
|
|
913
|
+
# clear recording if requested (helps with continuous running)
|
|
914
|
+
if clear and self.isRecBufferFull:
|
|
915
|
+
# work out how many samples is 0.1s
|
|
916
|
+
toSave = min(
|
|
917
|
+
int(0.2 * self._sampleRateHz),
|
|
918
|
+
int(self.maxRecordingSize / 2)
|
|
919
|
+
)
|
|
920
|
+
# get last 0.1s so we still have enough for volume measurement
|
|
921
|
+
savedSamples = self._recording._samples[-toSave:, :]
|
|
922
|
+
# clear samples
|
|
923
|
+
self._recording.clear()
|
|
924
|
+
# reassign saved samples
|
|
925
|
+
self._recording.write(savedSamples)
|
|
926
|
+
# dispatch to listeners
|
|
927
|
+
for listener in self.listeners:
|
|
928
|
+
listener.receiveMessage(message)
|
|
929
|
+
|
|
800
930
|
|
|
801
931
|
class RecordingBuffer:
|
|
802
932
|
"""Class for a storing a recording from a stream.
|
psychopy/hardware/minolta.py
CHANGED
|
@@ -12,19 +12,12 @@ These are optional components that can be obtained by installing the
|
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"support.")
|
|
24
|
-
except Exception as e:
|
|
25
|
-
logging.error(
|
|
26
|
-
"Error encountered while loading `psychopy-minolta`. Check logs for "
|
|
27
|
-
"more information.")
|
|
28
|
-
|
|
29
|
-
if __name__ == "__main__":
|
|
15
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CS100A(PluginStub, plugin="psychopy-minolta", doclink="https://psychopy.github.io/psychopy-minolta/coder/CS100A"):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LS100(PluginStub, plugin="psychopy-minolta", doclink="https://psychopy.github.io/psychopy-minolta/coder/LS100"):
|
|
30
23
|
pass
|
psychopy/hardware/photodiode.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from psychopy import layout, logging
|
|
2
|
+
from psychopy import core, layout, logging
|
|
3
3
|
from psychopy.hardware import base, DeviceManager
|
|
4
4
|
from psychopy.localization import _translate
|
|
5
5
|
from psychopy.hardware import keyboard
|
|
@@ -100,8 +100,62 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
100
100
|
matches.append(resp)
|
|
101
101
|
|
|
102
102
|
return matches
|
|
103
|
+
|
|
104
|
+
def findChannels(self, win):
|
|
105
|
+
"""
|
|
106
|
+
Flash the entire window white to check which channels are detecting light from the given
|
|
107
|
+
window.
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
win : psychopy.visual.Window
|
|
112
|
+
Window to flash white.
|
|
113
|
+
"""
|
|
114
|
+
from psychopy import visual
|
|
115
|
+
# clock for timeouts
|
|
116
|
+
timeoutClock = core.Clock()
|
|
117
|
+
# box to cover screen
|
|
118
|
+
rect = visual.Rect(
|
|
119
|
+
win,
|
|
120
|
+
size=(2, 2), pos=(0, 0), units="norm",
|
|
121
|
+
autoDraw=False
|
|
122
|
+
)
|
|
123
|
+
win.flip()
|
|
124
|
+
# show black
|
|
125
|
+
rect.fillColor = "black"
|
|
126
|
+
rect.draw()
|
|
127
|
+
win.flip()
|
|
128
|
+
# wait 250ms for flip to happen and photodiode to catch it
|
|
129
|
+
timeoutClock.reset()
|
|
130
|
+
while timeoutClock.getTime() < 0.25:
|
|
131
|
+
self.dispatchMessages()
|
|
132
|
+
# finish dispatching any messages which are only partially received
|
|
133
|
+
while self.hasUnfinishedMessage():
|
|
134
|
+
self.dispatchMessages()
|
|
135
|
+
# clear caught messages so we're starting afresh
|
|
136
|
+
self.clearResponses()
|
|
137
|
+
# show white
|
|
138
|
+
rect.fillColor = "white"
|
|
139
|
+
rect.draw()
|
|
140
|
+
win.flip()
|
|
141
|
+
# wait 250ms for flip to happen and photodiode to catch it
|
|
142
|
+
timeoutClock.reset()
|
|
143
|
+
while timeoutClock.getTime() < 0.25:
|
|
144
|
+
self.dispatchMessages()
|
|
145
|
+
# finish dispatching any messages which are only partially received
|
|
146
|
+
while self.hasUnfinishedMessage():
|
|
147
|
+
self.dispatchMessages()
|
|
148
|
+
# start off with no channels
|
|
149
|
+
channels = []
|
|
150
|
+
# iterate through potential channels
|
|
151
|
+
for i, state in enumerate(self.state):
|
|
152
|
+
# if any detected the flash, append it
|
|
153
|
+
if state:
|
|
154
|
+
channels.append(i)
|
|
155
|
+
|
|
156
|
+
return channels
|
|
157
|
+
|
|
158
|
+
def findPhotodiode(self, win, channel=None):
|
|
105
159
|
"""
|
|
106
160
|
Draws rectangles on the screen and records photodiode responses to recursively find the location of the diode.
|
|
107
161
|
|
|
@@ -114,6 +168,8 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
114
168
|
Size of the area of certainty. Essentially, the size of the last (smallest) rectangle which the photodiode
|
|
115
169
|
was able to detect.
|
|
116
170
|
"""
|
|
171
|
+
# timeout clock
|
|
172
|
+
timeoutClock = core.Clock()
|
|
117
173
|
# keyboard to check for escape
|
|
118
174
|
kb = keyboard.Keyboard(deviceName="photodiodeValidatorKeyboard")
|
|
119
175
|
# stash autodraw
|
|
@@ -130,7 +186,7 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
130
186
|
# add low opacity label
|
|
131
187
|
label = visual.TextBox2(
|
|
132
188
|
win,
|
|
133
|
-
text="Finding photodiode...",
|
|
189
|
+
text=f"Finding photodiode...",
|
|
134
190
|
fillColor=(0, 0, 0), color=(80, 80, 80), colorSpace="rgb255",
|
|
135
191
|
pos=(0, 0), size=(2, 2), units="norm",
|
|
136
192
|
alignment="center",
|
|
@@ -144,9 +200,29 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
144
200
|
autoDraw=False
|
|
145
201
|
)
|
|
146
202
|
|
|
147
|
-
|
|
203
|
+
# if not given a channel, use first one which is responsive to the win
|
|
204
|
+
if channel is None:
|
|
205
|
+
# get responsive channels
|
|
206
|
+
responsiveChannels = self.findChannels(win=win)
|
|
207
|
+
# use first responsive channel
|
|
208
|
+
if responsiveChannels:
|
|
209
|
+
channel = responsiveChannels[0]
|
|
210
|
+
else:
|
|
211
|
+
# if no channels are responsive, use 0th channel and let scanQuadrants fail cleanly
|
|
212
|
+
channel = 0
|
|
213
|
+
# update label text once we have a channel
|
|
214
|
+
label.text = f"Finding photodiode {channel}..."
|
|
215
|
+
|
|
216
|
+
def scanQuadrants(responsive=False):
|
|
148
217
|
"""
|
|
149
|
-
Recursively shrink the rectangle around the position of the photodiode until it's too
|
|
218
|
+
Recursively shrink the rectangle around the position of the photodiode until it's too
|
|
219
|
+
small to detect.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
responsive : bool
|
|
224
|
+
When calling manually, this should always be left as False! Will be set to True if
|
|
225
|
+
any response was received from the photodiode.
|
|
150
226
|
"""
|
|
151
227
|
# work out width and height of area
|
|
152
228
|
w, h = rect.size
|
|
@@ -162,6 +238,11 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
162
238
|
(r - w / 4, t - h / 4), # top right
|
|
163
239
|
(l + w / 4, b + h / 4), # bottom left
|
|
164
240
|
(r - w / 4, b + h / 4), # bottom right
|
|
241
|
+
rect.pos, # center
|
|
242
|
+
(l + w / 2, t - h / 4), # top center
|
|
243
|
+
(l + w / 2, b + h / 4), # bottom center
|
|
244
|
+
(l + w / 4, b + h / 2), # center left
|
|
245
|
+
(r - w / 4, b + h / 2), # center right
|
|
165
246
|
]:
|
|
166
247
|
# position rect
|
|
167
248
|
rect.pos = (x, y)
|
|
@@ -170,24 +251,97 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
170
251
|
label.draw()
|
|
171
252
|
rect.draw()
|
|
172
253
|
win.flip()
|
|
173
|
-
#
|
|
174
|
-
|
|
254
|
+
# wait for flip to happen and photodiode to catch it (max 250ms)
|
|
255
|
+
timeoutClock.reset()
|
|
256
|
+
self.clearResponses()
|
|
257
|
+
while not self.responses and timeoutClock.getTime() < 0.25:
|
|
258
|
+
self.dispatchMessages()
|
|
259
|
+
# finish dispatching any messages which are only partially received
|
|
260
|
+
while self.hasUnfinishedMessage():
|
|
261
|
+
self.dispatchMessages()
|
|
175
262
|
# check for escape before entering recursion
|
|
176
263
|
if kb.getKeys(['escape']):
|
|
177
|
-
return
|
|
264
|
+
return None
|
|
178
265
|
# poll photodiode
|
|
179
266
|
if self.getState(channel):
|
|
267
|
+
# mark that we've got a response
|
|
268
|
+
responsive = True
|
|
180
269
|
# if it detected this rectangle, recur
|
|
181
|
-
return scanQuadrants()
|
|
270
|
+
return scanQuadrants(responsive=responsive)
|
|
182
271
|
# if none of these have returned, rect is too small to cover the whole photodiode, so return
|
|
183
|
-
return
|
|
272
|
+
return responsive
|
|
184
273
|
|
|
185
274
|
# reset state
|
|
186
275
|
self.state = [None] * self.channels
|
|
187
276
|
self.dispatchMessages()
|
|
188
277
|
self.clearResponses()
|
|
189
278
|
# recursively shrink rect around the photodiode
|
|
190
|
-
scanQuadrants()
|
|
279
|
+
responsive = scanQuadrants()
|
|
280
|
+
# if cancelled, warn and continue
|
|
281
|
+
if responsive is None:
|
|
282
|
+
logging.warn(
|
|
283
|
+
"`findPhotodiode` procedure cancelled by user."
|
|
284
|
+
)
|
|
285
|
+
return (
|
|
286
|
+
layout.Position(self.pos, units="norm", win=win),
|
|
287
|
+
layout.Position(self.size, units="norm", win=win),
|
|
288
|
+
)
|
|
289
|
+
# if we didn't get any responses at all, prompt to try again
|
|
290
|
+
if not responsive:
|
|
291
|
+
# set label text to alert user
|
|
292
|
+
label.text = (
|
|
293
|
+
"Received no responses from photodiode during `findPhotodiode`. Photodiode may not "
|
|
294
|
+
"be connected or may be configured incorrectly.\n"
|
|
295
|
+
"\n"
|
|
296
|
+
"To continue, use the arrow keys to move the photodiode patch and use the "
|
|
297
|
+
"plus/minus keys to resize it.\n"
|
|
298
|
+
"\n"
|
|
299
|
+
"Press ENTER when finished."
|
|
300
|
+
)
|
|
301
|
+
label.foreColor = "red"
|
|
302
|
+
# revert to defaults
|
|
303
|
+
self.units = rect.units = "norm"
|
|
304
|
+
self.size = rect.size = (0.1, 0.1)
|
|
305
|
+
self.pos = rect.pos = (0.9, -0.9)
|
|
306
|
+
# start a frame loop until they press enter
|
|
307
|
+
keys = []
|
|
308
|
+
res = 0.05
|
|
309
|
+
while "return" not in keys:
|
|
310
|
+
# get keys
|
|
311
|
+
keys = kb.getKeys()
|
|
312
|
+
# skip if escape pressed
|
|
313
|
+
if "escape" in keys:
|
|
314
|
+
return None
|
|
315
|
+
# move rect according to arrow keys
|
|
316
|
+
pos = list(rect.pos)
|
|
317
|
+
if "left" in keys:
|
|
318
|
+
pos[0] -= res
|
|
319
|
+
if "right" in keys:
|
|
320
|
+
pos[0] += res
|
|
321
|
+
if "up" in keys:
|
|
322
|
+
pos[1] += res
|
|
323
|
+
if "down" in keys:
|
|
324
|
+
pos[1] -= res
|
|
325
|
+
rect.pos = self.pos = pos
|
|
326
|
+
# resize rect according to +- keys
|
|
327
|
+
size = rect.size
|
|
328
|
+
if "equal" in keys:
|
|
329
|
+
size = [sz * 2 for sz in size]
|
|
330
|
+
if "minus" in keys:
|
|
331
|
+
size = [sz / 2 for sz in size]
|
|
332
|
+
rect.size = self.size = size
|
|
333
|
+
# show label and square
|
|
334
|
+
label.draw()
|
|
335
|
+
rect.draw()
|
|
336
|
+
# flip
|
|
337
|
+
win.flip()
|
|
338
|
+
# wait for a keypress
|
|
339
|
+
kb.waitKeys()
|
|
340
|
+
# return defaults
|
|
341
|
+
return (
|
|
342
|
+
layout.Position(self.pos, units="norm", win=win),
|
|
343
|
+
layout.Position(self.size, units="norm", win=win),
|
|
344
|
+
)
|
|
191
345
|
# clear all the events created by this process
|
|
192
346
|
self.state = [None] * self.channels
|
|
193
347
|
self.dispatchMessages()
|
|
@@ -207,7 +361,17 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
207
361
|
layout.Position(self.size, units="norm", win=win),
|
|
208
362
|
)
|
|
209
363
|
|
|
210
|
-
def findThreshold(self, win, channel):
|
|
364
|
+
def findThreshold(self, win, channel=None):
|
|
365
|
+
# if not given a channel, find for all channels
|
|
366
|
+
if channel is None:
|
|
367
|
+
thresholds = []
|
|
368
|
+
# iterate through channels
|
|
369
|
+
for channel in range(self.channels):
|
|
370
|
+
thresholds.append(
|
|
371
|
+
self.findThreshold(win, channel=channel)
|
|
372
|
+
)
|
|
373
|
+
# return array of thresholds
|
|
374
|
+
return thresholds
|
|
211
375
|
# keyboard to check for escape/continue
|
|
212
376
|
kb = keyboard.Keyboard(deviceName="photodiodeValidatorKeyboard")
|
|
213
377
|
# stash autodraw
|
|
@@ -223,7 +387,7 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
223
387
|
# add low opacity label
|
|
224
388
|
label = visual.TextBox2(
|
|
225
389
|
win,
|
|
226
|
-
text="Finding best threshold for photodiode...",
|
|
390
|
+
text=f"Finding best threshold for photodiode {channel}...",
|
|
227
391
|
fillColor=None, color=(0, 0, 0), colorSpace="rgb",
|
|
228
392
|
pos=(0, 0), size=(2, 2), units="norm",
|
|
229
393
|
alignment="center",
|
|
@@ -294,8 +458,10 @@ class BasePhotodiodeGroup(base.BaseResponseDevice):
|
|
|
294
458
|
win.retrieveAutoDraw()
|
|
295
459
|
# flip
|
|
296
460
|
win.flip()
|
|
461
|
+
# set to found threshold
|
|
462
|
+
self._setThreshold(int(threshold), channel=channel)
|
|
297
463
|
|
|
298
|
-
return threshold
|
|
464
|
+
return int(threshold)
|
|
299
465
|
|
|
300
466
|
def setThreshold(self, threshold, channel):
|
|
301
467
|
if isinstance(channel, (list, tuple)):
|
|
@@ -485,6 +651,9 @@ class ScreenBufferSampler(BasePhotodiodeGroup):
|
|
|
485
651
|
win = self.win
|
|
486
652
|
else:
|
|
487
653
|
self.win = win
|
|
654
|
+
# handle None
|
|
655
|
+
if channel is None:
|
|
656
|
+
channel = 0
|
|
488
657
|
# there's no physical photodiode, so just pick a reasonable place for it
|
|
489
658
|
self._pos = layout.Position((0.95, -0.95), units="norm", win=win)
|
|
490
659
|
self._size = layout.Size((0.05, 0.05), units="norm", win=win)
|
|
@@ -493,7 +662,13 @@ class ScreenBufferSampler(BasePhotodiodeGroup):
|
|
|
493
662
|
return self._pos, self._size
|
|
494
663
|
|
|
495
664
|
def findThreshold(self, win=None, channel=0):
|
|
496
|
-
|
|
665
|
+
if win is None:
|
|
666
|
+
win = self.win
|
|
667
|
+
else:
|
|
668
|
+
self.win = win
|
|
669
|
+
# handle None
|
|
670
|
+
if channel is None:
|
|
671
|
+
channel = 0
|
|
497
672
|
# there's no physical photodiode, so just pick a reasonable threshold
|
|
498
673
|
self.setThreshold(127, channel=channel)
|
|
499
674
|
|
|
@@ -503,7 +678,7 @@ class ScreenBufferSampler(BasePhotodiodeGroup):
|
|
|
503
678
|
class PhotodiodeValidator:
|
|
504
679
|
|
|
505
680
|
def __init__(
|
|
506
|
-
self, win, diode, channel,
|
|
681
|
+
self, win, diode, channel=None,
|
|
507
682
|
variability=1/60,
|
|
508
683
|
report="log",
|
|
509
684
|
autoLog=False):
|
|
@@ -18,34 +18,23 @@ __all__ = [
|
|
|
18
18
|
'getAllPhotometerClasses'
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
import
|
|
21
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
22
22
|
|
|
23
23
|
# Special handling for legacy classes which have been offloaded to optional
|
|
24
24
|
# packages. This will change to allow more flexibility in the future to avoid
|
|
25
25
|
# updating this package for additions to these sub-packages. We'll need a
|
|
26
26
|
# photometer type to do that, but for now we're doing it like this.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
except Exception:
|
|
30
|
-
ColorCAL = OptiCAL = None
|
|
27
|
+
from psychopy.hardware.crs.colorcal import ColorCAL
|
|
28
|
+
from psychopy.hardware.crs.optical import OptiCAL
|
|
31
29
|
|
|
32
30
|
# Photo Resaerch Inc. spectroradiometers
|
|
33
|
-
|
|
34
|
-
from ..pr import PR655, PR650
|
|
35
|
-
except Exception:
|
|
36
|
-
PR655 = PR650 = None
|
|
31
|
+
from psychopy.hardware.pr import PR655, PR650
|
|
37
32
|
|
|
38
33
|
# Konica Minolta light-measuring devices
|
|
39
|
-
|
|
40
|
-
from ..minolta import LS100, CS100A
|
|
41
|
-
except Exception:
|
|
42
|
-
LS100 = CS100A = None
|
|
34
|
+
from psychopy.hardware.minolta import LS100, CS100A
|
|
43
35
|
|
|
44
36
|
# Gamma scientific devices
|
|
45
|
-
|
|
46
|
-
from ..gammasci import S470
|
|
47
|
-
except Exception:
|
|
48
|
-
S470 = None
|
|
37
|
+
from psychopy.hardware.gammasci import S470
|
|
49
38
|
|
|
50
39
|
# photometer interfaces will be stored here after being registered
|
|
51
40
|
photometerInterfaces = {}
|
|
@@ -123,8 +112,11 @@ def getAllPhotometers():
|
|
|
123
112
|
'ColorCAL', 'OptiCAL', 'S470', 'PR650', 'PR655', 'LS100', 'CS100A')
|
|
124
113
|
incPhotomList = []
|
|
125
114
|
for photName in optionalPhotometers:
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
try:
|
|
116
|
+
photClass = globals()[photName]
|
|
117
|
+
except (ImportError, AttributeError):
|
|
118
|
+
continue
|
|
119
|
+
if issubclass(photClass, PluginStub):
|
|
128
120
|
continue
|
|
129
121
|
incPhotomList.append(photClass)
|
|
130
122
|
|
psychopy/hardware/pr.py
CHANGED
|
@@ -12,19 +12,12 @@ These are optional components that can be obtained by installing the
|
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"session to enable support.")
|
|
24
|
-
except Exception as e:
|
|
25
|
-
logging.error(
|
|
26
|
-
"Error encountered while loading `psychopy-photoresearch`. Check logs "
|
|
27
|
-
"for more information.")
|
|
28
|
-
|
|
29
|
-
if __name__ == "__main__":
|
|
15
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PR650(PluginStub, plugin="psychopy-photoresearch", doclink="https://psychopy.github.io/psychopy-photoresearch/coder/PR650"):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PR655(PluginStub, plugin="psychopy-photoresearch", doclink="https://psychopy.github.io/psychopy-photoresearch/coder/PR655"):
|
|
30
23
|
pass
|