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/experiment/loops.py
CHANGED
|
@@ -17,6 +17,7 @@ The code that writes out a *_lastrun.py experiment file is (in order):
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from copy import deepcopy
|
|
20
|
+
from pathlib import Path
|
|
20
21
|
from xml.etree.ElementTree import Element
|
|
21
22
|
|
|
22
23
|
from psychopy.experiment import getInitVals
|
|
@@ -111,7 +112,11 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
111
112
|
hint=_translate("Name of a file specifying the parameters for "
|
|
112
113
|
"each condition (.csv, .xlsx, or .pkl). Browse "
|
|
113
114
|
"to select a file. Right-click to preview file "
|
|
114
|
-
"contents, or create a new file.")
|
|
115
|
+
"contents, or create a new file."),
|
|
116
|
+
ctrlParams={
|
|
117
|
+
'template': Path(__file__).parent / "loopTemplate.xltx"
|
|
118
|
+
}
|
|
119
|
+
)
|
|
115
120
|
self.params['endPoints'] = Param(
|
|
116
121
|
list(endPoints), valType='num', inputType="single", updates=None, allowedUpdates=None,
|
|
117
122
|
label=_translate('End points'),
|
|
@@ -153,34 +158,45 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
153
158
|
inits = getInitVals(self.params)
|
|
154
159
|
# import conditions from file?
|
|
155
160
|
if self.params['conditionsFile'].val in ['None', None, 'none', '']:
|
|
156
|
-
|
|
161
|
+
inits['trialList'] = (
|
|
162
|
+
"[None]"
|
|
163
|
+
)
|
|
157
164
|
elif self.params['Selected rows'].val in ['None', None, 'none', '']:
|
|
158
165
|
# just a conditions file with no sub-selection
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
inits['trialList'] = (
|
|
167
|
+
"data.importConditions(%(conditionsFile)s)"
|
|
168
|
+
) % inits
|
|
161
169
|
else:
|
|
162
170
|
# a subset of a conditions file
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
inits['trialList'] = (
|
|
172
|
+
"data.importConditions(\n"
|
|
173
|
+
" %(conditionsFile)s, \n"
|
|
174
|
+
" selection=%(Selected rows)s\n"
|
|
175
|
+
")\n"
|
|
176
|
+
) % inits
|
|
165
177
|
# also a 'thisName' for use in "for thisTrial in trials:"
|
|
166
178
|
makeLoopIndex = self.exp.namespace.makeLoopIndex
|
|
167
|
-
self.thisName = makeLoopIndex(self.params['name'].val)
|
|
179
|
+
self.thisName = inits['loopIndex'] = makeLoopIndex(self.params['name'].val)
|
|
168
180
|
# write the code
|
|
169
|
-
code = (
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
code = (
|
|
182
|
+
"\n"
|
|
183
|
+
"# set up handler to look after randomisation of conditions etc\n"
|
|
184
|
+
"%(name)s = data.TrialHandler2(\n"
|
|
185
|
+
" name='%(name)s',\n"
|
|
186
|
+
" nReps=%(nReps)s, \n"
|
|
187
|
+
" method=%(loopType)s, \n"
|
|
188
|
+
" extraInfo=expInfo, \n"
|
|
189
|
+
" originPath=-1, \n"
|
|
190
|
+
" trialList=%(trialList)s, \n"
|
|
191
|
+
" seed=%(random seed)s, \n"
|
|
192
|
+
")\n"
|
|
193
|
+
)
|
|
172
194
|
buff.writeIndentedLines(code % inits)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
code = " seed=%(random seed)s, name='%(name)s')\n"
|
|
195
|
+
code = (
|
|
196
|
+
"thisExp.addLoop(%(name)s) # add the loop to the experiment\n"
|
|
197
|
+
"%(loopIndex)s = %(name)s.trialList[0] # so we can initialise stimuli with some values\n"
|
|
198
|
+
)
|
|
178
199
|
buff.writeIndentedLines(code % inits)
|
|
179
|
-
|
|
180
|
-
code = ("thisExp.addLoop(%(name)s) # add the loop to the experiment\n" +
|
|
181
|
-
self.thisName + " = %(name)s.trialList[0] " +
|
|
182
|
-
"# so we can initialise stimuli with some values\n")
|
|
183
|
-
buff.writeIndentedLines(code % self.params)
|
|
184
200
|
# unclutter the namespace
|
|
185
201
|
if not self.exp.prefsBuilder['unclutteredNamespace']:
|
|
186
202
|
code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
|
|
@@ -189,6 +205,14 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
189
205
|
" globals()[paramName] = %(name)s[paramName]\n")
|
|
190
206
|
buff.writeIndentedLines(code % {'name': self.thisName})
|
|
191
207
|
|
|
208
|
+
# send data to Liaison before loop starts
|
|
209
|
+
if self.params['isTrials'].val:
|
|
210
|
+
buff.writeIndentedLines(
|
|
211
|
+
"if thisSession is not None:\n"
|
|
212
|
+
" # if running in a Session with a Liaison client, send data up to now\n"
|
|
213
|
+
" thisSession.sendExperimentData()\n"
|
|
214
|
+
)
|
|
215
|
+
|
|
192
216
|
# then run the trials loop
|
|
193
217
|
code = "\nfor %s in %s:\n"
|
|
194
218
|
buff.writeIndentedLines(code % (self.thisName, self.params['name']))
|
|
@@ -200,18 +224,13 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
200
224
|
)
|
|
201
225
|
buff.writeIndentedLines(code % self.params)
|
|
202
226
|
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
" timers=[routineTimer], \n"
|
|
211
|
-
" playbackComponents=[]\n"
|
|
212
|
-
")\n"
|
|
213
|
-
)
|
|
214
|
-
buff.writeIndentedLines(code)
|
|
227
|
+
# send data to Liaison at start of each iteration
|
|
228
|
+
if self.params['isTrials'].val:
|
|
229
|
+
buff.writeIndentedLines(
|
|
230
|
+
"if thisSession is not None:\n"
|
|
231
|
+
" # if running in a Session with a Liaison client, send data up to now\n"
|
|
232
|
+
" thisSession.sendExperimentData()\n"
|
|
233
|
+
)
|
|
215
234
|
# unclutter the namespace
|
|
216
235
|
if not self.exp.prefsBuilder['unclutteredNamespace']:
|
|
217
236
|
code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
|
|
@@ -331,9 +350,6 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
331
350
|
buff.writeIndentedLines(
|
|
332
351
|
"thisExp.nextEntry()\n"
|
|
333
352
|
"\n"
|
|
334
|
-
"if thisSession is not None:\n"
|
|
335
|
-
" # if running in a Session with a Liaison client, send data up to now\n"
|
|
336
|
-
" thisSession.sendExperimentData()\n"
|
|
337
353
|
)
|
|
338
354
|
# end of the loop. dedent
|
|
339
355
|
buff.setIndentLevel(-1, relative=True)
|
|
@@ -341,7 +357,13 @@ class TrialHandler(_BaseLoopHandler):
|
|
|
341
357
|
% (self.params['nReps'], self.params['name']))
|
|
342
358
|
buff.writeIndented("\n")
|
|
343
359
|
# save data
|
|
344
|
-
if self.params['isTrials'].val
|
|
360
|
+
if self.params['isTrials'].val:
|
|
361
|
+
# send final data to Liaison
|
|
362
|
+
buff.writeIndentedLines(
|
|
363
|
+
"if thisSession is not None:\n"
|
|
364
|
+
" # if running in a Session with a Liaison client, send data up to now\n"
|
|
365
|
+
" thisSession.sendExperimentData()\n"
|
|
366
|
+
)
|
|
345
367
|
# a string to show all the available variables (if the conditions
|
|
346
368
|
# isn't just None or [None])
|
|
347
369
|
saveExcel = self.exp.settings.params['Save excel file'].val
|
|
@@ -615,11 +637,37 @@ class MultiStairHandler(_BaseLoopHandler):
|
|
|
615
637
|
label=_translate('Conditions'),
|
|
616
638
|
hint=_translate("A list of dictionaries describing the "
|
|
617
639
|
"differences between each staircase"))
|
|
640
|
+
|
|
641
|
+
def getTemplate():
|
|
642
|
+
"""
|
|
643
|
+
Method to get the template for this loop's chosen stair type. This is specified as a
|
|
644
|
+
method rather than a simple value as the control needs to update its target according
|
|
645
|
+
to the current value of stairType.
|
|
646
|
+
|
|
647
|
+
Returns
|
|
648
|
+
-------
|
|
649
|
+
pathlib.Path
|
|
650
|
+
Path to the appropriate template file
|
|
651
|
+
"""
|
|
652
|
+
# root folder
|
|
653
|
+
root = Path(__file__).parent
|
|
654
|
+
# get file path according to stairType param
|
|
655
|
+
if self.params['stairType'] == "QUEST":
|
|
656
|
+
return root / "questTemplate.xltx"
|
|
657
|
+
elif self.params['stairType'] == "questplus":
|
|
658
|
+
return root / "questPlusTemplate.xltx"
|
|
659
|
+
else:
|
|
660
|
+
return root / "staircaseTemplate.xltx"
|
|
661
|
+
|
|
618
662
|
self.params['conditionsFile'] = Param(
|
|
619
663
|
conditionsFile, valType='file', inputType='table', updates=None, allowedUpdates=None,
|
|
620
664
|
label=_translate('Conditions'),
|
|
621
665
|
hint=_translate("An xlsx or csv file specifying the parameters "
|
|
622
|
-
"for each condition")
|
|
666
|
+
"for each condition"),
|
|
667
|
+
ctrlParams={
|
|
668
|
+
'template': getTemplate
|
|
669
|
+
}
|
|
670
|
+
)
|
|
623
671
|
self.params['isTrials'] = Param(
|
|
624
672
|
isTrials, valType='bool', inputType='bool', updates=None, allowedUpdates=None,
|
|
625
673
|
label=_translate("Is trials"),
|
psychopy/experiment/params.py
CHANGED
|
@@ -116,38 +116,73 @@ class Param():
|
|
|
116
116
|
def __init__(self, val, valType, inputType=None, allowedVals=None, allowedTypes=None,
|
|
117
117
|
hint="", label="", updates=None, allowedUpdates=None,
|
|
118
118
|
allowedLabels=None, direct=True,
|
|
119
|
-
canBePath=True,
|
|
119
|
+
canBePath=True, ctrlParams=None,
|
|
120
120
|
categ="Basic"):
|
|
121
121
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
val : any
|
|
126
|
+
The value for this parameter
|
|
127
|
+
valType : str
|
|
128
|
+
The type of this parameter, one of:
|
|
129
|
+
- str: A string, will be compiled with " around it
|
|
130
|
+
- extendedStr: A long string, will be compiled with " around it and linebreaks will
|
|
131
|
+
be preserved
|
|
132
|
+
- code: Some code, will be compiled verbatim or translated to JS (no ")
|
|
133
|
+
- extendedCode: A block of code, will be compiled verbatim or translated to JS and
|
|
134
|
+
linebreaks will be preserved
|
|
135
|
+
- file: A file path, will be compiled like str but will replace unescaped \ with /
|
|
136
|
+
- list: A list of values, will be compiled like code but if there's no [] or () then
|
|
137
|
+
these are added
|
|
138
|
+
Note that, if value begins with a $, it will always be treated as code regardless of
|
|
139
|
+
valType
|
|
140
|
+
inputType : str
|
|
141
|
+
The type of control to make for this parameter in Builder, one of:
|
|
142
|
+
- single: A single-line text control
|
|
143
|
+
- multi: A multi-line text control
|
|
144
|
+
- color: A single-line text control with a button to open the color picker
|
|
145
|
+
- survey: A single-line text control with a button to open Pavlovia surveys list
|
|
146
|
+
- file: A single-line text control with a button to open a file browser
|
|
147
|
+
- fileList: Several file controls with buttons to add/remove
|
|
148
|
+
- table: A file control with an additional button to open in Excel
|
|
149
|
+
- choice: A single-choice control (dropdown)
|
|
150
|
+
- multiChoice: A multi-choice control (tickboxes)
|
|
151
|
+
- richChoice: A single-choice control (dropdown) with rich text for each option
|
|
152
|
+
- bool: A single checkbox control
|
|
153
|
+
- dict: Several key:value pair controls with buttons to add/remove fields
|
|
154
|
+
allowedVals : list[str]
|
|
155
|
+
Possible vals for this param (e.g. units param can only be 'norm','pix',...),
|
|
156
|
+
these are used in the compiled code
|
|
157
|
+
allowedLabels : list[str] or None
|
|
158
|
+
Labels corresponding to each value in allowedVals, these are displayed in Builder but
|
|
159
|
+
not used in the compiled code. Leave as None to simply copy allowedVals.
|
|
160
|
+
hint : str
|
|
161
|
+
Tooltip to display when param is hovered over
|
|
162
|
+
label : str
|
|
163
|
+
Label to display next to param
|
|
164
|
+
updates : str
|
|
165
|
+
How often does this parameter update, usually one of:
|
|
166
|
+
- constant: Value is set just the once
|
|
167
|
+
- set every repeat: Value is set at the start of each Routine
|
|
168
|
+
- set every frame: Value is set each frame
|
|
169
|
+
allowedUpdates : list[str]
|
|
170
|
+
List of values to show in the choice control for updates.
|
|
171
|
+
direct : bool
|
|
172
|
+
Are we expecting the value of this param to directly appear in the compiled code?
|
|
173
|
+
Mostly used by the test suite to check that params which should be used are used.
|
|
174
|
+
canBePath : bool
|
|
175
|
+
Is it possible for this parameter to be a path? Setting to False will disable
|
|
176
|
+
filepath sanitization (e.g. for textbox you may not want to replace \ with /)
|
|
177
|
+
ctrlParams : dict
|
|
178
|
+
Extra information to pass to the control, such as the Excel template file to use in a
|
|
179
|
+
`table` control.
|
|
180
|
+
categ : str
|
|
181
|
+
Category (tab) under which this param appears in Builder.
|
|
182
|
+
|
|
183
|
+
Deprecated params
|
|
184
|
+
-----------------
|
|
185
|
+
allowedTypes
|
|
151
186
|
"""
|
|
152
187
|
super(Param, self).__init__()
|
|
153
188
|
self.label = label
|
|
@@ -165,6 +200,7 @@ class Param():
|
|
|
165
200
|
self.codeWanted = False
|
|
166
201
|
self.canBePath = canBePath
|
|
167
202
|
self.direct = direct
|
|
203
|
+
self.ctrlParams = ctrlParams or {}
|
|
168
204
|
self.plugin = None
|
|
169
205
|
if inputType:
|
|
170
206
|
self.inputType = inputType
|
|
@@ -311,8 +347,11 @@ class Param():
|
|
|
311
347
|
return False
|
|
312
348
|
# if not a clear alias, use bool method of value
|
|
313
349
|
return bool(self.val)
|
|
314
|
-
|
|
315
|
-
def
|
|
350
|
+
|
|
351
|
+
def copy(self):
|
|
352
|
+
"""
|
|
353
|
+
Create a copy of this Param object
|
|
354
|
+
"""
|
|
316
355
|
return Param(
|
|
317
356
|
val=self.val,
|
|
318
357
|
valType=self.valType,
|
|
@@ -329,6 +368,9 @@ class Param():
|
|
|
329
368
|
categ=self.categ
|
|
330
369
|
)
|
|
331
370
|
|
|
371
|
+
def __deepcopy__(self, memo):
|
|
372
|
+
return self.copy()
|
|
373
|
+
|
|
332
374
|
@property
|
|
333
375
|
def _xml(self):
|
|
334
376
|
# Make root element
|
|
@@ -337,7 +337,14 @@ class pythonTransformer(ast.NodeTransformer):
|
|
|
337
337
|
# The default first argument for pop is -1 (remove the last item).
|
|
338
338
|
elif func.attr == 'pop':
|
|
339
339
|
func.attr = 'splice'
|
|
340
|
-
|
|
340
|
+
# if no args, construct an index that's `<name>.length-1`
|
|
341
|
+
if not args:
|
|
342
|
+
args = ast.BinOp(
|
|
343
|
+
ast.Attribute(value=func.value, attr="length"),
|
|
344
|
+
ast.Sub(),
|
|
345
|
+
ast.Constant(1, kind="int")
|
|
346
|
+
)
|
|
347
|
+
# add 1 as a second argument so the last item is deleted
|
|
341
348
|
args = [args, [ast.Constant(value=1, kind=None)]]
|
|
342
349
|
|
|
343
350
|
return ast.Call(
|
|
Binary file
|
|
@@ -618,6 +618,23 @@ class Routine(list):
|
|
|
618
618
|
# create the frame loop for this routine
|
|
619
619
|
code = ('\n# --- Prepare to start Routine "%s" ---\n')
|
|
620
620
|
buff.writeIndentedLines(code % (self.name))
|
|
621
|
+
# get list of components which have an in-experiment object
|
|
622
|
+
comps = [
|
|
623
|
+
c.name for c in self
|
|
624
|
+
if 'startType' in c.params and c.type != 'Variable'
|
|
625
|
+
]
|
|
626
|
+
compStr = ", ".join(comps)
|
|
627
|
+
# create object
|
|
628
|
+
code = (
|
|
629
|
+
"# create an object to store info about Routine %(name)s\n"
|
|
630
|
+
"%(name)s = data.Routine(\n"
|
|
631
|
+
" name='%(name)s',\n"
|
|
632
|
+
" components=[{}],\n"
|
|
633
|
+
")\n"
|
|
634
|
+
"%(name)s.status = NOT_STARTED\n"
|
|
635
|
+
).format(compStr)
|
|
636
|
+
buff.writeIndentedLines(code % self.params)
|
|
637
|
+
|
|
621
638
|
code = (
|
|
622
639
|
'continueRoutine = True\n'
|
|
623
640
|
)
|
|
@@ -626,21 +643,29 @@ class Routine(list):
|
|
|
626
643
|
# can we use non-slip timing?
|
|
627
644
|
maxTime, useNonSlip = self.getMaxTime()
|
|
628
645
|
|
|
646
|
+
# this is the beginning of the routine, before the loop starts
|
|
629
647
|
code = "# update component parameters for each repeat\n"
|
|
630
648
|
buff.writeIndentedLines(code)
|
|
631
|
-
# This is the beginning of the routine, before the loop starts
|
|
632
649
|
for event in self:
|
|
650
|
+
# don't write Routine Settings just yet...
|
|
651
|
+
if event is self.settings:
|
|
652
|
+
continue
|
|
653
|
+
# write the other Components'
|
|
633
654
|
event.writeRoutineStartCode(buff)
|
|
634
655
|
event.writeRoutineStartValidationCode(buff)
|
|
656
|
+
# write the Routine Settings code last
|
|
657
|
+
self.settings.writeRoutineStartCode(buff)
|
|
658
|
+
self.settings.writeRoutineStartValidationCode(buff)
|
|
635
659
|
|
|
636
660
|
code = '# keep track of which components have finished\n'
|
|
637
661
|
buff.writeIndentedLines(code)
|
|
638
|
-
#
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
662
|
+
# legacy code to support old `...Components` variable
|
|
663
|
+
code = (
|
|
664
|
+
"%(name)sComponents = %(name)s.components"
|
|
665
|
+
)
|
|
666
|
+
buff.writeIndentedLines(code % self.params)
|
|
642
667
|
|
|
643
|
-
code = ("for thisComponent in {name}
|
|
668
|
+
code = ("for thisComponent in {name}.components:\n"
|
|
644
669
|
" thisComponent.tStart = None\n"
|
|
645
670
|
" thisComponent.tStop = None\n"
|
|
646
671
|
" thisComponent.tStartRefresh = None\n"
|
|
@@ -655,12 +680,23 @@ class Routine(list):
|
|
|
655
680
|
'\n# --- Run Routine "{name}" ---\n')
|
|
656
681
|
buff.writeIndentedLines(code.format(name=self.name,
|
|
657
682
|
clockName=self._clockName))
|
|
683
|
+
# check for the trials loop ending this Routine
|
|
684
|
+
if len(self.exp.flow._loopList):
|
|
685
|
+
loop = self.exp.flow._loopList[-1]
|
|
686
|
+
code = (
|
|
687
|
+
"# if trial has changed, end Routine now\n"
|
|
688
|
+
"if isinstance({name}, data.TrialHandler2) and {thisName}.thisN != {"
|
|
689
|
+
"name}.thisTrial.thisN:\n"
|
|
690
|
+
" continueRoutine = False\n"
|
|
691
|
+
).format(name=loop.name, thisName=loop.thisName)
|
|
692
|
+
buff.writeIndentedLines(code)
|
|
693
|
+
|
|
658
694
|
# initial value for forceRoutineEnded (needs to happen now as Code components will have executed
|
|
659
695
|
# their Begin Routine code)
|
|
660
696
|
code = (
|
|
661
|
-
'routineForceEnded = not continueRoutine\n'
|
|
697
|
+
'%(name)s.forceEnded = routineForceEnded = not continueRoutine\n'
|
|
662
698
|
)
|
|
663
|
-
buff.writeIndentedLines(code)
|
|
699
|
+
buff.writeIndentedLines(code % self.params)
|
|
664
700
|
|
|
665
701
|
if useNonSlip:
|
|
666
702
|
code = f'while continueRoutine and routineTimer.getTime() < {maxTime}:\n'
|
|
@@ -706,21 +742,43 @@ class Routine(list):
|
|
|
706
742
|
)
|
|
707
743
|
buff.writeIndentedLines(code)
|
|
708
744
|
|
|
745
|
+
# handle pausing
|
|
746
|
+
playbackComponents = [
|
|
747
|
+
comp.name for comp in self
|
|
748
|
+
if type(comp).__name__ in ("MovieComponent", "SoundComponent")
|
|
749
|
+
]
|
|
750
|
+
playbackComponentsStr = ", ".join(playbackComponents)
|
|
751
|
+
code = (
|
|
752
|
+
"# pause experiment here if requested\n"
|
|
753
|
+
"if thisExp.status == PAUSED:\n"
|
|
754
|
+
" pauseExperiment(\n"
|
|
755
|
+
" thisExp=thisExp, \n"
|
|
756
|
+
" win=win, \n"
|
|
757
|
+
" timers=[routineTimer], \n"
|
|
758
|
+
" playbackComponents=[{playbackComponentsStr}]\n"
|
|
759
|
+
" )\n"
|
|
760
|
+
" # skip the frame we paused on\n"
|
|
761
|
+
" continue"
|
|
762
|
+
)
|
|
763
|
+
code = code.format(playbackComponentsStr=playbackComponentsStr)
|
|
764
|
+
buff.writeIndentedLines(code)
|
|
765
|
+
|
|
709
766
|
# are we done yet?
|
|
710
767
|
code = (
|
|
711
|
-
'\n
|
|
768
|
+
'\n'
|
|
769
|
+
'# check if all components have finished\n'
|
|
712
770
|
'if not continueRoutine: # a component has requested a '
|
|
713
771
|
'forced-end of Routine\n'
|
|
714
|
-
' routineForceEnded = True\n'
|
|
772
|
+
' %(name)s.forceEnded = routineForceEnded = True\n'
|
|
715
773
|
' break\n'
|
|
716
774
|
'continueRoutine = False # will revert to True if at least '
|
|
717
775
|
'one component still running\n'
|
|
718
|
-
'for thisComponent in %
|
|
776
|
+
'for thisComponent in %(name)s.components:\n'
|
|
719
777
|
' if hasattr(thisComponent, "status") and '
|
|
720
778
|
'thisComponent.status != FINISHED:\n'
|
|
721
779
|
' continueRoutine = True\n'
|
|
722
780
|
' break # at least one component has not yet finished\n')
|
|
723
|
-
buff.writeIndentedLines(code % self.
|
|
781
|
+
buff.writeIndentedLines(code % self.params)
|
|
724
782
|
|
|
725
783
|
# update screen
|
|
726
784
|
code = ('\n# refresh the screen\n'
|
|
@@ -733,23 +791,25 @@ class Routine(list):
|
|
|
733
791
|
buff.setIndentLevel(-1, True)
|
|
734
792
|
|
|
735
793
|
# write the code for each component for the end of the routine
|
|
736
|
-
code = ('\n# --- Ending Routine "%s" ---\n'
|
|
737
|
-
'for thisComponent in %
|
|
794
|
+
code = ('\n# --- Ending Routine "%(name)s" ---\n'
|
|
795
|
+
'for thisComponent in %(name)s.components:\n'
|
|
738
796
|
' if hasattr(thisComponent, "setAutoDraw"):\n'
|
|
739
797
|
' thisComponent.setAutoDraw(False)\n')
|
|
740
|
-
buff.writeIndentedLines(code %
|
|
798
|
+
buff.writeIndentedLines(code % self.params)
|
|
741
799
|
for event in self:
|
|
742
800
|
event.writeRoutineEndCode(buff)
|
|
743
801
|
|
|
744
802
|
if useNonSlip:
|
|
745
803
|
code = (
|
|
746
804
|
"# using non-slip timing so subtract the expected duration of this Routine (unless ended on request)\n"
|
|
747
|
-
"if
|
|
805
|
+
"if %(name)s.maxDurationReached:\n"
|
|
806
|
+
" routineTimer.addTime(-%(name)s.maxDuration)\n"
|
|
807
|
+
"elif %(name)s.forceEnded:\n"
|
|
748
808
|
" routineTimer.reset()\n"
|
|
749
809
|
"else:\n"
|
|
750
|
-
" routineTimer.addTime(
|
|
751
|
-
)
|
|
752
|
-
buff.writeIndentedLines(code %
|
|
810
|
+
" routineTimer.addTime(-{:f})\n"
|
|
811
|
+
).format(maxTime)
|
|
812
|
+
buff.writeIndentedLines(code % self.params)
|
|
753
813
|
|
|
754
814
|
def writeRoutineBeginCodeJS(self, buff, modular):
|
|
755
815
|
|
|
@@ -773,13 +833,21 @@ class Routine(list):
|
|
|
773
833
|
maxTime, useNonSlip = self.getMaxTime()
|
|
774
834
|
if useNonSlip:
|
|
775
835
|
buff.writeIndented('routineTimer.add(%f);\n' % (maxTime))
|
|
836
|
+
# keep track of whether max duration is reached
|
|
837
|
+
code = (
|
|
838
|
+
"%(name)sMaxDurationReached = false;\n"
|
|
839
|
+
)
|
|
840
|
+
buff.writeIndentedLines(code % self.params)
|
|
776
841
|
|
|
777
842
|
code = "// update component parameters for each repeat\n"
|
|
778
843
|
buff.writeIndentedLines(code)
|
|
779
844
|
# This is the beginning of the routine, before the loop starts
|
|
780
845
|
for thisCompon in self:
|
|
846
|
+
if thisCompon is self.settings:
|
|
847
|
+
continue
|
|
781
848
|
if "PsychoJS" in thisCompon.targets:
|
|
782
849
|
thisCompon.writeRoutineStartCodeJS(buff)
|
|
850
|
+
self.settings.writeRoutineStartCodeJS(buff)
|
|
783
851
|
|
|
784
852
|
code = ("// keep track of which components have finished\n"
|
|
785
853
|
"%(name)sComponents = [];\n" % self.params)
|
|
@@ -937,7 +1005,16 @@ class Routine(list):
|
|
|
937
1005
|
compon.writeRoutineEndCodeJS(buff)
|
|
938
1006
|
|
|
939
1007
|
# reset routineTimer at the *very end* of all non-nonSlip routines
|
|
940
|
-
if
|
|
1008
|
+
if useNonSlip:
|
|
1009
|
+
code = (
|
|
1010
|
+
"if (%(name)sMaxDurationReached) {{\n"
|
|
1011
|
+
" routineTimer.add(%(name)sMaxDuration);\n"
|
|
1012
|
+
"}} else {{\n"
|
|
1013
|
+
" routineTimer.add(-{:f});\n"
|
|
1014
|
+
"}}\n"
|
|
1015
|
+
).format(maxTime)
|
|
1016
|
+
buff.writeIndented(code % self.params)
|
|
1017
|
+
else:
|
|
941
1018
|
code = ('// the Routine "%s" was not non-slip safe, so reset '
|
|
942
1019
|
'the non-slip timer\n'
|
|
943
1020
|
'routineTimer.reset();\n\n')
|
|
@@ -1002,7 +1079,7 @@ class Routine(list):
|
|
|
1002
1079
|
if duration == FOREVER:
|
|
1003
1080
|
# only the *start* of an unlimited event should contribute
|
|
1004
1081
|
# to maxTime
|
|
1005
|
-
duration =
|
|
1082
|
+
duration = 0 # plus some minimal duration so it's visible
|
|
1006
1083
|
# now see if we have a end t value that beats the previous max
|
|
1007
1084
|
try:
|
|
1008
1085
|
# will fail if either value is not defined:
|
|
@@ -1014,8 +1091,11 @@ class Routine(list):
|
|
|
1014
1091
|
rtDur, numericStop = self.settings.getDuration()
|
|
1015
1092
|
if rtDur != FOREVER:
|
|
1016
1093
|
maxTime = rtDur
|
|
1094
|
+
# if nonslip is actively requested, force it
|
|
1095
|
+
if self.settings.params['forceNonSlip'] and maxTime not in (0, FOREVER):
|
|
1096
|
+
nonSlipSafe = True
|
|
1017
1097
|
# if there are no components, default to 10s
|
|
1018
|
-
if maxTime
|
|
1098
|
+
if maxTime in (0, None):
|
|
1019
1099
|
maxTime = 10
|
|
1020
1100
|
nonSlipSafe = False
|
|
1021
1101
|
return maxTime, nonSlipSafe
|
|
Binary file
|
|
@@ -91,7 +91,11 @@ class CounterbalanceRoutine(BaseStandaloneRoutine):
|
|
|
91
91
|
hint=_translate(
|
|
92
92
|
"Name of a file specifying the parameters for each group (.csv, .xlsx, or .pkl). Browse to select "
|
|
93
93
|
"a file. Right-click to preview file contents, or create a new file."
|
|
94
|
-
)
|
|
94
|
+
),
|
|
95
|
+
ctrlParams={
|
|
96
|
+
'template': Path(__file__).parent / "counterbalanceItems.xltx"
|
|
97
|
+
}
|
|
98
|
+
)
|
|
95
99
|
|
|
96
100
|
self.params['conditionsVariable'] = Param(
|
|
97
101
|
conditionsVariable, valType='code', inputType="single", categ="Basic",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -19,7 +19,7 @@ class PhotodiodeValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
19
19
|
iconFile = Path(__file__).parent / 'photodiode_validator.png'
|
|
20
20
|
tooltip = _translate('Photodiode validator')
|
|
21
21
|
deviceClasses = []
|
|
22
|
-
version = "
|
|
22
|
+
version = "2025.1.0"
|
|
23
23
|
|
|
24
24
|
def __init__(
|
|
25
25
|
self,
|
|
@@ -169,8 +169,9 @@ class PhotodiodeValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
169
169
|
channel, valType="code", inputType="single", categ="Device",
|
|
170
170
|
label=_translate("Photodiode channel"),
|
|
171
171
|
hint=_translate(
|
|
172
|
-
"If relevant, a channel number attached to the photodiode, to distinguish it
|
|
173
|
-
"the same port."
|
|
172
|
+
"If relevant, a channel number attached to the photodiode, to distinguish it "
|
|
173
|
+
"from other photodiodes on the same port. Leave blank to use the first photodiode "
|
|
174
|
+
"which can detect the Window."
|
|
174
175
|
)
|
|
175
176
|
)
|
|
176
177
|
|
|
@@ -266,14 +267,14 @@ class PhotodiodeValidatorRoutine(BaseValidatorRoutine, PluginDevicesMixin):
|
|
|
266
267
|
" report=%(report)s,\n"
|
|
267
268
|
")\n"
|
|
268
269
|
)
|
|
269
|
-
buff.writeIndentedLines(code %
|
|
270
|
+
buff.writeIndentedLines(code % inits)
|
|
270
271
|
# connect stimuli
|
|
271
272
|
for stim in self.findConnectedStimuli():
|
|
272
273
|
code = (
|
|
273
274
|
"# connect {stim} to %(name)s\n"
|
|
274
275
|
"%(name)s.connectStimulus({stim})\n"
|
|
275
276
|
).format(stim=stim.params['name'])
|
|
276
|
-
buff.writeIndentedLines(code %
|
|
277
|
+
buff.writeIndentedLines(code % inits)
|
|
277
278
|
|
|
278
279
|
def writeRoutineStartValidationCode(self, buff, stim):
|
|
279
280
|
"""
|
|
Binary file
|