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/data/trial.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import json
|
|
4
5
|
import os
|
|
5
6
|
import sys
|
|
6
7
|
import copy
|
|
@@ -419,10 +420,12 @@ class TrialHandler(_BaseTrialHandler):
|
|
|
419
420
|
for thisDataOut in dataOut:
|
|
420
421
|
# make a string version of the data and then format it
|
|
421
422
|
tmpData = dataAnal[thisDataOut][stimN]
|
|
423
|
+
replaceNone = False
|
|
422
424
|
if hasattr(tmpData, 'tolist'): # is a numpy array
|
|
423
425
|
strVersion = str(tmpData.tolist())
|
|
424
426
|
# for numeric data replace None with a blank cell
|
|
425
|
-
if tmpData.dtype.kind not in
|
|
427
|
+
if tmpData.dtype.kind not in 'SaUV':
|
|
428
|
+
replaceNone = True
|
|
426
429
|
strVersion = strVersion.replace('None', '')
|
|
427
430
|
elif tmpData in [None, 'None']:
|
|
428
431
|
strVersion = ''
|
|
@@ -434,18 +437,25 @@ class TrialHandler(_BaseTrialHandler):
|
|
|
434
437
|
strVersion = "--"
|
|
435
438
|
# handle list of values (e.g. rt_raw )
|
|
436
439
|
if (len(strVersion) and
|
|
437
|
-
|
|
438
|
-
|
|
440
|
+
strVersion[0] in '[(' and
|
|
441
|
+
strVersion[-1] in '])'):
|
|
439
442
|
strVersion = strVersion[1:-1] # skip first and last chars
|
|
440
443
|
# handle lists of lists (e.g. raw of multiple key presses)
|
|
441
444
|
if (len(strVersion) and
|
|
442
|
-
|
|
443
|
-
|
|
445
|
+
strVersion[0] in '[(' and
|
|
446
|
+
strVersion[-1] in '])'):
|
|
447
|
+
if replaceNone:
|
|
448
|
+
# Add None back so that the str is valid for eval
|
|
449
|
+
strVersion = strVersion.replace('[,', '[None,')
|
|
450
|
+
strVersion = strVersion.replace(', ,', ', None,')
|
|
444
451
|
tup = eval(strVersion) # convert back to a tuple
|
|
445
452
|
for entry in tup:
|
|
446
453
|
# contents of each entry is a list or tuple so keep in
|
|
447
454
|
# quotes to avoid probs with delim
|
|
448
|
-
|
|
455
|
+
currentEntry = str(entry)
|
|
456
|
+
if replaceNone:
|
|
457
|
+
currentEntry = currentEntry.replace('None', '')
|
|
458
|
+
thisLine.append(currentEntry)
|
|
449
459
|
else:
|
|
450
460
|
thisLine.extend(strVersion.split(','))
|
|
451
461
|
|
|
@@ -612,7 +622,7 @@ class TrialHandler(_BaseTrialHandler):
|
|
|
612
622
|
Defaults to `utf-8-sig`.
|
|
613
623
|
|
|
614
624
|
"""
|
|
615
|
-
if self.thisTrialN <
|
|
625
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
616
626
|
# if both are < 1 we haven't started
|
|
617
627
|
logging.info('TrialHandler.saveAsWideText called but no '
|
|
618
628
|
'trials completed. Nothing saved')
|
|
@@ -736,6 +746,74 @@ class TrialHandler(_BaseTrialHandler):
|
|
|
736
746
|
self.getExp().addData(thisType, value)
|
|
737
747
|
|
|
738
748
|
|
|
749
|
+
class Trial(dict):
|
|
750
|
+
def __init__(self, parent, thisN, thisRepN, thisTrialN, thisIndex, data=None):
|
|
751
|
+
dict.__init__(self)
|
|
752
|
+
# TrialHandler containing this trial
|
|
753
|
+
self.parent = parent
|
|
754
|
+
# state information about this trial
|
|
755
|
+
self.thisN = thisN
|
|
756
|
+
self.thisRepN = thisRepN
|
|
757
|
+
self.thisTrialN = thisTrialN
|
|
758
|
+
self.thisIndex = thisIndex
|
|
759
|
+
# data for this trial
|
|
760
|
+
if data is None:
|
|
761
|
+
data = {}
|
|
762
|
+
else:
|
|
763
|
+
data = data.copy()
|
|
764
|
+
self.data = data
|
|
765
|
+
|
|
766
|
+
def __repr__(self):
|
|
767
|
+
return (
|
|
768
|
+
f"<Trial {self.thisN} ({self.thisTrialN} in rep {self.thisRepN}) "
|
|
769
|
+
f"data={ {key: val for key,val in self.items()} }>"
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
@property
|
|
773
|
+
def data(self):
|
|
774
|
+
# return self when getting data (so it's modified by modifying data)
|
|
775
|
+
return self
|
|
776
|
+
|
|
777
|
+
@data.setter
|
|
778
|
+
def data(self, value: dict):
|
|
779
|
+
# when setting data, clear self...
|
|
780
|
+
self.clear()
|
|
781
|
+
# ... and set each value from the given dict
|
|
782
|
+
for key, val in value.items():
|
|
783
|
+
self[key] = val
|
|
784
|
+
|
|
785
|
+
def getDict(self):
|
|
786
|
+
"""
|
|
787
|
+
Get this Trial as a dict.
|
|
788
|
+
|
|
789
|
+
Returns
|
|
790
|
+
-------
|
|
791
|
+
dict
|
|
792
|
+
Dict containing information for this Trial.
|
|
793
|
+
"""
|
|
794
|
+
return {
|
|
795
|
+
'type': "trial_data",
|
|
796
|
+
'thisN': self.thisN,
|
|
797
|
+
'thisRepN': self.thisRepN,
|
|
798
|
+
'thisTrialN': self.thisTrialN,
|
|
799
|
+
'thisIndex': self.thisIndex,
|
|
800
|
+
'data': {key: val for key, val in self.items()},
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
def getJSON(self):
|
|
804
|
+
"""
|
|
805
|
+
Serialize this Trial to a JSON format.
|
|
806
|
+
|
|
807
|
+
Returns
|
|
808
|
+
-------
|
|
809
|
+
str
|
|
810
|
+
The results of Trial.getDict expressed as a JSON string
|
|
811
|
+
"""
|
|
812
|
+
return json.dumps(
|
|
813
|
+
self.getDict()
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
|
|
739
817
|
class TrialHandler2(_BaseTrialHandler):
|
|
740
818
|
"""Class to handle trial sequencing and data storage.
|
|
741
819
|
|
|
@@ -860,19 +938,15 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
860
938
|
self.remainingIndices = []
|
|
861
939
|
self.prevIndices = []
|
|
862
940
|
self.method = method
|
|
863
|
-
self.thisRepN = 0 # records which repetition or pass we are on
|
|
864
|
-
self.thisTrialN = -1 # records trial number within this repetition
|
|
865
|
-
self.thisN = -1
|
|
866
|
-
self.thisIndex = None # index of current trial in the conditions list
|
|
867
|
-
self.thisTrial = {}
|
|
868
|
-
self.finished = False
|
|
869
941
|
self.extraInfo = extraInfo
|
|
870
942
|
self.seed = seed
|
|
871
943
|
self._rng = np.random.default_rng(seed=seed)
|
|
872
944
|
self._trialAborted = False
|
|
873
945
|
|
|
874
946
|
# store a list of dicts, convert to pandas DataFrame on access
|
|
875
|
-
self.
|
|
947
|
+
self.elapsedTrials = []
|
|
948
|
+
self.upcomingTrials = None
|
|
949
|
+
self.thisTrial = None
|
|
876
950
|
|
|
877
951
|
self.originPath, self.origin = self.getOriginPathAndFile(originPath)
|
|
878
952
|
self._exp = None # the experiment handler that owns me!
|
|
@@ -941,7 +1015,7 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
941
1015
|
Note that data are stored internally as a list of dictionaries,
|
|
942
1016
|
one per trial. These are converted to a DataFrame on access.
|
|
943
1017
|
"""
|
|
944
|
-
return pd.DataFrame(self.
|
|
1018
|
+
return pd.DataFrame(self.elapsedTrials)
|
|
945
1019
|
|
|
946
1020
|
def __next__(self):
|
|
947
1021
|
"""Advances to next trial and returns it.
|
|
@@ -963,51 +1037,22 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
963
1037
|
break # break out of the forever loop
|
|
964
1038
|
# do stuff here for the trial
|
|
965
1039
|
"""
|
|
966
|
-
#
|
|
967
|
-
self.
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
if self.
|
|
971
|
-
self.
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
sequence *= self.nReps
|
|
981
|
-
# NB permutation *returns* a shuffled array
|
|
982
|
-
self.remainingIndices = list(self._rng.permutation(sequence))
|
|
983
|
-
elif (self.method in ('sequential', 'random') and
|
|
984
|
-
self.thisRepN < self.nReps):
|
|
985
|
-
# start a new repetition
|
|
986
|
-
self.thisTrialN = 0
|
|
987
|
-
self.thisRepN += 1
|
|
988
|
-
if self.method == 'random':
|
|
989
|
-
self._rng.shuffle(sequence) # shuffle (is in-place)
|
|
990
|
-
self.remainingIndices = list(sequence)
|
|
991
|
-
else:
|
|
992
|
-
# we've finished
|
|
993
|
-
self.finished = True
|
|
994
|
-
self._terminate() # raises Stop (code won't go beyond here)
|
|
995
|
-
|
|
996
|
-
# fetch the trial info
|
|
997
|
-
if len(self.trialList) == 0:
|
|
998
|
-
self.thisIndex = 0
|
|
999
|
-
self.thisTrial = {}
|
|
1000
|
-
else:
|
|
1001
|
-
self.thisIndex = self.remainingIndices.pop(0)
|
|
1002
|
-
# if None then use empty dict
|
|
1003
|
-
thisTrial = self.trialList[self.thisIndex] or {}
|
|
1004
|
-
self.thisTrial = copy.copy(thisTrial)
|
|
1005
|
-
# for fullRandom check how many times this has come up before
|
|
1006
|
-
if self.method == 'fullRandom':
|
|
1007
|
-
self.thisRepN = self.prevIndices.count(self.thisIndex)
|
|
1040
|
+
# mark previous trial as elapsed
|
|
1041
|
+
if self.thisTrial is not None:
|
|
1042
|
+
self.elapsedTrials.append(self.thisTrial)
|
|
1043
|
+
# if upcoming is None, recaculate
|
|
1044
|
+
if self.upcomingTrials is None:
|
|
1045
|
+
self.calculateUpcoming()
|
|
1046
|
+
# if upcoming is empty, finish
|
|
1047
|
+
if not self.upcomingTrials:
|
|
1048
|
+
self.finished = True
|
|
1049
|
+
self.thisTrial = None
|
|
1050
|
+
self._terminate()
|
|
1051
|
+
raise StopIteration
|
|
1052
|
+
# get first upcoming trial
|
|
1053
|
+
self.thisTrial = self.upcomingTrials.pop(0)
|
|
1008
1054
|
|
|
1009
1055
|
# update data structure with new info
|
|
1010
|
-
self._data.append(self.thisTrial) # update the data list of dicts
|
|
1011
1056
|
self.addData('thisN', self.thisN)
|
|
1012
1057
|
self.addData('thisTrialN', self.thisTrialN)
|
|
1013
1058
|
self.addData('thisRepN', self.thisRepN)
|
|
@@ -1016,21 +1061,114 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
1016
1061
|
vals = (self.thisRepN, self.thisTrialN, self.thisTrial)
|
|
1017
1062
|
logging.exp(msg % vals, obj=self.thisTrial)
|
|
1018
1063
|
|
|
1019
|
-
#
|
|
1020
|
-
|
|
1064
|
+
# update experiment handler entry
|
|
1065
|
+
exp = self.getExp()
|
|
1066
|
+
if exp is not None:
|
|
1067
|
+
exp.updateEntryFromLoop(self)
|
|
1021
1068
|
|
|
1022
1069
|
return self.thisTrial
|
|
1023
1070
|
|
|
1024
1071
|
next = __next__ # allows user to call without a loop `val = trials.next()`
|
|
1025
1072
|
|
|
1026
1073
|
@property
|
|
1027
|
-
def
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1074
|
+
def thisN(self):
|
|
1075
|
+
if self.thisTrial is None:
|
|
1076
|
+
if len(self.elapsedTrials):
|
|
1077
|
+
return self.elapsedTrials[-1].thisN
|
|
1078
|
+
else:
|
|
1079
|
+
return -1
|
|
1080
|
+
return self.thisTrial.thisN
|
|
1081
|
+
|
|
1082
|
+
@property
|
|
1083
|
+
def thisTrialN(self):
|
|
1084
|
+
if self.thisTrial is None:
|
|
1085
|
+
if len(self.elapsedTrials):
|
|
1086
|
+
return self.elapsedTrials[-1].thisTrialN
|
|
1087
|
+
else:
|
|
1088
|
+
return -1
|
|
1089
|
+
return self.thisTrial.thisTrialN
|
|
1090
|
+
|
|
1091
|
+
@property
|
|
1092
|
+
def thisRepN(self):
|
|
1093
|
+
if self.thisTrial is None:
|
|
1094
|
+
if len(self.elapsedTrials):
|
|
1095
|
+
return self.elapsedTrials[-1].thisRepN
|
|
1096
|
+
else:
|
|
1097
|
+
return -1
|
|
1098
|
+
return self.thisTrial.thisRepN
|
|
1099
|
+
|
|
1100
|
+
def calculateUpcoming(self, fromIndex=-1):
|
|
1101
|
+
"""Rebuild the sequence of trial/state info as if running the trials
|
|
1102
|
+
|
|
1103
|
+
Args:
|
|
1104
|
+
fromIndex (int, optional): the point in the sequnce from where to rebuild. Defaults to -1.
|
|
1032
1105
|
"""
|
|
1033
|
-
|
|
1106
|
+
# clear upcoming
|
|
1107
|
+
self.upcomingTrials = []
|
|
1108
|
+
# start off at 0 trial
|
|
1109
|
+
thisTrialN = 0
|
|
1110
|
+
thisN = 0
|
|
1111
|
+
thisRepN = 0
|
|
1112
|
+
# empty array to store indices once taken
|
|
1113
|
+
prevIndices = []
|
|
1114
|
+
# empty array to store remaining indices
|
|
1115
|
+
remainingIndices = []
|
|
1116
|
+
# iterate a while loop until we run out of trials
|
|
1117
|
+
while thisN < (self.nReps * len(self.trialList)):
|
|
1118
|
+
if not remainingIndices:
|
|
1119
|
+
# we've just started, or just starting a new repeat
|
|
1120
|
+
sequence = list(range(len(self.trialList)))
|
|
1121
|
+
if (self.method == 'fullRandom' and
|
|
1122
|
+
thisN < (self.nReps * len(self.trialList))):
|
|
1123
|
+
# we've only just started on a fullRandom sequence
|
|
1124
|
+
sequence *= self.nReps
|
|
1125
|
+
# NB permutation *returns* a shuffled array
|
|
1126
|
+
remainingIndices = list(self._rng.permutation(sequence))
|
|
1127
|
+
elif (self.method in ('sequential', 'random') and
|
|
1128
|
+
thisRepN < self.nReps):
|
|
1129
|
+
thisTrialN = 0
|
|
1130
|
+
thisRepN += 1
|
|
1131
|
+
if self.method == 'random':
|
|
1132
|
+
self._rng.shuffle(sequence) # shuffle (is in-place)
|
|
1133
|
+
remainingIndices = list(sequence)
|
|
1134
|
+
else:
|
|
1135
|
+
# we've finished
|
|
1136
|
+
break
|
|
1137
|
+
|
|
1138
|
+
if thisN < len(self.elapsedTrials):
|
|
1139
|
+
# trial has already happened - get its value
|
|
1140
|
+
thisTrial = self.elapsedTrials[thisN]
|
|
1141
|
+
# remove from remaining
|
|
1142
|
+
remainingIndices.pop(remainingIndices.index(thisTrial.thisIndex))
|
|
1143
|
+
else:
|
|
1144
|
+
# fetch the trial info
|
|
1145
|
+
if len(self.trialList) == 0:
|
|
1146
|
+
thisIndex = 0
|
|
1147
|
+
thisTrial = {}
|
|
1148
|
+
else:
|
|
1149
|
+
thisIndex = remainingIndices.pop(0)
|
|
1150
|
+
# if None then use empty dict
|
|
1151
|
+
thisTrial = self.trialList[thisIndex] or {}
|
|
1152
|
+
thisTrial = copy.copy(thisTrial)
|
|
1153
|
+
# make Trial object
|
|
1154
|
+
thisTrial = Trial(
|
|
1155
|
+
self,
|
|
1156
|
+
thisN=thisN,
|
|
1157
|
+
thisRepN=thisRepN,
|
|
1158
|
+
thisTrialN=thisTrialN,
|
|
1159
|
+
thisIndex=thisIndex,
|
|
1160
|
+
data=thisTrial
|
|
1161
|
+
)
|
|
1162
|
+
# otherwise, append trial
|
|
1163
|
+
self.upcomingTrials.append(thisTrial)
|
|
1164
|
+
# for fullRandom check how many times this has come up before
|
|
1165
|
+
if self.method == 'fullRandom':
|
|
1166
|
+
thisTrial.thisRepN = prevIndices.count(thisTrial.thisIndex)
|
|
1167
|
+
# update prev indices
|
|
1168
|
+
prevIndices.append(thisTrial.thisIndex)
|
|
1169
|
+
# update pointer for next trials
|
|
1170
|
+
thisTrialN += 1 # number of trial this pass
|
|
1171
|
+
thisN += 1 # number of trial in total
|
|
1034
1172
|
|
|
1035
1173
|
def abortCurrentTrial(self, action='random'):
|
|
1036
1174
|
"""Abort the current trial.
|
|
@@ -1051,39 +1189,169 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
1051
1189
|
not used.
|
|
1052
1190
|
|
|
1053
1191
|
"""
|
|
1054
|
-
#
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1192
|
+
# clear this trial so it's not appended to elapsed
|
|
1193
|
+
self.thisTrial = None
|
|
1194
|
+
# clear upcoming trials so they're recalculated on next iteration
|
|
1195
|
+
self.upcomingTrials = None
|
|
1196
|
+
|
|
1197
|
+
@property
|
|
1198
|
+
def finished(self):
|
|
1199
|
+
"""
|
|
1200
|
+
Whether this loop has finished or not. Will be True if there are no upcoming trials and
|
|
1201
|
+
False if there are any. Set `.finished = True` to skip all remaining trials (equivalent to
|
|
1202
|
+
calling `.skipTrials()` with a value larger than the number of trials remaining)
|
|
1203
|
+
|
|
1204
|
+
Returns
|
|
1205
|
+
-------
|
|
1206
|
+
bool
|
|
1207
|
+
True if there are no upcoming trials, False otherwise.
|
|
1208
|
+
"""
|
|
1209
|
+
return not bool(self.upcomingTrials)
|
|
1210
|
+
|
|
1211
|
+
@finished.setter
|
|
1212
|
+
def finished(self, value):
|
|
1213
|
+
# when setting finished to True, skip all remaining trials
|
|
1214
|
+
if value:
|
|
1215
|
+
self.upcomingTrials = []
|
|
1216
|
+
else:
|
|
1217
|
+
self.calculateUpcoming()
|
|
1218
|
+
|
|
1219
|
+
def skipTrials(self, n=1):
|
|
1220
|
+
"""
|
|
1221
|
+
Skip ahead n trials - the trials inbetween will be marked as "skipped". If you try to
|
|
1222
|
+
skip past the last trial, will log a warning and skip *to* the last trial.
|
|
1223
|
+
|
|
1224
|
+
Parameters
|
|
1225
|
+
----------
|
|
1226
|
+
n : int
|
|
1227
|
+
Number of trials to skip ahead
|
|
1228
|
+
"""
|
|
1229
|
+
# if skipping past last trial, print warning and skip to last trial
|
|
1230
|
+
if n > len(self.upcomingTrials):
|
|
1231
|
+
logging.warn(
|
|
1232
|
+
f"Requested skip of {n} trials when only {len(self.elapsedTrials)} trials are upcoming. "
|
|
1233
|
+
f"Skipping to the last upcoming trial."
|
|
1234
|
+
)
|
|
1235
|
+
n = len(self.upcomingTrials)
|
|
1236
|
+
# iterate n times
|
|
1237
|
+
for i in range(n):
|
|
1238
|
+
# before iterating, add "skipped" to data
|
|
1239
|
+
self.addData("skipped", True)
|
|
1240
|
+
# advance row in data file
|
|
1241
|
+
if self.getExp() is not None:
|
|
1242
|
+
self.getExp().nextEntry()
|
|
1243
|
+
# iterate
|
|
1244
|
+
self.__next__()
|
|
1245
|
+
|
|
1246
|
+
def rewindTrials(self, n=1):
|
|
1247
|
+
"""
|
|
1248
|
+
Rewind back n trials - previously elapsed trials will return to being upcoming. If you
|
|
1249
|
+
try to rewind before the first trial, will log a warning and rewind *to* the first trial.
|
|
1250
|
+
|
|
1251
|
+
Parameters
|
|
1252
|
+
----------
|
|
1253
|
+
n : int
|
|
1254
|
+
Number of trials to rewind back
|
|
1255
|
+
"""
|
|
1256
|
+
# treat -n as n
|
|
1257
|
+
n = abs(n)
|
|
1258
|
+
# if rewinding past first trial, print warning and rewind to first trial
|
|
1259
|
+
if n > len(self.elapsedTrials):
|
|
1260
|
+
logging.warn(
|
|
1261
|
+
f"Requested rewind of {n} trials when only {len(self.elapsedTrials)} trials have "
|
|
1262
|
+
f"elapsed. Rewinding to the first trial."
|
|
1263
|
+
)
|
|
1264
|
+
n = len(self.elapsedTrials)
|
|
1265
|
+
# start with no trials
|
|
1266
|
+
rewound = [self.thisTrial]
|
|
1267
|
+
# pop the last n values from elapsed trials
|
|
1268
|
+
for i in range(n):
|
|
1269
|
+
rewound = [self.elapsedTrials.pop(-1)] + rewound
|
|
1270
|
+
# set thisTrial from first rewound value
|
|
1271
|
+
self.thisTrial = rewound.pop(0)
|
|
1272
|
+
# prepend rewound trials to upcoming array
|
|
1273
|
+
self.upcomingTrials = rewound + self.upcomingTrials
|
|
1274
|
+
|
|
1275
|
+
def getCurrentTrial(self):
|
|
1276
|
+
"""
|
|
1277
|
+
Returns the current trial (`.thisTrial`)
|
|
1278
|
+
|
|
1279
|
+
Returns
|
|
1280
|
+
-------
|
|
1281
|
+
Trial
|
|
1282
|
+
The current trial
|
|
1283
|
+
"""
|
|
1284
|
+
return self.thisTrial
|
|
1285
|
+
|
|
1286
|
+
def getAllTrials(self):
|
|
1287
|
+
"""
|
|
1288
|
+
Returns all trials (elapsed, current and upcoming) with an index indicating which trial is
|
|
1289
|
+
the current trial.
|
|
1290
|
+
|
|
1291
|
+
Returns
|
|
1292
|
+
-------
|
|
1293
|
+
list[Trial]
|
|
1294
|
+
List of trials, in order (oldest to newest)
|
|
1295
|
+
int
|
|
1296
|
+
Index of the current trial in this list
|
|
1297
|
+
"""
|
|
1298
|
+
return (self.elapsedTrials or []) + [self.thisTrial] + (self.upcomingTrials or []), len(self.elapsedTrials)
|
|
1074
1299
|
|
|
1075
1300
|
def getFutureTrial(self, n=1):
|
|
1076
|
-
"""
|
|
1301
|
+
"""
|
|
1302
|
+
Returns the condition for n trials into the future, without
|
|
1077
1303
|
advancing the trials. Returns 'None' if attempting to go beyond
|
|
1078
1304
|
the last trial.
|
|
1305
|
+
|
|
1306
|
+
Returns
|
|
1307
|
+
-------
|
|
1308
|
+
Trial or None
|
|
1309
|
+
Trial object for n trials into the future.
|
|
1079
1310
|
"""
|
|
1080
|
-
#
|
|
1081
|
-
|
|
1082
|
-
|
|
1311
|
+
# make sure n is an integer
|
|
1312
|
+
if isinstance(n, str) and n.isnumeric():
|
|
1313
|
+
n = int(n)
|
|
1314
|
+
# return None if requesting beyond last trial
|
|
1315
|
+
if self.upcomingTrials is None or n > len(self.upcomingTrials):
|
|
1083
1316
|
return None
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1317
|
+
# return the corresponding trial from upcoming trials array
|
|
1318
|
+
return self.upcomingTrials[n-1]
|
|
1319
|
+
|
|
1320
|
+
def getFutureTrials(self, n=None, start=0):
|
|
1321
|
+
"""
|
|
1322
|
+
Returns Trial objects for a given range in the future. Will start looking at `start` trials
|
|
1323
|
+
in the future and will return n trials from then, so e.g. to get all trials from 2 in the
|
|
1324
|
+
future to 5 in the future you would use `start=2` and `n=3`.
|
|
1325
|
+
|
|
1326
|
+
Parameters
|
|
1327
|
+
----------
|
|
1328
|
+
n : int, optional
|
|
1329
|
+
How many trials into the future to look, by default None. Leave as None to show all
|
|
1330
|
+
future trials
|
|
1331
|
+
start : int, optional
|
|
1332
|
+
How many trials into the future to start looking at, by default 0
|
|
1333
|
+
|
|
1334
|
+
Returns
|
|
1335
|
+
-------
|
|
1336
|
+
list[Trial or None]
|
|
1337
|
+
List of Trial objects n long. Any trials beyond the last trial are None.
|
|
1338
|
+
"""
|
|
1339
|
+
# if there are no future trials, return a blank list
|
|
1340
|
+
if self.upcomingTrials is None:
|
|
1341
|
+
return []
|
|
1342
|
+
# if None, get all future trials
|
|
1343
|
+
if n is None:
|
|
1344
|
+
n = len(self.upcomingTrials) - start
|
|
1345
|
+
# blank list to store trials in
|
|
1346
|
+
trials = []
|
|
1347
|
+
# iterate through n trials
|
|
1348
|
+
for i in range(n):
|
|
1349
|
+
# add each to the list
|
|
1350
|
+
trials.append(
|
|
1351
|
+
self.getFutureTrial(start + i + 1)
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
return trials
|
|
1087
1355
|
|
|
1088
1356
|
def getEarlierTrial(self, n=-1):
|
|
1089
1357
|
"""Returns the condition information from n trials previously.
|
|
@@ -1093,7 +1361,188 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
1093
1361
|
# treat positive offset values as equivalent to negative ones:
|
|
1094
1362
|
if n > 0:
|
|
1095
1363
|
n = n * -1
|
|
1096
|
-
return
|
|
1364
|
+
# return None if requesting before first trial
|
|
1365
|
+
if self.upcomingTrials is None or abs(n) > len(self.upcomingTrials):
|
|
1366
|
+
return None
|
|
1367
|
+
# return the corresponding trial from elapsed trials array
|
|
1368
|
+
return self.elapsedTrials[n]
|
|
1369
|
+
|
|
1370
|
+
def _createOutputArray(self, stimOut, dataOut, delim=None,
|
|
1371
|
+
matrixOnly=False):
|
|
1372
|
+
"""Does the leg-work for saveAsText and saveAsExcel.
|
|
1373
|
+
Combines stimOut with ._parseDataOutput()
|
|
1374
|
+
"""
|
|
1375
|
+
if (stimOut == [] and
|
|
1376
|
+
len(self.trialList) and
|
|
1377
|
+
hasattr(self.trialList[0], 'keys')):
|
|
1378
|
+
stimOut = list(self.trialList[0].keys())
|
|
1379
|
+
# these get added somewhere (by DataHandler?)
|
|
1380
|
+
if 'n' in stimOut:
|
|
1381
|
+
stimOut.remove('n')
|
|
1382
|
+
if 'float' in stimOut:
|
|
1383
|
+
stimOut.remove('float')
|
|
1384
|
+
|
|
1385
|
+
lines = []
|
|
1386
|
+
# parse the dataout section of the output
|
|
1387
|
+
dataOut, dataAnal, dataHead = self._createOutputArrayData(dataOut)
|
|
1388
|
+
if not matrixOnly:
|
|
1389
|
+
thisLine = []
|
|
1390
|
+
lines.append(thisLine)
|
|
1391
|
+
# write a header line
|
|
1392
|
+
for heading in list(stimOut) + dataHead:
|
|
1393
|
+
if heading == 'ran_sum':
|
|
1394
|
+
heading = 'n'
|
|
1395
|
+
elif heading == 'order_raw':
|
|
1396
|
+
heading = 'order'
|
|
1397
|
+
thisLine.append(heading)
|
|
1398
|
+
|
|
1399
|
+
# loop through stimuli, writing data
|
|
1400
|
+
for stimN in range(len(self.trialList)):
|
|
1401
|
+
thisLine = []
|
|
1402
|
+
lines.append(thisLine)
|
|
1403
|
+
# first the params for this stim (from self.trialList)
|
|
1404
|
+
for heading in stimOut:
|
|
1405
|
+
thisLine.append(self.trialList[stimN][heading])
|
|
1406
|
+
|
|
1407
|
+
# then the data for this stim (from self.data)
|
|
1408
|
+
for thisDataOut in dataOut:
|
|
1409
|
+
# make a string version of the data and then format it
|
|
1410
|
+
tmpData = dataAnal[thisDataOut][stimN]
|
|
1411
|
+
if hasattr(tmpData, 'tolist'): # is a numpy array
|
|
1412
|
+
strVersion = str(tmpData.tolist())
|
|
1413
|
+
# for numeric data replace None with a blank cell
|
|
1414
|
+
if tmpData.dtype.kind not in ['SaUV']:
|
|
1415
|
+
strVersion = strVersion.replace('None', '')
|
|
1416
|
+
elif tmpData in [None, 'None']:
|
|
1417
|
+
strVersion = ''
|
|
1418
|
+
else:
|
|
1419
|
+
strVersion = str(tmpData)
|
|
1420
|
+
|
|
1421
|
+
if strVersion == '()':
|
|
1422
|
+
# 'no data' in masked array should show as "--"
|
|
1423
|
+
strVersion = "--"
|
|
1424
|
+
# handle list of values (e.g. rt_raw )
|
|
1425
|
+
if (len(strVersion) and
|
|
1426
|
+
strVersion[0] in '[(' and
|
|
1427
|
+
strVersion[-1] in '])'):
|
|
1428
|
+
strVersion = strVersion[1:-1] # skip first and last chars
|
|
1429
|
+
# handle lists of lists (e.g. raw of multiple key presses)
|
|
1430
|
+
if (len(strVersion) and
|
|
1431
|
+
strVersion[0] in '[(' and
|
|
1432
|
+
strVersion[-1] in '])'):
|
|
1433
|
+
tup = eval(strVersion) # convert back to a tuple
|
|
1434
|
+
for entry in tup:
|
|
1435
|
+
# contents of each entry is a list or tuple so keep in
|
|
1436
|
+
# quotes to avoid probs with delim
|
|
1437
|
+
thisLine.append(str(entry))
|
|
1438
|
+
else:
|
|
1439
|
+
thisLine.extend(strVersion.split(','))
|
|
1440
|
+
|
|
1441
|
+
# add self.extraInfo
|
|
1442
|
+
if (self.extraInfo != None) and not matrixOnly:
|
|
1443
|
+
lines.append([])
|
|
1444
|
+
# give a single line of space and then a heading
|
|
1445
|
+
lines.append(['extraInfo'])
|
|
1446
|
+
for key, value in list(self.extraInfo.items()):
|
|
1447
|
+
lines.append([key, value])
|
|
1448
|
+
return lines
|
|
1449
|
+
|
|
1450
|
+
def _createOutputArrayData(self, dataOut):
|
|
1451
|
+
"""This just creates the dataOut part of the output matrix.
|
|
1452
|
+
It is called by _createOutputArray() which creates the header
|
|
1453
|
+
line and adds the stimOut columns
|
|
1454
|
+
"""
|
|
1455
|
+
dataHead = [] # will store list of data headers
|
|
1456
|
+
dataAnal = dict([]) # will store data that has been analyzed
|
|
1457
|
+
if type(dataOut) == str:
|
|
1458
|
+
# don't do list convert or we get a list of letters
|
|
1459
|
+
dataOut = [dataOut]
|
|
1460
|
+
elif type(dataOut) != list:
|
|
1461
|
+
dataOut = list(dataOut)
|
|
1462
|
+
|
|
1463
|
+
# expand any 'all' dataTypes to be full list of available dataTypes
|
|
1464
|
+
allDataTypes = list(self.data.keys())
|
|
1465
|
+
# ready to go through standard data types
|
|
1466
|
+
dataOutNew = []
|
|
1467
|
+
for thisDataOut in dataOut:
|
|
1468
|
+
if thisDataOut == 'n':
|
|
1469
|
+
# n is really just the sum of the ran trials
|
|
1470
|
+
dataOutNew.append('ran_sum')
|
|
1471
|
+
continue # no need to do more with this one
|
|
1472
|
+
# then break into dataType and analysis
|
|
1473
|
+
dataType, analType = thisDataOut.rsplit('_', 1)
|
|
1474
|
+
if dataType == 'all':
|
|
1475
|
+
dataOutNew.extend(
|
|
1476
|
+
[key + "_" + analType for key in allDataTypes])
|
|
1477
|
+
if 'order_mean' in dataOutNew:
|
|
1478
|
+
dataOutNew.remove('order_mean')
|
|
1479
|
+
if 'order_std' in dataOutNew:
|
|
1480
|
+
dataOutNew.remove('order_std')
|
|
1481
|
+
else:
|
|
1482
|
+
dataOutNew.append(thisDataOut)
|
|
1483
|
+
dataOut = dataOutNew
|
|
1484
|
+
# sort so all datatypes come together, rather than all analtypes
|
|
1485
|
+
dataOut.sort()
|
|
1486
|
+
|
|
1487
|
+
# do the various analyses, keeping track of fails (e.g. mean of a
|
|
1488
|
+
# string)
|
|
1489
|
+
dataOutInvalid = []
|
|
1490
|
+
# add back special data types (n and order)
|
|
1491
|
+
if 'ran_sum' in dataOut:
|
|
1492
|
+
# move n to the first column
|
|
1493
|
+
dataOut.remove('ran_sum')
|
|
1494
|
+
dataOut.insert(0, 'ran_sum')
|
|
1495
|
+
if 'order_raw' in dataOut:
|
|
1496
|
+
# move order_raw to the second column
|
|
1497
|
+
dataOut.remove('order_raw')
|
|
1498
|
+
dataOut.append('order_raw')
|
|
1499
|
+
# do the necessary analysis on the data
|
|
1500
|
+
for thisDataOutN, thisDataOut in enumerate(dataOut):
|
|
1501
|
+
dataType, analType = thisDataOut.rsplit('_', 1)
|
|
1502
|
+
if not dataType in self.data:
|
|
1503
|
+
# that analysis can't be done
|
|
1504
|
+
dataOutInvalid.append(thisDataOut)
|
|
1505
|
+
continue
|
|
1506
|
+
thisData = self.data[dataType]
|
|
1507
|
+
|
|
1508
|
+
# set the header
|
|
1509
|
+
dataHead.append(dataType + '_' + analType)
|
|
1510
|
+
# analyse thisData using numpy module
|
|
1511
|
+
if analType in dir(np):
|
|
1512
|
+
try:
|
|
1513
|
+
# will fail if we try to take mean of a string for example
|
|
1514
|
+
if analType == 'std':
|
|
1515
|
+
thisAnal = np.std(thisData, axis=1, ddof=0)
|
|
1516
|
+
# normalise by N-1 instead. This should work by
|
|
1517
|
+
# setting ddof=1 but doesn't as of 08/2010 (because
|
|
1518
|
+
# of using a masked array?)
|
|
1519
|
+
N = thisData.shape[1]
|
|
1520
|
+
if N == 1:
|
|
1521
|
+
thisAnal *= 0 # prevent a divide-by-zero error
|
|
1522
|
+
else:
|
|
1523
|
+
sqrt = np.sqrt
|
|
1524
|
+
thisAnal = thisAnal * sqrt(N) / sqrt(N - 1)
|
|
1525
|
+
else:
|
|
1526
|
+
thisAnal = eval("np.%s(thisData,1)" % analType)
|
|
1527
|
+
except Exception:
|
|
1528
|
+
# that analysis doesn't work
|
|
1529
|
+
dataHead.remove(dataType + '_' + analType)
|
|
1530
|
+
dataOutInvalid.append(thisDataOut)
|
|
1531
|
+
continue # to next analysis
|
|
1532
|
+
elif analType == 'raw':
|
|
1533
|
+
thisAnal = thisData
|
|
1534
|
+
else:
|
|
1535
|
+
raise AttributeError('You can only use analyses from numpy')
|
|
1536
|
+
# add extra cols to header if necess
|
|
1537
|
+
if len(thisAnal.shape) > 1:
|
|
1538
|
+
for n in range(thisAnal.shape[1] - 1):
|
|
1539
|
+
dataHead.append("")
|
|
1540
|
+
dataAnal[thisDataOut] = thisAnal
|
|
1541
|
+
|
|
1542
|
+
# remove invalid analyses (e.g. average of a string)
|
|
1543
|
+
for invalidAnal in dataOutInvalid:
|
|
1544
|
+
dataOut.remove(invalidAnal)
|
|
1545
|
+
return dataOut, dataAnal, dataHead
|
|
1097
1546
|
|
|
1098
1547
|
def saveAsWideText(self, fileName,
|
|
1099
1548
|
delim=None,
|
|
@@ -1150,7 +1599,7 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
1150
1599
|
Defaults to `utf-8-sig`.
|
|
1151
1600
|
|
|
1152
1601
|
"""
|
|
1153
|
-
if self.thisTrialN <
|
|
1602
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
1154
1603
|
# if both are < 1 we haven't started
|
|
1155
1604
|
logging.info('TrialHandler.saveAsWideText called but no '
|
|
1156
1605
|
'trials completed. Nothing saved')
|
|
@@ -1226,11 +1675,24 @@ class TrialHandler2(_BaseTrialHandler):
|
|
|
1226
1675
|
# store in the columns list to help ordering later
|
|
1227
1676
|
if thisType not in self.columns:
|
|
1228
1677
|
self.columns.append(thisType)
|
|
1678
|
+
# make sure we have a thisTrial
|
|
1679
|
+
if self.thisTrial is None:
|
|
1680
|
+
if self.upcomingTrials:
|
|
1681
|
+
self.thisTrial = self.upcomingTrials.pop(0)
|
|
1682
|
+
else:
|
|
1683
|
+
self.thisTrial = Trial(
|
|
1684
|
+
self,
|
|
1685
|
+
thisN=0,
|
|
1686
|
+
thisRepN=0,
|
|
1687
|
+
thisTrialN=0,
|
|
1688
|
+
thisIndex=0,
|
|
1689
|
+
data={}
|
|
1690
|
+
)
|
|
1229
1691
|
# save the actual value in a data dict
|
|
1230
1692
|
self.thisTrial[thisType] = value
|
|
1231
1693
|
if self.getExp() is not None:
|
|
1232
1694
|
# update the experiment handler too
|
|
1233
|
-
self.getExp().addData(thisType, value)
|
|
1695
|
+
self.getExp().addData(f"{self.name}.{thisType}", value)
|
|
1234
1696
|
|
|
1235
1697
|
|
|
1236
1698
|
class TrialHandlerExt(TrialHandler):
|
|
@@ -1868,7 +2330,7 @@ class TrialHandlerExt(TrialHandler):
|
|
|
1868
2330
|
Defaults to `utf-8-sig`.
|
|
1869
2331
|
|
|
1870
2332
|
"""
|
|
1871
|
-
if self.thisTrialN <
|
|
2333
|
+
if self.thisTrialN < 0 and self.thisRepN < 0:
|
|
1872
2334
|
# if both are < 1 we haven't started
|
|
1873
2335
|
logging.info('TrialHandler.saveAsWideText called but no trials'
|
|
1874
2336
|
' completed. Nothing saved')
|