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,1337 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from __future__ import absolute_import, print_function
|
|
5
|
+
|
|
6
|
+
from builtins import str
|
|
7
|
+
from builtins import object
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from xml.etree.ElementTree import Element
|
|
11
|
+
import re
|
|
12
|
+
import wx.__version__
|
|
13
|
+
import psychopy
|
|
14
|
+
from psychopy import logging
|
|
15
|
+
from psychopy.experiment.components import Param, _translate
|
|
16
|
+
from psychopy.experiment.routines.eyetracker_calibrate import EyetrackerCalibrationRoutine
|
|
17
|
+
from psychopy.tools.versionchooser import versionOptions, availableVersions, _versionFilter, latestVersion
|
|
18
|
+
from psychopy.constants import PY3
|
|
19
|
+
from psychopy.monitors import Monitor
|
|
20
|
+
from psychopy.iohub import util as ioUtil
|
|
21
|
+
from psychopy.alerts import alert
|
|
22
|
+
|
|
23
|
+
# for creating html output folders:
|
|
24
|
+
import shutil
|
|
25
|
+
import hashlib
|
|
26
|
+
from pkg_resources import parse_version
|
|
27
|
+
import ast # for doing literal eval to convert '["a","b"]' to a list
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def readTextFile(relPath):
|
|
31
|
+
fullPath = os.path.join(Path(__file__).parent, relPath)
|
|
32
|
+
with open(fullPath, "r") as f:
|
|
33
|
+
txt = f.read()
|
|
34
|
+
return txt
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# used when writing scripts and in namespace:
|
|
38
|
+
_numpyImports = ['sin', 'cos', 'tan', 'log', 'log10', 'pi', 'average',
|
|
39
|
+
'sqrt', 'std', 'deg2rad', 'rad2deg', 'linspace', 'asarray']
|
|
40
|
+
_numpyRandomImports = ['random', 'randint', 'normal', 'shuffle', 'choice as randchoice']
|
|
41
|
+
|
|
42
|
+
# this is not a standard component - it will appear on toolbar not in
|
|
43
|
+
# components panel
|
|
44
|
+
|
|
45
|
+
# only use _localized values for label values, nothing functional:
|
|
46
|
+
_localized = {'expName': _translate("Experiment name"),
|
|
47
|
+
'Show info dlg': _translate("Show info dialog"),
|
|
48
|
+
'Enable Escape': _translate("Enable Escape key"),
|
|
49
|
+
'Experiment info': _translate("Experiment info"),
|
|
50
|
+
'Data filename': _translate("Data filename"),
|
|
51
|
+
'Data file delimiter': _translate("Data file delimiter"),
|
|
52
|
+
'Full-screen window': _translate("Full-screen window"),
|
|
53
|
+
'Window size (pixels)': _translate("Window size (pixels)"),
|
|
54
|
+
'Screen': _translate('Screen'),
|
|
55
|
+
'Monitor': _translate("Monitor"),
|
|
56
|
+
'color': _translate("Color"),
|
|
57
|
+
'colorSpace': _translate("Color space"),
|
|
58
|
+
'Units': _translate("Units"),
|
|
59
|
+
'blendMode': _translate("Blend mode"),
|
|
60
|
+
'Show mouse': _translate("Show mouse"),
|
|
61
|
+
'Save log file': _translate("Save log file"),
|
|
62
|
+
'Save wide csv file':
|
|
63
|
+
_translate("Save csv file (trial-by-trial)"),
|
|
64
|
+
'Save csv file': _translate("Save csv file (summaries)"),
|
|
65
|
+
'Save excel file': _translate("Save excel file"),
|
|
66
|
+
'Save psydat file': _translate("Save psydat file"),
|
|
67
|
+
'logging level': _translate("Logging level"),
|
|
68
|
+
'Use version': _translate("Use PsychoPy version"),
|
|
69
|
+
'Completed URL': _translate("Completed URL"),
|
|
70
|
+
'Incomplete URL': _translate("Incomplete URL"),
|
|
71
|
+
'Output path': _translate("Output path"),
|
|
72
|
+
'Additional Resources': _translate("Additional Resources"),
|
|
73
|
+
'JS libs': _translate("JS libs"),
|
|
74
|
+
'Force stereo': _translate("Force stereo"),
|
|
75
|
+
'Export HTML': _translate("Export HTML")}
|
|
76
|
+
ioDeviceMap = dict(ioUtil.getDeviceNames())
|
|
77
|
+
#
|
|
78
|
+
#
|
|
79
|
+
# # customize the Proj ID Param class to
|
|
80
|
+
# class ProjIDParam(Param):
|
|
81
|
+
# @property
|
|
82
|
+
# def allowedVals(self):
|
|
83
|
+
# from psychopy.app.projects import catalog
|
|
84
|
+
# allowed = list(catalog.keys())
|
|
85
|
+
# # always allow the current val!
|
|
86
|
+
# if self.val not in allowed:
|
|
87
|
+
# allowed.append(self.val)
|
|
88
|
+
# # always allow blank (None)
|
|
89
|
+
# if '' not in allowed:
|
|
90
|
+
# allowed.append('')
|
|
91
|
+
# return allowed
|
|
92
|
+
# @allowedVals.setter
|
|
93
|
+
# def allowedVals(self, allowed):
|
|
94
|
+
# pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SettingsComponent(object):
|
|
98
|
+
"""This component stores general info about how to run the experiment
|
|
99
|
+
"""
|
|
100
|
+
targets = ['PsychoPy']
|
|
101
|
+
|
|
102
|
+
categories = ['Custom']
|
|
103
|
+
targets = ['PsychoPy', 'PsychoJS']
|
|
104
|
+
iconFile = Path(__file__).parent / 'settings.png'
|
|
105
|
+
tooltip = _translate("Edit settings for this experiment")
|
|
106
|
+
|
|
107
|
+
def __init__(self, parentName, exp, expName='', fullScr=True,
|
|
108
|
+
winSize=(1024, 768), screen=1, monitor='testMonitor',
|
|
109
|
+
showMouse=False, saveLogFile=True, showExpInfo=True,
|
|
110
|
+
expInfo="{'participant':'', 'session':'001'}",
|
|
111
|
+
units='height', logging='exp',
|
|
112
|
+
color='$[0,0,0]', colorSpace='rgb', enableEscape=True,
|
|
113
|
+
blendMode='avg',
|
|
114
|
+
saveXLSXFile=False, saveCSVFile=False, saveHDF5File=False,
|
|
115
|
+
saveWideCSVFile=True, savePsydatFile=True,
|
|
116
|
+
savedDataFolder='', savedDataDelim='auto',
|
|
117
|
+
useVersion='',
|
|
118
|
+
eyetracker="None",
|
|
119
|
+
mgMove='CONTINUOUS', mgBlink='MIDDLE_BUTTON', mgSaccade=0.5,
|
|
120
|
+
gpAddress='127.0.0.1', gpPort=4242,
|
|
121
|
+
elModel='EYELINK 1000 DESKTOP', elSimMode=False, elSampleRate=1000, elTrackEyes="RIGHT_EYE",
|
|
122
|
+
elLiveFiltering="FILTER_LEVEL_2", elDataFiltering="FILTER_LEVEL_OFF",
|
|
123
|
+
elTrackingMode='PUPIL_CR_TRACKING', elPupilMeasure='PUPIL_AREA', elPupilAlgorithm='ELLIPSE_FIT',
|
|
124
|
+
elAddress='100.1.1.1',
|
|
125
|
+
tbModel="", tbLicenseFile="", tbSerialNo="", tbSampleRate=60,
|
|
126
|
+
filename=None, exportHTML='on Sync'):
|
|
127
|
+
self.type = 'Settings'
|
|
128
|
+
self.exp = exp # so we can access the experiment if necess
|
|
129
|
+
self.exp.requirePsychopyLibs(['visual', 'gui'])
|
|
130
|
+
self.parentName = parentName
|
|
131
|
+
self.url = "https://www.psychopy.org/builder/settings.html"
|
|
132
|
+
self._monitor = None
|
|
133
|
+
|
|
134
|
+
# if filename is the default value fetch the builder pref for the
|
|
135
|
+
# folder instead
|
|
136
|
+
if filename is None:
|
|
137
|
+
filename = ("u'xxxx/%s_%s_%s' % (expInfo['participant'], expName,"
|
|
138
|
+
" expInfo['date'])")
|
|
139
|
+
if filename.startswith("u'xxxx"):
|
|
140
|
+
folder = self.exp.prefsBuilder['savedDataFolder'].strip()
|
|
141
|
+
filename = filename.replace("xxxx", folder)
|
|
142
|
+
|
|
143
|
+
# params
|
|
144
|
+
self.params = {}
|
|
145
|
+
self.depends = []
|
|
146
|
+
self.order = ['expName', 'Use version', 'Show info dlg', 'Enable Escape', 'Experiment info', # Basic tab
|
|
147
|
+
'Data filename', 'Data file delimiter', 'Save excel file', 'Save csv file', 'Save wide csv file',
|
|
148
|
+
'Save psydat file', 'Save hdf5 file', 'Save log file', 'logging level', # Data tab
|
|
149
|
+
'Audio lib', 'Audio latency priority', "Force stereo", # Audio tab
|
|
150
|
+
'HTML path', 'exportHTML', 'Completed URL', 'Incomplete URL', 'Resources', # Online tab
|
|
151
|
+
'Monitor', 'Screen', 'Full-screen window', 'Window size (pixels)', 'Units', 'color',
|
|
152
|
+
'colorSpace', # Screen tab
|
|
153
|
+
]
|
|
154
|
+
self.depends = []
|
|
155
|
+
# basic params
|
|
156
|
+
self.params['expName'] = Param(
|
|
157
|
+
expName, valType='str', inputType="single", allowedTypes=[],
|
|
158
|
+
hint=_translate("Name of the entire experiment (taken by default"
|
|
159
|
+
" from the filename on save)"),
|
|
160
|
+
label=_localized["expName"])
|
|
161
|
+
self.params['Show info dlg'] = Param(
|
|
162
|
+
showExpInfo, valType='bool', inputType="bool", allowedTypes=[],
|
|
163
|
+
hint=_translate("Start the experiment with a dialog to set info"
|
|
164
|
+
" (e.g.participant or condition)"),
|
|
165
|
+
label=_localized["Show info dlg"], categ='Basic')
|
|
166
|
+
self.params['Enable Escape'] = Param(
|
|
167
|
+
enableEscape, valType='bool', inputType="bool", allowedTypes=[],
|
|
168
|
+
hint=_translate("Enable the <esc> key, to allow subjects to quit"
|
|
169
|
+
" / break out of the experiment"),
|
|
170
|
+
label=_localized["Enable Escape"])
|
|
171
|
+
self.params['Experiment info'] = Param(
|
|
172
|
+
expInfo, valType='code', inputType="dict", allowedTypes=[],
|
|
173
|
+
hint=_translate("The info to present in a dialog box. Right-click"
|
|
174
|
+
" to check syntax and preview the dialog box."),
|
|
175
|
+
label=_localized["Experiment info"], categ='Basic')
|
|
176
|
+
self.params['Use version'] = Param(
|
|
177
|
+
useVersion, valType='str', inputType="choice",
|
|
178
|
+
# search for options locally only by default, otherwise sluggish
|
|
179
|
+
allowedVals=_versionFilter(versionOptions(), wx.__version__)
|
|
180
|
+
+ ['']
|
|
181
|
+
+ _versionFilter(availableVersions(), wx.__version__),
|
|
182
|
+
hint=_translate("The version of PsychoPy to use when running "
|
|
183
|
+
"the experiment."),
|
|
184
|
+
label=_localized["Use version"], categ='Basic')
|
|
185
|
+
|
|
186
|
+
# screen params
|
|
187
|
+
self.params['Full-screen window'] = Param(
|
|
188
|
+
fullScr, valType='bool', inputType="bool", allowedTypes=[],
|
|
189
|
+
hint=_translate("Run the experiment full-screen (recommended)"),
|
|
190
|
+
label=_localized["Full-screen window"], categ='Screen')
|
|
191
|
+
self.params['Window size (pixels)'] = Param(
|
|
192
|
+
winSize, valType='list', inputType="single", allowedTypes=[],
|
|
193
|
+
hint=_translate("Size of window (if not fullscreen)"),
|
|
194
|
+
label=_localized["Window size (pixels)"], categ='Screen')
|
|
195
|
+
self.params['Screen'] = Param(
|
|
196
|
+
screen, valType='num', inputType="spin", allowedTypes=[],
|
|
197
|
+
hint=_translate("Which physical screen to run on (1 or 2)"),
|
|
198
|
+
label=_localized["Screen"], categ='Screen')
|
|
199
|
+
self.params['Monitor'] = Param(
|
|
200
|
+
monitor, valType='str', inputType="single", allowedTypes=[],
|
|
201
|
+
hint=_translate("Name of the monitor (from Monitor Center). Right"
|
|
202
|
+
"-click to go there, then copy & paste a monitor "
|
|
203
|
+
"name here."),
|
|
204
|
+
label=_localized["Monitor"], categ="Screen")
|
|
205
|
+
self.params['color'] = Param(
|
|
206
|
+
color, valType='color', inputType="color", allowedTypes=[],
|
|
207
|
+
hint=_translate("Color of the screen (e.g. black, $[1.0,1.0,1.0],"
|
|
208
|
+
" $variable. Right-click to bring up a "
|
|
209
|
+
"color-picker.)"),
|
|
210
|
+
label=_localized["color"], categ='Screen')
|
|
211
|
+
self.params['colorSpace'] = Param(
|
|
212
|
+
colorSpace, valType='str', inputType="choice",
|
|
213
|
+
hint=_translate("Needed if color is defined numerically (see "
|
|
214
|
+
"PsychoPy documentation on color spaces)"),
|
|
215
|
+
allowedVals=['rgb', 'dkl', 'lms', 'hsv', 'hex'],
|
|
216
|
+
label=_localized["colorSpace"], categ="Screen")
|
|
217
|
+
self.params['Units'] = Param(
|
|
218
|
+
units, valType='str', inputType="choice", allowedTypes=[],
|
|
219
|
+
allowedVals=['use prefs', 'deg', 'pix', 'cm', 'norm', 'height',
|
|
220
|
+
'degFlatPos', 'degFlat'],
|
|
221
|
+
hint=_translate("Units to use for window/stimulus coordinates "
|
|
222
|
+
"(e.g. cm, pix, deg)"),
|
|
223
|
+
label=_localized["Units"], categ='Screen')
|
|
224
|
+
self.params['blendMode'] = Param(
|
|
225
|
+
blendMode, valType='str', inputType="choice",
|
|
226
|
+
allowedTypes=[], allowedVals=['add', 'avg'],
|
|
227
|
+
hint=_translate("Should new stimuli be added or averaged with "
|
|
228
|
+
"the stimuli that have been drawn already"),
|
|
229
|
+
label=_localized["blendMode"], categ='Screen')
|
|
230
|
+
self.params['Show mouse'] = Param(
|
|
231
|
+
showMouse, valType='bool', inputType="bool", allowedTypes=[],
|
|
232
|
+
hint=_translate("Should the mouse be visible on screen?"),
|
|
233
|
+
label=_localized["Show mouse"], categ='Screen')
|
|
234
|
+
|
|
235
|
+
# sound params
|
|
236
|
+
self.params['Force stereo'] = Param(
|
|
237
|
+
enableEscape, valType='bool', inputType="bool", allowedTypes=[], categ="Audio",
|
|
238
|
+
hint=_translate("Force audio to stereo (2-channel) output"),
|
|
239
|
+
label=_localized["Force stereo"])
|
|
240
|
+
self.params['Audio lib'] = Param(
|
|
241
|
+
'use prefs', valType='str', inputType="choice",
|
|
242
|
+
allowedVals=['use prefs', 'ptb', 'pyo', 'sounddevice', 'pygame'],
|
|
243
|
+
hint=_translate("Which Python sound engine do you want to play your sounds?"),
|
|
244
|
+
label=_translate("Audio library"), categ='Audio')
|
|
245
|
+
|
|
246
|
+
audioLatencyLabels = [
|
|
247
|
+
_translate('use prefs'),
|
|
248
|
+
'0: ' + _translate('Latency not important'),
|
|
249
|
+
'1: ' + _translate('Share low-latency driver'),
|
|
250
|
+
'2: ' + _translate('Exclusive low-latency'),
|
|
251
|
+
'3: ' + _translate('Aggressive low-latency'),
|
|
252
|
+
'4: ' + _translate('Latency critical'),
|
|
253
|
+
]
|
|
254
|
+
self.params['Audio latency priority'] = Param(
|
|
255
|
+
'use prefs', valType='str', inputType="choice",
|
|
256
|
+
allowedVals=['use prefs', '0', '1', '2', '3', '4'],
|
|
257
|
+
allowedLabels=audioLatencyLabels,
|
|
258
|
+
hint=_translate("How important is audio latency for you? If essential then you may need to get all your sounds in correct formats."),
|
|
259
|
+
label=_translate("Audio latency priority"), categ='Audio')
|
|
260
|
+
|
|
261
|
+
# data params
|
|
262
|
+
self.params['Data filename'] = Param(
|
|
263
|
+
filename, valType='code', inputType="single", allowedTypes=[],
|
|
264
|
+
hint=_translate("Code to create your custom file name base. Don"
|
|
265
|
+
"'t give a file extension - this will be added."),
|
|
266
|
+
label=_localized["Data filename"], categ='Data')
|
|
267
|
+
self.params['Data file delimiter'] = Param(
|
|
268
|
+
savedDataDelim, valType='str', inputType="choice",
|
|
269
|
+
allowedVals=['auto', 'comma', 'semicolon', 'tab'],
|
|
270
|
+
hint=_translate("What symbol should the data file use to separate columns? ""Auto"" will select a delimiter automatically from the filename."),
|
|
271
|
+
label=_translate("Data file delimiter"), categ='Data'
|
|
272
|
+
)
|
|
273
|
+
self.params['Save log file'] = Param(
|
|
274
|
+
saveLogFile, valType='bool', inputType="bool", allowedTypes=[],
|
|
275
|
+
hint=_translate("Save a detailed log (more detailed than the "
|
|
276
|
+
"excel/csv files) of the entire experiment"),
|
|
277
|
+
label=_localized["Save log file"], categ='Data')
|
|
278
|
+
self.params['Save wide csv file'] = Param(
|
|
279
|
+
saveWideCSVFile, valType='bool', inputType="bool", allowedTypes=[],
|
|
280
|
+
hint=_translate("Save data from loops in comma-separated-value "
|
|
281
|
+
"(.csv) format for maximum portability"),
|
|
282
|
+
label=_localized["Save wide csv file"], categ='Data')
|
|
283
|
+
self.params['Save csv file'] = Param(
|
|
284
|
+
saveCSVFile, valType='bool', inputType="bool", allowedTypes=[],
|
|
285
|
+
hint=_translate("Save data from loops in comma-separated-value "
|
|
286
|
+
"(.csv) format for maximum portability"),
|
|
287
|
+
label=_localized["Save csv file"], categ='Data')
|
|
288
|
+
self.params['Save excel file'] = Param(
|
|
289
|
+
saveXLSXFile, valType='bool', inputType="bool", allowedTypes=[],
|
|
290
|
+
hint=_translate("Save data from loops in Excel (.xlsx) format"),
|
|
291
|
+
label=_localized["Save excel file"], categ='Data')
|
|
292
|
+
self.params['Save psydat file'] = Param(
|
|
293
|
+
savePsydatFile, valType='bool', inputType="bool", allowedVals=[True],
|
|
294
|
+
hint=_translate("Save data from loops in psydat format. This is "
|
|
295
|
+
"useful for python programmers to generate "
|
|
296
|
+
"analysis scripts."),
|
|
297
|
+
label=_localized["Save psydat file"], categ='Data')
|
|
298
|
+
self.params['Save hdf5 file'] = Param(
|
|
299
|
+
saveHDF5File, valType='bool', inputType="bool",
|
|
300
|
+
hint=_translate("Save data from eyetrackers in hdf5 format. This is "
|
|
301
|
+
"useful for viewing and analyzing complex data in structures."),
|
|
302
|
+
label=_translate("Save hdf5 file"), categ='Data')
|
|
303
|
+
self.params['logging level'] = Param(
|
|
304
|
+
logging, valType='code', inputType="choice",
|
|
305
|
+
allowedVals=['error', 'warning', 'data', 'exp', 'info', 'debug'],
|
|
306
|
+
hint=_translate("How much output do you want in the log files? "
|
|
307
|
+
"('error' is fewest messages, 'debug' is most)"),
|
|
308
|
+
label=_localized["logging level"], categ='Data')
|
|
309
|
+
|
|
310
|
+
# HTML output params
|
|
311
|
+
# self.params['OSF Project ID'] = ProjIDParam(
|
|
312
|
+
# '', valType='str', # automatically updates to allow choices
|
|
313
|
+
# hint=_translate("The ID of this project (e.g. 5bqpc)"),
|
|
314
|
+
# label="OSF Project ID", categ='Online')
|
|
315
|
+
self.params['HTML path'] = Param(
|
|
316
|
+
'', valType='str', inputType="single", allowedTypes=[],
|
|
317
|
+
hint=_translate("Place the HTML files will be saved locally "),
|
|
318
|
+
label="Output path", categ='Online')
|
|
319
|
+
self.params['Resources'] = Param(
|
|
320
|
+
[], valType='list', inputType="fileList", allowedTypes=[],
|
|
321
|
+
hint=_translate("Any additional resources needed"),
|
|
322
|
+
label="Additional Resources", categ='Online')
|
|
323
|
+
self.params['Completed URL'] = Param(
|
|
324
|
+
'', valType='str', inputType="single",
|
|
325
|
+
hint=_translate("Where should participants be redirected after the experiment on completion\n"
|
|
326
|
+
" INSERT COMPLETION URL E.G.?"),
|
|
327
|
+
label="Completed URL", categ='Online')
|
|
328
|
+
self.params['Incomplete URL'] = Param(
|
|
329
|
+
'', valType='str', inputType="single",
|
|
330
|
+
hint=_translate("Where participants are redirected if they do not complete the task\n"
|
|
331
|
+
" INSERT INCOMPLETION URL E.G.?"),
|
|
332
|
+
label="Incomplete URL", categ='Online')
|
|
333
|
+
self.params['exportHTML'] = Param(
|
|
334
|
+
exportHTML, valType='str', inputType="choice",
|
|
335
|
+
allowedVals=['on Save', 'on Sync', 'manually'],
|
|
336
|
+
hint=_translate("When to export experiment to the HTML folder."),
|
|
337
|
+
label=_localized["Export HTML"], categ='Online')
|
|
338
|
+
|
|
339
|
+
# Eyetracking params
|
|
340
|
+
self.order += ["eyetracker",
|
|
341
|
+
"gpAddress", "gpPort",
|
|
342
|
+
"elModel", "elAddress", "elSimMode"]
|
|
343
|
+
|
|
344
|
+
# Hide params when not relevant to current eyetracker
|
|
345
|
+
trackerParams = {
|
|
346
|
+
"MouseGaze": ["mgMove", "mgBlink", "mgSaccade"],
|
|
347
|
+
"GazePoint": ["gpAddress", "gpPort"],
|
|
348
|
+
"SR Research Ltd": ["elModel", "elSimMode", "elSampleRate", "elTrackEyes", "elLiveFiltering",
|
|
349
|
+
"elDataFiltering", "elTrackingMode", "elPupilMeasure", "elPupilAlgorithm",
|
|
350
|
+
"elAddress"],
|
|
351
|
+
"Tobii Technology": ["tbModel", "tbLicenseFile", "tbSerialNo", "tbSampleRate"],
|
|
352
|
+
}
|
|
353
|
+
for tracker in trackerParams:
|
|
354
|
+
for depParam in trackerParams[tracker]:
|
|
355
|
+
self.depends.append(
|
|
356
|
+
{"dependsOn": "eyetracker", # must be param name
|
|
357
|
+
"condition": "=='"+tracker+"'", # val to check for
|
|
358
|
+
"param": depParam, # param property to alter
|
|
359
|
+
"true": "show", # what to do with param if condition is True
|
|
360
|
+
"false": "hide", # permitted: hide, show, enable, disable
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
self.depends.append(
|
|
364
|
+
{"dependsOn": "eyetracker", # must be param name
|
|
365
|
+
"condition": f" in {list(trackerParams)}", # val to check for
|
|
366
|
+
"param": "Save hdf5 file", # param property to alter
|
|
367
|
+
"true": "enable", # what to do with param if condition is True
|
|
368
|
+
"false": "disable", # permitted: hide, show, enable, disable
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
self.params['eyetracker'] = Param(
|
|
373
|
+
eyetracker, valType='str', inputType="choice",
|
|
374
|
+
allowedVals=list(ioDeviceMap) + ["None"],
|
|
375
|
+
hint=_translate("What kind of eye tracker should PsychoPy use? Select 'MouseGaze' to use "
|
|
376
|
+
"the mouse to simulate eye movement (for debugging without a tracker connected)"),
|
|
377
|
+
label=_translate("Eyetracker Device"), categ="Eyetracking"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
#mousegaze
|
|
381
|
+
self.params['mgMove'] = Param(
|
|
382
|
+
mgMove, valType='str', inputType="choice",
|
|
383
|
+
allowedVals=['CONTINUOUS', 'LEFT_BUTTON', 'MIDDLE_BUTTON', 'RIGHT_BUTTON'],
|
|
384
|
+
hint=_translate("Mouse button to press for eye movement."),
|
|
385
|
+
label=_translate("Move Button"), categ="Eyetracking"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
self.params['mgBlink'] = Param(
|
|
389
|
+
mgBlink, valType='list', inputType="multiChoice",
|
|
390
|
+
allowedVals=['LEFT_BUTTON', 'MIDDLE_BUTTON', 'RIGHT_BUTTON'],
|
|
391
|
+
hint=_translate("Mouse button to press for a blink."),
|
|
392
|
+
label=_translate("Blink Button"), categ="Eyetracking"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
self.params['mgSaccade'] = Param(
|
|
396
|
+
mgSaccade, valType='num', inputType="single",
|
|
397
|
+
hint=_translate("Visual degree threshold for Saccade event creation."),
|
|
398
|
+
label=_translate("Saccade Threshold"), categ="Eyetracking"
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# gazepoint
|
|
402
|
+
self.params['gpAddress'] = Param(
|
|
403
|
+
gpAddress, valType='str', inputType="single",
|
|
404
|
+
hint=_translate("IP Address of the computer running GazePoint Control."),
|
|
405
|
+
label=_translate("GazePoint IP Address"), categ="Eyetracking"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
self.params['gpPort'] = Param(
|
|
409
|
+
gpPort, valType='num', inputType="single",
|
|
410
|
+
hint=_translate("Port of the GazePoint Control server. Usually 4242."),
|
|
411
|
+
label=_translate("GazePoint Port"), categ="Eyetracking"
|
|
412
|
+
)
|
|
413
|
+
# eyelink
|
|
414
|
+
self.params['elModel'] = Param(
|
|
415
|
+
elModel, valType='str', inputType="choice",
|
|
416
|
+
allowedVals=['EYELINK 1000 DESKTOP', 'EYELINK 1000 TOWER', 'EYELINK 1000 REMOTE',
|
|
417
|
+
'EYELINK 1000 LONG RANGE'],
|
|
418
|
+
hint=_translate("Eye tracker model."),
|
|
419
|
+
label=_translate("Model Name"), categ="Eyetracking"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
self.params['elSimMode'] = Param(
|
|
423
|
+
elSimMode, valType='bool', inputType="bool",
|
|
424
|
+
hint=_translate("Set the EyeLink to run in mouse simulation mode."),
|
|
425
|
+
label=_translate("Mouse Simulation Mode"), categ="Eyetracking"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
self.params['elSampleRate'] = Param(
|
|
429
|
+
elSampleRate, valType='num', inputType="choice",
|
|
430
|
+
allowedVals=['250', '500', '1000', '2000'],
|
|
431
|
+
hint=_translate("Eye tracker sampling rate."),
|
|
432
|
+
label=_translate("Sampling Rate"), categ="Eyetracking"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
self.params['elTrackEyes'] = Param(
|
|
436
|
+
elTrackEyes, valType='str', inputType="choice",
|
|
437
|
+
allowedVals=['LEFT_EYE', 'RIGHT_EYE', 'BOTH'],
|
|
438
|
+
hint=_translate("Select with eye(s) to track."),
|
|
439
|
+
label=_translate("Track Eyes"), categ="Eyetracking"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
self.params['elLiveFiltering'] = Param(
|
|
443
|
+
elLiveFiltering, valType='str', inputType="choice",
|
|
444
|
+
allowedVals=['FILTER_LEVEL_OFF', 'FILTER_LEVEL_1', 'FILTER_LEVEL_2'],
|
|
445
|
+
hint=_translate("Filter eye sample data live, as it is streamed to the driving device. "
|
|
446
|
+
"This may reduce the sampling speed."),
|
|
447
|
+
label=_translate("Live Sample Filtering"), categ="Eyetracking"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
self.params['elDataFiltering'] = Param(
|
|
451
|
+
elDataFiltering, valType='str', inputType="choice",
|
|
452
|
+
allowedVals=['FILTER_LEVEL_OFF', 'FILTER_LEVEL_1', 'FILTER_LEVEL_2'],
|
|
453
|
+
hint=_translate("Filter eye sample data when it is saved to the output file. This will "
|
|
454
|
+
"not affect the sampling speed."),
|
|
455
|
+
label=_translate("Saved Sample Filtering"), categ="Eyetracking"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
self.params['elTrackingMode'] = Param(
|
|
459
|
+
elTrackingMode, valType='str', inputType="choice",
|
|
460
|
+
allowedVals=['PUPIL_CR_TRACKING', 'PUPIL_ONLY_TRACKING'],
|
|
461
|
+
hint=_translate("Track Pupil-CR or Pupil only."),
|
|
462
|
+
label=_translate("Pupil Tracking Mode"), categ="Eyetracking"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
self.params['elPupilAlgorithm'] = Param(
|
|
466
|
+
elPupilAlgorithm, valType='str', inputType="choice",
|
|
467
|
+
allowedVals=['ELLIPSE_FIT', 'CENTROID_FIT'],
|
|
468
|
+
hint=_translate("Algorithm used to detect the pupil center."),
|
|
469
|
+
label=_translate("Pupil Center Algorithm"), categ="Eyetracking"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
self.params['elPupilMeasure'] = Param(
|
|
473
|
+
elPupilMeasure, valType='str', inputType="choice",
|
|
474
|
+
allowedVals=['PUPIL_AREA', 'PUPIL_DIAMETER', 'NEITHER'],
|
|
475
|
+
hint=_translate("Type of pupil data to record."),
|
|
476
|
+
label=_translate("Pupil Data Type"), categ="Eyetracking"
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
self.params['elAddress'] = Param(
|
|
480
|
+
elAddress, valType='str', inputType="single",
|
|
481
|
+
hint=_translate("IP Address of the EyeLink *Host* computer."),
|
|
482
|
+
label=_translate("EyeLink IP Address"), categ="Eyetracking"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# tobii
|
|
486
|
+
self.params['tbModel'] = Param(
|
|
487
|
+
tbModel, valType='str', inputType="single",
|
|
488
|
+
hint=_translate("Eye tracker model."),
|
|
489
|
+
label=_translate("Model Name"), categ="Eyetracking"
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
self.params['tbLicenseFile'] = Param(
|
|
493
|
+
tbLicenseFile, valType='str', inputType="file",
|
|
494
|
+
hint=_translate("Eye tracker license file (optional)."),
|
|
495
|
+
label=_translate("License File"), categ="Eyetracking"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
self.params['tbSerialNo'] = Param(
|
|
499
|
+
tbSerialNo, valType='str', inputType="single",
|
|
500
|
+
hint=_translate("Eye tracker serial number (optional)."),
|
|
501
|
+
label=_translate("Serial Number"), categ="Eyetracking"
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
self.params['tbSampleRate'] = Param(
|
|
505
|
+
tbSampleRate, valType='num', inputType="single",
|
|
506
|
+
hint=_translate("Eye tracker sampling rate."),
|
|
507
|
+
label=_translate("Sampling Rate"), categ="Eyetracking"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def xml(self):
|
|
512
|
+
# Make root element
|
|
513
|
+
element = Element("Settings")
|
|
514
|
+
# Add an element for each parameter
|
|
515
|
+
for key, param in sorted(self.params.items()):
|
|
516
|
+
if key == 'name':
|
|
517
|
+
continue
|
|
518
|
+
# Create node
|
|
519
|
+
paramNode = param.xml
|
|
520
|
+
paramNode.set("name", key)
|
|
521
|
+
# Add node
|
|
522
|
+
element.append(paramNode)
|
|
523
|
+
return element
|
|
524
|
+
|
|
525
|
+
def getInfo(self):
|
|
526
|
+
"""Rather than converting the value of params['Experiment Info']
|
|
527
|
+
into a dict from a string (which can lead to errors) use this function
|
|
528
|
+
:return: expInfo as a dict
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
infoStr = self.params['Experiment info'].val.strip()
|
|
532
|
+
if len(infoStr) == 0:
|
|
533
|
+
return {}
|
|
534
|
+
try:
|
|
535
|
+
infoDict = ast.literal_eval(infoStr)
|
|
536
|
+
# check for strings of lists: "['male','female']"
|
|
537
|
+
for key in infoDict:
|
|
538
|
+
val = infoDict[key]
|
|
539
|
+
if (hasattr(val, 'startswith')
|
|
540
|
+
and val.startswith('[') and val.endswith(']')):
|
|
541
|
+
try:
|
|
542
|
+
infoDict[key] = ast.literal_eval(val)
|
|
543
|
+
except (ValueError, SyntaxError):
|
|
544
|
+
logging.warning("Tried and failed to parse {!r}"
|
|
545
|
+
"as a list of values."
|
|
546
|
+
.format(val))
|
|
547
|
+
elif val in ['True', 'False']:
|
|
548
|
+
infoDict[key] = ast.literal_eval(val)
|
|
549
|
+
|
|
550
|
+
except (ValueError, SyntaxError):
|
|
551
|
+
"""under Python3 {'participant':'', 'session':02} raises an error because
|
|
552
|
+
ints can't have leading zeros. We will check for those and correct them
|
|
553
|
+
tests = ["{'participant':'', 'session':02}",
|
|
554
|
+
"{'participant':'', 'session':02}",
|
|
555
|
+
"{'participant':'', 'session': 0043}",
|
|
556
|
+
"{'participant':'', 'session':02, 'id':009}",
|
|
557
|
+
]
|
|
558
|
+
"""
|
|
559
|
+
|
|
560
|
+
def entryToString(match):
|
|
561
|
+
entry = match.group(0)
|
|
562
|
+
digits = re.split(r": *", entry)[1]
|
|
563
|
+
return ':{}'.format(repr(digits))
|
|
564
|
+
|
|
565
|
+
# 0 or more spaces, 1-5 zeros, 0 or more digits:
|
|
566
|
+
pattern = re.compile(r": *0{1,5}\d*")
|
|
567
|
+
try:
|
|
568
|
+
infoDict = eval(re.sub(pattern, entryToString, infoStr))
|
|
569
|
+
except SyntaxError: # still a syntax error, possibly caused by user
|
|
570
|
+
msg = ('Builder Expt: syntax error in '
|
|
571
|
+
'"Experiment info" settings (expected a dict)')
|
|
572
|
+
logging.error(msg)
|
|
573
|
+
raise AttributeError(msg)
|
|
574
|
+
return infoDict
|
|
575
|
+
|
|
576
|
+
def getType(self):
|
|
577
|
+
return self.__class__.__name__
|
|
578
|
+
|
|
579
|
+
def getShortType(self):
|
|
580
|
+
return self.getType().replace('Component', '')
|
|
581
|
+
|
|
582
|
+
def getSaveDataDir(self):
|
|
583
|
+
if 'Saved data folder' in self.params:
|
|
584
|
+
# we have a param for the folder (deprecated since 1.80)
|
|
585
|
+
saveToDir = self.params['Saved data folder'].val.strip()
|
|
586
|
+
if not saveToDir: # it was blank so try preferences
|
|
587
|
+
saveToDir = self.exp.prefsBuilder['savedDataFolder'].strip()
|
|
588
|
+
else:
|
|
589
|
+
saveToDir = os.path.dirname(self.params['Data filename'].val)
|
|
590
|
+
return saveToDir or u'data'
|
|
591
|
+
|
|
592
|
+
def writeUseVersion(self, buff):
|
|
593
|
+
if self.params['Use version'].val:
|
|
594
|
+
code = ('\nimport psychopy\n'
|
|
595
|
+
'psychopy.useVersion({})\n\n')
|
|
596
|
+
val = repr(self.params['Use version'].val)
|
|
597
|
+
buff.writeIndentedLines(code.format(val))
|
|
598
|
+
|
|
599
|
+
def writeInitCode(self, buff, version, localDateTime):
|
|
600
|
+
|
|
601
|
+
buff.write(
|
|
602
|
+
'#!/usr/bin/env python\n'
|
|
603
|
+
'# -*- coding: utf-8 -*-\n'
|
|
604
|
+
'"""\nThis experiment was created using PsychoPy3 Experiment '
|
|
605
|
+
'Builder (v%s),\n'
|
|
606
|
+
' on %s\n' % (version, localDateTime) +
|
|
607
|
+
'If you publish work using this script the most relevant '
|
|
608
|
+
'publication is:\n\n'
|
|
609
|
+
u' Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, '
|
|
610
|
+
u'Kastman E, Lindeløv JK. (2019) \n'
|
|
611
|
+
' PsychoPy2: Experiments in behavior made easy Behav Res 51: 195. \n'
|
|
612
|
+
' https://doi.org/10.3758/s13428-018-01193-y\n'
|
|
613
|
+
'\n"""\n'
|
|
614
|
+
"\nfrom __future__ import absolute_import, division\n")
|
|
615
|
+
|
|
616
|
+
self.writeUseVersion(buff)
|
|
617
|
+
|
|
618
|
+
psychopyImports = []
|
|
619
|
+
customImports = []
|
|
620
|
+
for import_ in self.exp.requiredImports:
|
|
621
|
+
if import_.importFrom == 'psychopy':
|
|
622
|
+
psychopyImports.append(import_.importName)
|
|
623
|
+
else:
|
|
624
|
+
customImports.append(import_)
|
|
625
|
+
|
|
626
|
+
buff.writelines(
|
|
627
|
+
"\nfrom psychopy import locale_setup\n"
|
|
628
|
+
"from psychopy import prefs\n"
|
|
629
|
+
)
|
|
630
|
+
# adjust the prefs for this study if needed
|
|
631
|
+
if self.params['Audio lib'].val.lower() != 'use prefs':
|
|
632
|
+
buff.writelines(
|
|
633
|
+
"prefs.hardware['audioLib'] = {}\n".format(self.params['Audio lib'])
|
|
634
|
+
)
|
|
635
|
+
if self.params['Audio latency priority'].val.lower() != 'use prefs':
|
|
636
|
+
buff.writelines(
|
|
637
|
+
"prefs.hardware['audioLatencyMode'] = {}\n".format(self.params['Audio latency priority'])
|
|
638
|
+
)
|
|
639
|
+
buff.write(
|
|
640
|
+
"from psychopy import %s\n" % ', '.join(psychopyImports) +
|
|
641
|
+
"from psychopy.constants import (NOT_STARTED, STARTED, PLAYING,"
|
|
642
|
+
" PAUSED,\n"
|
|
643
|
+
" STOPPED, FINISHED, PRESSED, "
|
|
644
|
+
"RELEASED, FOREVER)\n\n"
|
|
645
|
+
"import numpy as np # whole numpy lib is available, "
|
|
646
|
+
"prepend 'np.'\n"
|
|
647
|
+
"from numpy import (%s,\n" % ', '.join(_numpyImports[:7]) +
|
|
648
|
+
" %s)\n" % ', '.join(_numpyImports[7:]) +
|
|
649
|
+
"from numpy.random import %s\n" % ', '.join(_numpyRandomImports) +
|
|
650
|
+
"import os # handy system and path functions\n" +
|
|
651
|
+
"import sys # to get file system encoding\n"
|
|
652
|
+
"\n")
|
|
653
|
+
|
|
654
|
+
if not self.params['eyetracker'] == "None":
|
|
655
|
+
code = (
|
|
656
|
+
"import psychopy.iohub as io\n"
|
|
657
|
+
)
|
|
658
|
+
buff.writeIndentedLines(code)
|
|
659
|
+
|
|
660
|
+
# Write custom import statements, line by line.
|
|
661
|
+
for import_ in customImports:
|
|
662
|
+
importName = import_.importName
|
|
663
|
+
importFrom = import_.importFrom
|
|
664
|
+
importAs = import_.importAs
|
|
665
|
+
|
|
666
|
+
statement = ''
|
|
667
|
+
if importFrom:
|
|
668
|
+
statement += "from %s " % importFrom
|
|
669
|
+
|
|
670
|
+
statement += "import %s" % importName
|
|
671
|
+
|
|
672
|
+
if importAs:
|
|
673
|
+
statement += " as %s" % importAs
|
|
674
|
+
|
|
675
|
+
statement += "\n"
|
|
676
|
+
buff.write(statement)
|
|
677
|
+
|
|
678
|
+
buff.write("\n")
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def prepareResourcesJS(self):
|
|
682
|
+
"""Sets up the resources folder and writes the info.php file for PsychoJS
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
join = os.path.join
|
|
686
|
+
|
|
687
|
+
def copyTreeWithMD5(src, dst):
|
|
688
|
+
"""Copies the tree but checks SHA for each file first
|
|
689
|
+
"""
|
|
690
|
+
# despite time to check the md5 hashes this func gives speed-up
|
|
691
|
+
# over about 20% over using shutil.rmtree() and copytree()
|
|
692
|
+
for root, subDirs, files in os.walk(src):
|
|
693
|
+
relPath = os.path.relpath(root, src)
|
|
694
|
+
for thisDir in subDirs:
|
|
695
|
+
if not os.path.isdir(join(root, thisDir)):
|
|
696
|
+
os.makedirs(join(root, thisDir))
|
|
697
|
+
for thisFile in files:
|
|
698
|
+
copyFileWithMD5(join(root, thisFile),
|
|
699
|
+
join(dst, relPath, thisFile))
|
|
700
|
+
|
|
701
|
+
def copyFileWithMD5(src, dst):
|
|
702
|
+
"""Copies a file but only if doesn't exist or SHA is diff
|
|
703
|
+
"""
|
|
704
|
+
if os.path.isfile(dst):
|
|
705
|
+
with open(dst, 'rb') as f:
|
|
706
|
+
dstMD5 = hashlib.md5(f.read()).hexdigest()
|
|
707
|
+
with open(src, 'rb') as f:
|
|
708
|
+
srcMD5 = hashlib.md5(f.read()).hexdigest()
|
|
709
|
+
if srcMD5 == dstMD5:
|
|
710
|
+
return # already matches - do nothing
|
|
711
|
+
# if we got here then the file exists but not the same
|
|
712
|
+
# delete and replace. TODO: In future this should check date
|
|
713
|
+
os.remove(dst)
|
|
714
|
+
# either didn't exist or has been deleted
|
|
715
|
+
folder = os.path.split(dst)[0]
|
|
716
|
+
if not os.path.isdir(folder):
|
|
717
|
+
os.makedirs(folder)
|
|
718
|
+
shutil.copy2(src, dst)
|
|
719
|
+
|
|
720
|
+
# write info.php file
|
|
721
|
+
folder = os.path.dirname(self.exp.expPath)
|
|
722
|
+
if not os.path.isdir(folder):
|
|
723
|
+
os.mkdir(folder)
|
|
724
|
+
|
|
725
|
+
# is email a defined parameter for this version
|
|
726
|
+
if 'email' in self.params:
|
|
727
|
+
email = repr(self.params['email'].val)
|
|
728
|
+
else:
|
|
729
|
+
email = "''"
|
|
730
|
+
# populate resources folder
|
|
731
|
+
resFolder = join(folder, 'resources')
|
|
732
|
+
if not os.path.isdir(resFolder):
|
|
733
|
+
os.mkdir(resFolder)
|
|
734
|
+
resourceFiles = self.exp.getResourceFiles()
|
|
735
|
+
|
|
736
|
+
for srcFile in resourceFiles:
|
|
737
|
+
dstAbs = os.path.normpath(join(resFolder, srcFile['rel']))
|
|
738
|
+
dstFolder = os.path.split(dstAbs)[0]
|
|
739
|
+
if not os.path.isdir(dstFolder):
|
|
740
|
+
os.makedirs(dstFolder)
|
|
741
|
+
copyFileWithMD5(srcFile['abs'], dstAbs)
|
|
742
|
+
|
|
743
|
+
def writeInitCodeJS(self, buff, version, localDateTime, modular=True):
|
|
744
|
+
# create resources folder
|
|
745
|
+
if self.exp.htmlFolder:
|
|
746
|
+
self.prepareResourcesJS()
|
|
747
|
+
jsFilename = os.path.basename(os.path.splitext(self.exp.filename)[0])
|
|
748
|
+
|
|
749
|
+
# decide if we need anchored useVersion or leave plain
|
|
750
|
+
useVer = self.params['Use version'].val
|
|
751
|
+
if useVer == '':
|
|
752
|
+
useVer = version
|
|
753
|
+
elif useVer == 'latest':
|
|
754
|
+
useVer = latestVersion()
|
|
755
|
+
|
|
756
|
+
# do we shorten minor versions ('3.4.2' to '3.4')?
|
|
757
|
+
# only from 3.2 onwards
|
|
758
|
+
if (parse_version('3.2')) <= parse_version(useVer) < parse_version('2021') \
|
|
759
|
+
and len(useVer.split('.')) > 2:
|
|
760
|
+
# e.g. 2020.2 not 2021.2.5
|
|
761
|
+
useVer = '.'.join(useVer.split('.')[:2])
|
|
762
|
+
elif len(useVer.split('.')) > 3:
|
|
763
|
+
# e.g. 2021.1.0 not 2021.1.0.dev3
|
|
764
|
+
useVer = '.'.join(useVer.split('.')[:3])
|
|
765
|
+
|
|
766
|
+
# prepend the hyphen
|
|
767
|
+
versionStr = '-{}'.format(useVer)
|
|
768
|
+
|
|
769
|
+
# html header
|
|
770
|
+
template = readTextFile("JS_htmlHeader.tmpl")
|
|
771
|
+
header = template.format(
|
|
772
|
+
name=jsFilename,
|
|
773
|
+
version=versionStr,
|
|
774
|
+
params=self.params)
|
|
775
|
+
jsFile = self.exp.expPath
|
|
776
|
+
folder = os.path.dirname(jsFile)
|
|
777
|
+
if not os.path.isdir(folder):
|
|
778
|
+
os.makedirs(folder)
|
|
779
|
+
with open(os.path.join(folder, "index.html"), 'wb') as html:
|
|
780
|
+
html.write(header.encode())
|
|
781
|
+
html.close()
|
|
782
|
+
|
|
783
|
+
# Write header comment
|
|
784
|
+
starLen = "*"*(len(jsFilename) + 9)
|
|
785
|
+
code = ("/%s \n"
|
|
786
|
+
" * %s Test *\n"
|
|
787
|
+
" %s/\n\n")
|
|
788
|
+
buff.writeIndentedLines(code % (starLen, jsFilename.title(), starLen))
|
|
789
|
+
|
|
790
|
+
# Write imports if modular
|
|
791
|
+
if modular:
|
|
792
|
+
code = (
|
|
793
|
+
"import {{ core, data, sound, util, visual }} from './lib/psychojs-2021.2.0.js';\n"
|
|
794
|
+
"const {{ PsychoJS }} = core;\n"
|
|
795
|
+
"const {{ TrialHandler }} = data;\n"
|
|
796
|
+
"const {{ Scheduler }} = util;\n"
|
|
797
|
+
"//some handy aliases as in the psychopy scripts;\n"
|
|
798
|
+
"const {{ abs, sin, cos, PI: pi, sqrt }} = Math;\n"
|
|
799
|
+
"const {{ round }} = util;\n"
|
|
800
|
+
"\n").format(version=versionStr)
|
|
801
|
+
buff.writeIndentedLines(code)
|
|
802
|
+
|
|
803
|
+
code = ("\n// store info about the experiment session:\n"
|
|
804
|
+
"let expName = '%s'; // from the Builder filename that created this script\n"
|
|
805
|
+
"let expInfo = %s;\n"
|
|
806
|
+
"\n" % (jsFilename, self.getInfo()))
|
|
807
|
+
buff.writeIndentedLines(code)
|
|
808
|
+
|
|
809
|
+
def writeExpSetupCodeJS(self, buff, version):
|
|
810
|
+
|
|
811
|
+
# write the code to set up experiment
|
|
812
|
+
buff.setIndentLevel(0, relative=False)
|
|
813
|
+
template = readTextFile("JS_setupExp.tmpl")
|
|
814
|
+
setRedirectURL = ''
|
|
815
|
+
if len(self.params['Completed URL'].val) or len(self.params['Incomplete URL'].val):
|
|
816
|
+
setRedirectURL = ("psychoJS.setRedirectUrls({completedURL}, {incompleteURL});\n"
|
|
817
|
+
.format(completedURL=self.params['Completed URL'],
|
|
818
|
+
incompleteURL=self.params['Incomplete URL']))
|
|
819
|
+
# check where to save data variables
|
|
820
|
+
# if self.params['OSF Project ID'].val:
|
|
821
|
+
# saveType = "OSF_VIA_EXPERIMENT_SERVER"
|
|
822
|
+
# projID = "'{}'".format(self.params['OSF Project ID'].val)
|
|
823
|
+
# else:
|
|
824
|
+
# saveType = "EXPERIMENT_SERVER"
|
|
825
|
+
# projID = 'undefined'
|
|
826
|
+
code = template.format(
|
|
827
|
+
params=self.params,
|
|
828
|
+
name=self.params['expName'].val,
|
|
829
|
+
loggingLevel=self.params['logging level'].val.upper(),
|
|
830
|
+
setRedirectURL=setRedirectURL,
|
|
831
|
+
version=version,
|
|
832
|
+
)
|
|
833
|
+
buff.writeIndentedLines(code)
|
|
834
|
+
|
|
835
|
+
def writeStartCode(self, buff, version):
|
|
836
|
+
|
|
837
|
+
if not PY3:
|
|
838
|
+
decodingInfo = ".decode(sys.getfilesystemencoding())"
|
|
839
|
+
else:
|
|
840
|
+
decodingInfo = ""
|
|
841
|
+
code = ("# Ensure that relative paths start from the same directory "
|
|
842
|
+
"as this script\n"
|
|
843
|
+
"_thisDir = os.path.dirname(os.path.abspath(__file__))"
|
|
844
|
+
"{decoding}\n"
|
|
845
|
+
"os.chdir(_thisDir)\n\n"
|
|
846
|
+
"# Store info about the experiment session\n"
|
|
847
|
+
"psychopyVersion = '{version}'\n".format(decoding=decodingInfo,
|
|
848
|
+
version=version))
|
|
849
|
+
buff.writeIndentedLines(code)
|
|
850
|
+
|
|
851
|
+
if self.params['expName'].val in [None, '']:
|
|
852
|
+
buff.writeIndented("expName = 'untitled.py'\n")
|
|
853
|
+
else:
|
|
854
|
+
code = ("expName = %s # from the Builder filename that created"
|
|
855
|
+
" this script\n")
|
|
856
|
+
buff.writeIndented(code % self.params['expName'])
|
|
857
|
+
|
|
858
|
+
if PY3: # in Py3 dicts are chrono-sorted
|
|
859
|
+
sorting = "False"
|
|
860
|
+
else: # in Py2, with no natural order, at least be alphabetical
|
|
861
|
+
sorting = "True"
|
|
862
|
+
expInfoDict = self.getInfo()
|
|
863
|
+
buff.writeIndented("expInfo = %s\n" % repr(expInfoDict))
|
|
864
|
+
if self.params['Show info dlg'].val:
|
|
865
|
+
buff.writeIndentedLines(
|
|
866
|
+
f"dlg = gui.DlgFromDict(dictionary=expInfo, "
|
|
867
|
+
f"sortKeys={sorting}, title=expName)\n"
|
|
868
|
+
f"if dlg.OK == False:\n"
|
|
869
|
+
f" core.quit() # user pressed cancel\n"
|
|
870
|
+
)
|
|
871
|
+
buff.writeIndentedLines(
|
|
872
|
+
"expInfo['date'] = data.getDateStr() # add a simple timestamp\n"
|
|
873
|
+
"expInfo['expName'] = expName\n"
|
|
874
|
+
"expInfo['psychopyVersion'] = psychopyVersion\n")
|
|
875
|
+
level = self.params['logging level'].val.upper()
|
|
876
|
+
|
|
877
|
+
saveToDir = self.getSaveDataDir()
|
|
878
|
+
buff.writeIndentedLines("\n# Data file name stem = absolute path +"
|
|
879
|
+
" name; later add .psyexp, .csv, .log, etc\n")
|
|
880
|
+
# deprecated code: before v1.80.00 we had 'Saved data folder' param
|
|
881
|
+
# fairly fixed filename
|
|
882
|
+
if 'Saved data folder' in self.params:
|
|
883
|
+
participantField = ''
|
|
884
|
+
for field in ('participant', 'Participant', 'Subject', 'Observer'):
|
|
885
|
+
if field in expInfoDict:
|
|
886
|
+
participantField = field
|
|
887
|
+
self.params['Data filename'].val = (
|
|
888
|
+
repr(saveToDir) + " + os.sep + '%s_%s' % (expInfo['" +
|
|
889
|
+
field + "'], expInfo['date'])")
|
|
890
|
+
break
|
|
891
|
+
if not participantField:
|
|
892
|
+
# no participant-type field, so skip that part of filename
|
|
893
|
+
self.params['Data filename'].val = repr(
|
|
894
|
+
saveToDir) + " + os.path.sep + expInfo['date']"
|
|
895
|
+
# so that we don't overwrite users changes doing this again
|
|
896
|
+
del self.params['Saved data folder']
|
|
897
|
+
|
|
898
|
+
# now write that data file name to the script
|
|
899
|
+
if not self.params['Data filename'].val: # i.e., the user deleted it
|
|
900
|
+
self.params['Data filename'].val = (
|
|
901
|
+
repr(saveToDir) +
|
|
902
|
+
" + os.sep + u'psychopy_data_' + data.getDateStr()")
|
|
903
|
+
# detect if user wanted an absolute path -- else make absolute:
|
|
904
|
+
filename = self.params['Data filename'].val.lstrip('"\'')
|
|
905
|
+
# (filename.startswith('/') or filename[1] == ':'):
|
|
906
|
+
if filename == os.path.abspath(filename):
|
|
907
|
+
buff.writeIndented("filename = %s\n" %
|
|
908
|
+
self.params['Data filename'])
|
|
909
|
+
else:
|
|
910
|
+
buff.writeIndented("filename = _thisDir + os.sep + %s\n" %
|
|
911
|
+
self.params['Data filename'])
|
|
912
|
+
|
|
913
|
+
# set up the ExperimentHandler
|
|
914
|
+
code = ("\n# An ExperimentHandler isn't essential but helps with "
|
|
915
|
+
"data saving\n"
|
|
916
|
+
"thisExp = data.ExperimentHandler(name=expName, version='',\n"
|
|
917
|
+
" extraInfo=expInfo, runtimeInfo=None,\n"
|
|
918
|
+
" originPath=%s,\n")
|
|
919
|
+
buff.writeIndentedLines(code % repr(self.exp.expPath))
|
|
920
|
+
|
|
921
|
+
code = (" savePickle=%(Save psydat file)s, saveWideText=%(Save "
|
|
922
|
+
"wide csv file)s,\n dataFileName=filename)\n")
|
|
923
|
+
buff.writeIndentedLines(code % self.params)
|
|
924
|
+
|
|
925
|
+
if self.params['Save log file'].val:
|
|
926
|
+
code = ("# save a log file for detail verbose info\nlogFile = "
|
|
927
|
+
"logging.LogFile(filename+'.log', level=logging.%s)\n")
|
|
928
|
+
buff.writeIndentedLines(code % level)
|
|
929
|
+
buff.writeIndented("logging.console.setLevel(logging.WARNING) "
|
|
930
|
+
"# this outputs to the screen, not a file\n")
|
|
931
|
+
|
|
932
|
+
if self.exp.settings.params['Enable Escape'].val:
|
|
933
|
+
buff.writeIndentedLines("\nendExpNow = False # flag for 'escape'"
|
|
934
|
+
" or other condition => quit the exp\n")
|
|
935
|
+
|
|
936
|
+
buff.writeIndented("frameTolerance = 0.001 # how close to onset before 'same' frame\n")
|
|
937
|
+
|
|
938
|
+
def writeIohubCode(self, buff):
|
|
939
|
+
# Substitute inits
|
|
940
|
+
inits = self.params.copy()
|
|
941
|
+
if inits['mgMove'].val == "CONTINUOUS":
|
|
942
|
+
inits['mgMove'].val = "$"
|
|
943
|
+
|
|
944
|
+
# Make ioConfig dict
|
|
945
|
+
code = (
|
|
946
|
+
"\n"
|
|
947
|
+
"# Setup eyetracking\n"
|
|
948
|
+
)
|
|
949
|
+
buff.writeIndentedLines(code % inits)
|
|
950
|
+
if self.params['eyetracker'] == "None":
|
|
951
|
+
code = (
|
|
952
|
+
"ioDevice = ioConfig = ioSession = ioServer = eyetracker = None\n"
|
|
953
|
+
)
|
|
954
|
+
buff.writeIndentedLines(code % inits)
|
|
955
|
+
else:
|
|
956
|
+
# Alert user if window is not fullscreen
|
|
957
|
+
if not self.params['Full-screen window'].val:
|
|
958
|
+
alert(code=4540)
|
|
959
|
+
# Alert user if no monitor config
|
|
960
|
+
if self.params['Monitor'].val in ["", None, "None"]:
|
|
961
|
+
alert(code=4545)
|
|
962
|
+
# Alert user if they need calibration and don't have it
|
|
963
|
+
if self.params['eyetracker'].val != "MouseGaze":
|
|
964
|
+
if not any(isinstance(rt, EyetrackerCalibrationRoutine)
|
|
965
|
+
for rt in self.exp.flow):
|
|
966
|
+
alert(code=4510, strFields={"eyetracker": self.params['eyetracker'].val})
|
|
967
|
+
# Write code
|
|
968
|
+
code = (
|
|
969
|
+
"ioDevice = '" + ioDeviceMap[self.params['eyetracker'].val] + "'\n"
|
|
970
|
+
"ioConfig = {\n"
|
|
971
|
+
)
|
|
972
|
+
buff.writeIndentedLines(code % inits)
|
|
973
|
+
buff.setIndentLevel(1, relative=True)
|
|
974
|
+
code = (
|
|
975
|
+
"ioDevice: {\n"
|
|
976
|
+
)
|
|
977
|
+
buff.writeIndentedLines(code % inits)
|
|
978
|
+
buff.setIndentLevel(1, relative=True)
|
|
979
|
+
code = (
|
|
980
|
+
"'name': 'tracker',\n"
|
|
981
|
+
)
|
|
982
|
+
buff.writeIndentedLines(code % inits)
|
|
983
|
+
# Initialise for MouseGaze
|
|
984
|
+
if self.params['eyetracker'] == "MouseGaze":
|
|
985
|
+
code = (
|
|
986
|
+
"'controls': {\n"
|
|
987
|
+
)
|
|
988
|
+
buff.writeIndentedLines(code % inits)
|
|
989
|
+
buff.setIndentLevel(1, relative=True)
|
|
990
|
+
code = (
|
|
991
|
+
"'move': [%(mgMove)s],\n"
|
|
992
|
+
"'blink':%(mgBlink)s,\n"
|
|
993
|
+
"'saccade_threshold': %(mgSaccade)s,\n"
|
|
994
|
+
)
|
|
995
|
+
buff.writeIndentedLines(code % inits)
|
|
996
|
+
buff.setIndentLevel(-1, relative=True)
|
|
997
|
+
code = (
|
|
998
|
+
"}\n"
|
|
999
|
+
)
|
|
1000
|
+
buff.writeIndentedLines(code % inits)
|
|
1001
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1002
|
+
code = (
|
|
1003
|
+
"}\n"
|
|
1004
|
+
)
|
|
1005
|
+
buff.writeIndentedLines(code % inits)
|
|
1006
|
+
|
|
1007
|
+
elif self.params['eyetracker'] == "GazePoint":
|
|
1008
|
+
code = (
|
|
1009
|
+
"'network_settings': {\n"
|
|
1010
|
+
)
|
|
1011
|
+
buff.writeIndentedLines(code % inits)
|
|
1012
|
+
buff.setIndentLevel(1, relative=True)
|
|
1013
|
+
code = (
|
|
1014
|
+
"'ip_address': %(gpAddress)s,\n"
|
|
1015
|
+
"'port': %(gpPort)s\n"
|
|
1016
|
+
)
|
|
1017
|
+
buff.writeIndentedLines(code % inits)
|
|
1018
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1019
|
+
code = (
|
|
1020
|
+
"}\n"
|
|
1021
|
+
)
|
|
1022
|
+
buff.writeIndentedLines(code % inits)
|
|
1023
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1024
|
+
code = (
|
|
1025
|
+
"}\n"
|
|
1026
|
+
)
|
|
1027
|
+
buff.writeIndentedLines(code % inits)
|
|
1028
|
+
|
|
1029
|
+
elif self.params['eyetracker'] == "Tobii Technology":
|
|
1030
|
+
code = (
|
|
1031
|
+
"'model_name': %(tbModel)s,\n"
|
|
1032
|
+
"'serial_number': %(tbSerialNo)s,\n"
|
|
1033
|
+
"'runtime_settings': {\n"
|
|
1034
|
+
)
|
|
1035
|
+
buff.writeIndentedLines(code % inits)
|
|
1036
|
+
buff.setIndentLevel(1, relative=True)
|
|
1037
|
+
code = (
|
|
1038
|
+
"'sampling_rate': %(tbSampleRate)s,\n"
|
|
1039
|
+
)
|
|
1040
|
+
buff.writeIndentedLines(code % inits)
|
|
1041
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1042
|
+
code = (
|
|
1043
|
+
"}\n"
|
|
1044
|
+
)
|
|
1045
|
+
buff.writeIndentedLines(code % inits)
|
|
1046
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1047
|
+
code = (
|
|
1048
|
+
"}\n"
|
|
1049
|
+
)
|
|
1050
|
+
buff.writeIndentedLines(code % inits)
|
|
1051
|
+
|
|
1052
|
+
elif self.params['eyetracker'] == "SR Research Ltd":
|
|
1053
|
+
code = (
|
|
1054
|
+
"'model_name': %(elModel)s,\n"
|
|
1055
|
+
"'simulation_mode': %(elSimMode)s,\n"
|
|
1056
|
+
"'network_settings': %(elAddress)s,\n"
|
|
1057
|
+
"'default_native_data_file_name': 'EXPFILE',\n"
|
|
1058
|
+
"'runtime_settings': {\n"
|
|
1059
|
+
)
|
|
1060
|
+
buff.writeIndentedLines(code % inits)
|
|
1061
|
+
buff.setIndentLevel(1, relative=True)
|
|
1062
|
+
code = (
|
|
1063
|
+
"'sampling_rate': %(elSampleRate)s,\n"
|
|
1064
|
+
"'track_eyes': %(elTrackEyes)s,\n"
|
|
1065
|
+
"'sample_filtering': {\n"
|
|
1066
|
+
)
|
|
1067
|
+
buff.writeIndentedLines(code % inits)
|
|
1068
|
+
buff.setIndentLevel(1, relative=True)
|
|
1069
|
+
code = (
|
|
1070
|
+
"'sample_filtering': %(elDataFiltering)s,\n"
|
|
1071
|
+
"'elLiveFiltering': %(elLiveFiltering)s,\n"
|
|
1072
|
+
)
|
|
1073
|
+
buff.writeIndentedLines(code % inits)
|
|
1074
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1075
|
+
code = (
|
|
1076
|
+
"},\n"
|
|
1077
|
+
"'vog_settings': {\n"
|
|
1078
|
+
)
|
|
1079
|
+
buff.writeIndentedLines(code % inits)
|
|
1080
|
+
buff.setIndentLevel(1, relative=True)
|
|
1081
|
+
code = (
|
|
1082
|
+
"'pupil_measure_types': %(elPupilMeasure)s,\n"
|
|
1083
|
+
"'tracking_mode': %(elTrackingMode)s,\n"
|
|
1084
|
+
"'pupil_center_algorithm': %(elPupilAlgorithm)s,\n"
|
|
1085
|
+
)
|
|
1086
|
+
buff.writeIndentedLines(code % inits)
|
|
1087
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1088
|
+
code = (
|
|
1089
|
+
"}\n"
|
|
1090
|
+
)
|
|
1091
|
+
buff.writeIndentedLines(code % inits)
|
|
1092
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1093
|
+
code = (
|
|
1094
|
+
"}\n"
|
|
1095
|
+
)
|
|
1096
|
+
buff.writeIndentedLines(code % inits)
|
|
1097
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1098
|
+
code = (
|
|
1099
|
+
"}\n"
|
|
1100
|
+
)
|
|
1101
|
+
buff.writeIndentedLines(code % inits)
|
|
1102
|
+
|
|
1103
|
+
# Close ioConfig dict
|
|
1104
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1105
|
+
code = (
|
|
1106
|
+
"}\n"
|
|
1107
|
+
)
|
|
1108
|
+
buff.writeIndentedLines(code % inits)
|
|
1109
|
+
|
|
1110
|
+
# Start iohub server
|
|
1111
|
+
code = (
|
|
1112
|
+
"ioSession = '1'\n"
|
|
1113
|
+
"if 'session' in expInfo:\n"
|
|
1114
|
+
)
|
|
1115
|
+
buff.writeIndentedLines(code % inits)
|
|
1116
|
+
buff.setIndentLevel(1, relative=True)
|
|
1117
|
+
code = (
|
|
1118
|
+
"ioSession = str(expInfo['session'])\n"
|
|
1119
|
+
)
|
|
1120
|
+
buff.writeIndentedLines(code % inits)
|
|
1121
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1122
|
+
if self.params['Save hdf5 file'].val:
|
|
1123
|
+
saveStr = " experiment_code=%(expName)s, session_code=ioSession, datastore_name=filename,"
|
|
1124
|
+
else:
|
|
1125
|
+
saveStr = ""
|
|
1126
|
+
code = (
|
|
1127
|
+
f"ioServer = io.launchHubServer(window=win,{saveStr} **ioConfig)\n"
|
|
1128
|
+
f"eyetracker = ioServer.getDevice('tracker')\n"
|
|
1129
|
+
)
|
|
1130
|
+
<<<<<<< HEAD
|
|
1131
|
+
buff.writeIndentedLines(code % self.params)
|
|
1132
|
+
|
|
1133
|
+
# Make default keyboard
|
|
1134
|
+
code = (
|
|
1135
|
+
"\n"
|
|
1136
|
+
"# create a default keyboard (e.g. to check for escape)\n"
|
|
1137
|
+
"defaultKeyboard = keyboard.Keyboard()\n"
|
|
1138
|
+
)
|
|
1139
|
+
buff.writeIndentedLines(code % self.params)
|
|
1140
|
+
=======
|
|
1141
|
+
buff.writeIndentedLines(code % inits)
|
|
1142
|
+
# Make default keyboard
|
|
1143
|
+
code = (
|
|
1144
|
+
"\n"
|
|
1145
|
+
"# create a default keyboard (e.g. to check for escape)\n"
|
|
1146
|
+
"defaultKeyboard = keyboard.Keyboard()\n"
|
|
1147
|
+
)
|
|
1148
|
+
buff.writeIndentedLines(code % inits)
|
|
1149
|
+
>>>>>>> todd/_continuousMouseGze
|
|
1150
|
+
|
|
1151
|
+
def writeWindowCode(self, buff):
|
|
1152
|
+
"""Setup the window code.
|
|
1153
|
+
"""
|
|
1154
|
+
buff.writeIndentedLines("\n# Setup the Window\n")
|
|
1155
|
+
# get parameters for the Window
|
|
1156
|
+
fullScr = self.params['Full-screen window'].val
|
|
1157
|
+
# if fullscreen then hide the mouse, unless its requested explicitly
|
|
1158
|
+
allowGUI = (not bool(fullScr)) or bool(self.params['Show mouse'].val)
|
|
1159
|
+
allowStencil = False
|
|
1160
|
+
# NB routines is a dict:
|
|
1161
|
+
for thisRoutine in list(self.exp.routines.values()):
|
|
1162
|
+
# a single routine is a list of components:
|
|
1163
|
+
for thisComp in thisRoutine:
|
|
1164
|
+
if thisComp.type == 'Aperture':
|
|
1165
|
+
allowStencil = True
|
|
1166
|
+
if thisComp.type == 'RatingScale':
|
|
1167
|
+
allowGUI = True # to have a mouse
|
|
1168
|
+
|
|
1169
|
+
requestedScreenNumber = int(self.params['Screen'].val)
|
|
1170
|
+
nScreens = 10
|
|
1171
|
+
# try:
|
|
1172
|
+
# nScreens = wx.Display.GetCount()
|
|
1173
|
+
# except Exception:
|
|
1174
|
+
# # will fail if application hasn't been created (e.g. in test
|
|
1175
|
+
# # environments)
|
|
1176
|
+
# nScreens = 10
|
|
1177
|
+
if requestedScreenNumber > nScreens:
|
|
1178
|
+
logging.warn("Requested screen can't be found. Writing script "
|
|
1179
|
+
"using first available screen.")
|
|
1180
|
+
screenNumber = 0
|
|
1181
|
+
else:
|
|
1182
|
+
# computer has 1 as first screen
|
|
1183
|
+
screenNumber = requestedScreenNumber - 1
|
|
1184
|
+
|
|
1185
|
+
size = self.params['Window size (pixels)']
|
|
1186
|
+
winType = self.exp.prefsGeneral['winType']
|
|
1187
|
+
|
|
1188
|
+
code = ("win = visual.Window(\n size=%s, fullscr=%s, screen=%s, "
|
|
1189
|
+
"\n winType='%s', allowGUI=%s, allowStencil=%s,\n")
|
|
1190
|
+
vals = (size, fullScr, screenNumber, winType, allowGUI, allowStencil)
|
|
1191
|
+
buff.writeIndented(code % vals)
|
|
1192
|
+
|
|
1193
|
+
code = (" monitor=%(Monitor)s, color=%(color)s, "
|
|
1194
|
+
"colorSpace=%(colorSpace)s,\n")
|
|
1195
|
+
if self.params['blendMode'].val:
|
|
1196
|
+
code += " blendMode=%(blendMode)s, useFBO=True, \n"
|
|
1197
|
+
|
|
1198
|
+
if self.params['Units'].val != 'use prefs':
|
|
1199
|
+
code += " units=%(Units)s"
|
|
1200
|
+
code = code.rstrip(', \n') + ')\n'
|
|
1201
|
+
buff.writeIndentedLines(code % self.params)
|
|
1202
|
+
|
|
1203
|
+
# Import here to avoid circular dependency!
|
|
1204
|
+
from psychopy.experiment._experiment import RequiredImport
|
|
1205
|
+
microphoneImport = RequiredImport(importName='microphone',
|
|
1206
|
+
importFrom='psychopy',
|
|
1207
|
+
importAs='')
|
|
1208
|
+
if microphoneImport in self.exp.requiredImports: # need a pyo Server
|
|
1209
|
+
buff.writeIndentedLines("\n# Enable sound input/output:\n"
|
|
1210
|
+
"microphone.switchOn()\n")
|
|
1211
|
+
|
|
1212
|
+
code = ("# store frame rate of monitor if we can measure it\n"
|
|
1213
|
+
"expInfo['frameRate'] = win.getActualFrameRate()\n"
|
|
1214
|
+
"if expInfo['frameRate'] != None:\n"
|
|
1215
|
+
" frameDur = 1.0 / round(expInfo['frameRate'])\n"
|
|
1216
|
+
"else:\n"
|
|
1217
|
+
" frameDur = 1.0 / 60.0 # could not measure, so guess\n")
|
|
1218
|
+
buff.writeIndentedLines(code)
|
|
1219
|
+
|
|
1220
|
+
def writeWindowCodeJS(self, buff):
|
|
1221
|
+
"""Setup the JS window code.
|
|
1222
|
+
"""
|
|
1223
|
+
# Replace instances of 'use prefs'
|
|
1224
|
+
units = self.params['Units'].val
|
|
1225
|
+
if units == 'use prefs':
|
|
1226
|
+
units = 'height'
|
|
1227
|
+
|
|
1228
|
+
code = ("// init psychoJS:\n"
|
|
1229
|
+
"const psychoJS = new PsychoJS({{\n"
|
|
1230
|
+
" debug: true\n"
|
|
1231
|
+
"}});\n\n"
|
|
1232
|
+
"// open window:\n"
|
|
1233
|
+
"psychoJS.openWindow({{\n"
|
|
1234
|
+
" fullscr: {fullScr},\n"
|
|
1235
|
+
" color: new util.Color({params[color]}),\n"
|
|
1236
|
+
" units: '{units}',\n"
|
|
1237
|
+
" waitBlanking: true\n"
|
|
1238
|
+
"}});\n").format(fullScr=str(self.params['Full-screen window']).lower(),
|
|
1239
|
+
params=self.params,
|
|
1240
|
+
units=units)
|
|
1241
|
+
buff.writeIndentedLines(code)
|
|
1242
|
+
|
|
1243
|
+
def writeEndCode(self, buff):
|
|
1244
|
+
"""Write code for end of experiment (e.g. close log file).
|
|
1245
|
+
"""
|
|
1246
|
+
code = ('\n# Flip one final time so any remaining win.callOnFlip() \n'
|
|
1247
|
+
'# and win.timeOnFlip() tasks get executed before quitting\n'
|
|
1248
|
+
'win.flip()\n\n')
|
|
1249
|
+
buff.writeIndentedLines(code)
|
|
1250
|
+
|
|
1251
|
+
buff.writeIndented("# these shouldn't be strictly necessary "
|
|
1252
|
+
"(should auto-save)\n")
|
|
1253
|
+
if self.params['Save wide csv file'].val:
|
|
1254
|
+
buff.writeIndented("thisExp.saveAsWideText(filename+'.csv', "
|
|
1255
|
+
"delim={})\n".format(self.params['Data file delimiter']))
|
|
1256
|
+
if self.params['Save psydat file'].val:
|
|
1257
|
+
buff.writeIndented("thisExp.saveAsPickle(filename)\n")
|
|
1258
|
+
if self.params['Save log file'].val:
|
|
1259
|
+
buff.writeIndented("logging.flush()\n")
|
|
1260
|
+
code = ("# make sure everything is closed down\n"
|
|
1261
|
+
"thisExp.abort() # or data files will save again on exit\n"
|
|
1262
|
+
"win.close()\n"
|
|
1263
|
+
"core.quit()\n")
|
|
1264
|
+
buff.writeIndentedLines(code)
|
|
1265
|
+
|
|
1266
|
+
def writeEndCodeJS(self, buff):
|
|
1267
|
+
endLoopInteration = ("\nfunction endLoopIteration(scheduler, snapshot) {\n"
|
|
1268
|
+
" // ------Prepare for next entry------\n"
|
|
1269
|
+
" return async function () {\n"
|
|
1270
|
+
" if (typeof snapshot !== 'undefined') {\n"
|
|
1271
|
+
" // ------Check if user ended loop early------\n"
|
|
1272
|
+
" if (snapshot.finished) {\n"
|
|
1273
|
+
" // Check for and save orphaned data\n"
|
|
1274
|
+
" if (psychoJS.experiment.isEntryEmpty()) {\n"
|
|
1275
|
+
" psychoJS.experiment.nextEntry(snapshot);\n"
|
|
1276
|
+
" }\n"
|
|
1277
|
+
" scheduler.stop();\n"
|
|
1278
|
+
" } else {\n"
|
|
1279
|
+
" const thisTrial = snapshot.getCurrentTrial();\n"
|
|
1280
|
+
" if (typeof thisTrial === 'undefined' || !('isTrials' in thisTrial) || thisTrial.isTrials) {\n"
|
|
1281
|
+
" psychoJS.experiment.nextEntry(snapshot);\n"
|
|
1282
|
+
" }\n"
|
|
1283
|
+
" }\n"
|
|
1284
|
+
" return Scheduler.Event.NEXT;\n"
|
|
1285
|
+
" }\n"
|
|
1286
|
+
" };\n"
|
|
1287
|
+
"}\n")
|
|
1288
|
+
buff.writeIndentedLines(endLoopInteration)
|
|
1289
|
+
|
|
1290
|
+
recordLoopIterationFunc = ("\nfunction importConditions(currentLoop) {\n"
|
|
1291
|
+
" return async function () {\n"
|
|
1292
|
+
" psychoJS.importAttributes(currentLoop.getCurrentTrial());\n"
|
|
1293
|
+
" return Scheduler.Event.NEXT;\n"
|
|
1294
|
+
" };\n"
|
|
1295
|
+
"}\n")
|
|
1296
|
+
buff.writeIndentedLines(recordLoopIterationFunc)
|
|
1297
|
+
|
|
1298
|
+
code = ("\nasync function quitPsychoJS(message, isCompleted) {\n")
|
|
1299
|
+
buff.writeIndented(code)
|
|
1300
|
+
buff.setIndentLevel(1, relative=True)
|
|
1301
|
+
code = ("// Check for and save orphaned data\n"
|
|
1302
|
+
"if (psychoJS.experiment.isEntryEmpty()) {\n"
|
|
1303
|
+
" psychoJS.experiment.nextEntry();\n"
|
|
1304
|
+
"}\n")
|
|
1305
|
+
buff.writeIndentedLines(code)
|
|
1306
|
+
|
|
1307
|
+
# Write End Experiment code component
|
|
1308
|
+
for thisRoutine in list(self.exp.routines.values()):
|
|
1309
|
+
# a single routine is a list of components:
|
|
1310
|
+
for thisComp in thisRoutine:
|
|
1311
|
+
if thisComp.type == 'Code':
|
|
1312
|
+
buff.writeIndented("\n")
|
|
1313
|
+
thisComp.writeExperimentEndCodeJS(buff)
|
|
1314
|
+
buff.writeIndented("\n")
|
|
1315
|
+
|
|
1316
|
+
code = ("psychoJS.window.close();\n"
|
|
1317
|
+
"psychoJS.quit({message: message, isCompleted: isCompleted});\n\n"
|
|
1318
|
+
"return Scheduler.Event.QUIT;\n")
|
|
1319
|
+
buff.writeIndentedLines(code)
|
|
1320
|
+
|
|
1321
|
+
buff.setIndentLevel(-1, relative=True)
|
|
1322
|
+
buff.writeIndented("}\n")
|
|
1323
|
+
buff.setIndentLevel(-1)
|
|
1324
|
+
|
|
1325
|
+
@property
|
|
1326
|
+
def monitor(self):
|
|
1327
|
+
"""Stores a monitor object for the experiment so that it
|
|
1328
|
+
doesn't have to be fetched from disk repeatedly"""
|
|
1329
|
+
# remember to set _monitor to None periodically (start of script build?)
|
|
1330
|
+
# so that we do reload occasionally
|
|
1331
|
+
if not self._monitor:
|
|
1332
|
+
self._monitor = Monitor(self.params['Monitor'].val)
|
|
1333
|
+
return self._monitor
|
|
1334
|
+
|
|
1335
|
+
@monitor.setter
|
|
1336
|
+
def monitor(self, monitor):
|
|
1337
|
+
self._monitor = monitor
|