psychopy 2024.1.1__py3-none-any.whl → 2024.1.3__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/__init__.py +2 -2
- psychopy/alerts/_alerts.py +6 -2
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -0
- psychopy/app/builder/dialogs/paramCtrls.py +4 -2
- psychopy/app/builder/localizedStrings.py +16 -4
- 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_ES/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/ja_JP/LC_MESSAGE/messages.po +3548 -5422
- 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/app/plugin_manager/plugins.py +1 -1
- psychopy/app/preferencesDlg.py +2 -1
- psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +552 -550
- psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +572 -570
- psychopy/demos/coder/timing/timeByFrames.py +7 -1
- psychopy/experiment/components/_base.py +122 -0
- psychopy/experiment/components/aperture/__init__.py +6 -2
- psychopy/experiment/components/brush/__init__.py +3 -1
- psychopy/experiment/components/button/__init__.py +6 -2
- psychopy/experiment/components/buttonBox/__init__.py +2 -1
- psychopy/experiment/components/camera/__init__.py +13 -0
- psychopy/experiment/components/code/__init__.py +34 -1
- psychopy/experiment/components/form/formItems.xltx +0 -0
- psychopy/experiment/components/mouse/__init__.py +12 -0
- psychopy/experiment/components/polygon/__init__.py +14 -3
- psychopy/experiment/components/settings/__init__.py +4 -1
- psychopy/experiment/components/sound/__init__.py +1 -1
- psychopy/experiment/components/text/__init__.py +1 -1
- psychopy/experiment/params.py +17 -0
- psychopy/experiment/routines/_base.py +122 -0
- psychopy/experiment/routines/counterbalance/__init__.py +1 -1
- psychopy/hardware/button.py +4 -3
- psychopy/hardware/camera/__init__.py +6 -0
- psychopy/hardware/keyboard.py +8 -3
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +1 -1
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +1 -1
- psychopy/localization/generateTranslationTemplate.py +45 -19
- psychopy/localization/messages.pot +5049 -3418
- psychopy/preferences/Darwin.spec +2 -0
- psychopy/preferences/FreeBSD.spec +2 -0
- psychopy/preferences/Linux.spec +2 -0
- psychopy/preferences/Windows.spec +2 -0
- psychopy/preferences/baseNoArch.spec +2 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/hints.py +118 -97
- psychopy/tests/test_experiment/test_components/__init__.py +1 -1
- psychopy/tests/test_experiment/test_components/{test_ButtonBox.py → test_ButtonBoxComponent.py} +9 -27
- psychopy/tests/test_experiment/test_components/{test_Code.py → test_CodeComponent.py} +8 -20
- psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
- psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
- psychopy/tests/test_experiment/test_components/{test_Mouse.py → test_MouseComponent.py} +44 -57
- psychopy/tests/test_experiment/test_components/{test_Polygon.py → test_PolygonComponent.py} +9 -25
- psychopy/tests/test_experiment/test_components/{test_ResourceManager.py → test_ResourceManagerComponent.py} +3 -13
- psychopy/tests/test_experiment/test_components/{test_Settings.py → test_SettingsComponent.py} +1 -3
- psychopy/tests/test_experiment/test_components/{test_Static.py → test_StaticComponent.py} +3 -12
- psychopy/tests/test_experiment/test_components/test_all_components.py +8 -66
- psychopy/tests/test_experiment/test_components/test_base_components.py +212 -125
- psychopy/tools/fontmanager.py +1 -1
- psychopy/tools/versionchooser.py +1 -1
- psychopy/visual/noise.py +9 -0
- psychopy/visual/radial.py +8 -0
- psychopy/visual/secondorder.py +9 -0
- psychopy/visual/textbox2/textbox2.py +19 -21
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/METADATA +1 -1
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/RECORD +95 -66
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/WHEEL +1 -1
- psychopy/tests/test_experiment/test_components/test_Image.py +0 -24
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,8 +15,14 @@ from psychopy import visual, logging, core, event
|
|
|
15
15
|
visual.useFBO = True # if available (try without for comparison)
|
|
16
16
|
|
|
17
17
|
import matplotlib
|
|
18
|
-
matplotlib.use('QtAgg') # change this to control the plotting 'back end'
|
|
19
18
|
import pylab
|
|
19
|
+
import sys
|
|
20
|
+
if sys.platform == "darwin":
|
|
21
|
+
# on Mac...
|
|
22
|
+
matplotlib.use('QtAgg')
|
|
23
|
+
else:
|
|
24
|
+
# on any other OS...
|
|
25
|
+
matplotlib.use('Qt4Agg')
|
|
20
26
|
|
|
21
27
|
nIntervals = 500
|
|
22
28
|
win = visual.Window([1280, 1024], fullscr=True, allowGUI=False, waitBlanking=True)
|
|
@@ -7,6 +7,7 @@ Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
|
|
|
7
7
|
Distributed under the terms of the GNU General Public License (GPL).
|
|
8
8
|
"""
|
|
9
9
|
import copy
|
|
10
|
+
import textwrap
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from xml.etree.ElementTree import Element
|
|
12
13
|
|
|
@@ -35,6 +36,8 @@ class BaseComponent:
|
|
|
35
36
|
tooltip = ""
|
|
36
37
|
# what version was this Component added in?
|
|
37
38
|
version = "0.0.0"
|
|
39
|
+
# is it still in beta?
|
|
40
|
+
beta = False
|
|
38
41
|
|
|
39
42
|
def __init__(self, exp, parentName, name='',
|
|
40
43
|
startType='time (s)', startVal='',
|
|
@@ -1035,6 +1038,125 @@ class BaseComponent:
|
|
|
1035
1038
|
# if validation code indented the buffer, dedent
|
|
1036
1039
|
buff.setIndentLevel(-indent, relative=True)
|
|
1037
1040
|
|
|
1041
|
+
def getFullDocumentation(self, fmt="rst"):
|
|
1042
|
+
"""
|
|
1043
|
+
Automatically generate documentation for this Component. We recommend using this as a
|
|
1044
|
+
starting point, but checking the documentation yourself afterwards and adding any more
|
|
1045
|
+
detail you'd like to include (e.g. usage examples)
|
|
1046
|
+
|
|
1047
|
+
Parameters
|
|
1048
|
+
----------
|
|
1049
|
+
fmt : str
|
|
1050
|
+
Format to write documentation in. One of:
|
|
1051
|
+
- "rst": Restructured text (numpy style)
|
|
1052
|
+
-"md": Markdown (mkdocs style)
|
|
1053
|
+
"""
|
|
1054
|
+
|
|
1055
|
+
# make sure format is correct
|
|
1056
|
+
assert fmt in ("md", "rst"), (
|
|
1057
|
+
f"Unrecognised format {fmt}, allowed formats are 'md' and 'rst'."
|
|
1058
|
+
)
|
|
1059
|
+
# define templates for md and rst
|
|
1060
|
+
h1 = {
|
|
1061
|
+
'md': "# %s",
|
|
1062
|
+
'rst': (
|
|
1063
|
+
"-------------------------------\n"
|
|
1064
|
+
"%s\n"
|
|
1065
|
+
"-------------------------------"
|
|
1066
|
+
)
|
|
1067
|
+
}[fmt]
|
|
1068
|
+
h2 = {
|
|
1069
|
+
'md': "## %s",
|
|
1070
|
+
'rst': (
|
|
1071
|
+
"%s\n"
|
|
1072
|
+
"-------------------------------"
|
|
1073
|
+
)
|
|
1074
|
+
}[fmt]
|
|
1075
|
+
h3 = {
|
|
1076
|
+
'md': "### %s",
|
|
1077
|
+
'rst': (
|
|
1078
|
+
"%s\n"
|
|
1079
|
+
"==============================="
|
|
1080
|
+
)
|
|
1081
|
+
}[fmt]
|
|
1082
|
+
h4 = {
|
|
1083
|
+
'md': "#### `%s`",
|
|
1084
|
+
'rst': "%s"
|
|
1085
|
+
}[fmt]
|
|
1086
|
+
|
|
1087
|
+
# start off with nothing
|
|
1088
|
+
content = ""
|
|
1089
|
+
# header and class docstring
|
|
1090
|
+
content += (
|
|
1091
|
+
f"{h1 % type(self).__name__}\n"
|
|
1092
|
+
f"{textwrap.dedent(self.__doc__ or '')}\n"
|
|
1093
|
+
f"\n"
|
|
1094
|
+
)
|
|
1095
|
+
# attributes
|
|
1096
|
+
content += (
|
|
1097
|
+
f"{h4 % 'Categories:'}\n"
|
|
1098
|
+
f" {', '.join(self.categories)}\n"
|
|
1099
|
+
f"{h4 % 'Works in:'}\n"
|
|
1100
|
+
f" {', '.join(self.targets)}\n"
|
|
1101
|
+
f"\n"
|
|
1102
|
+
)
|
|
1103
|
+
# beta warning
|
|
1104
|
+
if self.beta:
|
|
1105
|
+
content += (
|
|
1106
|
+
f"**Note: Since this is still in beta, keep an eye out for bug fixes.**\n"
|
|
1107
|
+
f"\n"
|
|
1108
|
+
)
|
|
1109
|
+
# params heading
|
|
1110
|
+
content += (
|
|
1111
|
+
f"{h2 % 'Parameters'}\n"
|
|
1112
|
+
f"\n"
|
|
1113
|
+
)
|
|
1114
|
+
# sort params by category
|
|
1115
|
+
byCateg = {}
|
|
1116
|
+
for param in self.params.values():
|
|
1117
|
+
if param.categ not in byCateg:
|
|
1118
|
+
byCateg[param.categ] = []
|
|
1119
|
+
byCateg[param.categ].append(param)
|
|
1120
|
+
# iterate through categs
|
|
1121
|
+
for categ, params in byCateg.items():
|
|
1122
|
+
# write a heading for each categ
|
|
1123
|
+
content += (
|
|
1124
|
+
f"{h3 % categ}\n"
|
|
1125
|
+
f"\n"
|
|
1126
|
+
)
|
|
1127
|
+
# add each param...
|
|
1128
|
+
for param in params:
|
|
1129
|
+
# write basics (heading and description)
|
|
1130
|
+
content += (
|
|
1131
|
+
f"{h4 % param.label}\n"
|
|
1132
|
+
f" {param.hint}\n"
|
|
1133
|
+
)
|
|
1134
|
+
# if there are options, display them
|
|
1135
|
+
if bool(param.allowedVals) or bool(param.allowedLabels):
|
|
1136
|
+
# if no allowed labels, use allowed vals
|
|
1137
|
+
options = param.allowedLabels or param.allowedVals
|
|
1138
|
+
# handle callable methods
|
|
1139
|
+
if callable(options):
|
|
1140
|
+
content += (
|
|
1141
|
+
f"\n"
|
|
1142
|
+
f" Options are generated live, so will vary according to your setup.\n"
|
|
1143
|
+
)
|
|
1144
|
+
else:
|
|
1145
|
+
# write heading
|
|
1146
|
+
content += (
|
|
1147
|
+
f" \n"
|
|
1148
|
+
f" Options:\n"
|
|
1149
|
+
)
|
|
1150
|
+
# add list item for each option
|
|
1151
|
+
for opt in options:
|
|
1152
|
+
content += (
|
|
1153
|
+
f" - {opt}\n"
|
|
1154
|
+
)
|
|
1155
|
+
# add newline at the end
|
|
1156
|
+
content += "\n"
|
|
1157
|
+
|
|
1158
|
+
return content
|
|
1159
|
+
|
|
1038
1160
|
@property
|
|
1039
1161
|
def name(self):
|
|
1040
1162
|
return self.params['name'].val
|
|
@@ -17,8 +17,12 @@ __author__ = 'Jeremy Gray, Jon Peirce'
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ApertureComponent(PolygonComponent):
|
|
20
|
-
"""
|
|
21
|
-
|
|
20
|
+
"""
|
|
21
|
+
This component can be used to filter the visual display, as if the subject is looking at it
|
|
22
|
+
through an opening (i.e. add an image component, as the background image, then add an aperture
|
|
23
|
+
to show part of the image). Only one aperture is enabled at a time; you can't "double up": a
|
|
24
|
+
second aperture takes precedence.
|
|
25
|
+
"""
|
|
22
26
|
|
|
23
27
|
categories = ['Stimuli']
|
|
24
28
|
targets = ['PsychoPy']
|
|
@@ -10,7 +10,9 @@ from psychopy.experiment.components import BaseVisualComponent, Param, getInitVa
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class BrushComponent(BaseVisualComponent):
|
|
13
|
-
"""
|
|
13
|
+
"""
|
|
14
|
+
This component is a freehand drawing tool.
|
|
15
|
+
"""
|
|
14
16
|
|
|
15
17
|
categories = ['Responses']
|
|
16
18
|
targets = ['PsychoPy', 'PsychoJS']
|
|
@@ -15,7 +15,12 @@ from psychopy.experiment.py2js_transpiler import translatePythonToJavaScript
|
|
|
15
15
|
|
|
16
16
|
class ButtonComponent(BaseVisualComponent):
|
|
17
17
|
"""
|
|
18
|
-
|
|
18
|
+
This component allows you to show a static textbox which ends the routine and/or triggers
|
|
19
|
+
a "callback" (some custom code) when pressed. The nice thing about the button component is
|
|
20
|
+
that you can allow mouse/touch responses with a single component instead of needing 3 separate
|
|
21
|
+
components i.e. a textbox component (to display as a "clickable" thing), a mouse component
|
|
22
|
+
(to click the textbox) and a code component (not essential, but for example to check if a
|
|
23
|
+
clicked response was correct or incorrect).
|
|
19
24
|
"""
|
|
20
25
|
categories = ['Responses']
|
|
21
26
|
targets = ['PsychoPy', 'PsychoJS']
|
|
@@ -365,7 +370,6 @@ class ButtonComponent(BaseVisualComponent):
|
|
|
365
370
|
code = (
|
|
366
371
|
"// store time of first click\n"
|
|
367
372
|
"%(name)s.timesOn.push(%(name)s.clock.getTime());\n"
|
|
368
|
-
"%(name)s.numClicks += 1;\n"
|
|
369
373
|
"// store time clicked until\n"
|
|
370
374
|
"%(name)s.timesOff.push(%(name)s.clock.getTime());\n"
|
|
371
375
|
)
|
|
@@ -6,12 +6,13 @@ from psychopy.localization import _translate
|
|
|
6
6
|
|
|
7
7
|
class ButtonBoxComponent(BaseDeviceComponent, PluginDevicesMixin):
|
|
8
8
|
"""
|
|
9
|
-
|
|
9
|
+
Component for getting button presses from a button box device.
|
|
10
10
|
"""
|
|
11
11
|
categories = ['Responses'] # which section(s) in the components panel
|
|
12
12
|
targets = ['PsychoPy']
|
|
13
13
|
iconFile = Path(__file__).parent / 'buttonBox.png'
|
|
14
14
|
tooltip = _translate('Button Box: Get input from a button box')
|
|
15
|
+
beta = True
|
|
15
16
|
|
|
16
17
|
def __init__(
|
|
17
18
|
self, exp, parentName,
|
|
@@ -27,6 +27,19 @@ micSampleRates = {r[1]: r[0] for r in sampleRateQualityLevels.values()}
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class CameraComponent(BaseDeviceComponent):
|
|
30
|
+
"""
|
|
31
|
+
This component provides a way to use the webcam to record participants during an experiment.
|
|
32
|
+
|
|
33
|
+
**Note: For online experiments, the browser will notify participants to allow use of webcam before the start of the task.**
|
|
34
|
+
|
|
35
|
+
When recording via webcam, specify the starting time relative to the start of the routine (see `start` below) and a stop time (= duration in seconds).
|
|
36
|
+
A blank duration evaluates to recording for 0.000s.
|
|
37
|
+
|
|
38
|
+
The resulting video files are saved in .mp4 format if recorded locally and saved in .webm if recorded online. There will be one file per recording. The files appear in a new folder within the data directory in a folder called data_cam_recorded. The file names include the unix (epoch) time of the onset of the recording with milliseconds, e.g., `recording_cam_2022-06-16_14h32.42.064.mp4`.
|
|
39
|
+
|
|
40
|
+
**Note: For online experiments, the recordings can only be downloaded from the "Download results" button from the study's Pavlovia page.**
|
|
41
|
+
"""
|
|
42
|
+
|
|
30
43
|
categories = ['Responses']
|
|
31
44
|
targets = ["PsychoPy", "PsychoJS"]
|
|
32
45
|
version = "2022.2.0"
|
|
@@ -12,7 +12,40 @@ from psychopy.alerts import alerttools
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class CodeComponent(BaseComponent):
|
|
15
|
-
"""
|
|
15
|
+
"""
|
|
16
|
+
This Component can be used to insert short pieces of python code into your experiments. This
|
|
17
|
+
might be create a variable that you want for another :ref:`Component <components>`,
|
|
18
|
+
to manipulate images before displaying them, to interact with hardware for which there isn't
|
|
19
|
+
yet a pre-packaged component in |PsychoPy| (e.g. writing code to interact with the
|
|
20
|
+
serial/parallel ports). See `code uses`_ below.
|
|
21
|
+
|
|
22
|
+
Be aware that the code for each of the components in your :ref:`Routine <routines>` are
|
|
23
|
+
executed in the order they appear on the :ref:`Routine <routines>` display (from top to
|
|
24
|
+
bottom). If you want your `Code Component` to alter a variable to be used by another
|
|
25
|
+
component immediately, then it needs to be above that component in the view. You may want the
|
|
26
|
+
code not to take effect until next frame however, in which case put it at the bottom of the
|
|
27
|
+
:ref:`Routine <routines>`. You can move `Components` up and down the :ref:`Routine <routines>`
|
|
28
|
+
by right-clicking on their icons.
|
|
29
|
+
|
|
30
|
+
Within your code you can use other variables and modules from the script. For example, all
|
|
31
|
+
routines have a stopwatch-style :class:`~psychopy.core.Clock` associated with them, which
|
|
32
|
+
gets reset at the beginning of that repeat of the routine. So if you have a :ref:`Routine
|
|
33
|
+
<routines>` called trial, there will be a :class:`~psychopy.core.Clock` called trialClock and
|
|
34
|
+
so you can get the time (in sec) from the beginning of the trial by using::
|
|
35
|
+
|
|
36
|
+
currentT = trialClock.getTime()
|
|
37
|
+
|
|
38
|
+
To see what other variables you might want to use, and also what terms you need to avoid in
|
|
39
|
+
your chunks of code, :ref:`compile your script <compileScript>` before inserting the code
|
|
40
|
+
object and take a look at the contents of that script.
|
|
41
|
+
|
|
42
|
+
Note that this page is concerned with `Code Components` specifically, and not all cases in
|
|
43
|
+
which you might use python syntax within the Builder. It is also possible to put code into
|
|
44
|
+
a non-code input field (such as the duration or text of a `Text Component`). The syntax there
|
|
45
|
+
is slightly different (requiring a `$` to trigger the special handling, or `\\$` to avoid
|
|
46
|
+
triggering special handling). The syntax to use within a Code Component is always regular
|
|
47
|
+
python syntax.
|
|
48
|
+
"""
|
|
16
49
|
|
|
17
50
|
categories = ['Custom']
|
|
18
51
|
targets = ['PsychoPy', 'PsychoJS']
|
|
Binary file
|
|
@@ -364,10 +364,22 @@ class MouseComponent(BaseComponent):
|
|
|
364
364
|
buff.writeIndentedLines(code % self.params)
|
|
365
365
|
buff.setIndentLevel(1, relative=True)
|
|
366
366
|
dedent += 1
|
|
367
|
+
# keep track of whether something's been written
|
|
368
|
+
hasContent = False
|
|
369
|
+
# write code to check clickable stim, if there are any
|
|
367
370
|
if self.params['clickable'].val:
|
|
368
371
|
self._writeClickableObjectsCode(buff)
|
|
372
|
+
hasContent = True
|
|
373
|
+
# write code to check correct stim, if there are any
|
|
369
374
|
if self.params['storeCorrect']:
|
|
370
375
|
self._writeCorrectAnsCode(buff)
|
|
376
|
+
hasContent = True
|
|
377
|
+
# if current if statement has no content, add a pass
|
|
378
|
+
if not hasContent:
|
|
379
|
+
buff.writeIndentedLines(
|
|
380
|
+
"pass"
|
|
381
|
+
)
|
|
382
|
+
|
|
371
383
|
return buff, dedent
|
|
372
384
|
|
|
373
385
|
# No mouse tracking, end routine on any or valid click
|
|
@@ -279,9 +279,20 @@ class PolygonComponent(BaseVisualComponent):
|
|
|
279
279
|
code += (" ori: {ori}, pos: {pos},\n"
|
|
280
280
|
" anchor: {anchor},\n"
|
|
281
281
|
" lineWidth: {lineWidth}, \n"
|
|
282
|
-
" colorSpace: {colorSpace},\n"
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
" colorSpace: {colorSpace},\n")
|
|
283
|
+
|
|
284
|
+
if inits['lineColor'] == 'undefined':
|
|
285
|
+
code += " lineColor: {lineColor},\n"
|
|
286
|
+
else:
|
|
287
|
+
code += " lineColor: new util.Color({lineColor}),\n"
|
|
288
|
+
|
|
289
|
+
if inits['fillColor'] == 'undefined':
|
|
290
|
+
code += " fillColor: {fillColor},\n"
|
|
291
|
+
else:
|
|
292
|
+
code += " fillColor: new util.Color({fillColor}),\n"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
code += ( " fillColor: {fillColor},\n"
|
|
285
296
|
" opacity: {opacity}, depth: {depth}, interpolate: {interpolate},\n"
|
|
286
297
|
"}});\n\n")
|
|
287
298
|
|
|
@@ -993,12 +993,15 @@ class SettingsComponent:
|
|
|
993
993
|
"PILOTING = core.setPilotModeFromArgs()\n"
|
|
994
994
|
"# start off with values from experiment settings\n"
|
|
995
995
|
"_fullScr = %(Full-screen window)s\n"
|
|
996
|
+
"_winSize = %(Window size (pixels))s\n"
|
|
996
997
|
"_loggingLevel = logging.getLevel('%(logging level)s')\n"
|
|
997
998
|
"# if in pilot mode, apply overrides according to preferences\n"
|
|
998
999
|
"if PILOTING:\n"
|
|
999
1000
|
" # force windowed mode\n"
|
|
1000
1001
|
" if prefs.piloting['forceWindowed']:\n"
|
|
1001
1002
|
" _fullScr = False\n"
|
|
1003
|
+
" # set window size\n"
|
|
1004
|
+
" _winSize = prefs.piloting['forcedWindowSize']\n"
|
|
1002
1005
|
" # override logging level\n"
|
|
1003
1006
|
" _loggingLevel = logging.getLevel(\n"
|
|
1004
1007
|
" prefs.piloting['pilotLoggingLevel']\n"
|
|
@@ -1796,7 +1799,7 @@ class SettingsComponent:
|
|
|
1796
1799
|
"if win is None:\n"
|
|
1797
1800
|
" # if not given a window to setup, make one\n"
|
|
1798
1801
|
" win = visual.Window(\n"
|
|
1799
|
-
" size
|
|
1802
|
+
" size=_winSize, fullscr=_fullScr, screen=%(screenNumber)s,\n"
|
|
1800
1803
|
" winType=%(winType)s, allowStencil=%(allowStencil)s,\n"
|
|
1801
1804
|
" monitor=%(Monitor)s, color=%(color)s, colorSpace=%(colorSpace)s,\n"
|
|
1802
1805
|
" backgroundImage=%(backgroundImg)s, backgroundFit=%(backgroundFit)s,\n"
|
|
@@ -63,7 +63,7 @@ class SoundComponent(BaseDeviceComponent):
|
|
|
63
63
|
" to specify Hz (e.g. 440) or a filename")
|
|
64
64
|
self.params['sound'] = Param(
|
|
65
65
|
sound, valType='str', inputType="file", allowedTypes=[], updates='constant', categ='Basic',
|
|
66
|
-
allowedUpdates=['
|
|
66
|
+
allowedUpdates=['set every repeat'],
|
|
67
67
|
hint=hnt,
|
|
68
68
|
label=_translate("Sound"))
|
|
69
69
|
_allowed = ['constant', 'set every repeat', 'set every frame']
|
|
@@ -22,7 +22,7 @@ class TextComponent(BaseVisualComponent):
|
|
|
22
22
|
def __init__(self, exp, parentName, name='text',
|
|
23
23
|
# effectively just a display-value
|
|
24
24
|
text=_translate('Any text\n\nincluding line breaks'),
|
|
25
|
-
font='
|
|
25
|
+
font='Arial', units='from exp settings',
|
|
26
26
|
color='white', colorSpace='rgb',
|
|
27
27
|
pos=(0, 0), letterHeight=0.05, ori=0,
|
|
28
28
|
startType='time (s)', startVal=0.0,
|
psychopy/experiment/params.py
CHANGED
|
@@ -312,6 +312,23 @@ class Param():
|
|
|
312
312
|
# if not a clear alias, use bool method of value
|
|
313
313
|
return bool(self.val)
|
|
314
314
|
|
|
315
|
+
def __deepcopy__(self, memo):
|
|
316
|
+
return Param(
|
|
317
|
+
val=self.val,
|
|
318
|
+
valType=self.valType,
|
|
319
|
+
inputType=self.inputType,
|
|
320
|
+
allowedVals=self.allowedVals,
|
|
321
|
+
allowedTypes=self.allowedTypes,
|
|
322
|
+
hint=self.hint,
|
|
323
|
+
label=self.label,
|
|
324
|
+
updates=self.updates,
|
|
325
|
+
allowedUpdates=self.allowedUpdates,
|
|
326
|
+
allowedLabels=self.allowedLabels,
|
|
327
|
+
direct=self.direct,
|
|
328
|
+
canBePath=self.canBePath,
|
|
329
|
+
categ=self.categ
|
|
330
|
+
)
|
|
331
|
+
|
|
315
332
|
@property
|
|
316
333
|
def _xml(self):
|
|
317
334
|
# Make root element
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"""Describes the Flow of an experiment
|
|
9
9
|
"""
|
|
10
10
|
import copy
|
|
11
|
+
import textwrap
|
|
11
12
|
|
|
12
13
|
from psychopy.constants import FOREVER
|
|
13
14
|
from xml.etree.ElementTree import Element
|
|
@@ -27,6 +28,8 @@ class BaseStandaloneRoutine:
|
|
|
27
28
|
limit = float('inf')
|
|
28
29
|
# what version was this Routine added in?
|
|
29
30
|
version = "0.0.0"
|
|
31
|
+
# is it still in beta?
|
|
32
|
+
beta = False
|
|
30
33
|
|
|
31
34
|
def __init__(self, exp, name='',
|
|
32
35
|
stopType='duration (s)', stopVal='',
|
|
@@ -225,6 +228,125 @@ class BaseStandaloneRoutine:
|
|
|
225
228
|
def getStatics(self):
|
|
226
229
|
return []
|
|
227
230
|
|
|
231
|
+
def getFullDocumentation(self, fmt="rst"):
|
|
232
|
+
"""
|
|
233
|
+
Automatically generate documentation for this Component. We recommend using this as a
|
|
234
|
+
starting point, but checking the documentation yourself afterwards and adding any more
|
|
235
|
+
detail you'd like to include (e.g. usage examples)
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
fmt : str
|
|
240
|
+
Format to write documentation in. One of:
|
|
241
|
+
- "rst": Restructured text (numpy style)
|
|
242
|
+
-"md": Markdown (mkdocs style)
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
# make sure format is correct
|
|
246
|
+
assert fmt in ("md", "rst"), (
|
|
247
|
+
f"Unrecognised format {fmt}, allowed formats are 'md' and 'rst'."
|
|
248
|
+
)
|
|
249
|
+
# define templates for md and rst
|
|
250
|
+
h1 = {
|
|
251
|
+
'md': "# %s",
|
|
252
|
+
'rst': (
|
|
253
|
+
"-------------------------------\n"
|
|
254
|
+
"%s\n"
|
|
255
|
+
"-------------------------------"
|
|
256
|
+
)
|
|
257
|
+
}[fmt]
|
|
258
|
+
h2 = {
|
|
259
|
+
'md': "## %s",
|
|
260
|
+
'rst': (
|
|
261
|
+
"%s\n"
|
|
262
|
+
"-------------------------------"
|
|
263
|
+
)
|
|
264
|
+
}[fmt]
|
|
265
|
+
h3 = {
|
|
266
|
+
'md': "### %s",
|
|
267
|
+
'rst': (
|
|
268
|
+
"%s\n"
|
|
269
|
+
"==============================="
|
|
270
|
+
)
|
|
271
|
+
}[fmt]
|
|
272
|
+
h4 = {
|
|
273
|
+
'md': "#### `%s`",
|
|
274
|
+
'rst': "%s"
|
|
275
|
+
}[fmt]
|
|
276
|
+
|
|
277
|
+
# start off with nothing
|
|
278
|
+
content = ""
|
|
279
|
+
# header and class docstring
|
|
280
|
+
content += (
|
|
281
|
+
f"{h1 % type(self).__name__}\n"
|
|
282
|
+
f"{textwrap.dedent(self.__doc__ or '')}\n"
|
|
283
|
+
f"\n"
|
|
284
|
+
)
|
|
285
|
+
# attributes
|
|
286
|
+
content += (
|
|
287
|
+
f"{h4 % 'Categories:'}\n"
|
|
288
|
+
f" {', '.join(self.categories)}\n"
|
|
289
|
+
f"{h4 % 'Works in:'}\n"
|
|
290
|
+
f" {', '.join(self.targets)}\n"
|
|
291
|
+
f"\n"
|
|
292
|
+
)
|
|
293
|
+
# beta warning
|
|
294
|
+
if self.beta:
|
|
295
|
+
content += (
|
|
296
|
+
f"**Note: Since this is still in beta, keep an eye out for bug fixes.**\n"
|
|
297
|
+
f"\n"
|
|
298
|
+
)
|
|
299
|
+
# params heading
|
|
300
|
+
content += (
|
|
301
|
+
f"{h2 % 'Parameters'}\n"
|
|
302
|
+
f"\n"
|
|
303
|
+
)
|
|
304
|
+
# sort params by category
|
|
305
|
+
byCateg = {}
|
|
306
|
+
for param in self.params.values():
|
|
307
|
+
if param.categ not in byCateg:
|
|
308
|
+
byCateg[param.categ] = []
|
|
309
|
+
byCateg[param.categ].append(param)
|
|
310
|
+
# iterate through categs
|
|
311
|
+
for categ, params in byCateg.items():
|
|
312
|
+
# write a heading for each categ
|
|
313
|
+
content += (
|
|
314
|
+
f"{h3 % categ}\n"
|
|
315
|
+
f"\n"
|
|
316
|
+
)
|
|
317
|
+
# add each param...
|
|
318
|
+
for param in params:
|
|
319
|
+
# write basics (heading and description)
|
|
320
|
+
content += (
|
|
321
|
+
f"{h4 % param.label}\n"
|
|
322
|
+
f" {param.hint}\n"
|
|
323
|
+
)
|
|
324
|
+
# if there are options, display them
|
|
325
|
+
if bool(param.allowedVals) or bool(param.allowedLabels):
|
|
326
|
+
# if no allowed labels, use allowed vals
|
|
327
|
+
options = param.allowedLabels or param.allowedVals
|
|
328
|
+
# handle callable methods
|
|
329
|
+
if callable(options):
|
|
330
|
+
content += (
|
|
331
|
+
f"\n"
|
|
332
|
+
f" Options are generated live, so will vary according to your setup.\n"
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
# write heading
|
|
336
|
+
content += (
|
|
337
|
+
f" \n"
|
|
338
|
+
f" Options:\n"
|
|
339
|
+
)
|
|
340
|
+
# add list item for each option
|
|
341
|
+
for opt in options:
|
|
342
|
+
content += (
|
|
343
|
+
f" - {opt}\n"
|
|
344
|
+
)
|
|
345
|
+
# add newline at the end
|
|
346
|
+
content += "\n"
|
|
347
|
+
|
|
348
|
+
return content
|
|
349
|
+
|
|
228
350
|
@property
|
|
229
351
|
def name(self):
|
|
230
352
|
if hasattr(self, 'params'):
|
|
@@ -43,7 +43,7 @@ class CounterbalanceRoutine(BaseStandaloneRoutine):
|
|
|
43
43
|
self.params['specMode'] = Param(
|
|
44
44
|
specMode, valType="str", inputType="choice", categ="Basic",
|
|
45
45
|
allowedVals=["uniform", "file"],
|
|
46
|
-
allowedLabels=[_translate("Num. groups"), _translate("Conditions file")],
|
|
46
|
+
allowedLabels=[_translate("Num. groups"), _translate("Conditions file (local only)")],
|
|
47
47
|
label=_translate("Groups from..."),
|
|
48
48
|
hint=_translate(
|
|
49
49
|
"Specify groups using an Excel file (for fine tuned control), specify as a variable name, or specify a "
|
psychopy/hardware/button.py
CHANGED
|
@@ -177,10 +177,11 @@ class ButtonBox:
|
|
|
177
177
|
if device in DeviceManager.devices:
|
|
178
178
|
self.device = DeviceManager.getDevice(device)
|
|
179
179
|
else:
|
|
180
|
+
# don't use formatted string literals in _translate()
|
|
180
181
|
raise ValueError(_translate(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
))
|
|
182
|
+
"Could not find device named '{device}', make sure it has been set up "
|
|
183
|
+
"in DeviceManager."
|
|
184
|
+
).format(device))
|
|
184
185
|
|
|
185
186
|
# starting value for status (Builder)
|
|
186
187
|
self.status = constants.NOT_STARTED
|
|
@@ -2196,6 +2196,9 @@ class Camera:
|
|
|
2196
2196
|
def stop(self):
|
|
2197
2197
|
"""Stop recording frames and audio (if available).
|
|
2198
2198
|
"""
|
|
2199
|
+
if self._captureThread is None: # do nothing if not open
|
|
2200
|
+
return
|
|
2201
|
+
|
|
2199
2202
|
if not self._captureThread.isOpen():
|
|
2200
2203
|
raise RuntimeError("Cannot stop recording, stream is not open.")
|
|
2201
2204
|
|
|
@@ -2217,6 +2220,9 @@ class Camera:
|
|
|
2217
2220
|
to save the frames to disk.
|
|
2218
2221
|
|
|
2219
2222
|
"""
|
|
2223
|
+
if self._captureThread is None: # nop
|
|
2224
|
+
return
|
|
2225
|
+
|
|
2220
2226
|
if not self._captureThread.isOpen():
|
|
2221
2227
|
raise RuntimeError("Cannot close stream, stream is not open.")
|
|
2222
2228
|
|
psychopy/hardware/keyboard.py
CHANGED
|
@@ -206,8 +206,13 @@ class Keyboard(AttributeGetSetMixin):
|
|
|
206
206
|
self.rt = [] # response time(s)
|
|
207
207
|
self.time = [] # Epoch
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
@property
|
|
210
|
+
def clock(self):
|
|
211
|
+
return self.device.clock
|
|
212
|
+
|
|
213
|
+
@clock.setter
|
|
214
|
+
def clock(self, value):
|
|
215
|
+
self.device.clock = value
|
|
211
216
|
|
|
212
217
|
def getBackend(self):
|
|
213
218
|
return self.device.getBackend()
|
|
@@ -568,7 +573,7 @@ class KeyboardDevice(BaseResponseDevice, aliases=["keyboard"]):
|
|
|
568
573
|
if message.type == "KEYBOARD_PRESS":
|
|
569
574
|
# if message is from a key down event, make a new response
|
|
570
575
|
response = KeyPress(code=message.char, tDown=message.time, name=message.key)
|
|
571
|
-
response.rt = response.tDown
|
|
576
|
+
response.rt = response.tDown - self.clock.getLastResetTime()
|
|
572
577
|
self._keysStillDown.append(response)
|
|
573
578
|
else:
|
|
574
579
|
# if message is from a key up event, alter existing response
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import psychopy.logging as logging
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from psychopy_eyetracker_gazepoint import gp3
|
|
9
|
+
from psychopy_eyetracker_gazepoint.gazepoint import gp3
|
|
10
10
|
except (ModuleNotFoundError, ImportError, NameError):
|
|
11
11
|
logging.error(
|
|
12
12
|
"The Gazepoint eyetracker requires package "
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import psychopy.logging as logging
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from psychopy_eyetracker_gazepoint.gp3.calibration import (
|
|
9
|
+
from psychopy_eyetracker_gazepoint.gazepoint.gp3.calibration import (
|
|
10
10
|
GazepointCalibrationProcedure)
|
|
11
11
|
except (ModuleNotFoundError, ImportError, NameError):
|
|
12
12
|
logging.error(
|