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.

Files changed (96) hide show
  1. psychopy/__init__.py +2 -2
  2. psychopy/alerts/_alerts.py +6 -2
  3. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -0
  4. psychopy/app/builder/dialogs/paramCtrls.py +4 -2
  5. psychopy/app/builder/localizedStrings.py +16 -4
  6. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  7. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  8. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  9. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  10. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  11. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  12. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  13. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  14. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  15. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  16. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  17. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  18. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  19. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  20. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  21. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  22. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3548 -5422
  23. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  24. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  25. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  26. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  27. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  28. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  29. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  30. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  31. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  32. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  33. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  34. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  35. psychopy/app/plugin_manager/plugins.py +1 -1
  36. psychopy/app/preferencesDlg.py +2 -1
  37. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +552 -550
  38. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +572 -570
  39. psychopy/demos/coder/timing/timeByFrames.py +7 -1
  40. psychopy/experiment/components/_base.py +122 -0
  41. psychopy/experiment/components/aperture/__init__.py +6 -2
  42. psychopy/experiment/components/brush/__init__.py +3 -1
  43. psychopy/experiment/components/button/__init__.py +6 -2
  44. psychopy/experiment/components/buttonBox/__init__.py +2 -1
  45. psychopy/experiment/components/camera/__init__.py +13 -0
  46. psychopy/experiment/components/code/__init__.py +34 -1
  47. psychopy/experiment/components/form/formItems.xltx +0 -0
  48. psychopy/experiment/components/mouse/__init__.py +12 -0
  49. psychopy/experiment/components/polygon/__init__.py +14 -3
  50. psychopy/experiment/components/settings/__init__.py +4 -1
  51. psychopy/experiment/components/sound/__init__.py +1 -1
  52. psychopy/experiment/components/text/__init__.py +1 -1
  53. psychopy/experiment/params.py +17 -0
  54. psychopy/experiment/routines/_base.py +122 -0
  55. psychopy/experiment/routines/counterbalance/__init__.py +1 -1
  56. psychopy/hardware/button.py +4 -3
  57. psychopy/hardware/camera/__init__.py +6 -0
  58. psychopy/hardware/keyboard.py +8 -3
  59. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +1 -1
  60. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -1
  61. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +1 -1
  62. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +1 -1
  63. psychopy/localization/generateTranslationTemplate.py +45 -19
  64. psychopy/localization/messages.pot +5049 -3418
  65. psychopy/preferences/Darwin.spec +2 -0
  66. psychopy/preferences/FreeBSD.spec +2 -0
  67. psychopy/preferences/Linux.spec +2 -0
  68. psychopy/preferences/Windows.spec +2 -0
  69. psychopy/preferences/baseNoArch.spec +2 -0
  70. psychopy/preferences/generateHints.py +2 -1
  71. psychopy/preferences/hints.py +118 -97
  72. psychopy/tests/test_experiment/test_components/__init__.py +1 -1
  73. psychopy/tests/test_experiment/test_components/{test_ButtonBox.py → test_ButtonBoxComponent.py} +9 -27
  74. psychopy/tests/test_experiment/test_components/{test_Code.py → test_CodeComponent.py} +8 -20
  75. psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
  76. psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
  77. psychopy/tests/test_experiment/test_components/{test_Mouse.py → test_MouseComponent.py} +44 -57
  78. psychopy/tests/test_experiment/test_components/{test_Polygon.py → test_PolygonComponent.py} +9 -25
  79. psychopy/tests/test_experiment/test_components/{test_ResourceManager.py → test_ResourceManagerComponent.py} +3 -13
  80. psychopy/tests/test_experiment/test_components/{test_Settings.py → test_SettingsComponent.py} +1 -3
  81. psychopy/tests/test_experiment/test_components/{test_Static.py → test_StaticComponent.py} +3 -12
  82. psychopy/tests/test_experiment/test_components/test_all_components.py +8 -66
  83. psychopy/tests/test_experiment/test_components/test_base_components.py +212 -125
  84. psychopy/tools/fontmanager.py +1 -1
  85. psychopy/tools/versionchooser.py +1 -1
  86. psychopy/visual/noise.py +9 -0
  87. psychopy/visual/radial.py +8 -0
  88. psychopy/visual/secondorder.py +9 -0
  89. psychopy/visual/textbox2/textbox2.py +19 -21
  90. {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/METADATA +1 -1
  91. {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/RECORD +95 -66
  92. {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/WHEEL +1 -1
  93. psychopy/tests/test_experiment/test_components/test_Image.py +0 -24
  94. {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/entry_points.txt +0 -0
  95. {psychopy-2024.1.1.dist-info → psychopy-2024.1.3.dist-info}/licenses/AUTHORS.md +0 -0
  96. {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
- """An event class for using GL stencil to restrict the viewing area to a
21
- circle or square of a given size and position"""
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
- """A class for drawing freehand responses"""
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
- A component for presenting a clickable textbox with a programmable callback
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
- """An event class for inserting arbitrary code into Builder experiments"""
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']
@@ -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
- " lineColor: new util.Color({lineColor}),\n"
284
- " fillColor: new util.Color({fillColor}),\n"
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=%(size)s, fullscr=_fullScr, screen=%(screenNumber)s,\n"
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=['constant', 'set every repeat'],
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='Open Sans', units='from exp settings',
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,
@@ -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 "
@@ -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
- f"Could not find device named '{device}', make sure it has been set up "
182
- f"in DeviceManager."
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
 
@@ -206,8 +206,13 @@ class Keyboard(AttributeGetSetMixin):
206
206
  self.rt = [] # response time(s)
207
207
  self.time = [] # Epoch
208
208
 
209
- # get clock from device
210
- self.clock = self.device.clock
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 import (
9
+ from psychopy_eyetracker_gazepoint.gazepoint.gp3 import (
10
10
  __file__,
11
11
  EyeTracker,
12
12
  MonocularEyeSampleEvent,
@@ -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(