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,823 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Part of the PsychoPy library
|
|
6
|
+
Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2021 Open Science Tools Ltd.
|
|
7
|
+
Distributed under the terms of the GNU General Public License (GPL).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import absolute_import, print_function
|
|
11
|
+
|
|
12
|
+
from builtins import str, object, super
|
|
13
|
+
from past.builtins import basestring
|
|
14
|
+
|
|
15
|
+
from psychopy import prefs
|
|
16
|
+
from psychopy.constants import FOREVER
|
|
17
|
+
from ..params import Param
|
|
18
|
+
from psychopy.experiment.utils import CodeGenerationException
|
|
19
|
+
from psychopy.experiment.utils import unescapedDollarSign_re
|
|
20
|
+
from psychopy.experiment.params import getCodeFromParamStr
|
|
21
|
+
from psychopy.alerts import alerttools
|
|
22
|
+
from psychopy.colors import colorSpaces
|
|
23
|
+
|
|
24
|
+
from psychopy.localization import _translate, _localized
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaseComponent(object):
|
|
28
|
+
"""A template for components, defining the methods to be overridden"""
|
|
29
|
+
# override the categories property below
|
|
30
|
+
# an attribute of the class, determines the section in the components panel
|
|
31
|
+
categories = ['Custom']
|
|
32
|
+
targets = ['PsychoPy']
|
|
33
|
+
|
|
34
|
+
def __init__(self, exp, parentName, name='',
|
|
35
|
+
startType='time (s)', startVal='',
|
|
36
|
+
stopType='duration (s)', stopVal='',
|
|
37
|
+
startEstim='', durationEstim='',
|
|
38
|
+
saveStartStop=True, syncScreenRefresh=False,
|
|
39
|
+
disabled=False):
|
|
40
|
+
self.type = 'Base'
|
|
41
|
+
self.exp = exp # so we can access the experiment if necess
|
|
42
|
+
self.parentName = parentName # to access the routine too if needed
|
|
43
|
+
|
|
44
|
+
self.params = {}
|
|
45
|
+
self.depends = [] # allows params to turn each other off/on
|
|
46
|
+
"""{
|
|
47
|
+
"dependsOn": "shape",
|
|
48
|
+
"condition": "=='n vertices",
|
|
49
|
+
"param": "n vertices",
|
|
50
|
+
"true": "enable", # what to do with param if condition is True
|
|
51
|
+
"false": "disable", # permitted: hide, show, enable, disable
|
|
52
|
+
}"""
|
|
53
|
+
|
|
54
|
+
msg = _translate(
|
|
55
|
+
"Name of this component (alpha-numeric or _, no spaces)")
|
|
56
|
+
self.params['name'] = Param(name,
|
|
57
|
+
valType='code', inputType="single", categ='Basic',
|
|
58
|
+
hint=msg,
|
|
59
|
+
label=_localized['name'])
|
|
60
|
+
|
|
61
|
+
msg = _translate("How do you want to define your start point?")
|
|
62
|
+
self.params['startType'] = Param(startType,
|
|
63
|
+
valType='str', inputType="choice", categ='Basic',
|
|
64
|
+
allowedVals=['time (s)', 'frame N', 'condition'],
|
|
65
|
+
hint=msg,
|
|
66
|
+
label=_localized['startType'])
|
|
67
|
+
|
|
68
|
+
msg = _translate("How do you want to define your end point?")
|
|
69
|
+
self.params['stopType'] = Param(stopType,
|
|
70
|
+
valType='str', inputType="choice", categ='Basic',
|
|
71
|
+
allowedVals=['duration (s)', 'duration (frames)', 'time (s)',
|
|
72
|
+
'frame N', 'condition'],
|
|
73
|
+
hint=msg,
|
|
74
|
+
label=_localized['stopType'])
|
|
75
|
+
|
|
76
|
+
self.params['startVal'] = Param(startVal,
|
|
77
|
+
valType='num', inputType="single", categ='Basic',
|
|
78
|
+
hint=_translate("When does the component start?"), allowedTypes=[],
|
|
79
|
+
label=_localized['startVal'])
|
|
80
|
+
|
|
81
|
+
self.params['stopVal'] = Param(stopVal,
|
|
82
|
+
valType='num', inputType="single", categ='Basic',
|
|
83
|
+
updates='constant', allowedUpdates=[], allowedTypes=[],
|
|
84
|
+
hint=_translate("When does the component end? (blank is endless)"),
|
|
85
|
+
label=_localized['stopVal'])
|
|
86
|
+
|
|
87
|
+
msg = _translate("(Optional) expected start (s), purely for "
|
|
88
|
+
"representing in the timeline")
|
|
89
|
+
self.params['startEstim'] = Param(startEstim,
|
|
90
|
+
valType='num', inputType="single", categ='Basic',
|
|
91
|
+
hint=msg,allowedTypes=[],
|
|
92
|
+
label=_localized['startEstim'])
|
|
93
|
+
|
|
94
|
+
msg = _translate("(Optional) expected duration (s), purely for "
|
|
95
|
+
"representing in the timeline")
|
|
96
|
+
self.params['durationEstim'] = Param(durationEstim,
|
|
97
|
+
valType='num', inputType="single", categ='Basic',
|
|
98
|
+
hint=msg, allowedTypes=[],
|
|
99
|
+
label=_localized['durationEstim'])
|
|
100
|
+
|
|
101
|
+
msg = _translate("Store the onset/offset times in the data file "
|
|
102
|
+
"(as well as in the log file).")
|
|
103
|
+
self.params['saveStartStop'] = Param(saveStartStop,
|
|
104
|
+
valType='bool', inputType="bool", categ='Data',
|
|
105
|
+
hint=msg, allowedTypes=[],
|
|
106
|
+
label=_translate('Save onset/offset times'))
|
|
107
|
+
|
|
108
|
+
msg = _translate("Synchronize times with screen refresh (good for "
|
|
109
|
+
"visual stimuli and responses based on them)")
|
|
110
|
+
self.params['syncScreenRefresh'] = Param(syncScreenRefresh,
|
|
111
|
+
valType='bool', inputType="bool", categ="Data",
|
|
112
|
+
hint=msg, allowedTypes=[],
|
|
113
|
+
label=_translate('Sync timing with screen refresh'))
|
|
114
|
+
|
|
115
|
+
msg = _translate("Disable this component")
|
|
116
|
+
self.params['disabled'] = Param(disabled,
|
|
117
|
+
valType='bool', inputType="bool", categ="Testing",
|
|
118
|
+
hint=msg, allowedTypes=[],
|
|
119
|
+
label=_translate('Disable component'))
|
|
120
|
+
|
|
121
|
+
self.order = ['name'] # name first, then timing, then others
|
|
122
|
+
|
|
123
|
+
def integrityCheck(self):
|
|
124
|
+
"""
|
|
125
|
+
Run component integrity checks for non-visual components
|
|
126
|
+
"""
|
|
127
|
+
alerttools.testDisabled(self)
|
|
128
|
+
alerttools.testStartEndTiming(self)
|
|
129
|
+
|
|
130
|
+
def _dubiousConstantUpdates(self):
|
|
131
|
+
"""Return a list of fields in component that are set to be constant
|
|
132
|
+
but seem intended to be dynamic. Some code fields are constant, and
|
|
133
|
+
some denoted as code by $ are constant.
|
|
134
|
+
"""
|
|
135
|
+
warnings = []
|
|
136
|
+
# treat expInfo as likely to be constant; also treat its keys as
|
|
137
|
+
# constant because its handy to make a short-cut in code:
|
|
138
|
+
# exec(key+'=expInfo[key]')
|
|
139
|
+
expInfo = self.exp.settings.getInfo()
|
|
140
|
+
keywords = self.exp.namespace.nonUserBuilder[:]
|
|
141
|
+
keywords.extend(['expInfo'] + list(expInfo.keys()))
|
|
142
|
+
reserved = set(keywords).difference({'random', 'rand'})
|
|
143
|
+
for key in self.params:
|
|
144
|
+
field = self.params[key]
|
|
145
|
+
if (not hasattr(field, 'val') or
|
|
146
|
+
not isinstance(field.val, basestring)):
|
|
147
|
+
continue # continue == no problem, no warning
|
|
148
|
+
if not (field.allowedUpdates and
|
|
149
|
+
isinstance(field.allowedUpdates, list) and
|
|
150
|
+
len(field.allowedUpdates) and
|
|
151
|
+
field.updates == 'constant'):
|
|
152
|
+
continue
|
|
153
|
+
# now have only non-empty, possibly-code, and 'constant' updating
|
|
154
|
+
if field.valType == 'str':
|
|
155
|
+
if not bool(unescapedDollarSign_re.search(field.val)):
|
|
156
|
+
continue
|
|
157
|
+
code = getCodeFromParamStr(field.val)
|
|
158
|
+
elif field.valType == 'code':
|
|
159
|
+
code = field.val
|
|
160
|
+
else:
|
|
161
|
+
continue
|
|
162
|
+
# get var names in the code; no names == constant
|
|
163
|
+
try:
|
|
164
|
+
names = compile(code, '', 'eval').co_names
|
|
165
|
+
except SyntaxError:
|
|
166
|
+
continue
|
|
167
|
+
# ignore reserved words:
|
|
168
|
+
if not set(names).difference(reserved):
|
|
169
|
+
continue
|
|
170
|
+
warnings.append((field, key))
|
|
171
|
+
return warnings or [(None, None)]
|
|
172
|
+
|
|
173
|
+
def writeInitCode(self, buff):
|
|
174
|
+
"""Write any code that a component needs that should only ever be done
|
|
175
|
+
at start of an experiment, BEFORE window creation.
|
|
176
|
+
"""
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
def writeStartCode(self, buff):
|
|
180
|
+
"""Write any code that a component needs that should only ever be done
|
|
181
|
+
at start of an experiment, AFTER window creation.
|
|
182
|
+
"""
|
|
183
|
+
# e.g., create a data subdirectory unique to that component type.
|
|
184
|
+
# Note: settings.writeStartCode() is done first, then
|
|
185
|
+
# Routine.writeStartCode() will call this method for each component in
|
|
186
|
+
# each routine
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def writeFrameCode(self, buff):
|
|
190
|
+
"""Write the code that will be called every frame
|
|
191
|
+
"""
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
def writeRoutineStartCode(self, buff):
|
|
195
|
+
"""Write the code that will be called at the beginning of
|
|
196
|
+
a routine (e.g. to update stimulus parameters)
|
|
197
|
+
"""
|
|
198
|
+
self.writeParamUpdates(buff, 'set every repeat')
|
|
199
|
+
|
|
200
|
+
def writeRoutineStartCodeJS(self, buff):
|
|
201
|
+
"""Same as writeRoutineStartCode, but for JS
|
|
202
|
+
"""
|
|
203
|
+
self.writeParamUpdatesJS(buff, 'set every repeat')
|
|
204
|
+
|
|
205
|
+
def writeRoutineEndCode(self, buff):
|
|
206
|
+
"""Write the code that will be called at the end of
|
|
207
|
+
a routine (e.g. to save data)
|
|
208
|
+
"""
|
|
209
|
+
if 'saveStartStop' in self.params and self.params['saveStartStop'].val:
|
|
210
|
+
|
|
211
|
+
# what loop are we in (or thisExp)?
|
|
212
|
+
if len(self.exp.flow._loopList):
|
|
213
|
+
currLoop = self.exp.flow._loopList[-1] # last (outer-most) loop
|
|
214
|
+
else:
|
|
215
|
+
currLoop = self.exp._expHandler
|
|
216
|
+
|
|
217
|
+
if 'Stair' in currLoop.type:
|
|
218
|
+
addDataFunc = 'addOtherData'
|
|
219
|
+
else:
|
|
220
|
+
addDataFunc = 'addData'
|
|
221
|
+
|
|
222
|
+
loop = currLoop.params['name']
|
|
223
|
+
name = self.params['name']
|
|
224
|
+
if self.params['syncScreenRefresh'].val:
|
|
225
|
+
code = (
|
|
226
|
+
f"{loop}.{addDataFunc}('{name}.started', {name}.tStartRefresh)\n"
|
|
227
|
+
f"{loop}.{addDataFunc}('{name}.stopped', {name}.tStopRefresh)\n"
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
code = (
|
|
231
|
+
f"{loop}.{addDataFunc}('{name}.started', {name}.tStart)\n"
|
|
232
|
+
f"{loop}.{addDataFunc}('{name}.stopped', {name}.tStop)\n"
|
|
233
|
+
)
|
|
234
|
+
buff.writeIndentedLines(code)
|
|
235
|
+
|
|
236
|
+
def writeRoutineEndCodeJS(self, buff):
|
|
237
|
+
"""Write the code that will be called at the end of
|
|
238
|
+
a routine (e.g. to save data)
|
|
239
|
+
"""
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
def writeExperimentEndCode(self, buff):
|
|
243
|
+
"""Write the code that will be called at the end of
|
|
244
|
+
an experiment (e.g. save log files or reset hardware)
|
|
245
|
+
"""
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
def writeStartTestCode(self, buff):
|
|
249
|
+
"""Test whether we need to start
|
|
250
|
+
"""
|
|
251
|
+
if self.params['syncScreenRefresh']:
|
|
252
|
+
tCompare = 'tThisFlip'
|
|
253
|
+
else:
|
|
254
|
+
tCompare = 't'
|
|
255
|
+
params = self.params
|
|
256
|
+
t = tCompare
|
|
257
|
+
if self.params['startType'].val == 'time (s)':
|
|
258
|
+
# if startVal is an empty string then set to be 0.0
|
|
259
|
+
if (isinstance(self.params['startVal'].val, basestring) and
|
|
260
|
+
not self.params['startVal'].val.strip()):
|
|
261
|
+
self.params['startVal'].val = '0.0'
|
|
262
|
+
code = (f"if {params['name']}.status == NOT_STARTED and "
|
|
263
|
+
f"{t} >= {params['startVal']}-frameTolerance:\n")
|
|
264
|
+
elif self.params['startType'].val == 'frame N':
|
|
265
|
+
code = (f"if {params['name']}.status == NOT_STARTED and "
|
|
266
|
+
f"frameN >= {params['startVal']}:\n")
|
|
267
|
+
elif self.params['startType'].val == 'condition':
|
|
268
|
+
code = (f"if {params['name']}.status == NOT_STARTED and "
|
|
269
|
+
f"{params['startVal']}:\n")
|
|
270
|
+
else:
|
|
271
|
+
msg = f"Not a known startType ({params['startVal']}) for {params['name']}"
|
|
272
|
+
raise CodeGenerationException(msg % self.params)
|
|
273
|
+
|
|
274
|
+
buff.writeIndented(code)
|
|
275
|
+
|
|
276
|
+
buff.setIndentLevel(+1, relative=True)
|
|
277
|
+
params = self.params
|
|
278
|
+
code = (f"# keep track of start time/frame for later\n"
|
|
279
|
+
f"{params['name']}.frameNStart = frameN # exact frame index\n"
|
|
280
|
+
f"{params['name']}.tStart = t # local t and not account for scr refresh\n"
|
|
281
|
+
f"{params['name']}.tStartRefresh = tThisFlipGlobal # on global time\n")
|
|
282
|
+
if self.type != "Sound": # for sounds, don't update to actual frame time
|
|
283
|
+
code += (f"win.timeOnFlip({params['name']}, 'tStartRefresh')"
|
|
284
|
+
f" # time at next scr refresh\n")
|
|
285
|
+
buff.writeIndentedLines(code)
|
|
286
|
+
|
|
287
|
+
def writeStartTestCodeJS(self, buff):
|
|
288
|
+
"""Test whether we need to start
|
|
289
|
+
"""
|
|
290
|
+
params = self.params
|
|
291
|
+
if self.params['startType'].val == 'time (s)':
|
|
292
|
+
# if startVal is an empty string then set to be 0.0
|
|
293
|
+
if (isinstance(self.params['startVal'].val, basestring) and
|
|
294
|
+
not self.params['startVal'].val.strip()):
|
|
295
|
+
self.params['startVal'].val = '0.0'
|
|
296
|
+
code = (f"if (t >= {params['startVal']} "
|
|
297
|
+
f"&& {params['name']}.status === PsychoJS.Status.NOT_STARTED) {{\n")
|
|
298
|
+
elif self.params['startType'].val == 'frame N':
|
|
299
|
+
code = (f"if (frameN >= {params['startVal']} "
|
|
300
|
+
f"&& {params['name']}.status === PsychoJS.Status.NOT_STARTED) {{\n")
|
|
301
|
+
elif self.params['startType'].val == 'condition':
|
|
302
|
+
code = (f"if (({params['startVal']}) "
|
|
303
|
+
f"&& {params['name']}.status === PsychoJS.Status.NOT_STARTED) {{\n")
|
|
304
|
+
else:
|
|
305
|
+
msg = f"Not a known startType ({params['startVal']}) for {params['name']}"
|
|
306
|
+
raise CodeGenerationException(msg)
|
|
307
|
+
|
|
308
|
+
buff.writeIndented(code)
|
|
309
|
+
|
|
310
|
+
buff.setIndentLevel(+1, relative=True)
|
|
311
|
+
code = (f"// keep track of start time/frame for later\n"
|
|
312
|
+
f"{params['name']}.tStart = t; // (not accounting for frame time here)\n"
|
|
313
|
+
f"{params['name']}.frameNStart = frameN; // exact frame index\n\n")
|
|
314
|
+
buff.writeIndentedLines(code)
|
|
315
|
+
|
|
316
|
+
def writeStopTestCode(self, buff):
|
|
317
|
+
"""Test whether we need to stop
|
|
318
|
+
"""
|
|
319
|
+
params = self.params
|
|
320
|
+
buff.writeIndentedLines(f"if {params['name']}.status == STARTED:\n")
|
|
321
|
+
buff.setIndentLevel(+1, relative=True)
|
|
322
|
+
|
|
323
|
+
if self.params['stopType'].val == 'time (s)':
|
|
324
|
+
code = (f"# is it time to stop? (based on local clock)\n"
|
|
325
|
+
f"if tThisFlip > {params['stopVal']}-frameTolerance:\n"
|
|
326
|
+
)
|
|
327
|
+
# duration in time (s)
|
|
328
|
+
elif (self.params['stopType'].val == 'duration (s)'):
|
|
329
|
+
code = (f"# is it time to stop? (based on global clock, using actual start)\n"
|
|
330
|
+
f"if tThisFlipGlobal > {params['name']}.tStartRefresh + {params['stopVal']}-frameTolerance:\n")
|
|
331
|
+
elif self.params['stopType'].val == 'duration (frames)':
|
|
332
|
+
code = (f"if frameN >= ({params['name']}.frameNStart + {params['stopVal']}):\n")
|
|
333
|
+
elif self.params['stopType'].val == 'frame N':
|
|
334
|
+
code = f"if frameN >= {params['stopVal']}:\n"
|
|
335
|
+
elif self.params['stopType'].val == 'condition':
|
|
336
|
+
code = f"if bool({params['stopVal']}):\n"
|
|
337
|
+
else:
|
|
338
|
+
msg = (f"Didn't write any stop line for startType={params['startType']}, "
|
|
339
|
+
f"stopType={params['stopType']}")
|
|
340
|
+
raise CodeGenerationException(msg)
|
|
341
|
+
|
|
342
|
+
buff.writeIndentedLines(code)
|
|
343
|
+
buff.setIndentLevel(+1, relative=True)
|
|
344
|
+
code = (f"# keep track of stop time/frame for later\n"
|
|
345
|
+
f"{params['name']}.tStop = t # not accounting for scr refresh\n"
|
|
346
|
+
f"{params['name']}.frameNStop = frameN # exact frame index\n"
|
|
347
|
+
f"win.timeOnFlip({params['name']}, 'tStopRefresh')"
|
|
348
|
+
f" # time at next scr refresh\n")
|
|
349
|
+
buff.writeIndentedLines(code)
|
|
350
|
+
|
|
351
|
+
def writeStopTestCodeJS(self, buff):
|
|
352
|
+
"""Test whether we need to stop
|
|
353
|
+
"""
|
|
354
|
+
params = self.params
|
|
355
|
+
if self.params['stopType'].val == 'time (s)':
|
|
356
|
+
code = (f"frameRemains = {params['stopVal']} "
|
|
357
|
+
f" - psychoJS.window.monitorFramePeriod * 0.75;"
|
|
358
|
+
f" // most of one frame period left\n"
|
|
359
|
+
f"if (({params['name']}.status === PsychoJS.Status.STARTED || {params['name']}.status === PsychoJS.Status.FINISHED) "
|
|
360
|
+
f"&& t >= frameRemains) {{\n")
|
|
361
|
+
# duration in time (s)
|
|
362
|
+
elif (self.params['stopType'].val == 'duration (s)' and
|
|
363
|
+
self.params['startType'].val == 'time (s)'):
|
|
364
|
+
code = (f"frameRemains = {params['startVal']} + {params['stopVal']}"
|
|
365
|
+
f" - psychoJS.window.monitorFramePeriod * 0.75;"
|
|
366
|
+
f" // most of one frame period left\n"
|
|
367
|
+
f"if ({params['name']}.status === PsychoJS.Status.STARTED "
|
|
368
|
+
f"&& t >= frameRemains) {{\n")
|
|
369
|
+
# start at frame and end with duratio (need to use approximate)
|
|
370
|
+
elif self.params['stopType'].val == 'duration (s)':
|
|
371
|
+
code = (f"if ({params['name']}.status === PsychoJS.Status.STARTED "
|
|
372
|
+
f"&& t >= ({params['name']}.tStart + {params['stopVal']})) {{\n")
|
|
373
|
+
elif self.params['stopType'].val == 'duration (frames)':
|
|
374
|
+
code = (f"if ({params['name']}.status === PsychoJS.Status.STARTED "
|
|
375
|
+
f"&& frameN >= ({params['name']}.frameNStart + {params['stopVal']})) {{\n")
|
|
376
|
+
elif self.params['stopType'].val == 'frame N':
|
|
377
|
+
code = (f"if ({params['name']}.status === PsychoJS.Status.STARTED "
|
|
378
|
+
f"&& frameN >= {params['stopVal']}) {{\n")
|
|
379
|
+
elif self.params['stopType'].val == 'condition':
|
|
380
|
+
code = (f"if ({params['name']}.status === PsychoJS.Status.STARTED "
|
|
381
|
+
f"&& Boolean({params['stopVal']})) {{\n")
|
|
382
|
+
else:
|
|
383
|
+
msg = (f"Didn't write any stop line for startType="
|
|
384
|
+
f"{params['startType']}, "
|
|
385
|
+
f"stopType={params['stopType']}")
|
|
386
|
+
raise CodeGenerationException(msg)
|
|
387
|
+
|
|
388
|
+
buff.writeIndentedLines(code)
|
|
389
|
+
buff.setIndentLevel(+1, relative=True)
|
|
390
|
+
|
|
391
|
+
def writeParamUpdates(self, buff, updateType, paramNames=None,
|
|
392
|
+
target="PsychoPy"):
|
|
393
|
+
"""write updates to the buffer for each parameter that needs it
|
|
394
|
+
updateType can be 'experiment', 'routine' or 'frame'
|
|
395
|
+
"""
|
|
396
|
+
if paramNames is None:
|
|
397
|
+
paramNames = list(self.params.keys())
|
|
398
|
+
for thisParamName in paramNames:
|
|
399
|
+
if thisParamName == 'advancedParams':
|
|
400
|
+
continue # advancedParams is not really a parameter itself
|
|
401
|
+
thisParam = self.params[thisParamName]
|
|
402
|
+
if thisParam.updates == updateType:
|
|
403
|
+
self.writeParamUpdate(
|
|
404
|
+
buff, self.params['name'],
|
|
405
|
+
thisParamName, thisParam, thisParam.updates,
|
|
406
|
+
target=target)
|
|
407
|
+
|
|
408
|
+
def writeParamUpdatesJS(self, buff, updateType, paramNames=None):
|
|
409
|
+
"""Pass this to the standard writeParamUpdates but with new 'target'
|
|
410
|
+
"""
|
|
411
|
+
self.writeParamUpdates(buff, updateType, paramNames,
|
|
412
|
+
target="PsychoJS")
|
|
413
|
+
|
|
414
|
+
def writeParamUpdate(self, buff, compName, paramName, val, updateType,
|
|
415
|
+
params=None, target="PsychoPy"):
|
|
416
|
+
"""Writes an update string for a single parameter.
|
|
417
|
+
This should not need overriding for different components - try to keep
|
|
418
|
+
constant
|
|
419
|
+
"""
|
|
420
|
+
if params is None:
|
|
421
|
+
params = self.params
|
|
422
|
+
# first work out the name for the set____() function call
|
|
423
|
+
if paramName == 'advancedParams':
|
|
424
|
+
return # advancedParams is not really a parameter itself
|
|
425
|
+
elif paramName == 'letterHeight':
|
|
426
|
+
paramCaps = 'Height' # setHeight for TextStim
|
|
427
|
+
elif paramName == 'image' and self.getType() == 'PatchComponent':
|
|
428
|
+
paramCaps = 'Tex' # setTex for PatchStim
|
|
429
|
+
elif paramName == 'sf':
|
|
430
|
+
paramCaps = 'SF' # setSF, not SetSf
|
|
431
|
+
elif paramName == 'coherence':
|
|
432
|
+
paramCaps = 'FieldCoherence'
|
|
433
|
+
elif paramName == 'fieldPos':
|
|
434
|
+
paramCaps = 'FieldPos'
|
|
435
|
+
else:
|
|
436
|
+
paramCaps = paramName[0].capitalize() + paramName[1:]
|
|
437
|
+
|
|
438
|
+
# code conversions for PsychoJS
|
|
439
|
+
if target == 'PsychoJS':
|
|
440
|
+
endStr = ';'
|
|
441
|
+
try:
|
|
442
|
+
valStr = str(val).strip()
|
|
443
|
+
except TypeError:
|
|
444
|
+
if isinstance(val, Param):
|
|
445
|
+
val = val.val
|
|
446
|
+
raise TypeError(f"Value of parameter {paramName} of component {compName} "
|
|
447
|
+
f"could not be converted to JS. Value is {val}")
|
|
448
|
+
# convert (0,0.5) to [0,0.5] but don't convert "rand()" to "rand[]"
|
|
449
|
+
if valStr.startswith("(") and valStr.endswith(")"):
|
|
450
|
+
valStr = valStr.replace("(", "[", 1)
|
|
451
|
+
valStr = valStr[::-1].replace(")", "]", 1)[
|
|
452
|
+
::-1] # replace from right
|
|
453
|
+
# filenames (e.g. for image) need to be loaded from resources
|
|
454
|
+
if paramName in ["sound"]:
|
|
455
|
+
valStr = (f"psychoJS.resourceManager.getResource({valStr})")
|
|
456
|
+
else:
|
|
457
|
+
endStr = ''
|
|
458
|
+
|
|
459
|
+
# then write the line
|
|
460
|
+
if updateType == 'set every frame' and target == 'PsychoPy':
|
|
461
|
+
loggingStr = ', log=False'
|
|
462
|
+
if updateType == 'set every frame' and target == 'PsychoJS':
|
|
463
|
+
loggingStr = ', false' # don't give the keyword 'log' in JS
|
|
464
|
+
else:
|
|
465
|
+
loggingStr = ''
|
|
466
|
+
|
|
467
|
+
if target == 'PsychoPy':
|
|
468
|
+
if paramName == 'color':
|
|
469
|
+
buff.writeIndented(f"{compName}.setColor({params['color']}, colorSpace={params['colorSpace']}")
|
|
470
|
+
buff.write(f"{loggingStr}){endStr}\n")
|
|
471
|
+
elif paramName == 'sound':
|
|
472
|
+
stopVal = params['stopVal'].val
|
|
473
|
+
if stopVal in ['', None, -1, 'None']:
|
|
474
|
+
stopVal = '-1'
|
|
475
|
+
buff.writeIndented(f"{compName}.setSound({params['sound']}, secs={stopVal}){endStr}\n")
|
|
476
|
+
else:
|
|
477
|
+
buff.writeIndented(f"{compName}.set{paramCaps}({val}{loggingStr}){endStr}\n")
|
|
478
|
+
elif target == 'PsychoJS':
|
|
479
|
+
# write the line
|
|
480
|
+
if paramName == 'color':
|
|
481
|
+
buff.writeIndented(f"{compName}.setColor(new util.Color({params['color']})")
|
|
482
|
+
buff.write(f"{loggingStr}){endStr}\n")
|
|
483
|
+
elif paramName == 'fillColor':
|
|
484
|
+
buff.writeIndented(f"{compName}.setFillColor(new util.Color({params['fillColor']})")
|
|
485
|
+
buff.write(f"{loggingStr}){endStr}\n")
|
|
486
|
+
elif paramName == 'lineColor':
|
|
487
|
+
buff.writeIndented(f"{compName}.setLineColor(new util.Color({params['lineColor']})")
|
|
488
|
+
buff.write(f"{loggingStr}){endStr}\n")
|
|
489
|
+
elif paramName == 'sound':
|
|
490
|
+
stopVal = params['stopVal']
|
|
491
|
+
if stopVal in ['', None, -1, 'None']:
|
|
492
|
+
stopVal = '-1'
|
|
493
|
+
buff.writeIndented(f"{compName}.setSound({params['sound']}, secs={stopVal}){endStr}\n")
|
|
494
|
+
else:
|
|
495
|
+
buff.writeIndented(f"{compName}.set{paramCaps}({val}{loggingStr}){endStr}\n")
|
|
496
|
+
|
|
497
|
+
def checkNeedToUpdate(self, updateType):
|
|
498
|
+
"""Determine whether this component has any parameters set to repeat
|
|
499
|
+
at this level
|
|
500
|
+
|
|
501
|
+
usage::
|
|
502
|
+
True/False = checkNeedToUpdate(self, updateType)
|
|
503
|
+
|
|
504
|
+
"""
|
|
505
|
+
for thisParamName in self.params:
|
|
506
|
+
if thisParamName == 'advancedParams':
|
|
507
|
+
continue
|
|
508
|
+
thisParam = self.params[thisParamName]
|
|
509
|
+
if thisParam.updates == updateType:
|
|
510
|
+
return True
|
|
511
|
+
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
def getStartAndDuration(self):
|
|
515
|
+
"""Determine the start and duration of the stimulus
|
|
516
|
+
purely for Routine rendering purposes in the app (does not affect
|
|
517
|
+
actual drawing during the experiment)
|
|
518
|
+
|
|
519
|
+
start, duration, nonSlipSafe = component.getStartAndDuration()
|
|
520
|
+
|
|
521
|
+
nonSlipSafe indicates that the component's duration is a known fixed
|
|
522
|
+
value and can be used in non-slip global clock timing (e.g for fMRI)
|
|
523
|
+
"""
|
|
524
|
+
if not 'startType' in self.params:
|
|
525
|
+
# this component does not have any start/stop
|
|
526
|
+
return None, None, True
|
|
527
|
+
|
|
528
|
+
startType = self.params['startType'].val
|
|
529
|
+
stopType = self.params['stopType'].val
|
|
530
|
+
numericStart = canBeNumeric(self.params['startVal'].val)
|
|
531
|
+
numericStop = canBeNumeric(self.params['stopVal'].val)
|
|
532
|
+
|
|
533
|
+
# deduce a start time (s) if possible
|
|
534
|
+
# user has given a time estimate
|
|
535
|
+
if canBeNumeric(self.params['startEstim'].val):
|
|
536
|
+
startTime = float(self.params['startEstim'].val)
|
|
537
|
+
elif startType == 'time (s)' and numericStart:
|
|
538
|
+
startTime = float(self.params['startVal'].val)
|
|
539
|
+
else:
|
|
540
|
+
startTime = None
|
|
541
|
+
|
|
542
|
+
if stopType == 'time (s)' and numericStop and startTime is not None:
|
|
543
|
+
duration = float(self.params['stopVal'].val) - startTime
|
|
544
|
+
elif stopType == 'duration (s)' and numericStop:
|
|
545
|
+
duration = float(self.params['stopVal'].val)
|
|
546
|
+
else:
|
|
547
|
+
# deduce duration (s) if possible. Duration used because component
|
|
548
|
+
# time icon needs width
|
|
549
|
+
if canBeNumeric(self.params['durationEstim'].val):
|
|
550
|
+
duration = float(self.params['durationEstim'].val)
|
|
551
|
+
elif self.params['stopVal'].val in ['', '-1', 'None']:
|
|
552
|
+
duration = FOREVER # infinite duration
|
|
553
|
+
else:
|
|
554
|
+
duration = None
|
|
555
|
+
|
|
556
|
+
nonSlipSafe = numericStop and (numericStart or stopType == 'time (s)')
|
|
557
|
+
return startTime, duration, nonSlipSafe
|
|
558
|
+
|
|
559
|
+
def getPosInRoutine(self):
|
|
560
|
+
"""Find the index (position) in the parent Routine (0 for top)
|
|
561
|
+
"""
|
|
562
|
+
routine = self.exp.routines[self.parentName]
|
|
563
|
+
return routine.index(self)
|
|
564
|
+
|
|
565
|
+
def getType(self):
|
|
566
|
+
"""Returns the name of the current object class"""
|
|
567
|
+
return self.__class__.__name__
|
|
568
|
+
|
|
569
|
+
def getShortType(self):
|
|
570
|
+
"""Replaces word component with empty string"""
|
|
571
|
+
return self.getType().replace('Component', '')
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
class BaseVisualComponent(BaseComponent):
|
|
575
|
+
"""Base class for most visual stimuli
|
|
576
|
+
"""
|
|
577
|
+
# an attribute of the class, determines section in the components panel
|
|
578
|
+
categories = ['Stimuli']
|
|
579
|
+
|
|
580
|
+
def __init__(self, exp, parentName, name='',
|
|
581
|
+
<<<<<<< HEAD
|
|
582
|
+
units='from exp settings', color='white', fillColor="None", borderColor="None",
|
|
583
|
+
pos=(0, 0), size=(0, 0), ori=0, colorSpace='rgb', opacity=1,
|
|
584
|
+
=======
|
|
585
|
+
units='from exp settings', color='$[1,1,1]', borderColor="", fillColor="",
|
|
586
|
+
pos=(0, 0), size=(0, 0), ori=0, colorSpace='rgb', opacity=1, contrast=1,
|
|
587
|
+
>>>>>>> f82462d1d00d58215937a827405ef7fb9983041b
|
|
588
|
+
startType='time (s)', startVal='',
|
|
589
|
+
stopType='duration (s)', stopVal='',
|
|
590
|
+
startEstim='', durationEstim='',
|
|
591
|
+
saveStartStop=True, syncScreenRefresh=True):
|
|
592
|
+
|
|
593
|
+
super(BaseVisualComponent, self).__init__(
|
|
594
|
+
exp, parentName, name,
|
|
595
|
+
startType=startType, startVal=startVal,
|
|
596
|
+
stopType=stopType, stopVal=stopVal,
|
|
597
|
+
startEstim=startEstim, durationEstim=durationEstim,
|
|
598
|
+
saveStartStop=saveStartStop,
|
|
599
|
+
syncScreenRefresh=syncScreenRefresh)
|
|
600
|
+
|
|
601
|
+
self.exp.requirePsychopyLibs(
|
|
602
|
+
['visual']) # needs this psychopy lib to operate
|
|
603
|
+
|
|
604
|
+
self.order += [
|
|
605
|
+
"color",
|
|
606
|
+
"fillColor",
|
|
607
|
+
"borderColor",
|
|
608
|
+
"colorSpace",
|
|
609
|
+
"opacity",
|
|
610
|
+
"size",
|
|
611
|
+
"pos",
|
|
612
|
+
"units",
|
|
613
|
+
"anchor",
|
|
614
|
+
"ori",
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
msg = _translate("Units of dimensions for this stimulus")
|
|
618
|
+
self.params['units'] = Param(units,
|
|
619
|
+
valType='str', inputType="choice", categ='Layout',
|
|
620
|
+
allowedVals=['from exp settings', 'deg', 'cm', 'pix', 'norm',
|
|
621
|
+
'height', 'degFlatPos', 'degFlat'],
|
|
622
|
+
hint=msg,
|
|
623
|
+
label=_localized['units'])
|
|
624
|
+
|
|
625
|
+
msg = _translate("Foreground color of this stimulus (e.g. $[1,1,0], red );"
|
|
626
|
+
" Right-click to bring up a color-picker (rgb only)")
|
|
627
|
+
self.params['color'] = Param(color,
|
|
628
|
+
valType='color', inputType="color", categ='Appearance',
|
|
629
|
+
allowedTypes=[],
|
|
630
|
+
updates='constant',
|
|
631
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
632
|
+
hint=msg,
|
|
633
|
+
label=_localized['color'])
|
|
634
|
+
|
|
635
|
+
msg = _translate("In what format (color space) have you specified "
|
|
636
|
+
"the foreground color? (rgb, dkl, lms, hsv)")
|
|
637
|
+
self.params['colorSpace'] = Param(colorSpace,
|
|
638
|
+
valType='str', inputType="choice", categ='Appearance',
|
|
639
|
+
allowedVals=['named', 'rgb', 'dkl', 'lms', 'hsv'],
|
|
640
|
+
updates='constant',
|
|
641
|
+
hint=msg,
|
|
642
|
+
label=_localized['colorSpace'])
|
|
643
|
+
|
|
644
|
+
msg = _translate("Fill color of this stimulus (e.g. $[1,1,0], red );"
|
|
645
|
+
" Right-click to bring up a color-picker (rgb only)")
|
|
646
|
+
self.params['fillColor'] = Param(fillColor,
|
|
647
|
+
valType='color', inputType="color", categ='Appearance',
|
|
648
|
+
updates='constant', allowedTypes=[],
|
|
649
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
650
|
+
hint=msg,
|
|
651
|
+
label=_localized['fillColor'])
|
|
652
|
+
|
|
653
|
+
msg = _translate("In what format (color space) have you specified "
|
|
654
|
+
"the fill color? (rgb, dkl, lms, hsv)")
|
|
655
|
+
self.params['fillColorSpace'] = Param(colorSpace,
|
|
656
|
+
valType='str', inputType="choice", categ='Appearance',
|
|
657
|
+
allowedVals=['rgb', 'dkl', 'lms', 'hsv'],
|
|
658
|
+
updates='constant',
|
|
659
|
+
hint=msg,
|
|
660
|
+
label=_localized['fillColorSpace'])
|
|
661
|
+
|
|
662
|
+
msg = _translate("Color of this stimulus (e.g. $[1,1,0], red );"
|
|
663
|
+
" Right-click to bring up a color-picker (rgb only)")
|
|
664
|
+
self.params['borderColor'] = Param(borderColor,
|
|
665
|
+
valType='color', inputType="color", categ='Appearance',
|
|
666
|
+
updates='constant',allowedTypes=[],
|
|
667
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
668
|
+
hint=msg,
|
|
669
|
+
label=_localized['borderColor'])
|
|
670
|
+
|
|
671
|
+
msg = _translate("In what format (color space) have you specified "
|
|
672
|
+
"the border color? (rgb, dkl, lms, hsv)")
|
|
673
|
+
self.params['borderColorSpace'] = Param(colorSpace,
|
|
674
|
+
valType='str', inputType="choice", categ='Appearance',
|
|
675
|
+
allowedVals=['rgb', 'dkl', 'lms', 'hsv'],
|
|
676
|
+
updates='constant',
|
|
677
|
+
hint=msg,
|
|
678
|
+
label=_localized['borderColorSpace'])
|
|
679
|
+
|
|
680
|
+
msg = _translate("Opacity of the stimulus (1=opaque, 0=fully "
|
|
681
|
+
"transparent, 0.5=translucent)")
|
|
682
|
+
self.params['opacity'] = Param(opacity,
|
|
683
|
+
valType='num', inputType="single", categ='Appearance',
|
|
684
|
+
updates='constant', allowedTypes=[],
|
|
685
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
686
|
+
hint=msg,
|
|
687
|
+
label=_localized['opacity'])
|
|
688
|
+
|
|
689
|
+
msg = _translate("Contrast of the stimulus (1.0=unchanged contrast, "
|
|
690
|
+
"0.5=decrease contrast, 0.0=uniform/no contrast, "
|
|
691
|
+
"-0.5=slightly inverted, -1.0=totally inverted)")
|
|
692
|
+
self.params['contrast'] = Param(
|
|
693
|
+
contrast, valType='code', allowedTypes=[], categ='Appearance',
|
|
694
|
+
updates='constant',
|
|
695
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
696
|
+
hint=msg,
|
|
697
|
+
label=_localized['contrast'])
|
|
698
|
+
|
|
699
|
+
msg = _translate("Position of this stimulus (e.g. [1,2] )")
|
|
700
|
+
self.params['pos'] = Param(pos,
|
|
701
|
+
valType='list', inputType="single", categ='Layout',
|
|
702
|
+
updates='constant', allowedTypes=[],
|
|
703
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
704
|
+
hint=msg,
|
|
705
|
+
label=_localized['pos'])
|
|
706
|
+
|
|
707
|
+
msg = _translate("Size of this stimulus (either a single value or "
|
|
708
|
+
"x,y pair, e.g. 2.5, [1,2] ")
|
|
709
|
+
self.params['size'] = Param(size,
|
|
710
|
+
valType='list', inputType="single", categ='Layout',
|
|
711
|
+
updates='constant', allowedTypes=[],
|
|
712
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
713
|
+
hint=msg,
|
|
714
|
+
label=_localized['size'])
|
|
715
|
+
|
|
716
|
+
self.params['ori'] = Param(ori,
|
|
717
|
+
valType='num', inputType="spin", categ='Layout',
|
|
718
|
+
updates='constant', allowedTypes=[], allowedVals=[-360,360],
|
|
719
|
+
allowedUpdates=['constant', 'set every repeat', 'set every frame'],
|
|
720
|
+
hint=_translate("Orientation of this stimulus (in deg)"),
|
|
721
|
+
label=_localized['ori'])
|
|
722
|
+
|
|
723
|
+
self.params['syncScreenRefresh'].readOnly = True
|
|
724
|
+
|
|
725
|
+
def integrityCheck(self):
|
|
726
|
+
"""
|
|
727
|
+
Run component integrity checks.
|
|
728
|
+
"""
|
|
729
|
+
super().integrityCheck() # run parent class checks first
|
|
730
|
+
|
|
731
|
+
win = alerttools.TestWin(self.exp)
|
|
732
|
+
|
|
733
|
+
# get units for this stimulus
|
|
734
|
+
if 'units' in self.params: # e.g. BrushComponent doesn't have this
|
|
735
|
+
units = self.params['units'].val
|
|
736
|
+
else:
|
|
737
|
+
units = None
|
|
738
|
+
if units == 'use experiment settings':
|
|
739
|
+
units = self.exp.settings.params[
|
|
740
|
+
'Units'].val # this 1 uppercase
|
|
741
|
+
if not units or units == 'use preferences':
|
|
742
|
+
units = prefs.general['units']
|
|
743
|
+
|
|
744
|
+
# tests for visual stimuli
|
|
745
|
+
alerttools.testSize(self, win, units)
|
|
746
|
+
alerttools.testPos(self, win, units)
|
|
747
|
+
alerttools.testAchievableVisualOnsetOffset(self)
|
|
748
|
+
alerttools.testValidVisualStimTiming(self)
|
|
749
|
+
alerttools.testFramesAsInt(self)
|
|
750
|
+
|
|
751
|
+
def writeFrameCode(self, buff):
|
|
752
|
+
"""Write the code that will be called every frame
|
|
753
|
+
"""
|
|
754
|
+
params = self.params
|
|
755
|
+
buff.writeIndented(f"\n")
|
|
756
|
+
buff.writeIndented(f"# *{params['name']}* updates\n")
|
|
757
|
+
# writes an if statement to determine whether to draw etc
|
|
758
|
+
self.writeStartTestCode(buff)
|
|
759
|
+
buff.writeIndented(f"{params['name']}.setAutoDraw(True)\n")
|
|
760
|
+
# to get out of the if statement
|
|
761
|
+
buff.setIndentLevel(-1, relative=True)
|
|
762
|
+
|
|
763
|
+
# test for stop (only if there was some setting for duration or stop)
|
|
764
|
+
if self.params['stopVal'].val not in ('', None, -1, 'None'):
|
|
765
|
+
# writes an if statement to determine whether to draw etc
|
|
766
|
+
self.writeStopTestCode(buff)
|
|
767
|
+
buff.writeIndented(f"{params['name']}.setAutoDraw(False)\n")
|
|
768
|
+
# to get out of the if statement
|
|
769
|
+
buff.setIndentLevel(-2, relative=True)
|
|
770
|
+
|
|
771
|
+
# set parameters that need updating every frame
|
|
772
|
+
# do any params need updating? (this method inherited from _base)
|
|
773
|
+
if self.checkNeedToUpdate('set every frame'):
|
|
774
|
+
buff.writeIndented(f"if {params['name']}.status == STARTED: # only update if drawing\n")
|
|
775
|
+
buff.setIndentLevel(+1, relative=True) # to enter the if block
|
|
776
|
+
self.writeParamUpdates(buff, 'set every frame')
|
|
777
|
+
buff.setIndentLevel(-1, relative=True) # to exit the if block
|
|
778
|
+
|
|
779
|
+
def writeFrameCodeJS(self, buff):
|
|
780
|
+
"""Write the code that will be called every frame
|
|
781
|
+
"""
|
|
782
|
+
params = self.params
|
|
783
|
+
if "PsychoJS" not in self.targets:
|
|
784
|
+
buff.writeIndented(f"// *{params['name']}* not supported by PsychoJS\n")
|
|
785
|
+
return
|
|
786
|
+
|
|
787
|
+
buff.writeIndentedLines(f"\n// *{params['name']}* updates\n")
|
|
788
|
+
# writes an if statement to determine whether to draw etc
|
|
789
|
+
self.writeStartTestCodeJS(buff)
|
|
790
|
+
buff.writeIndented(f"{params['name']}.setAutoDraw(true);\n")
|
|
791
|
+
# to get out of the if statement
|
|
792
|
+
buff.setIndentLevel(-1, relative=True)
|
|
793
|
+
buff.writeIndented("}\n\n")
|
|
794
|
+
|
|
795
|
+
# test for stop (only if there was some setting for duration or stop)
|
|
796
|
+
if self.params['stopVal'].val not in ('', None, -1, 'None'):
|
|
797
|
+
# writes an if statement to determine whether to draw etc
|
|
798
|
+
self.writeStopTestCodeJS(buff)
|
|
799
|
+
buff.writeIndented(f"{params['name']}.setAutoDraw(false);\n")
|
|
800
|
+
# to get out of the if statement
|
|
801
|
+
buff.setIndentLevel(-1, relative=True)
|
|
802
|
+
buff.writeIndented("}\n")
|
|
803
|
+
|
|
804
|
+
# set parameters that need updating every frame
|
|
805
|
+
# do any params need updating? (this method inherited from _base)
|
|
806
|
+
if self.checkNeedToUpdate('set every frame'):
|
|
807
|
+
buff.writeIndentedLines(f"\nif ({params['name']}.status === PsychoJS.Status.STARTED){{ "
|
|
808
|
+
f"// only update if being drawn\n")
|
|
809
|
+
buff.setIndentLevel(+1, relative=True) # to enter the if block
|
|
810
|
+
self.writeParamUpdatesJS(buff, 'set every frame')
|
|
811
|
+
buff.setIndentLevel(-1, relative=True) # to exit the if block
|
|
812
|
+
buff.writeIndented("}\n")
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def canBeNumeric(inStr):
|
|
816
|
+
"""Determines whether the input can be converted to a float
|
|
817
|
+
(using a try: float(instr))
|
|
818
|
+
"""
|
|
819
|
+
try:
|
|
820
|
+
float(inStr)
|
|
821
|
+
return True
|
|
822
|
+
except Exception:
|
|
823
|
+
return False
|