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/app/runner/runner.py
CHANGED
|
@@ -902,7 +902,7 @@ class RunnerPanel(wx.Panel, ScriptProcess, handlers.ThemeMixin):
|
|
|
902
902
|
# disable stop
|
|
903
903
|
self.ribbon.buttons['pystop'].Disable()
|
|
904
904
|
# switch mode
|
|
905
|
-
self.ribbon.buttons['pyswitch'].setMode(runMode == "run")
|
|
905
|
+
self.ribbon.buttons['pyswitch'].setMode(runMode == "run", silent=True)
|
|
906
906
|
# update
|
|
907
907
|
self.updateAlerts()
|
|
908
908
|
self.app.updateWindowMenu()
|
|
@@ -1235,6 +1235,11 @@ class RunnerRibbon(ribbon.FrameRibbon):
|
|
|
1235
1235
|
section="pavlovia", name="pavuser", frame=parent
|
|
1236
1236
|
)
|
|
1237
1237
|
|
|
1238
|
+
self.addSeparator()
|
|
1239
|
+
|
|
1240
|
+
# --- Plugin sections ---
|
|
1241
|
+
self.addPluginSections("psychopy.app.builder")
|
|
1242
|
+
|
|
1238
1243
|
# --- Views ---
|
|
1239
1244
|
self.addStretchSpacer()
|
|
1240
1245
|
self.addSeparator()
|
|
@@ -93,6 +93,18 @@ class StdOutRich(wx.richtext.RichTextCtrl, _BaseErrorHandler, handlers.ThemeMixi
|
|
|
93
93
|
font=font.obj,
|
|
94
94
|
)),
|
|
95
95
|
}
|
|
96
|
+
# connect logging levels to styles
|
|
97
|
+
from psychopy import logging
|
|
98
|
+
self.logLevelStyles = {
|
|
99
|
+
logging.NOTSET: self._styles['base'],
|
|
100
|
+
logging.DEBUG: self._styles['info'],
|
|
101
|
+
logging.INFO: self._styles['info'],
|
|
102
|
+
logging.EXP: self._styles['info'],
|
|
103
|
+
logging.DATA: self._styles['info'],
|
|
104
|
+
logging.WARNING: self._styles['warning'],
|
|
105
|
+
logging.ERROR: self._styles['error'],
|
|
106
|
+
logging.CRITICAL: self._styles['error'],
|
|
107
|
+
}
|
|
96
108
|
|
|
97
109
|
def onURL(self, evt=None):
|
|
98
110
|
wx.BeginBusyCursor()
|
|
@@ -119,6 +131,7 @@ class StdOutRich(wx.richtext.RichTextCtrl, _BaseErrorHandler, handlers.ThemeMixi
|
|
|
119
131
|
class WordFetcher:
|
|
120
132
|
File "C:\\Program Files\\wxPython2.8 Docs and Demos\\samples\\hangman\\hangman.py", line 23, in WordFetcher
|
|
121
133
|
"""
|
|
134
|
+
from psychopy import logging
|
|
122
135
|
|
|
123
136
|
if type(inStr) == AlertEntry:
|
|
124
137
|
alert = inStr
|
|
@@ -174,17 +187,20 @@ class StdOutRich(wx.richtext.RichTextCtrl, _BaseErrorHandler, handlers.ThemeMixi
|
|
|
174
187
|
self.BeginURL(thisLine)
|
|
175
188
|
self.WriteText(thisLine)
|
|
176
189
|
self.EndURL()
|
|
177
|
-
elif
|
|
178
|
-
# this line contains
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
elif re.findall(logging._levelNamesRe, thisLine):
|
|
191
|
+
# this line contains a logging message
|
|
192
|
+
lvl = logging.NOTSET
|
|
193
|
+
# for each styled level...
|
|
194
|
+
for thisLvl, style in self.logLevelStyles.items():
|
|
195
|
+
# get its name
|
|
196
|
+
name = logging._levelNames[thisLvl]
|
|
197
|
+
# look for name in the current line
|
|
198
|
+
if len(re.findall(name, thisLine)) > 0:
|
|
199
|
+
# if found, set level and stop looking
|
|
200
|
+
lvl = thisLvl
|
|
201
|
+
break
|
|
202
|
+
# if level allowed by prefs, set style and write
|
|
203
|
+
self.BeginStyle(self.logLevelStyles[lvl])
|
|
188
204
|
self.WriteText(thisLine)
|
|
189
205
|
else:
|
|
190
206
|
# anything else
|
psychopy/app/themes/icons.py
CHANGED
|
@@ -4,7 +4,7 @@ from abc import ABC
|
|
|
4
4
|
import numpy
|
|
5
5
|
import wx
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from psychopy import prefs
|
|
7
|
+
from psychopy import prefs, logging
|
|
8
8
|
from . import theme as appTheme
|
|
9
9
|
|
|
10
10
|
retStr = ""
|
|
@@ -18,7 +18,7 @@ class BaseIcon:
|
|
|
18
18
|
self.bitmaps = {}
|
|
19
19
|
self._bitmap = None
|
|
20
20
|
self.size = size
|
|
21
|
-
|
|
21
|
+
self.stem = stem
|
|
22
22
|
if theme in (appTheme.icons, None) and stem in iconCache:
|
|
23
23
|
# Duplicate relevant attributes if relevant (depends on subclass)
|
|
24
24
|
self.bitmaps = iconCache[stem].bitmaps
|
|
@@ -30,6 +30,19 @@ class BaseIcon:
|
|
|
30
30
|
# Store ref to self in iconCache if using app theme
|
|
31
31
|
if theme in (appTheme.icons, None):
|
|
32
32
|
iconCache[stem] = self
|
|
33
|
+
|
|
34
|
+
def reload(self, theme=None):
|
|
35
|
+
"""
|
|
36
|
+
Get all images associated with this icon again. This is useful when changeing theme to one
|
|
37
|
+
with different icons.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
theme : str, optional
|
|
42
|
+
Theme to get icons from, by default will use the current theme
|
|
43
|
+
"""
|
|
44
|
+
self._populate(stem=self.stem, theme=theme)
|
|
45
|
+
|
|
33
46
|
|
|
34
47
|
def _populate(self, stem, theme=None):
|
|
35
48
|
raise NotImplementedError(
|
|
@@ -132,6 +145,8 @@ class ButtonIcon(BaseIcon):
|
|
|
132
145
|
theme = appTheme.icons
|
|
133
146
|
# Get all files in the resource folder containing the given stem
|
|
134
147
|
matches = [f for f in resources.glob(f"**/{stem}*.png")]
|
|
148
|
+
# get all icons from plugins
|
|
149
|
+
matches += findPluginIcons(stem)
|
|
135
150
|
# Create blank arrays to store retina and non-retina files
|
|
136
151
|
ret = {}
|
|
137
152
|
nret = {}
|
|
@@ -248,3 +263,38 @@ class ComponentIcon(BaseIcon):
|
|
|
248
263
|
self._beta = wx.Bitmap(combined)
|
|
249
264
|
|
|
250
265
|
return self._beta
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def findPluginIcons(stem):
|
|
269
|
+
"""
|
|
270
|
+
Search icons added by plugins for any matching the given file stem.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
stem : str
|
|
275
|
+
The file stem (aka the filename without `.png` on the end) to search for - will also find
|
|
276
|
+
files beginning with the given stem (e.g. "globe" will also find "globe@2x.png")
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
list[Path]
|
|
281
|
+
List of file paths for found icons
|
|
282
|
+
"""
|
|
283
|
+
from psychopy.plugins import getEntryPointGroup
|
|
284
|
+
# start off with no files
|
|
285
|
+
files = []
|
|
286
|
+
# iterate through found entry points
|
|
287
|
+
for ep in getEntryPointGroup("psychopy.app.themes.icons"):
|
|
288
|
+
try:
|
|
289
|
+
# try to load module
|
|
290
|
+
mod = ep.load()
|
|
291
|
+
# get module folder
|
|
292
|
+
folder = Path(mod.__file__).parent
|
|
293
|
+
# add all matching .png files from that folder
|
|
294
|
+
for file in folder.glob(f"**/{stem}*.png"):
|
|
295
|
+
files.append(file)
|
|
296
|
+
except:
|
|
297
|
+
# if it fails for any reason, skip it
|
|
298
|
+
logging.warn(f"Failed to load {ep.value}")
|
|
299
|
+
|
|
300
|
+
return files
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
psychopy/assets/next.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changes in 2023.1.0
|
|
2
|
+
|
|
3
|
+
## Introducing: Plugins
|
|
4
|
+
Over the years, PsychoPy has gotten pretty big! We have a lot more functionality than when we
|
|
5
|
+
first started, but with that comes more packages and dependencies. In 2023.1.0 we introduced
|
|
6
|
+
plugins - making certain functinality optional rather than coming with PsychoPy by default.
|
|
7
|
+
Check out the "Plugins & packages manager" in the "Tools" menu to see what's available.
|
|
8
|
+
|
|
9
|
+
[Click here for more information about plugins.](https://www.psychopy.org/usingplugins.html)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changes in 2024.1.0
|
|
2
|
+
|
|
3
|
+
## Introducing: Pilot mode
|
|
4
|
+
You may have noticed a new toggle button in the Builder toolbar - pressing it changes the run
|
|
5
|
+
buttons from green to orange. This button toggles the new "pilot mode" on and off. When in pilot
|
|
6
|
+
mode, the "Run in JS" button will run your experiment locally in your browser rather than on
|
|
7
|
+
Pavlovia, and the "Run in Python" button will run your experiment with a few (configurable)
|
|
8
|
+
adjustments, most noticeably:
|
|
9
|
+
- It will always run in Windowed mode, making it easier to escape if something goes wrong
|
|
10
|
+
- It will always run with full logging, so you get the maximum amount of info
|
|
11
|
+
|
|
12
|
+
To make it extra clear that you are in piloting mode, there's also a pretty noticeable orange
|
|
13
|
+
border around the window, so you don't run on actual participants while in this mode.
|
|
14
|
+
|
|
15
|
+
The purpose of piloting mode is to give you a safer environment to build your experiment in,
|
|
16
|
+
after which you can switch to running mode when you're ready to start gathering data.
|
|
File without changes
|
psychopy/clock.py
CHANGED
|
@@ -23,7 +23,7 @@ import time
|
|
|
23
23
|
import sys
|
|
24
24
|
from datetime import datetime
|
|
25
25
|
|
|
26
|
-
from
|
|
26
|
+
from packaging.version import Version
|
|
27
27
|
|
|
28
28
|
try:
|
|
29
29
|
import pyglet
|
|
@@ -507,7 +507,7 @@ def _dispatchWindowEvents():
|
|
|
507
507
|
# let's see if pyglet collected any event in meantime
|
|
508
508
|
try:
|
|
509
509
|
# this takes focus away from command line terminal window:
|
|
510
|
-
if
|
|
510
|
+
if Version(pyglet.version) < Version('1.2'):
|
|
511
511
|
# events for sounds/video should run independently of wait()
|
|
512
512
|
pyglet.media.dispatch_events()
|
|
513
513
|
except AttributeError:
|
psychopy/colors.py
CHANGED
|
@@ -389,7 +389,8 @@ class Color:
|
|
|
389
389
|
adj = np.clip(self.rgb * contrast, -1, 1)
|
|
390
390
|
buffer = self.copy()
|
|
391
391
|
buffer.rgb = adj
|
|
392
|
-
|
|
392
|
+
self._renderCache[space] = getattr(buffer, space)
|
|
393
|
+
return self._renderCache[space]
|
|
393
394
|
|
|
394
395
|
def __repr__(self):
|
|
395
396
|
"""If colour is printed, it will display its class and value.
|
psychopy/compatibility.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from packaging.version import Version
|
|
4
5
|
import psychopy.data
|
|
5
6
|
|
|
6
7
|
######### Begin Compatibility Class Definitions #########
|
|
@@ -93,3 +94,54 @@ def checkCompatibility(old, new, prefs=None, fix=True):
|
|
|
93
94
|
msg += "\nNo known compatibility issues"
|
|
94
95
|
|
|
95
96
|
return (not warning), msg
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def checkUpdatesInfo(old, new):
|
|
100
|
+
"""
|
|
101
|
+
Checks whether we need to display information from a new update, e.g. introducing a new feature.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
old : str or packaging.version.Version
|
|
106
|
+
Last version which was opened
|
|
107
|
+
new : str or packaging.version.Version
|
|
108
|
+
Current version
|
|
109
|
+
prefs : psychopy.preferences.Preferences
|
|
110
|
+
Preferences for the app
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
list[str]
|
|
115
|
+
List of strings with markdown content for relevant updates
|
|
116
|
+
"""
|
|
117
|
+
from psychopy.preferences import prefs
|
|
118
|
+
# make sure both versions are Version objects
|
|
119
|
+
if isinstance(old, str):
|
|
120
|
+
old = Version(old)
|
|
121
|
+
if isinstance(new, str):
|
|
122
|
+
new = Version(new)
|
|
123
|
+
# start off with no messages
|
|
124
|
+
messages = []
|
|
125
|
+
# if not a new version, no action needed
|
|
126
|
+
if old >= new:
|
|
127
|
+
return messages
|
|
128
|
+
# find changes folder
|
|
129
|
+
changesDir = Path(prefs.paths['psychopy']) / "changes"
|
|
130
|
+
# if it is a new version, check for updates
|
|
131
|
+
for file in changesDir.glob("*.md"):
|
|
132
|
+
# try to Version-ise target
|
|
133
|
+
try:
|
|
134
|
+
target = Version(file.stem)
|
|
135
|
+
except (TypeError, ValueError):
|
|
136
|
+
# skip if it fails
|
|
137
|
+
continue
|
|
138
|
+
# have we just crossed the target version?
|
|
139
|
+
if old < target < new:
|
|
140
|
+
# load the markdown file
|
|
141
|
+
msg = file.read_text(encoding="utf-8")
|
|
142
|
+
# add its contents to messages array
|
|
143
|
+
messages.append(msg)
|
|
144
|
+
# reverse messages so they go from most-to-least recent
|
|
145
|
+
messages.reverse()
|
|
146
|
+
|
|
147
|
+
return messages
|
|
Binary file
|
|
@@ -45,26 +45,28 @@ BOMS = {
|
|
|
45
45
|
BOM_UTF16_LE: ('utf16_le', 'utf_16'),
|
|
46
46
|
BOM_UTF16: ('utf_16', 'utf_16'),
|
|
47
47
|
}
|
|
48
|
+
|
|
48
49
|
# All legal variants of the BOM codecs.
|
|
49
|
-
# TODO: the list of aliases is not meant to be exhaustive, is there a
|
|
50
|
-
# better way ?
|
|
51
50
|
BOM_LIST = {
|
|
52
51
|
'utf_16': 'utf_16',
|
|
53
|
-
'u16': 'utf_16',
|
|
54
52
|
'utf16': 'utf_16',
|
|
55
53
|
'utf-16': 'utf_16',
|
|
56
|
-
'utf16_be': 'utf16_be',
|
|
57
54
|
'utf_16_be': 'utf16_be',
|
|
58
55
|
'utf-16be': 'utf16_be',
|
|
59
|
-
'utf16_le': 'utf16_le',
|
|
60
56
|
'utf_16_le': 'utf16_le',
|
|
61
57
|
'utf-16le': 'utf16_le',
|
|
62
58
|
'utf_8': 'utf_8',
|
|
63
|
-
'u8': 'utf_8',
|
|
64
|
-
'utf': 'utf_8',
|
|
65
59
|
'utf8': 'utf_8',
|
|
66
60
|
'utf-8': 'utf_8',
|
|
67
|
-
|
|
61
|
+
'u16': 'utf_16', # Add mapping for 'u16'
|
|
62
|
+
'u8': 'utf_8', # Add mapping for 'u8'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Add regular expressions for matching variations
|
|
66
|
+
for encoding in ['utf-16', 'utf-8']:
|
|
67
|
+
regex = re.compile(fr'{encoding}(?:_|\-)?(?:be|le)?|u16|u8', re.IGNORECASE)
|
|
68
|
+
for match in filter(regex.fullmatch, BOM_LIST.keys()):
|
|
69
|
+
BOM_LIST[match] = BOM_LIST[encoding]
|
|
68
70
|
|
|
69
71
|
# Map of encodings to the BOM to write.
|
|
70
72
|
BOM_SET = {
|
psychopy/data/__init__.py
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from packaging.version import Version
|
|
7
7
|
|
|
8
8
|
from .base import DataHandler
|
|
9
|
+
from .routine import Routine
|
|
9
10
|
from .experiment import ExperimentHandler
|
|
10
11
|
from .trial import TrialHandler, TrialHandler2, TrialHandlerExt, TrialType
|
|
11
12
|
from .staircase import (StairHandler, QuestHandler, PsiHandler,
|
|
@@ -27,7 +28,7 @@ from .fit import (FitFunction, FitCumNormal, FitLogistic, FitNakaRushton,
|
|
|
27
28
|
try:
|
|
28
29
|
# import openpyxl
|
|
29
30
|
import openpyxl
|
|
30
|
-
if
|
|
31
|
+
if Version(openpyxl.__version__) >= Version('2.4.0'):
|
|
31
32
|
# openpyxl moved get_column_letter to utils.cell
|
|
32
33
|
from openpyxl.utils.cell import get_column_letter
|
|
33
34
|
else:
|
psychopy/data/base.py
CHANGED
|
@@ -11,7 +11,7 @@ import codecs
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pandas as pd
|
|
13
13
|
import json_tricks
|
|
14
|
-
from
|
|
14
|
+
from packaging.version import Version
|
|
15
15
|
|
|
16
16
|
import psychopy
|
|
17
17
|
from psychopy import logging
|
|
@@ -23,7 +23,7 @@ from .utils import _getExcelCellName
|
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
25
|
import openpyxl
|
|
26
|
-
if
|
|
26
|
+
if Version(openpyxl.__version__) >= Version('2.4.0'):
|
|
27
27
|
# openpyxl moved get_column_letter to utils.cell
|
|
28
28
|
from openpyxl.utils.cell import get_column_letter
|
|
29
29
|
else:
|
|
@@ -120,7 +120,7 @@ class _BaseTrialHandler(_ComparisonMixin):
|
|
|
120
120
|
"""
|
|
121
121
|
fileName = pathToString(fileName)
|
|
122
122
|
|
|
123
|
-
if self.thisTrialN <
|
|
123
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
124
124
|
# if both are < 1 we haven't started
|
|
125
125
|
if self.autoLog:
|
|
126
126
|
logging.info('.saveAsPickle() called but no trials completed.'
|
|
@@ -191,7 +191,7 @@ class _BaseTrialHandler(_ComparisonMixin):
|
|
|
191
191
|
if stimOut is None:
|
|
192
192
|
stimOut = []
|
|
193
193
|
|
|
194
|
-
if self.thisTrialN <
|
|
194
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
195
195
|
# if both are < 1 we haven't started
|
|
196
196
|
if self.autoLog:
|
|
197
197
|
logging.info('TrialHandler.saveAsText called but no trials'
|
|
@@ -306,7 +306,7 @@ class _BaseTrialHandler(_ComparisonMixin):
|
|
|
306
306
|
if stimOut is None:
|
|
307
307
|
stimOut = []
|
|
308
308
|
|
|
309
|
-
if self.thisTrialN <
|
|
309
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
310
310
|
# if both are < 1 we haven't started
|
|
311
311
|
if self.autoLog:
|
|
312
312
|
logging.info('TrialHandler.saveAsExcel called but no '
|
psychopy/data/experiment.py
CHANGED
|
@@ -9,6 +9,7 @@ import pandas as pd
|
|
|
9
9
|
|
|
10
10
|
from psychopy import constants, clock
|
|
11
11
|
from psychopy import logging
|
|
12
|
+
from psychopy.data.trial import TrialHandler2
|
|
12
13
|
from psychopy.tools.filetools import (openOutputFile, genDelimiter,
|
|
13
14
|
genFilenameFromDelimiter)
|
|
14
15
|
from psychopy.localization import _translate
|
|
@@ -456,6 +457,112 @@ class ExperimentHandler(_ComparisonMixin):
|
|
|
456
457
|
# set own status
|
|
457
458
|
self.status = constants.STOPPED
|
|
458
459
|
|
|
460
|
+
def skipTrials(self, n=1):
|
|
461
|
+
"""
|
|
462
|
+
Skip ahead n trials - the trials inbetween will be marked as "skipped". If you try to
|
|
463
|
+
skip past the last trial, will log a warning and skip *to* the last trial.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
n : int
|
|
468
|
+
Number of trials to skip ahead
|
|
469
|
+
"""
|
|
470
|
+
# return if there isn't a TrialHandler2 active
|
|
471
|
+
if not isinstance(self.currentLoop, TrialHandler2):
|
|
472
|
+
return
|
|
473
|
+
# skip trials in current loop
|
|
474
|
+
self.currentLoop.skipTrials(n)
|
|
475
|
+
|
|
476
|
+
def rewindTrials(self, n=1):
|
|
477
|
+
"""
|
|
478
|
+
Skip ahead n trials - the trials inbetween will be marked as "skipped". If you try to
|
|
479
|
+
skip past the last trial, will log a warning and skip *to* the last trial.
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
n : int
|
|
484
|
+
Number of trials to skip ahead
|
|
485
|
+
"""
|
|
486
|
+
# return if there isn't a TrialHandler2 active
|
|
487
|
+
if not isinstance(self.currentLoop, TrialHandler2):
|
|
488
|
+
return
|
|
489
|
+
# rewind trials in current loop
|
|
490
|
+
self.currentLoop.rewindTrials(n)
|
|
491
|
+
|
|
492
|
+
def getAllTrials(self):
|
|
493
|
+
"""
|
|
494
|
+
Returns all trials (elapsed, current and upcoming) with an index indicating which trial is
|
|
495
|
+
the current trial.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
list[Trial]
|
|
500
|
+
List of trials, in order (oldest to newest)
|
|
501
|
+
int
|
|
502
|
+
Index of the current trial in this list
|
|
503
|
+
"""
|
|
504
|
+
# return None if there isn't a TrialHandler2 active
|
|
505
|
+
if not isinstance(self.currentLoop, TrialHandler2):
|
|
506
|
+
return [None], 0
|
|
507
|
+
# get all trials from current loop
|
|
508
|
+
return self.currentLoop.getAllTrials()
|
|
509
|
+
|
|
510
|
+
def getCurrentTrial(self):
|
|
511
|
+
"""
|
|
512
|
+
Returns the current trial (`.thisTrial`)
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
Trial
|
|
517
|
+
The current trial
|
|
518
|
+
"""
|
|
519
|
+
# return None if there isn't a TrialHandler2 active
|
|
520
|
+
if not isinstance(self.currentLoop, TrialHandler2):
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
return self.currentLoop.getCurrentTrial()
|
|
524
|
+
|
|
525
|
+
def getFutureTrial(self, n=1):
|
|
526
|
+
"""
|
|
527
|
+
Returns the condition for n trials into the future, without
|
|
528
|
+
advancing the trials. Returns 'None' if attempting to go beyond
|
|
529
|
+
the last trial in the current loop, or if there is no current loop.
|
|
530
|
+
"""
|
|
531
|
+
# return None if there isn't a TrialHandler2 active
|
|
532
|
+
if not isinstance(self.currentLoop, TrialHandler2):
|
|
533
|
+
return None
|
|
534
|
+
# get future trial from current loop
|
|
535
|
+
return self.currentLoop.getFutureTrial(n)
|
|
536
|
+
|
|
537
|
+
def getFutureTrials(self, n=1, start=0):
|
|
538
|
+
"""
|
|
539
|
+
Returns Trial objects for a given range in the future. Will start looking at `start` trials
|
|
540
|
+
in the future and will return n trials from then, so e.g. to get all trials from 2 in the
|
|
541
|
+
future to 5 in the future you would use `start=2` and `n=3`.
|
|
542
|
+
|
|
543
|
+
Parameters
|
|
544
|
+
----------
|
|
545
|
+
n : int, optional
|
|
546
|
+
How many trials into the future to look, by default 1
|
|
547
|
+
start : int, optional
|
|
548
|
+
How many trials into the future to start looking at, by default 0
|
|
549
|
+
|
|
550
|
+
Returns
|
|
551
|
+
-------
|
|
552
|
+
list[Trial or None]
|
|
553
|
+
List of Trial objects n long. Any trials beyond the last trial are None.
|
|
554
|
+
"""
|
|
555
|
+
# blank list to store trials in
|
|
556
|
+
trials = []
|
|
557
|
+
# iterate through n trials
|
|
558
|
+
for i in range(n):
|
|
559
|
+
# add each to the list
|
|
560
|
+
trials.append(
|
|
561
|
+
self.getFutureTrial(start + i)
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return trials
|
|
565
|
+
|
|
459
566
|
def nextEntry(self):
|
|
460
567
|
"""Calling nextEntry indicates to the ExperimentHandler that the
|
|
461
568
|
current trial has ended and so further addData() calls correspond
|
|
@@ -464,9 +571,7 @@ class ExperimentHandler(_ComparisonMixin):
|
|
|
464
571
|
this = self.thisEntry
|
|
465
572
|
# fetch data from each (potentially-nested) loop
|
|
466
573
|
for thisLoop in self.loopsUnfinished:
|
|
467
|
-
|
|
468
|
-
for n, name in enumerate(names):
|
|
469
|
-
this[name] = vals[n]
|
|
574
|
+
self.updateEntryFromLoop(thisLoop)
|
|
470
575
|
# add the extraInfo dict to the data
|
|
471
576
|
if type(self.extraInfo) == dict:
|
|
472
577
|
this.update(self.extraInfo)
|
|
@@ -474,6 +579,24 @@ class ExperimentHandler(_ComparisonMixin):
|
|
|
474
579
|
# add new entry with its
|
|
475
580
|
self.thisEntry = {}
|
|
476
581
|
|
|
582
|
+
def updateEntryFromLoop(self, thisLoop):
|
|
583
|
+
"""
|
|
584
|
+
Add all values from the given loop to the current entry.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
thisLoop : BaseLoopHandler
|
|
589
|
+
Loop to get fields from
|
|
590
|
+
"""
|
|
591
|
+
# for each name and value in the current trial...
|
|
592
|
+
names, vals = self._getLoopInfo(thisLoop)
|
|
593
|
+
for n, name in enumerate(names):
|
|
594
|
+
# add/update value
|
|
595
|
+
self.thisEntry[name] = vals[n]
|
|
596
|
+
# make sure name is in data names
|
|
597
|
+
if name not in self.dataNames:
|
|
598
|
+
self.dataNames.append(name)
|
|
599
|
+
|
|
477
600
|
def getAllEntries(self):
|
|
478
601
|
"""Fetches a copy of all the entries including a final (orphan) entry
|
|
479
602
|
if that exists. This allows entries to be saved even if nextEntry() is
|
|
@@ -563,7 +686,9 @@ class ExperimentHandler(_ComparisonMixin):
|
|
|
563
686
|
encoding=encoding)
|
|
564
687
|
|
|
565
688
|
names = self._getAllParamNames()
|
|
566
|
-
|
|
689
|
+
for name in self.dataNames:
|
|
690
|
+
if name not in names:
|
|
691
|
+
names.append(name)
|
|
567
692
|
# names from the extraInfo dictionary
|
|
568
693
|
names.extend(self._getExtraInfo()[0])
|
|
569
694
|
if len(names) < 1:
|
|
@@ -676,6 +801,7 @@ class ExperimentHandler(_ComparisonMixin):
|
|
|
676
801
|
# put in context
|
|
677
802
|
context = {
|
|
678
803
|
'type': "trials_data",
|
|
804
|
+
'thisTrial': self.thisEntry,
|
|
679
805
|
'trials': trials.to_dict(orient="records"),
|
|
680
806
|
'priority': self.columnPriority,
|
|
681
807
|
'threshold': priorityThreshold,
|
psychopy/data/routine.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from psychopy import constants
|
|
2
|
+
|
|
3
|
+
class Routine:
|
|
4
|
+
"""
|
|
5
|
+
Object representing a Routine, used to store start/stop times and other aspects of Routine settings.
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
name : str
|
|
10
|
+
Name of the Routine
|
|
11
|
+
components : list[object]
|
|
12
|
+
List of handles to Components associated with this Routine
|
|
13
|
+
maxDuration : float or None
|
|
14
|
+
Maximum time this Routine can take. None if there is no maximum.
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
tStart : float or None
|
|
19
|
+
Time (UTC) when this Routine started
|
|
20
|
+
tStartRefresh : float or None
|
|
21
|
+
Time (UTC) of the first frame flip of this Routine
|
|
22
|
+
tStop : float or None
|
|
23
|
+
Time (UTC) when this Routine ended
|
|
24
|
+
tStopRefresh : float or None
|
|
25
|
+
Time (UTC) of the last frame flip of this Routine
|
|
26
|
+
maxDurationReached : bool
|
|
27
|
+
True if this Routine ended by its max duration being reached
|
|
28
|
+
skipped : bool
|
|
29
|
+
True if this Routine was skipped by the "Skip if..." parameter of its settings
|
|
30
|
+
forceEnded : bool
|
|
31
|
+
True if this Routine was forcibly ended (e.g. by a key press)
|
|
32
|
+
status : int
|
|
33
|
+
Value from psychopy.constants.status indicating whether this Routine has started, is finished, etc.
|
|
34
|
+
"""
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
name,
|
|
38
|
+
components=[],
|
|
39
|
+
maxDuration=None,
|
|
40
|
+
):
|
|
41
|
+
self.name = name
|
|
42
|
+
self.components = components
|
|
43
|
+
self.maxDuration = maxDuration
|
|
44
|
+
# start all times as None
|
|
45
|
+
self.tStart = None
|
|
46
|
+
self.tStartRefresh = None
|
|
47
|
+
self.tStop = None
|
|
48
|
+
self.tStopRefresh = None
|
|
49
|
+
# start off assuming not skipped, timed out or force ended
|
|
50
|
+
self.maxDurationReached = False
|
|
51
|
+
self.skipped = False
|
|
52
|
+
self.forceEnded = False
|
|
53
|
+
# starting status
|
|
54
|
+
self.status = constants.NOT_STARTED
|
|
55
|
+
|
|
56
|
+
|
psychopy/data/staircase.py
CHANGED
|
@@ -7,7 +7,7 @@ import pickle
|
|
|
7
7
|
import copy
|
|
8
8
|
import warnings
|
|
9
9
|
import numpy as np
|
|
10
|
-
from
|
|
10
|
+
from packaging.version import Version
|
|
11
11
|
|
|
12
12
|
import psychopy
|
|
13
13
|
from psychopy import logging
|
|
@@ -26,7 +26,7 @@ except ImportError:
|
|
|
26
26
|
try:
|
|
27
27
|
# import openpyxl
|
|
28
28
|
import openpyxl
|
|
29
|
-
if
|
|
29
|
+
if Version(openpyxl.__version__) >= Version('2.4.0'):
|
|
30
30
|
# openpyxl moved get_column_letter to utils.cell
|
|
31
31
|
from openpyxl.utils.cell import get_column_letter
|
|
32
32
|
else:
|