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.

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