psychopy 2024.2.1__py3-none-any.whl → 2024.2.4__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/GIT_SHA +1 -1
- psychopy/VERSION +1 -1
- psychopy/__init__.py +10 -1
- psychopy/__init__.py.orig +65 -0
- psychopy/app/{locale/ar_001/.DS_Store → .DS_Store} +0 -0
- psychopy/app/Resources/.DS_Store +0 -0
- psychopy/app/_psychopyApp.py +11 -3
- psychopy/app/appData.spec +1 -1
- psychopy/app/builder/builder.py +1 -1
- psychopy/app/builder/builder.py.orig +3932 -0
- psychopy/app/builder/dialogs/__init__.py.orig +1679 -0
- psychopy/app/builder/dialogs/paramCtrls.py +1 -1
- psychopy/app/builder/dialogs/paramCtrls.py.orig +713 -0
- psychopy/app/colorpicker/__init__.py.orig +411 -0
- psychopy/app/cortex.log +0 -0
- psychopy/app/jobs.py +8 -1
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +2452 -1731
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN.po +6127 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN_allFlagged.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/zh_CN_allFlagged.po +7366 -0
- psychopy/app/plugin_manager/dialog.py +9 -7
- psychopy/app/ribbon.py +2 -1
- psychopy/app/runner/runner.py +7 -5
- psychopy/clock.py +8 -4
- psychopy/core.py.orig +169 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/index.html +23 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/randomisedBlocks-legacy-browsers.js +423 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/randomisedBlocks.js +427 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/chooseBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/facesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/housesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/face03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/html/resources/stims/house03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.py +330 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks_lastrun.py +330 -0
- psychopy/demos/builder/Feature Demos/eyetracking/eyetracking.xml +298 -0
- psychopy/demos/builder/Feature Demos/eyetracking/eyetracking.xsd +120 -0
- psychopy/demos/builder/Tools/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.csv +38 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.log +3418 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.csv +2 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.log +15 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual.psyexp +323 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual.py +562 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual_lastrun.py +562 -0
- psychopy/demos/builder/Tools/gammaCalibration/questStairs.xlsx +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/readme.md +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/low_contrast.png +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/make_2nd_order_tex.py +59 -0
- psychopy/demos/builder/Tools/gammaCalibration/resources/second_order_tex.png +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/experiment control/info_gamma.pickle +0 -0
- psychopy/demos/coder/iohub/.iohpid +1 -0
- psychopy/demos/coder/iohub/eyetracking/.iohpid +1 -0
- psychopy/demos/coder/iohub/wintab/.DS_Store +0 -0
- psychopy/demos/coder/stimuli/.DS_Store +0 -0
- psychopy/demos/coder/stimuli/radialGratingContracting.py +29 -0
- psychopy/experiment/_experiment.py.orig +1032 -0
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/_base.py +13 -4
- psychopy/experiment/components/_base.py.orig +823 -0
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +10 -1
- psychopy/experiment/components/microphone/__init__.py.orig +490 -0
- psychopy/experiment/components/polygon/__init__.py +21 -22
- psychopy/experiment/components/settings/__init__.py +13 -14
- psychopy/experiment/components/settings/__init__.py.orig +1337 -0
- psychopy/experiment/components/textbox/__init__.py.orig +310 -0
- psychopy/experiment/components/webcam/.DS_Store +0 -0
- psychopy/experiment/components/webcam/light/.DS_Store +0 -0
- psychopy/experiment/flow.py +10 -8
- psychopy/experiment/loops.py.orig +829 -0
- psychopy/experiment/params.py +8 -3
- psychopy/experiment/params.py.orig +408 -0
- psychopy/experiment/routine.py.orig +503 -0
- psychopy/experiment/routines/_base.py +15 -6
- psychopy/experiment/routines/counterbalance/__init__.py +1 -0
- psychopy/gui/qtgui.py +14 -7
- psychopy/gui/util.py +10 -14
- psychopy/gui/wxgui.py +10 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/brainproducts.py.orig +680 -0
- psychopy/hardware/iolab.py.orig +238 -0
- psychopy/hardware/manager.py +1 -1
- psychopy/hardware/photodiode.py +59 -27
- psychopy/hardware/serialport.py +51 -0
- psychopy/hardware/speaker.py +4 -4
- psychopy/iohub/datastore/__init__.py.orig +443 -0
- psychopy/iohub/datastore/util.py.orig +692 -0
- psychopy/iohub/devices/mouse/darwin.py.orig +427 -0
- psychopy/iohub/devices/mouse/linux2.py.orig +198 -0
- psychopy/preferences/.DS_Store +0 -0
- psychopy/projects/pavlovia.py +10 -3
- psychopy/projects/pavlovia.py.orig +1295 -0
- psychopy/sound/backend_ptb.py +22 -5
- psychopy/sound/transcribe.py +24 -4
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestCircle_fill_local.png +0 -0
- psychopy/tests/data/__test.png +0 -0
- psychopy/tests/data/aperture1_normHexbackground_local.png +0 -0
- psychopy/tests/data/aperture1_norm_local.png +0 -0
- psychopy/tests/data/aperture2_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_height_local.png +0 -0
- psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_norm_local.png +0 -0
- psychopy/tests/data/beatandrcos_stencil_local.png +0 -0
- psychopy/tests/data/blend_add_height_local.png +0 -0
- psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
- psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
- psychopy/tests/data/blend_add_normNoShade_local.png +0 -0
- psychopy/tests/data/blend_add_norm_local.png +0 -0
- psychopy/tests/data/blend_add_stencil_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_height_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normNoShade_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_stencil_local.png +0 -0
- psychopy/tests/data/circleHex_height_local.png +0 -0
- psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
- psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
- psychopy/tests/data/circleHex_normNoShade_local.png +0 -0
- psychopy/tests/data/circleHex_norm_local.png +0 -0
- psychopy/tests/data/circleHex_stencil_local.png +0 -0
- psychopy/tests/data/color_comparison_local.png +0 -0
- psychopy/tests/data/corrFullRandom_local.csv +16 -0
- psychopy/tests/data/corrFullRandom_local.tsv +6 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/dots_height_local.png +0 -0
- psychopy/tests/data/dots_normAddBlend_local.png +0 -0
- psychopy/tests/data/dots_normHexbackground_local.png +0 -0
- psychopy/tests/data/dots_normNoShade_local.png +0 -0
- psychopy/tests/data/dots_norm_local.png +0 -0
- psychopy/tests/data/dots_stencil_local.png +0 -0
- psychopy/tests/data/elarray1_height_local.png +0 -0
- psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
- psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
- psychopy/tests/data/elarray1_norm_local.png +0 -0
- psychopy/tests/data/elarray1_stencil_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_stencil_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_stencil_local.png +0 -0
- psychopy/tests/data/gabor1_height_local.png +0 -0
- psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
- psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
- psychopy/tests/data/gabor1_normNoShade_local.png +0 -0
- psychopy/tests/data/gabor1_norm_local.png +0 -0
- psychopy/tests/data/gabor1_stencil_local.png +0 -0
- psychopy/tests/data/greyscale_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_height_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normNoShade_local.png +0 -0
- psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
- psychopy/tests/data/imageAndGauss_stencil_local.png +0 -0
- psychopy/tests/data/movFrame1_stencil_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/numpyImage_height_local.png +0 -0
- psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
- psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
- psychopy/tests/data/numpyImage_normNoShade_local.png +0 -0
- psychopy/tests/data/numpyImage_norm_local.png +0 -0
- psychopy/tests/data/numpyImage_stencil_local.png +0 -0
- psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
- psychopy/tests/data/shape2_1_normHexbackground_local.png +0 -0
- psychopy/tests/data/shape2_1_normNoShade_local.png +0 -0
- psychopy/tests/data/shape2_1_norm_local.png +0 -0
- psychopy/tests/data/shape2_1_stencil_local.png +0 -0
- psychopy/tests/data/testLoopsBlocks.psyexp_local.py +328 -0
- psychopy/tests/data/text1_height_local.png +0 -0
- psychopy/tests/data/text1_normAddBlend_local.png +0 -0
- psychopy/tests/data/text1_normHexbackground_local.png +0 -0
- psychopy/tests/data/text1_norm_local.png +0 -0
- psychopy/tests/data/text1_stencil_local.png +0 -0
- psychopy/tests/data/text2_height.png +0 -0
- psychopy/tests/data/text2_normAddBlend.png +0 -0
- psychopy/tests/data/text2_normHexbackground.png +0 -0
- psychopy/tests/data/text2_stencil.png +0 -0
- psychopy/tests/data/wedge1_height_local.png +0 -0
- psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
- psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
- psychopy/tests/data/wedge1_normNoShade_local.png +0 -0
- psychopy/tests/data/wedge1_norm_local.png +0 -0
- psychopy/tests/data/wedge1_stencil_local.png +0 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.csv +9 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.log +177 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.xlsx +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.csv +9 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.log +168 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.xlsx +0 -0
- psychopy/tests/test_data/.DS_Store +0 -0
- psychopy/tests/test_hardware/test_CRS_BitsSharp.py.orig +68 -0
- psychopy/tests/test_tools/test_arraytools.py +112 -0
- psychopy/tests/test_visual/test_image.py.orig +219 -0
- psychopy/tools/arraytools.py +47 -0
- psychopy/tools/versionchooser.py +1 -1
- psychopy/visual/backends/pygletbackend.py +26 -8
- psychopy/visual/basevisual.py.orig +1723 -0
- psychopy/visual/form.py.orig +1181 -0
- psychopy/visual/text.py.orig +752 -0
- psychopy/visual/textbox2/textbox2.py.orig +1315 -0
- psychopy/visual/window.py +13 -5
- psychopy/visual/windowwarp.py.orig +463 -0
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/METADATA +9 -9
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/RECORD +244 -78
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/WHEEL +1 -1
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/entry_points.txt +2 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy-2024.2.1.dist-info/licenses/AUTHORS.md +0 -138
- /psychopy/{app/locale/ar_001/LC_MESSAGE → demos/builder}/.DS_Store +0 -0
- /psychopy/{app/locale/es_ES/LC_MESSAGE → demos/builder/Experiments}/.DS_Store +0 -0
- /psychopy/{visual → demos/builder/Tools/gammaCalibration/data}/.DS_Store +0 -0
- {psychopy-2024.2.1.dist-info → psychopy-2024.2.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Part of the PsychoPy library
|
|
5
|
+
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2022 Open Science Tools Ltd.
|
|
6
|
+
# Distributed under the terms of the GNU General Public License (GPL).
|
|
7
|
+
|
|
8
|
+
"""Experiment classes:
|
|
9
|
+
Experiment, Flow, Routine, Param, Loop*, *Handlers, and NameSpace
|
|
10
|
+
|
|
11
|
+
The code that writes out a *_lastrun.py experiment file is (in order):
|
|
12
|
+
experiment.Experiment.writeScript() - starts things off, calls other parts
|
|
13
|
+
settings.SettingsComponent.writeStartCode()
|
|
14
|
+
experiment.Flow.writeBody()
|
|
15
|
+
which will call the .writeBody() methods from each component
|
|
16
|
+
settings.SettingsComponent.writeEndCode()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from copy import deepcopy
|
|
20
|
+
from xml.etree.ElementTree import Element
|
|
21
|
+
|
|
22
|
+
from psychopy.experiment import getInitVals
|
|
23
|
+
from psychopy.localization import _localized, _translate
|
|
24
|
+
from psychopy.experiment.params import Param
|
|
25
|
+
from .components import getInitVals, getAllComponents
|
|
26
|
+
|
|
27
|
+
class TrialHandler():
|
|
28
|
+
"""A looping experimental control object
|
|
29
|
+
(e.g. generating a psychopy TrialHandler or StairHandler).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, exp, name, loopType='random', nReps=5,
|
|
33
|
+
conditions=(), conditionsFile='', endPoints=(0, 1),
|
|
34
|
+
randomSeed='', selectedRows='', isTrials=True):
|
|
35
|
+
"""
|
|
36
|
+
@param name: name of the loop e.g. trials
|
|
37
|
+
@type name: string
|
|
38
|
+
@param loopType:
|
|
39
|
+
@type loopType: string ('rand', 'seq')
|
|
40
|
+
@param nReps: number of reps (for all conditions)
|
|
41
|
+
@type nReps:int
|
|
42
|
+
@param conditions: list of different trial conditions to be used
|
|
43
|
+
@type conditions: list (of dicts?)
|
|
44
|
+
@param conditionsFile: filename of the .csv file that
|
|
45
|
+
contains conditions info
|
|
46
|
+
@type conditions: string (filename)
|
|
47
|
+
"""
|
|
48
|
+
super(TrialHandler, self).__init__()
|
|
49
|
+
self.type = 'TrialHandler'
|
|
50
|
+
self.exp = exp
|
|
51
|
+
self.order = ['name'] # make name come first (others don't matter)
|
|
52
|
+
self.params = {}
|
|
53
|
+
self.params['name'] = Param(
|
|
54
|
+
name, valType='code', inputType="single", updates=None, allowedUpdates=None,
|
|
55
|
+
label=_localized['Name'],
|
|
56
|
+
hint=_translate("Name of this loop"))
|
|
57
|
+
self.params['nReps'] = Param(
|
|
58
|
+
nReps, valType='num', inputType="spin", updates=None, allowedUpdates=None,
|
|
59
|
+
label=_localized['nReps'],
|
|
60
|
+
hint=_translate("Number of repeats (for each condition)"))
|
|
61
|
+
self.params['conditions'] = Param(
|
|
62
|
+
list(conditions), valType='str', inputType="single",
|
|
63
|
+
updates=None, allowedUpdates=None,
|
|
64
|
+
label=_localized['conditions'],
|
|
65
|
+
hint=_translate("A list of dictionaries describing the "
|
|
66
|
+
"parameters in each condition"))
|
|
67
|
+
self.params['conditionsFile'] = Param(
|
|
68
|
+
conditionsFile, valType='file', inputType="table", updates=None, allowedUpdates=None,
|
|
69
|
+
label=_localized['conditions'],
|
|
70
|
+
hint=_translate("Name of a file specifying the parameters for "
|
|
71
|
+
"each condition (.csv, .xlsx, or .pkl). Browse "
|
|
72
|
+
"to select a file. Right-click to preview file "
|
|
73
|
+
"contents, or create a new file."))
|
|
74
|
+
self.params['endPoints'] = Param(
|
|
75
|
+
list(endPoints), valType='num', inputType="single", updates=None, allowedUpdates=None,
|
|
76
|
+
label=_localized['endPoints'],
|
|
77
|
+
hint=_translate("The start and end of the loop (see flow "
|
|
78
|
+
"timeline)"))
|
|
79
|
+
self.params['Selected rows'] = Param(
|
|
80
|
+
selectedRows, valType='str', inputType="single",
|
|
81
|
+
updates=None, allowedUpdates=None,
|
|
82
|
+
label=_localized['Selected rows'],
|
|
83
|
+
hint=_translate("Select just a subset of rows from your condition"
|
|
84
|
+
" file (the first is 0 not 1!). Examples: 0, "
|
|
85
|
+
"0:5, 5:-1"))
|
|
86
|
+
# NB staircase is added for the sake of the loop properties dialog:
|
|
87
|
+
self.params['loopType'] = Param(
|
|
88
|
+
loopType, valType='str', inputType="choice",
|
|
89
|
+
allowedVals=['random', 'sequential', 'fullRandom',
|
|
90
|
+
'staircase', 'interleaved staircases'],
|
|
91
|
+
label=_localized['loopType'],
|
|
92
|
+
hint=_translate("How should the next condition value(s) be "
|
|
93
|
+
"chosen?"))
|
|
94
|
+
self.params['random seed'] = Param(
|
|
95
|
+
randomSeed, valType='code', inputType="single", updates=None, allowedUpdates=None,
|
|
96
|
+
label=_localized['random seed'],
|
|
97
|
+
hint=_translate("To have a fixed random sequence provide an "
|
|
98
|
+
"integer of your choosing here. Leave blank to "
|
|
99
|
+
"have a new random sequence on each run of the "
|
|
100
|
+
"experiment."))
|
|
101
|
+
self.params['isTrials'] = Param(
|
|
102
|
+
isTrials, valType='bool', inputType="bool", updates=None, allowedUpdates=None,
|
|
103
|
+
label=_localized["Is trials"],
|
|
104
|
+
hint=_translate("Indicates that this loop generates TRIALS, "
|
|
105
|
+
"rather than BLOCKS of trials or stimuli within "
|
|
106
|
+
"a trial. It alters how data files are output"))
|
|
107
|
+
|
|
108
|
+
def writeInitCode(self, buff):
|
|
109
|
+
# no longer needed - initialise the trial handler just before it runs
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def writeInitCodeJS(self, buff):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def writeLoopStartCode(self, buff):
|
|
116
|
+
"""Write the code to create and run a sequence of trials
|
|
117
|
+
"""
|
|
118
|
+
# first create the handler init values
|
|
119
|
+
inits = getInitVals(self.params)
|
|
120
|
+
# import conditions from file?
|
|
121
|
+
if self.params['conditionsFile'].val in ['None', None, 'none', '']:
|
|
122
|
+
condsStr = "[None]"
|
|
123
|
+
elif self.params['Selected rows'].val in ['None', None, 'none', '']:
|
|
124
|
+
# just a conditions file with no sub-selection
|
|
125
|
+
_con = "data.importConditions(%s)"
|
|
126
|
+
condsStr = _con % self.params['conditionsFile']
|
|
127
|
+
else:
|
|
128
|
+
# a subset of a conditions file
|
|
129
|
+
condsStr = ("data.importConditions(%(conditionsFile)s, selection="
|
|
130
|
+
"%(Selected rows)s)") % self.params
|
|
131
|
+
# also a 'thisName' for use in "for thisTrial in trials:"
|
|
132
|
+
makeLoopIndex = self.exp.namespace.makeLoopIndex
|
|
133
|
+
self.thisName = makeLoopIndex(self.params['name'].val)
|
|
134
|
+
# write the code
|
|
135
|
+
code = ("\n# set up handler to look after randomisation of conditions etc\n"
|
|
136
|
+
"%(name)s = data.TrialHandler(nReps=%(nReps)s, method=%(loopType)s, \n"
|
|
137
|
+
" extraInfo=expInfo, originPath=-1,\n")
|
|
138
|
+
buff.writeIndentedLines(code % inits)
|
|
139
|
+
# the next line needs to be kept separate to preserve potential string formatting
|
|
140
|
+
# by the user in condStr (i.e. it shouldn't be a formatted string itself
|
|
141
|
+
code = " trialList=" + condsStr + ",\n" # conditions go here
|
|
142
|
+
buff.writeIndented(code)
|
|
143
|
+
code = " seed=%(random seed)s, name='%(name)s')\n"
|
|
144
|
+
buff.writeIndentedLines(code % inits)
|
|
145
|
+
|
|
146
|
+
code = ("thisExp.addLoop(%(name)s) # add the loop to the experiment\n" +
|
|
147
|
+
self.thisName + " = %(name)s.trialList[0] " +
|
|
148
|
+
"# so we can initialise stimuli with some values\n")
|
|
149
|
+
buff.writeIndentedLines(code % self.params)
|
|
150
|
+
# unclutter the namespace
|
|
151
|
+
if not self.exp.prefsBuilder['unclutteredNamespace']:
|
|
152
|
+
code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
|
|
153
|
+
"if %(name)s != None:\n"
|
|
154
|
+
" for paramName in %(name)s:\n"
|
|
155
|
+
" exec('{} = %(name)s[paramName]'.format(paramName))\n")
|
|
156
|
+
buff.writeIndentedLines(code % {'name': self.thisName})
|
|
157
|
+
|
|
158
|
+
# then run the trials loop
|
|
159
|
+
code = "\nfor %s in %s:\n"
|
|
160
|
+
buff.writeIndentedLines(code % (self.thisName, self.params['name']))
|
|
161
|
+
# fetch parameter info from conditions
|
|
162
|
+
buff.setIndentLevel(1, relative=True)
|
|
163
|
+
buff.writeIndented("currentLoop = %s\n" % self.params['name'])
|
|
164
|
+
# unclutter the namespace
|
|
165
|
+
if not self.exp.prefsBuilder['unclutteredNamespace']:
|
|
166
|
+
code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
|
|
167
|
+
"if %(name)s != None:\n"
|
|
168
|
+
" for paramName in %(name)s:\n"
|
|
169
|
+
" exec('{} = %(name)s[paramName]'.format(paramName))\n")
|
|
170
|
+
buff.writeIndentedLines(code % {'name': self.thisName})
|
|
171
|
+
|
|
172
|
+
def writeLoopStartCodeJS(self, buff, modular):
|
|
173
|
+
"""Write the code to create and run a sequence of trials
|
|
174
|
+
"""
|
|
175
|
+
# some useful variables
|
|
176
|
+
# create the variable "thisTrial" from "trials"
|
|
177
|
+
makeLoopIndex = self.exp.namespace.makeLoopIndex
|
|
178
|
+
self.thisName = makeLoopIndex(self.params['name'].val)
|
|
179
|
+
|
|
180
|
+
# Convert filepath separator
|
|
181
|
+
conditionsFile = self.params['conditionsFile'].val
|
|
182
|
+
self.params['conditionsFile'].val = conditionsFile.replace('\\\\', '/').replace('\\', '/')
|
|
183
|
+
# seed might be undefined
|
|
184
|
+
seed = self.params['random seed'].val or 'undefined'
|
|
185
|
+
if self.params['conditionsFile'].val in ['None', None, 'none', '']:
|
|
186
|
+
trialList='undefined'
|
|
187
|
+
elif self.params['Selected rows'].val in ['None', None, 'none', '']:
|
|
188
|
+
trialList = self.params['conditionsFile']
|
|
189
|
+
else:
|
|
190
|
+
trialList = ("TrialHandler.importConditions"
|
|
191
|
+
"(psychoJS.serverManager, {}, {})"
|
|
192
|
+
).format(self.params['conditionsFile'],
|
|
193
|
+
self.params['Selected rows'])
|
|
194
|
+
|
|
195
|
+
nReps = self.params['nReps'].val
|
|
196
|
+
if nReps in ['None', None, 'none', '']:
|
|
197
|
+
nReps = 'undefined'
|
|
198
|
+
elif isinstance(nReps, str):
|
|
199
|
+
nReps = nReps.strip("$")
|
|
200
|
+
|
|
201
|
+
code = ("\nfunction {loopName}LoopBegin({loopName}LoopScheduler, snapshot) {{\n"
|
|
202
|
+
" return async function() {{\n"
|
|
203
|
+
.format(loopName=self.params['name'],
|
|
204
|
+
loopType=(self.params['loopType'].val).upper(),
|
|
205
|
+
nReps=nReps,
|
|
206
|
+
trialList=trialList,
|
|
207
|
+
seed=seed))
|
|
208
|
+
buff.writeIndentedLines(code)
|
|
209
|
+
buff.setIndentLevel(2, relative=True)
|
|
210
|
+
code = ("TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop\n\n"
|
|
211
|
+
"// set up handler to look after randomisation of conditions etc\n"
|
|
212
|
+
"{loopName} = new TrialHandler({{\n"
|
|
213
|
+
" psychoJS: psychoJS,\n"
|
|
214
|
+
" nReps: {nReps}, method: TrialHandler.Method.{loopType},\n"
|
|
215
|
+
" extraInfo: expInfo, originPath: undefined,\n"
|
|
216
|
+
" trialList: {trialList},\n"
|
|
217
|
+
" seed: {seed}, name: '{loopName}'\n"
|
|
218
|
+
"}});\n"
|
|
219
|
+
"psychoJS.experiment.addLoop({loopName}); // add the loop to the experiment\n"
|
|
220
|
+
"currentLoop = {loopName}; // we're now the current loop\n"
|
|
221
|
+
.format(loopName=self.params['name'],
|
|
222
|
+
loopType=(self.params['loopType'].val).upper(),
|
|
223
|
+
nReps=nReps,
|
|
224
|
+
trialList=trialList,
|
|
225
|
+
seed=seed))
|
|
226
|
+
buff.writeIndentedLines(code)
|
|
227
|
+
|
|
228
|
+
# for the scheduler
|
|
229
|
+
if modular:
|
|
230
|
+
code = ("\n// Schedule all the trials in the trialList:\n"
|
|
231
|
+
"for (const {thisName} of {loopName}) {{\n"
|
|
232
|
+
" const snapshot = {loopName}.getSnapshot();\n"
|
|
233
|
+
" {loopName}LoopScheduler.add(importConditions(snapshot));\n")
|
|
234
|
+
else:
|
|
235
|
+
code = ("\n// Schedule all the trials in the trialList:\n"
|
|
236
|
+
"{loopName}.forEach(function() {{\n"
|
|
237
|
+
" const snapshot = {loopName}.getSnapshot();\n\n"
|
|
238
|
+
" {loopName}LoopScheduler.add(importConditions(snapshot));\n")
|
|
239
|
+
buff.writeIndentedLines(code.format(loopName=self.params['name'],
|
|
240
|
+
thisName=self.thisName))
|
|
241
|
+
# then we need to include begin, eachFrame and end code for each entry within that loop
|
|
242
|
+
loopDict = self.exp.flow.loopDict
|
|
243
|
+
thisLoop = loopDict[self] # dict containing lists of children
|
|
244
|
+
code = ""
|
|
245
|
+
for thisChild in thisLoop:
|
|
246
|
+
if thisChild.getType() == 'Routine':
|
|
247
|
+
code += (
|
|
248
|
+
" {loopName}LoopScheduler.add({childName}RoutineBegin(snapshot));\n"
|
|
249
|
+
" {loopName}LoopScheduler.add({childName}RoutineEachFrame());\n"
|
|
250
|
+
" {loopName}LoopScheduler.add({childName}RoutineEnd());\n"
|
|
251
|
+
.format(childName=thisChild.params['name'],
|
|
252
|
+
loopName=self.params['name'])
|
|
253
|
+
)
|
|
254
|
+
else: # for a LoopInitiator
|
|
255
|
+
code += (
|
|
256
|
+
" const {childName}LoopScheduler = new Scheduler(psychoJS);\n"
|
|
257
|
+
" {loopName}LoopScheduler.add({childName}LoopBegin({childName}LoopScheduler, snapshot));\n"
|
|
258
|
+
" {loopName}LoopScheduler.add({childName}LoopScheduler);\n"
|
|
259
|
+
" {loopName}LoopScheduler.add({childName}LoopEnd);\n"
|
|
260
|
+
.format(childName=thisChild.params['name'],
|
|
261
|
+
loopName=self.params['name'])
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
code += " {loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
|
|
265
|
+
code += "}}%s\n" % ([');', ''][modular])
|
|
266
|
+
code += ("\n"
|
|
267
|
+
"return Scheduler.Event.NEXT;\n")
|
|
268
|
+
buff.writeIndentedLines(code.format(loopName=self.params['name']))
|
|
269
|
+
buff.setIndentLevel(-2, relative=True)
|
|
270
|
+
buff.writeIndentedLines(
|
|
271
|
+
" }\n"
|
|
272
|
+
"}\n"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def writeLoopEndCode(self, buff):
|
|
276
|
+
# Just within the loop advance data line if loop is whole trials
|
|
277
|
+
if self.params['isTrials'].val == True:
|
|
278
|
+
buff.writeIndentedLines("thisExp.nextEntry()\n\n")
|
|
279
|
+
# end of the loop. dedent
|
|
280
|
+
buff.setIndentLevel(-1, relative=True)
|
|
281
|
+
buff.writeIndented("# completed %s repeats of '%s'\n"
|
|
282
|
+
% (self.params['nReps'], self.params['name']))
|
|
283
|
+
buff.writeIndented("\n")
|
|
284
|
+
# save data
|
|
285
|
+
if self.params['isTrials'].val == True:
|
|
286
|
+
# a string to show all the available variables (if the conditions
|
|
287
|
+
# isn't just None or [None])
|
|
288
|
+
saveExcel = self.exp.settings.params['Save excel file'].val
|
|
289
|
+
saveCSV = self.exp.settings.params['Save csv file'].val
|
|
290
|
+
# get parameter names
|
|
291
|
+
if saveExcel or saveCSV:
|
|
292
|
+
code = ("# get names of stimulus parameters\n"
|
|
293
|
+
"if %(name)s.trialList in ([], [None], None):\n"
|
|
294
|
+
" params = []\n"
|
|
295
|
+
"else:\n"
|
|
296
|
+
" params = %(name)s.trialList[0].keys()\n")
|
|
297
|
+
buff.writeIndentedLines(code % self.params)
|
|
298
|
+
# write out each type of file
|
|
299
|
+
if saveExcel or saveCSV:
|
|
300
|
+
buff.writeIndented("# save data for this loop\n")
|
|
301
|
+
if saveExcel:
|
|
302
|
+
code = ("%(name)s.saveAsExcel(filename + '.xlsx', sheetName='%(name)s',\n"
|
|
303
|
+
" stimOut=params,\n"
|
|
304
|
+
" dataOut=['n','all_mean','all_std', 'all_raw'])\n")
|
|
305
|
+
buff.writeIndentedLines(code % self.params)
|
|
306
|
+
if saveCSV:
|
|
307
|
+
code = ("%(name)s.saveAsText(filename + '%(name)s.csv', "
|
|
308
|
+
"delim=',',\n"
|
|
309
|
+
" stimOut=params,\n"
|
|
310
|
+
" dataOut=['n','all_mean','all_std', 'all_raw'])\n")
|
|
311
|
+
buff.writeIndentedLines(code % self.params)
|
|
312
|
+
|
|
313
|
+
def writeLoopEndCodeJS(self, buff):
|
|
314
|
+
# Just within the loop advance data line if loop is whole trials
|
|
315
|
+
code = ("\nasync function {funName}LoopEnd() {{\n"
|
|
316
|
+
" psychoJS.experiment.removeLoop({name});\n\n".format(funName=self.params['name'].val,
|
|
317
|
+
name=self.params['name']))
|
|
318
|
+
code += (" return Scheduler.Event.NEXT;\n"
|
|
319
|
+
"}\n")
|
|
320
|
+
buff.writeIndentedLines(code)
|
|
321
|
+
|
|
322
|
+
def getType(self):
|
|
323
|
+
return 'TrialHandler'
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def name(self):
|
|
327
|
+
return self.params['name'].val
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class StairHandler:
|
|
331
|
+
"""A staircase experimental control object.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
def __init__(self, exp, name, nReps='50', startVal='', nReversals='',
|
|
335
|
+
nUp=1, nDown=3, minVal=0, maxVal=1,
|
|
336
|
+
stepSizes='[4,4,2,2,1]', stepType='db', endPoints=(0, 1),
|
|
337
|
+
isTrials=True):
|
|
338
|
+
"""
|
|
339
|
+
@param name: name of the loop e.g. trials
|
|
340
|
+
@type name: string
|
|
341
|
+
@param nReps: number of reps (for all conditions)
|
|
342
|
+
@type nReps:int
|
|
343
|
+
"""
|
|
344
|
+
super(StairHandler, self).__init__()
|
|
345
|
+
self.type = 'StairHandler'
|
|
346
|
+
self.exp = exp
|
|
347
|
+
self.order = ['name'] # make name come first (others don't matter)
|
|
348
|
+
self.children = []
|
|
349
|
+
self.params = {}
|
|
350
|
+
self.params['name'] = Param(
|
|
351
|
+
name, valType='code',
|
|
352
|
+
hint=_translate("Name of this loop"),
|
|
353
|
+
label=_localized['Name'])
|
|
354
|
+
self.params['nReps'] = Param(
|
|
355
|
+
nReps, valType='num', inputType='spin',
|
|
356
|
+
label=_localized['nReps'],
|
|
357
|
+
hint=_translate("(Minimum) number of trials in the staircase"))
|
|
358
|
+
self.params['start value'] = Param(
|
|
359
|
+
startVal, valType='num', inputType='single',
|
|
360
|
+
label=_localized['start value'],
|
|
361
|
+
hint=_translate("The initial value of the parameter"))
|
|
362
|
+
self.params['max value'] = Param(
|
|
363
|
+
maxVal, valType='num', inputType='single',
|
|
364
|
+
label=_localized['max value'],
|
|
365
|
+
hint=_translate("The maximum value the parameter can take"))
|
|
366
|
+
self.params['min value'] = Param(
|
|
367
|
+
minVal, valType='num', inputType='single',
|
|
368
|
+
label=_localized['min value'],
|
|
369
|
+
hint=_translate("The minimum value the parameter can take"))
|
|
370
|
+
self.params['step sizes'] = Param(
|
|
371
|
+
stepSizes, valType='list', inputType='single',
|
|
372
|
+
label=_localized['step sizes'],
|
|
373
|
+
hint=_translate("The size of the jump at each step (can change"
|
|
374
|
+
" on each 'reversal')"))
|
|
375
|
+
self.params['step type'] = Param(
|
|
376
|
+
stepType, valType='str', inputType='choice', allowedVals=['lin', 'log', 'db'],
|
|
377
|
+
label=_localized['step type'],
|
|
378
|
+
hint=_translate("The units of the step size (e.g. 'linear' will"
|
|
379
|
+
" add/subtract that value each step, whereas "
|
|
380
|
+
"'log' will ad that many log units)"))
|
|
381
|
+
self.params['N up'] = Param(
|
|
382
|
+
nUp, valType='num', inputType='spin',
|
|
383
|
+
label=_localized['N up'],
|
|
384
|
+
hint=_translate("The number of 'incorrect' answers before the "
|
|
385
|
+
"value goes up"))
|
|
386
|
+
self.params['N down'] = Param(
|
|
387
|
+
nDown, valType='num', inputType='spin',
|
|
388
|
+
label=_localized['N down'],
|
|
389
|
+
hint=_translate("The number of 'correct' answers before the "
|
|
390
|
+
"value goes down"))
|
|
391
|
+
self.params['N reversals'] = Param(
|
|
392
|
+
nReversals, valType='num', inputType='spin',
|
|
393
|
+
label=_localized['N reversals'],
|
|
394
|
+
hint=_translate("Minimum number of times the staircase must "
|
|
395
|
+
"change direction before ending"))
|
|
396
|
+
# these two are really just for making the dialog easier (they won't
|
|
397
|
+
# be used to generate code)
|
|
398
|
+
self.params['loopType'] = Param(
|
|
399
|
+
'staircase', valType='str', inputType='choice',
|
|
400
|
+
allowedVals=['random', 'sequential', 'fullRandom', 'staircase',
|
|
401
|
+
'interleaved staircases'],
|
|
402
|
+
label=_localized['loopType'],
|
|
403
|
+
hint=_translate("How should the next trial value(s) be chosen?"))
|
|
404
|
+
# NB this is added for the sake of the loop properties dialog
|
|
405
|
+
self.params['endPoints'] = Param(
|
|
406
|
+
list(endPoints), valType='num', inputType='spin',
|
|
407
|
+
label=_localized['endPoints'],
|
|
408
|
+
hint=_translate('Where to loop from and to (see values currently'
|
|
409
|
+
' shown in the flow view)'))
|
|
410
|
+
self.params['isTrials'] = Param(
|
|
411
|
+
isTrials, valType='bool', inputType='bool', updates=None, allowedUpdates=None,
|
|
412
|
+
label=_localized["Is trials"],
|
|
413
|
+
hint=_translate("Indicates that this loop generates TRIALS, "
|
|
414
|
+
"rather than BLOCKS of trials or stimuli within"
|
|
415
|
+
" a trial. It alters how data files are output"))
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def name(self):
|
|
419
|
+
return self.params['name'].val
|
|
420
|
+
|
|
421
|
+
def writeInitCode(self, buff):
|
|
422
|
+
# not needed - initialise the staircase only when needed
|
|
423
|
+
pass
|
|
424
|
+
|
|
425
|
+
def writeLoopStartCode(self, buff):
|
|
426
|
+
# create the staircase
|
|
427
|
+
# also a 'thisName' for use in "for thisTrial in trials:"
|
|
428
|
+
makeLoopIndex = self.exp.namespace.makeLoopIndex
|
|
429
|
+
self.thisName = makeLoopIndex(self.params['name'].val)
|
|
430
|
+
if self.params['N reversals'].val in ("", None, 'None'):
|
|
431
|
+
self.params['N reversals'].val = '0'
|
|
432
|
+
# write the code
|
|
433
|
+
code = ('\n# --------Prepare to start Staircase "%(name)s" --------\n'
|
|
434
|
+
"# set up handler to look after next chosen value etc\n"
|
|
435
|
+
"%(name)s = data.StairHandler(startVal=%(start value)s, extraInfo=expInfo,\n"
|
|
436
|
+
" stepSizes=%(step sizes)s, stepType=%(step type)s,\n"
|
|
437
|
+
" nReversals=%(N reversals)s, nTrials=%(nReps)s, \n"
|
|
438
|
+
" nUp=%(N up)s, nDown=%(N down)s,\n"
|
|
439
|
+
" minVal=%(min value)s, maxVal=%(max value)s,\n"
|
|
440
|
+
" originPath=-1, name='%(name)s')\n"
|
|
441
|
+
"thisExp.addLoop(%(name)s) # add the loop to the experiment")
|
|
442
|
+
buff.writeIndentedLines(code % self.params)
|
|
443
|
+
code = "level = %s = %s # initialise some vals\n"
|
|
444
|
+
buff.writeIndented(code % (self.thisName, self.params['start value']))
|
|
445
|
+
# then run the trials
|
|
446
|
+
# work out a name for e.g. thisTrial in trials:
|
|
447
|
+
code = "\nfor %s in %s:\n"
|
|
448
|
+
buff.writeIndentedLines(code % (self.thisName, self.params['name']))
|
|
449
|
+
buff.setIndentLevel(1, relative=True)
|
|
450
|
+
buff.writeIndented("currentLoop = %s\n" % self.params['name'])
|
|
451
|
+
buff.writeIndented("level = %s\n" % self.thisName)
|
|
452
|
+
|
|
453
|
+
def writeLoopEndCode(self, buff):
|
|
454
|
+
# Just within the loop advance data line if loop is whole trials
|
|
455
|
+
if self.params['isTrials'].val:
|
|
456
|
+
buff.writeIndentedLines("thisExp.nextEntry()\n\n")
|
|
457
|
+
# end of the loop. dedent
|
|
458
|
+
buff.setIndentLevel(-1, relative=True)
|
|
459
|
+
buff.writeIndented("# staircase completed\n")
|
|
460
|
+
buff.writeIndented("\n")
|
|
461
|
+
# save data
|
|
462
|
+
if self.params['isTrials'].val:
|
|
463
|
+
if self.exp.settings.params['Save excel file'].val:
|
|
464
|
+
code = ("%(name)s.saveAsExcel(filename + '.xlsx',"
|
|
465
|
+
" sheetName='%(name)s')\n")
|
|
466
|
+
buff.writeIndented(code % self.params)
|
|
467
|
+
if self.exp.settings.params['Save csv file'].val:
|
|
468
|
+
code = ("%(name)s.saveAsText(filename + "
|
|
469
|
+
"'%(name)s.csv', delim=',')\n")
|
|
470
|
+
buff.writeIndented(code % self.params)
|
|
471
|
+
|
|
472
|
+
def getType(self):
|
|
473
|
+
return 'StairHandler'
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
class MultiStairHandler:
|
|
477
|
+
"""To handle multiple interleaved staircases
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
def __init__(self, exp, name, nReps='50', stairType='simple',
|
|
481
|
+
switchStairs='random',
|
|
482
|
+
conditions=(), conditionsFile='', endPoints=(0, 1),
|
|
483
|
+
isTrials=True):
|
|
484
|
+
"""
|
|
485
|
+
@param name: name of the loop e.g. trials
|
|
486
|
+
@type name: string
|
|
487
|
+
@param nReps: number of reps (for all conditions)
|
|
488
|
+
@type nReps:int
|
|
489
|
+
"""
|
|
490
|
+
super(MultiStairHandler, self).__init__()
|
|
491
|
+
self.type = 'MultiStairHandler'
|
|
492
|
+
self.exp = exp
|
|
493
|
+
self.order = ['name'] # make name come first
|
|
494
|
+
self.params = {}
|
|
495
|
+
self.params['name'] = Param(
|
|
496
|
+
name, valType='code', inputType='single',
|
|
497
|
+
label=_localized['Name'],
|
|
498
|
+
hint=_translate("Name of this loop"))
|
|
499
|
+
self.params['nReps'] = Param(
|
|
500
|
+
nReps, valType='num', inputType='spin',
|
|
501
|
+
label=_localized['nReps'],
|
|
502
|
+
hint=_translate("(Minimum) number of trials in *each* staircase"))
|
|
503
|
+
self.params['stairType'] = Param(
|
|
504
|
+
stairType, valType='str', inputType='choice',
|
|
505
|
+
allowedVals=['simple', 'QUEST'],
|
|
506
|
+
label=_localized['stairType'],
|
|
507
|
+
hint=_translate("How to select the next staircase to run"))
|
|
508
|
+
self.params['switchMethod'] = Param(
|
|
509
|
+
switchStairs, valType='str', inputType='choice',
|
|
510
|
+
allowedVals=['random', 'sequential', 'fullRandom'],
|
|
511
|
+
label=_localized['switchMethod'],
|
|
512
|
+
hint=_translate("How to select the next staircase to run"))
|
|
513
|
+
# these two are really just for making the dialog easier (they won't
|
|
514
|
+
# be used to generate code)
|
|
515
|
+
self.params['loopType'] = Param(
|
|
516
|
+
'staircase', valType='str', inputType='choice',
|
|
517
|
+
allowedVals=['random', 'sequential', 'fullRandom', 'staircase',
|
|
518
|
+
'interleaved staircases'],
|
|
519
|
+
label=_localized['loopType'],
|
|
520
|
+
hint=_translate("How should the next trial value(s) be chosen?"))
|
|
521
|
+
self.params['endPoints'] = Param(
|
|
522
|
+
list(endPoints), valType='num', inputType='spin',
|
|
523
|
+
label=_localized['endPoints'],
|
|
524
|
+
hint=_translate('Where to loop from and to (see values currently'
|
|
525
|
+
' shown in the flow view)'))
|
|
526
|
+
self.params['conditions'] = Param(
|
|
527
|
+
list(conditions), valType='list', inputType='single',
|
|
528
|
+
updates=None, allowedUpdates=None,
|
|
529
|
+
label=_localized['conditions'],
|
|
530
|
+
hint=_translate("A list of dictionaries describing the "
|
|
531
|
+
"differences between each staircase"))
|
|
532
|
+
self.params['conditionsFile'] = Param(
|
|
533
|
+
conditionsFile, valType='file', inputType='table', updates=None, allowedUpdates=None,
|
|
534
|
+
label=_localized['conditions'],
|
|
535
|
+
hint=_translate("An xlsx or csv file specifying the parameters "
|
|
536
|
+
"for each condition"))
|
|
537
|
+
self.params['isTrials'] = Param(
|
|
538
|
+
isTrials, valType='bool', inputType='bool', updates=None, allowedUpdates=None,
|
|
539
|
+
label=_localized["Is trials"],
|
|
540
|
+
hint=_translate("Indicates that this loop generates TRIALS, "
|
|
541
|
+
"rather than BLOCKS of trials or stimuli within "
|
|
542
|
+
"a trial. It alters how data files are output"))
|
|
543
|
+
pass # don't initialise at start of exp, create when needed
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def name(self):
|
|
547
|
+
return self.params['name'].val
|
|
548
|
+
|
|
549
|
+
<<<<<<< Updated upstream
|
|
550
|
+
=======
|
|
551
|
+
def writePreCodeJS(self, buff):
|
|
552
|
+
if self.params['stairType'] == 'QUEST':
|
|
553
|
+
buff.writeOnceIndentedLines(
|
|
554
|
+
"import jsQUEST from 'https://lib.pavlovia.org/vendors/jsQUEST.min.js';"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
>>>>>>> Stashed changes
|
|
558
|
+
def writeLoopStartCode(self, buff):
|
|
559
|
+
# create a 'thisName' for use in "for thisTrial in trials:"
|
|
560
|
+
makeLoopIndex = self.exp.namespace.makeLoopIndex
|
|
561
|
+
self.thisName = makeLoopIndex(self.params['name'].val)
|
|
562
|
+
# create the MultistairHander
|
|
563
|
+
code = ("\n# set up handler to look after randomisation of trials etc\n"
|
|
564
|
+
"conditions = data.importConditions(%(conditionsFile)s)\n"
|
|
565
|
+
"%(name)s = data.MultiStairHandler(stairType=%(stairType)s, "
|
|
566
|
+
"name='%(name)s',\n"
|
|
567
|
+
" nTrials=%(nReps)s,\n"
|
|
568
|
+
" conditions=conditions,\n"
|
|
569
|
+
" method=%(switchMethod)s,\n"
|
|
570
|
+
" originPath=-1)\n"
|
|
571
|
+
"thisExp.addLoop(%(name)s) # add the loop to the experiment\n"
|
|
572
|
+
"# initialise values for first condition\n"
|
|
573
|
+
"level = %(name)s._nextIntensity # initialise some vals\n"
|
|
574
|
+
"condition = %(name)s.currentStaircase.condition\n"
|
|
575
|
+
# start the loop:
|
|
576
|
+
"\nfor level, condition in %(name)s:\n")
|
|
577
|
+
buff.writeIndentedLines(code % self.params)
|
|
578
|
+
|
|
579
|
+
buff.setIndentLevel(1, relative=True)
|
|
580
|
+
buff.writeIndented("currentLoop = %(name)s\n" % (self.params))
|
|
581
|
+
# uncluttered namespace
|
|
582
|
+
if not self.exp.prefsBuilder['unclutteredNamespace']:
|
|
583
|
+
code = ("# abbreviate parameter names if possible (e.g. "
|
|
584
|
+
"rgb=condition.rgb)\n"
|
|
585
|
+
"for paramName in condition:\n"
|
|
586
|
+
" exec(paramName + '= condition[paramName]')\n")
|
|
587
|
+
buff.writeIndentedLines(code)
|
|
588
|
+
|
|
589
|
+
def writeLoopStartCodeJS(self, buff, modular):
|
|
590
|
+
inits = deepcopy(self.params)
|
|
591
|
+
# For JS, stairType needs to be code
|
|
592
|
+
inits['stairType'].valType = "code"
|
|
593
|
+
# Method needs to be code and upper
|
|
594
|
+
inits['switchMethod'].valType = "code"
|
|
595
|
+
inits['switchMethod'].val = inits['switchMethod'].val.upper()
|
|
596
|
+
|
|
597
|
+
code = (
|
|
598
|
+
"\nfunction %(name)sLoopBegin(%(name)sLoopScheduler, snapshot) {\n"
|
|
599
|
+
)
|
|
600
|
+
buff.writeIndentedLines(code % inits)
|
|
601
|
+
|
|
602
|
+
buff.setIndentLevel(1, relative=True)
|
|
603
|
+
code = (
|
|
604
|
+
"return async function() {\n"
|
|
605
|
+
)
|
|
606
|
+
buff.writeIndentedLines(code % inits)
|
|
607
|
+
|
|
608
|
+
buff.setIndentLevel(1, relative=True)
|
|
609
|
+
code = (
|
|
610
|
+
"// setup a MultiStairTrialHandler\n"
|
|
611
|
+
"%(name)sConditions = TrialHandler.importConditions(psychoJS.serverManager, %(conditionsFile)s);\n"
|
|
612
|
+
"%(name)s = new data.MultiStairHandler({stairType:MultiStairHandler.StaircaseType.%(stairType)s, \n"
|
|
613
|
+
)
|
|
614
|
+
buff.writeIndentedLines(code % inits)
|
|
615
|
+
|
|
616
|
+
buff.setIndentLevel(1, relative=True)
|
|
617
|
+
code = (
|
|
618
|
+
"psychoJS: psychoJS,\n"
|
|
619
|
+
"name: '%(name)s',\n"
|
|
620
|
+
"varName: '%(name)sVal',\n"
|
|
621
|
+
"nTrials: %(nReps)s,\n"
|
|
622
|
+
"conditions: %(name)sConditions,\n"
|
|
623
|
+
"method: TrialHandler.Method.%(switchMethod)s\n"
|
|
624
|
+
)
|
|
625
|
+
buff.writeIndentedLines(code % inits)
|
|
626
|
+
|
|
627
|
+
buff.setIndentLevel(-1, relative=True)
|
|
628
|
+
code = (
|
|
629
|
+
"});\n"
|
|
630
|
+
"psychoJS.experiment.addLoop(%(name)s); // add the loop to the experiment\n"
|
|
631
|
+
"currentLoop = %(name)s; // we're now the current loop\n"
|
|
632
|
+
"// Schedule all the trials in the trialList:\n"
|
|
633
|
+
"for (const thisQuestLoop of %(name)s) {\n"
|
|
634
|
+
)
|
|
635
|
+
buff.writeIndentedLines(code % inits)
|
|
636
|
+
|
|
637
|
+
buff.setIndentLevel(1, relative=True)
|
|
638
|
+
thisLoop = self.exp.flow.loopDict[self]
|
|
639
|
+
for thisChild in thisLoop:
|
|
640
|
+
if thisChild.getType() == 'Routine':
|
|
641
|
+
code = (
|
|
642
|
+
"const snapshot = %(name)s.getSnapshot();\n"
|
|
643
|
+
"{loopName}LoopScheduler.add(importConditions(snapshot));\n"
|
|
644
|
+
"{loopName}LoopScheduler.add({childName}RoutineBegin(snapshot));\n"
|
|
645
|
+
"{loopName}LoopScheduler.add({childName}RoutineEachFrame());\n"
|
|
646
|
+
"{loopName}LoopScheduler.add({childName}RoutineEnd());\n"
|
|
647
|
+
"{loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
|
|
648
|
+
.format(childName=thisChild.params['name'],
|
|
649
|
+
loopName=self.params['name'])
|
|
650
|
+
)
|
|
651
|
+
else: # for a LoopInitiator
|
|
652
|
+
code = (
|
|
653
|
+
"const snapshot = %(name)s.getSnapshot();\n"
|
|
654
|
+
"const {childName}LoopScheduler = new Scheduler(psychoJS);\n"
|
|
655
|
+
"{loopName}LoopScheduler.add(importConditions(snapshot));\n"
|
|
656
|
+
"{loopName}LoopScheduler.add({childName}LoopBegin({childName}LoopScheduler, snapshot));\n"
|
|
657
|
+
"{loopName}LoopScheduler.add({childName}LoopScheduler);\n"
|
|
658
|
+
"{loopName}LoopScheduler.add({childName}LoopEnd);\n"
|
|
659
|
+
"{loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
|
|
660
|
+
.format(childName=thisChild.params['name'],
|
|
661
|
+
loopName=self.params['name'])
|
|
662
|
+
)
|
|
663
|
+
buff.writeIndentedLines(code % inits)
|
|
664
|
+
|
|
665
|
+
buff.setIndentLevel(-1, relative=True)
|
|
666
|
+
code = (
|
|
667
|
+
"}"
|
|
668
|
+
"\n\n"
|
|
669
|
+
"return Scheduler.Event.NEXT;\n"
|
|
670
|
+
)
|
|
671
|
+
buff.writeIndentedLines(code % inits)
|
|
672
|
+
|
|
673
|
+
buff.setIndentLevel(-1, relative=True)
|
|
674
|
+
code = (
|
|
675
|
+
"}"
|
|
676
|
+
)
|
|
677
|
+
buff.writeIndentedLines(code % inits)
|
|
678
|
+
|
|
679
|
+
buff.setIndentLevel(-1, relative=True)
|
|
680
|
+
code = (
|
|
681
|
+
"}"
|
|
682
|
+
)
|
|
683
|
+
buff.writeIndentedLines(code % inits)
|
|
684
|
+
|
|
685
|
+
def writeLoopEndCode(self, buff):
|
|
686
|
+
# Just within the loop advance data line if loop is whole trials
|
|
687
|
+
if self.params['isTrials'].val:
|
|
688
|
+
buff.writeIndentedLines("thisExp.nextEntry()\n\n")
|
|
689
|
+
# end of the loop. dedent
|
|
690
|
+
buff.setIndentLevel(-1, relative=True)
|
|
691
|
+
buff.writeIndented("# all staircases completed\n")
|
|
692
|
+
buff.writeIndented("\n")
|
|
693
|
+
# save data
|
|
694
|
+
if self.params['isTrials'].val:
|
|
695
|
+
if self.exp.settings.params['Save excel file'].val:
|
|
696
|
+
code = "%(name)s.saveAsExcel(filename + '.xlsx')\n"
|
|
697
|
+
buff.writeIndented(code % self.params)
|
|
698
|
+
if self.exp.settings.params['Save csv file'].val:
|
|
699
|
+
code = ("%(name)s.saveAsText(filename + '%(name)s.csv', "
|
|
700
|
+
"delim=',')\n")
|
|
701
|
+
buff.writeIndented(code % self.params)
|
|
702
|
+
|
|
703
|
+
def writeLoopEndCodeJS(self, buff):
|
|
704
|
+
code = (
|
|
705
|
+
"\n"
|
|
706
|
+
"async function %(name)sLoopEnd() {\n"
|
|
707
|
+
)
|
|
708
|
+
buff.writeIndentedLines(code % self.params)
|
|
709
|
+
|
|
710
|
+
buff.setIndentLevel(1, relative=True)
|
|
711
|
+
code = (
|
|
712
|
+
"// terminate loop\n"
|
|
713
|
+
"psychoJS.experiment.removeLoop(%(name)s);\n"
|
|
714
|
+
"return Scheduler.Event.NEXT;\n"
|
|
715
|
+
)
|
|
716
|
+
buff.writeIndentedLines(code % self.params)
|
|
717
|
+
|
|
718
|
+
buff.setIndentLevel(-1, relative=True)
|
|
719
|
+
code = (
|
|
720
|
+
"}"
|
|
721
|
+
)
|
|
722
|
+
buff.writeIndentedLines(code % self.params)
|
|
723
|
+
|
|
724
|
+
def getType(self):
|
|
725
|
+
return 'MultiStairHandler'
|
|
726
|
+
|
|
727
|
+
def writeInitCode(self, buff):
|
|
728
|
+
# not needed - initialise the staircase only when needed
|
|
729
|
+
pass
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
class LoopInitiator:
|
|
733
|
+
"""A simple class for inserting into the flow.
|
|
734
|
+
This is created automatically when the loop is created"""
|
|
735
|
+
|
|
736
|
+
def __init__(self, loop):
|
|
737
|
+
super(LoopInitiator, self).__init__()
|
|
738
|
+
self.loop = loop
|
|
739
|
+
self.exp = loop.exp
|
|
740
|
+
loop.initiator = self
|
|
741
|
+
|
|
742
|
+
@property
|
|
743
|
+
def _xml(self):
|
|
744
|
+
# Make root element
|
|
745
|
+
element = Element("LoopInitiator")
|
|
746
|
+
element.set("loopType", self.loop.__class__.__name__)
|
|
747
|
+
element.set("name", self.loop.params['name'].val)
|
|
748
|
+
# Add an element for each parameter
|
|
749
|
+
for key, param in sorted(self.loop.params.items()):
|
|
750
|
+
# Create node
|
|
751
|
+
paramNode = Element("Param")
|
|
752
|
+
paramNode.set("name", key)
|
|
753
|
+
# Assign values
|
|
754
|
+
if hasattr(param, 'updates'):
|
|
755
|
+
paramNode.set('updates', "{}".format(param.updates))
|
|
756
|
+
if hasattr(param, 'val'):
|
|
757
|
+
paramNode.set('val', u"{}".format(param.val).replace("\n", " "))
|
|
758
|
+
if hasattr(param, 'valType'):
|
|
759
|
+
paramNode.set('valType', param.valType)
|
|
760
|
+
element.append(paramNode)
|
|
761
|
+
return element
|
|
762
|
+
|
|
763
|
+
@property
|
|
764
|
+
def name(self):
|
|
765
|
+
return self.loop.name
|
|
766
|
+
|
|
767
|
+
def getType(self):
|
|
768
|
+
return 'LoopInitiator'
|
|
769
|
+
|
|
770
|
+
def writeInitCode(self, buff):
|
|
771
|
+
self.loop.writeInitCode(buff)
|
|
772
|
+
|
|
773
|
+
def writeInitCodeJS(self, buff):
|
|
774
|
+
self.loop.writeInitCodeJS(buff)
|
|
775
|
+
|
|
776
|
+
def writeMainCode(self, buff):
|
|
777
|
+
self.loop.writeLoopStartCode(buff)
|
|
778
|
+
# we are now the inner-most loop
|
|
779
|
+
self.exp.flow._loopList.append(self.loop)
|
|
780
|
+
|
|
781
|
+
def writeMainCodeJS(self, buff, modular):
|
|
782
|
+
self.loop.writeLoopStartCodeJS(buff, modular)
|
|
783
|
+
# we are now the inner-most loop
|
|
784
|
+
self.exp.flow._loopList.append(self.loop)
|
|
785
|
+
|
|
786
|
+
def writeExperimentEndCode(self, buff): # not needed
|
|
787
|
+
pass
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
class LoopTerminator:
|
|
791
|
+
"""A simple class for inserting into the flow.
|
|
792
|
+
This is created automatically when the loop is created"""
|
|
793
|
+
|
|
794
|
+
def __init__(self, loop):
|
|
795
|
+
super(LoopTerminator, self).__init__()
|
|
796
|
+
self.loop = loop
|
|
797
|
+
self.exp = loop.exp
|
|
798
|
+
loop.terminator = self
|
|
799
|
+
|
|
800
|
+
@property
|
|
801
|
+
def _xml(self):
|
|
802
|
+
# Make root element
|
|
803
|
+
element = Element("LoopTerminator")
|
|
804
|
+
element.set("name", self.loop.params['name'].val)
|
|
805
|
+
|
|
806
|
+
return element
|
|
807
|
+
|
|
808
|
+
@property
|
|
809
|
+
def name(self):
|
|
810
|
+
return self.loop.name
|
|
811
|
+
|
|
812
|
+
def getType(self):
|
|
813
|
+
return 'LoopTerminator'
|
|
814
|
+
|
|
815
|
+
def writeInitCode(self, buff):
|
|
816
|
+
pass
|
|
817
|
+
|
|
818
|
+
def writeMainCode(self, buff):
|
|
819
|
+
self.loop.writeLoopEndCode(buff)
|
|
820
|
+
# _loopList[-1] will now be the inner-most loop
|
|
821
|
+
self.exp.flow._loopList.remove(self.loop)
|
|
822
|
+
|
|
823
|
+
def writeMainCodeJS(self, buff, modular):
|
|
824
|
+
self.loop.writeLoopEndCodeJS(buff)
|
|
825
|
+
# _loopList[-1] will now be the inner-most loop
|
|
826
|
+
self.exp.flow._loopList.remove(self.loop)
|
|
827
|
+
|
|
828
|
+
def writeExperimentEndCode(self, buff): # not needed
|
|
829
|
+
pass
|