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,829 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Part of the PsychoPy library
5
+ # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2022 Open Science Tools Ltd.
6
+ # Distributed under the terms of the GNU General Public License (GPL).
7
+
8
+ """Experiment classes:
9
+ Experiment, Flow, Routine, Param, Loop*, *Handlers, and NameSpace
10
+
11
+ The code that writes out a *_lastrun.py experiment file is (in order):
12
+ experiment.Experiment.writeScript() - starts things off, calls other parts
13
+ settings.SettingsComponent.writeStartCode()
14
+ experiment.Flow.writeBody()
15
+ which will call the .writeBody() methods from each component
16
+ settings.SettingsComponent.writeEndCode()
17
+ """
18
+
19
+ from copy import deepcopy
20
+ from xml.etree.ElementTree import Element
21
+
22
+ from psychopy.experiment import getInitVals
23
+ from psychopy.localization import _localized, _translate
24
+ from psychopy.experiment.params import Param
25
+ from .components import getInitVals, getAllComponents
26
+
27
+ class TrialHandler():
28
+ """A looping experimental control object
29
+ (e.g. generating a psychopy TrialHandler or StairHandler).
30
+ """
31
+
32
+ def __init__(self, exp, name, loopType='random', nReps=5,
33
+ conditions=(), conditionsFile='', endPoints=(0, 1),
34
+ randomSeed='', selectedRows='', isTrials=True):
35
+ """
36
+ @param name: name of the loop e.g. trials
37
+ @type name: string
38
+ @param loopType:
39
+ @type loopType: string ('rand', 'seq')
40
+ @param nReps: number of reps (for all conditions)
41
+ @type nReps:int
42
+ @param conditions: list of different trial conditions to be used
43
+ @type conditions: list (of dicts?)
44
+ @param conditionsFile: filename of the .csv file that
45
+ contains conditions info
46
+ @type conditions: string (filename)
47
+ """
48
+ super(TrialHandler, self).__init__()
49
+ self.type = 'TrialHandler'
50
+ self.exp = exp
51
+ self.order = ['name'] # make name come first (others don't matter)
52
+ self.params = {}
53
+ self.params['name'] = Param(
54
+ name, valType='code', inputType="single", updates=None, allowedUpdates=None,
55
+ label=_localized['Name'],
56
+ hint=_translate("Name of this loop"))
57
+ self.params['nReps'] = Param(
58
+ nReps, valType='num', inputType="spin", updates=None, allowedUpdates=None,
59
+ label=_localized['nReps'],
60
+ hint=_translate("Number of repeats (for each condition)"))
61
+ self.params['conditions'] = Param(
62
+ list(conditions), valType='str', inputType="single",
63
+ updates=None, allowedUpdates=None,
64
+ label=_localized['conditions'],
65
+ hint=_translate("A list of dictionaries describing the "
66
+ "parameters in each condition"))
67
+ self.params['conditionsFile'] = Param(
68
+ conditionsFile, valType='file', inputType="table", updates=None, allowedUpdates=None,
69
+ label=_localized['conditions'],
70
+ hint=_translate("Name of a file specifying the parameters for "
71
+ "each condition (.csv, .xlsx, or .pkl). Browse "
72
+ "to select a file. Right-click to preview file "
73
+ "contents, or create a new file."))
74
+ self.params['endPoints'] = Param(
75
+ list(endPoints), valType='num', inputType="single", updates=None, allowedUpdates=None,
76
+ label=_localized['endPoints'],
77
+ hint=_translate("The start and end of the loop (see flow "
78
+ "timeline)"))
79
+ self.params['Selected rows'] = Param(
80
+ selectedRows, valType='str', inputType="single",
81
+ updates=None, allowedUpdates=None,
82
+ label=_localized['Selected rows'],
83
+ hint=_translate("Select just a subset of rows from your condition"
84
+ " file (the first is 0 not 1!). Examples: 0, "
85
+ "0:5, 5:-1"))
86
+ # NB staircase is added for the sake of the loop properties dialog:
87
+ self.params['loopType'] = Param(
88
+ loopType, valType='str', inputType="choice",
89
+ allowedVals=['random', 'sequential', 'fullRandom',
90
+ 'staircase', 'interleaved staircases'],
91
+ label=_localized['loopType'],
92
+ hint=_translate("How should the next condition value(s) be "
93
+ "chosen?"))
94
+ self.params['random seed'] = Param(
95
+ randomSeed, valType='code', inputType="single", updates=None, allowedUpdates=None,
96
+ label=_localized['random seed'],
97
+ hint=_translate("To have a fixed random sequence provide an "
98
+ "integer of your choosing here. Leave blank to "
99
+ "have a new random sequence on each run of the "
100
+ "experiment."))
101
+ self.params['isTrials'] = Param(
102
+ isTrials, valType='bool', inputType="bool", updates=None, allowedUpdates=None,
103
+ label=_localized["Is trials"],
104
+ hint=_translate("Indicates that this loop generates TRIALS, "
105
+ "rather than BLOCKS of trials or stimuli within "
106
+ "a trial. It alters how data files are output"))
107
+
108
+ def writeInitCode(self, buff):
109
+ # no longer needed - initialise the trial handler just before it runs
110
+ pass
111
+
112
+ def writeInitCodeJS(self, buff):
113
+ pass
114
+
115
+ def writeLoopStartCode(self, buff):
116
+ """Write the code to create and run a sequence of trials
117
+ """
118
+ # first create the handler init values
119
+ inits = getInitVals(self.params)
120
+ # import conditions from file?
121
+ if self.params['conditionsFile'].val in ['None', None, 'none', '']:
122
+ condsStr = "[None]"
123
+ elif self.params['Selected rows'].val in ['None', None, 'none', '']:
124
+ # just a conditions file with no sub-selection
125
+ _con = "data.importConditions(%s)"
126
+ condsStr = _con % self.params['conditionsFile']
127
+ else:
128
+ # a subset of a conditions file
129
+ condsStr = ("data.importConditions(%(conditionsFile)s, selection="
130
+ "%(Selected rows)s)") % self.params
131
+ # also a 'thisName' for use in "for thisTrial in trials:"
132
+ makeLoopIndex = self.exp.namespace.makeLoopIndex
133
+ self.thisName = makeLoopIndex(self.params['name'].val)
134
+ # write the code
135
+ code = ("\n# set up handler to look after randomisation of conditions etc\n"
136
+ "%(name)s = data.TrialHandler(nReps=%(nReps)s, method=%(loopType)s, \n"
137
+ " extraInfo=expInfo, originPath=-1,\n")
138
+ buff.writeIndentedLines(code % inits)
139
+ # the next line needs to be kept separate to preserve potential string formatting
140
+ # by the user in condStr (i.e. it shouldn't be a formatted string itself
141
+ code = " trialList=" + condsStr + ",\n" # conditions go here
142
+ buff.writeIndented(code)
143
+ code = " seed=%(random seed)s, name='%(name)s')\n"
144
+ buff.writeIndentedLines(code % inits)
145
+
146
+ code = ("thisExp.addLoop(%(name)s) # add the loop to the experiment\n" +
147
+ self.thisName + " = %(name)s.trialList[0] " +
148
+ "# so we can initialise stimuli with some values\n")
149
+ buff.writeIndentedLines(code % self.params)
150
+ # unclutter the namespace
151
+ if not self.exp.prefsBuilder['unclutteredNamespace']:
152
+ code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
153
+ "if %(name)s != None:\n"
154
+ " for paramName in %(name)s:\n"
155
+ " exec('{} = %(name)s[paramName]'.format(paramName))\n")
156
+ buff.writeIndentedLines(code % {'name': self.thisName})
157
+
158
+ # then run the trials loop
159
+ code = "\nfor %s in %s:\n"
160
+ buff.writeIndentedLines(code % (self.thisName, self.params['name']))
161
+ # fetch parameter info from conditions
162
+ buff.setIndentLevel(1, relative=True)
163
+ buff.writeIndented("currentLoop = %s\n" % self.params['name'])
164
+ # unclutter the namespace
165
+ if not self.exp.prefsBuilder['unclutteredNamespace']:
166
+ code = ("# abbreviate parameter names if possible (e.g. rgb = %(name)s.rgb)\n"
167
+ "if %(name)s != None:\n"
168
+ " for paramName in %(name)s:\n"
169
+ " exec('{} = %(name)s[paramName]'.format(paramName))\n")
170
+ buff.writeIndentedLines(code % {'name': self.thisName})
171
+
172
+ def writeLoopStartCodeJS(self, buff, modular):
173
+ """Write the code to create and run a sequence of trials
174
+ """
175
+ # some useful variables
176
+ # create the variable "thisTrial" from "trials"
177
+ makeLoopIndex = self.exp.namespace.makeLoopIndex
178
+ self.thisName = makeLoopIndex(self.params['name'].val)
179
+
180
+ # Convert filepath separator
181
+ conditionsFile = self.params['conditionsFile'].val
182
+ self.params['conditionsFile'].val = conditionsFile.replace('\\\\', '/').replace('\\', '/')
183
+ # seed might be undefined
184
+ seed = self.params['random seed'].val or 'undefined'
185
+ if self.params['conditionsFile'].val in ['None', None, 'none', '']:
186
+ trialList='undefined'
187
+ elif self.params['Selected rows'].val in ['None', None, 'none', '']:
188
+ trialList = self.params['conditionsFile']
189
+ else:
190
+ trialList = ("TrialHandler.importConditions"
191
+ "(psychoJS.serverManager, {}, {})"
192
+ ).format(self.params['conditionsFile'],
193
+ self.params['Selected rows'])
194
+
195
+ nReps = self.params['nReps'].val
196
+ if nReps in ['None', None, 'none', '']:
197
+ nReps = 'undefined'
198
+ elif isinstance(nReps, str):
199
+ nReps = nReps.strip("$")
200
+
201
+ code = ("\nfunction {loopName}LoopBegin({loopName}LoopScheduler, snapshot) {{\n"
202
+ " return async function() {{\n"
203
+ .format(loopName=self.params['name'],
204
+ loopType=(self.params['loopType'].val).upper(),
205
+ nReps=nReps,
206
+ trialList=trialList,
207
+ seed=seed))
208
+ buff.writeIndentedLines(code)
209
+ buff.setIndentLevel(2, relative=True)
210
+ code = ("TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop\n\n"
211
+ "// set up handler to look after randomisation of conditions etc\n"
212
+ "{loopName} = new TrialHandler({{\n"
213
+ " psychoJS: psychoJS,\n"
214
+ " nReps: {nReps}, method: TrialHandler.Method.{loopType},\n"
215
+ " extraInfo: expInfo, originPath: undefined,\n"
216
+ " trialList: {trialList},\n"
217
+ " seed: {seed}, name: '{loopName}'\n"
218
+ "}});\n"
219
+ "psychoJS.experiment.addLoop({loopName}); // add the loop to the experiment\n"
220
+ "currentLoop = {loopName}; // we're now the current loop\n"
221
+ .format(loopName=self.params['name'],
222
+ loopType=(self.params['loopType'].val).upper(),
223
+ nReps=nReps,
224
+ trialList=trialList,
225
+ seed=seed))
226
+ buff.writeIndentedLines(code)
227
+
228
+ # for the scheduler
229
+ if modular:
230
+ code = ("\n// Schedule all the trials in the trialList:\n"
231
+ "for (const {thisName} of {loopName}) {{\n"
232
+ " const snapshot = {loopName}.getSnapshot();\n"
233
+ " {loopName}LoopScheduler.add(importConditions(snapshot));\n")
234
+ else:
235
+ code = ("\n// Schedule all the trials in the trialList:\n"
236
+ "{loopName}.forEach(function() {{\n"
237
+ " const snapshot = {loopName}.getSnapshot();\n\n"
238
+ " {loopName}LoopScheduler.add(importConditions(snapshot));\n")
239
+ buff.writeIndentedLines(code.format(loopName=self.params['name'],
240
+ thisName=self.thisName))
241
+ # then we need to include begin, eachFrame and end code for each entry within that loop
242
+ loopDict = self.exp.flow.loopDict
243
+ thisLoop = loopDict[self] # dict containing lists of children
244
+ code = ""
245
+ for thisChild in thisLoop:
246
+ if thisChild.getType() == 'Routine':
247
+ code += (
248
+ " {loopName}LoopScheduler.add({childName}RoutineBegin(snapshot));\n"
249
+ " {loopName}LoopScheduler.add({childName}RoutineEachFrame());\n"
250
+ " {loopName}LoopScheduler.add({childName}RoutineEnd());\n"
251
+ .format(childName=thisChild.params['name'],
252
+ loopName=self.params['name'])
253
+ )
254
+ else: # for a LoopInitiator
255
+ code += (
256
+ " const {childName}LoopScheduler = new Scheduler(psychoJS);\n"
257
+ " {loopName}LoopScheduler.add({childName}LoopBegin({childName}LoopScheduler, snapshot));\n"
258
+ " {loopName}LoopScheduler.add({childName}LoopScheduler);\n"
259
+ " {loopName}LoopScheduler.add({childName}LoopEnd);\n"
260
+ .format(childName=thisChild.params['name'],
261
+ loopName=self.params['name'])
262
+ )
263
+
264
+ code += " {loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
265
+ code += "}}%s\n" % ([');', ''][modular])
266
+ code += ("\n"
267
+ "return Scheduler.Event.NEXT;\n")
268
+ buff.writeIndentedLines(code.format(loopName=self.params['name']))
269
+ buff.setIndentLevel(-2, relative=True)
270
+ buff.writeIndentedLines(
271
+ " }\n"
272
+ "}\n"
273
+ )
274
+
275
+ def writeLoopEndCode(self, buff):
276
+ # Just within the loop advance data line if loop is whole trials
277
+ if self.params['isTrials'].val == True:
278
+ buff.writeIndentedLines("thisExp.nextEntry()\n\n")
279
+ # end of the loop. dedent
280
+ buff.setIndentLevel(-1, relative=True)
281
+ buff.writeIndented("# completed %s repeats of '%s'\n"
282
+ % (self.params['nReps'], self.params['name']))
283
+ buff.writeIndented("\n")
284
+ # save data
285
+ if self.params['isTrials'].val == True:
286
+ # a string to show all the available variables (if the conditions
287
+ # isn't just None or [None])
288
+ saveExcel = self.exp.settings.params['Save excel file'].val
289
+ saveCSV = self.exp.settings.params['Save csv file'].val
290
+ # get parameter names
291
+ if saveExcel or saveCSV:
292
+ code = ("# get names of stimulus parameters\n"
293
+ "if %(name)s.trialList in ([], [None], None):\n"
294
+ " params = []\n"
295
+ "else:\n"
296
+ " params = %(name)s.trialList[0].keys()\n")
297
+ buff.writeIndentedLines(code % self.params)
298
+ # write out each type of file
299
+ if saveExcel or saveCSV:
300
+ buff.writeIndented("# save data for this loop\n")
301
+ if saveExcel:
302
+ code = ("%(name)s.saveAsExcel(filename + '.xlsx', sheetName='%(name)s',\n"
303
+ " stimOut=params,\n"
304
+ " dataOut=['n','all_mean','all_std', 'all_raw'])\n")
305
+ buff.writeIndentedLines(code % self.params)
306
+ if saveCSV:
307
+ code = ("%(name)s.saveAsText(filename + '%(name)s.csv', "
308
+ "delim=',',\n"
309
+ " stimOut=params,\n"
310
+ " dataOut=['n','all_mean','all_std', 'all_raw'])\n")
311
+ buff.writeIndentedLines(code % self.params)
312
+
313
+ def writeLoopEndCodeJS(self, buff):
314
+ # Just within the loop advance data line if loop is whole trials
315
+ code = ("\nasync function {funName}LoopEnd() {{\n"
316
+ " psychoJS.experiment.removeLoop({name});\n\n".format(funName=self.params['name'].val,
317
+ name=self.params['name']))
318
+ code += (" return Scheduler.Event.NEXT;\n"
319
+ "}\n")
320
+ buff.writeIndentedLines(code)
321
+
322
+ def getType(self):
323
+ return 'TrialHandler'
324
+
325
+ @property
326
+ def name(self):
327
+ return self.params['name'].val
328
+
329
+
330
+ class StairHandler:
331
+ """A staircase experimental control object.
332
+ """
333
+
334
+ def __init__(self, exp, name, nReps='50', startVal='', nReversals='',
335
+ nUp=1, nDown=3, minVal=0, maxVal=1,
336
+ stepSizes='[4,4,2,2,1]', stepType='db', endPoints=(0, 1),
337
+ isTrials=True):
338
+ """
339
+ @param name: name of the loop e.g. trials
340
+ @type name: string
341
+ @param nReps: number of reps (for all conditions)
342
+ @type nReps:int
343
+ """
344
+ super(StairHandler, self).__init__()
345
+ self.type = 'StairHandler'
346
+ self.exp = exp
347
+ self.order = ['name'] # make name come first (others don't matter)
348
+ self.children = []
349
+ self.params = {}
350
+ self.params['name'] = Param(
351
+ name, valType='code',
352
+ hint=_translate("Name of this loop"),
353
+ label=_localized['Name'])
354
+ self.params['nReps'] = Param(
355
+ nReps, valType='num', inputType='spin',
356
+ label=_localized['nReps'],
357
+ hint=_translate("(Minimum) number of trials in the staircase"))
358
+ self.params['start value'] = Param(
359
+ startVal, valType='num', inputType='single',
360
+ label=_localized['start value'],
361
+ hint=_translate("The initial value of the parameter"))
362
+ self.params['max value'] = Param(
363
+ maxVal, valType='num', inputType='single',
364
+ label=_localized['max value'],
365
+ hint=_translate("The maximum value the parameter can take"))
366
+ self.params['min value'] = Param(
367
+ minVal, valType='num', inputType='single',
368
+ label=_localized['min value'],
369
+ hint=_translate("The minimum value the parameter can take"))
370
+ self.params['step sizes'] = Param(
371
+ stepSizes, valType='list', inputType='single',
372
+ label=_localized['step sizes'],
373
+ hint=_translate("The size of the jump at each step (can change"
374
+ " on each 'reversal')"))
375
+ self.params['step type'] = Param(
376
+ stepType, valType='str', inputType='choice', allowedVals=['lin', 'log', 'db'],
377
+ label=_localized['step type'],
378
+ hint=_translate("The units of the step size (e.g. 'linear' will"
379
+ " add/subtract that value each step, whereas "
380
+ "'log' will ad that many log units)"))
381
+ self.params['N up'] = Param(
382
+ nUp, valType='num', inputType='spin',
383
+ label=_localized['N up'],
384
+ hint=_translate("The number of 'incorrect' answers before the "
385
+ "value goes up"))
386
+ self.params['N down'] = Param(
387
+ nDown, valType='num', inputType='spin',
388
+ label=_localized['N down'],
389
+ hint=_translate("The number of 'correct' answers before the "
390
+ "value goes down"))
391
+ self.params['N reversals'] = Param(
392
+ nReversals, valType='num', inputType='spin',
393
+ label=_localized['N reversals'],
394
+ hint=_translate("Minimum number of times the staircase must "
395
+ "change direction before ending"))
396
+ # these two are really just for making the dialog easier (they won't
397
+ # be used to generate code)
398
+ self.params['loopType'] = Param(
399
+ 'staircase', valType='str', inputType='choice',
400
+ allowedVals=['random', 'sequential', 'fullRandom', 'staircase',
401
+ 'interleaved staircases'],
402
+ label=_localized['loopType'],
403
+ hint=_translate("How should the next trial value(s) be chosen?"))
404
+ # NB this is added for the sake of the loop properties dialog
405
+ self.params['endPoints'] = Param(
406
+ list(endPoints), valType='num', inputType='spin',
407
+ label=_localized['endPoints'],
408
+ hint=_translate('Where to loop from and to (see values currently'
409
+ ' shown in the flow view)'))
410
+ self.params['isTrials'] = Param(
411
+ isTrials, valType='bool', inputType='bool', updates=None, allowedUpdates=None,
412
+ label=_localized["Is trials"],
413
+ hint=_translate("Indicates that this loop generates TRIALS, "
414
+ "rather than BLOCKS of trials or stimuli within"
415
+ " a trial. It alters how data files are output"))
416
+
417
+ @property
418
+ def name(self):
419
+ return self.params['name'].val
420
+
421
+ def writeInitCode(self, buff):
422
+ # not needed - initialise the staircase only when needed
423
+ pass
424
+
425
+ def writeLoopStartCode(self, buff):
426
+ # create the staircase
427
+ # also a 'thisName' for use in "for thisTrial in trials:"
428
+ makeLoopIndex = self.exp.namespace.makeLoopIndex
429
+ self.thisName = makeLoopIndex(self.params['name'].val)
430
+ if self.params['N reversals'].val in ("", None, 'None'):
431
+ self.params['N reversals'].val = '0'
432
+ # write the code
433
+ code = ('\n# --------Prepare to start Staircase "%(name)s" --------\n'
434
+ "# set up handler to look after next chosen value etc\n"
435
+ "%(name)s = data.StairHandler(startVal=%(start value)s, extraInfo=expInfo,\n"
436
+ " stepSizes=%(step sizes)s, stepType=%(step type)s,\n"
437
+ " nReversals=%(N reversals)s, nTrials=%(nReps)s, \n"
438
+ " nUp=%(N up)s, nDown=%(N down)s,\n"
439
+ " minVal=%(min value)s, maxVal=%(max value)s,\n"
440
+ " originPath=-1, name='%(name)s')\n"
441
+ "thisExp.addLoop(%(name)s) # add the loop to the experiment")
442
+ buff.writeIndentedLines(code % self.params)
443
+ code = "level = %s = %s # initialise some vals\n"
444
+ buff.writeIndented(code % (self.thisName, self.params['start value']))
445
+ # then run the trials
446
+ # work out a name for e.g. thisTrial in trials:
447
+ code = "\nfor %s in %s:\n"
448
+ buff.writeIndentedLines(code % (self.thisName, self.params['name']))
449
+ buff.setIndentLevel(1, relative=True)
450
+ buff.writeIndented("currentLoop = %s\n" % self.params['name'])
451
+ buff.writeIndented("level = %s\n" % self.thisName)
452
+
453
+ def writeLoopEndCode(self, buff):
454
+ # Just within the loop advance data line if loop is whole trials
455
+ if self.params['isTrials'].val:
456
+ buff.writeIndentedLines("thisExp.nextEntry()\n\n")
457
+ # end of the loop. dedent
458
+ buff.setIndentLevel(-1, relative=True)
459
+ buff.writeIndented("# staircase completed\n")
460
+ buff.writeIndented("\n")
461
+ # save data
462
+ if self.params['isTrials'].val:
463
+ if self.exp.settings.params['Save excel file'].val:
464
+ code = ("%(name)s.saveAsExcel(filename + '.xlsx',"
465
+ " sheetName='%(name)s')\n")
466
+ buff.writeIndented(code % self.params)
467
+ if self.exp.settings.params['Save csv file'].val:
468
+ code = ("%(name)s.saveAsText(filename + "
469
+ "'%(name)s.csv', delim=',')\n")
470
+ buff.writeIndented(code % self.params)
471
+
472
+ def getType(self):
473
+ return 'StairHandler'
474
+
475
+
476
+ class MultiStairHandler:
477
+ """To handle multiple interleaved staircases
478
+ """
479
+
480
+ def __init__(self, exp, name, nReps='50', stairType='simple',
481
+ switchStairs='random',
482
+ conditions=(), conditionsFile='', endPoints=(0, 1),
483
+ isTrials=True):
484
+ """
485
+ @param name: name of the loop e.g. trials
486
+ @type name: string
487
+ @param nReps: number of reps (for all conditions)
488
+ @type nReps:int
489
+ """
490
+ super(MultiStairHandler, self).__init__()
491
+ self.type = 'MultiStairHandler'
492
+ self.exp = exp
493
+ self.order = ['name'] # make name come first
494
+ self.params = {}
495
+ self.params['name'] = Param(
496
+ name, valType='code', inputType='single',
497
+ label=_localized['Name'],
498
+ hint=_translate("Name of this loop"))
499
+ self.params['nReps'] = Param(
500
+ nReps, valType='num', inputType='spin',
501
+ label=_localized['nReps'],
502
+ hint=_translate("(Minimum) number of trials in *each* staircase"))
503
+ self.params['stairType'] = Param(
504
+ stairType, valType='str', inputType='choice',
505
+ allowedVals=['simple', 'QUEST'],
506
+ label=_localized['stairType'],
507
+ hint=_translate("How to select the next staircase to run"))
508
+ self.params['switchMethod'] = Param(
509
+ switchStairs, valType='str', inputType='choice',
510
+ allowedVals=['random', 'sequential', 'fullRandom'],
511
+ label=_localized['switchMethod'],
512
+ hint=_translate("How to select the next staircase to run"))
513
+ # these two are really just for making the dialog easier (they won't
514
+ # be used to generate code)
515
+ self.params['loopType'] = Param(
516
+ 'staircase', valType='str', inputType='choice',
517
+ allowedVals=['random', 'sequential', 'fullRandom', 'staircase',
518
+ 'interleaved staircases'],
519
+ label=_localized['loopType'],
520
+ hint=_translate("How should the next trial value(s) be chosen?"))
521
+ self.params['endPoints'] = Param(
522
+ list(endPoints), valType='num', inputType='spin',
523
+ label=_localized['endPoints'],
524
+ hint=_translate('Where to loop from and to (see values currently'
525
+ ' shown in the flow view)'))
526
+ self.params['conditions'] = Param(
527
+ list(conditions), valType='list', inputType='single',
528
+ updates=None, allowedUpdates=None,
529
+ label=_localized['conditions'],
530
+ hint=_translate("A list of dictionaries describing the "
531
+ "differences between each staircase"))
532
+ self.params['conditionsFile'] = Param(
533
+ conditionsFile, valType='file', inputType='table', updates=None, allowedUpdates=None,
534
+ label=_localized['conditions'],
535
+ hint=_translate("An xlsx or csv file specifying the parameters "
536
+ "for each condition"))
537
+ self.params['isTrials'] = Param(
538
+ isTrials, valType='bool', inputType='bool', updates=None, allowedUpdates=None,
539
+ label=_localized["Is trials"],
540
+ hint=_translate("Indicates that this loop generates TRIALS, "
541
+ "rather than BLOCKS of trials or stimuli within "
542
+ "a trial. It alters how data files are output"))
543
+ pass # don't initialise at start of exp, create when needed
544
+
545
+ @property
546
+ def name(self):
547
+ return self.params['name'].val
548
+
549
+ <<<<<<< Updated upstream
550
+ =======
551
+ def writePreCodeJS(self, buff):
552
+ if self.params['stairType'] == 'QUEST':
553
+ buff.writeOnceIndentedLines(
554
+ "import jsQUEST from 'https://lib.pavlovia.org/vendors/jsQUEST.min.js';"
555
+ )
556
+
557
+ >>>>>>> Stashed changes
558
+ def writeLoopStartCode(self, buff):
559
+ # create a 'thisName' for use in "for thisTrial in trials:"
560
+ makeLoopIndex = self.exp.namespace.makeLoopIndex
561
+ self.thisName = makeLoopIndex(self.params['name'].val)
562
+ # create the MultistairHander
563
+ code = ("\n# set up handler to look after randomisation of trials etc\n"
564
+ "conditions = data.importConditions(%(conditionsFile)s)\n"
565
+ "%(name)s = data.MultiStairHandler(stairType=%(stairType)s, "
566
+ "name='%(name)s',\n"
567
+ " nTrials=%(nReps)s,\n"
568
+ " conditions=conditions,\n"
569
+ " method=%(switchMethod)s,\n"
570
+ " originPath=-1)\n"
571
+ "thisExp.addLoop(%(name)s) # add the loop to the experiment\n"
572
+ "# initialise values for first condition\n"
573
+ "level = %(name)s._nextIntensity # initialise some vals\n"
574
+ "condition = %(name)s.currentStaircase.condition\n"
575
+ # start the loop:
576
+ "\nfor level, condition in %(name)s:\n")
577
+ buff.writeIndentedLines(code % self.params)
578
+
579
+ buff.setIndentLevel(1, relative=True)
580
+ buff.writeIndented("currentLoop = %(name)s\n" % (self.params))
581
+ # uncluttered namespace
582
+ if not self.exp.prefsBuilder['unclutteredNamespace']:
583
+ code = ("# abbreviate parameter names if possible (e.g. "
584
+ "rgb=condition.rgb)\n"
585
+ "for paramName in condition:\n"
586
+ " exec(paramName + '= condition[paramName]')\n")
587
+ buff.writeIndentedLines(code)
588
+
589
+ def writeLoopStartCodeJS(self, buff, modular):
590
+ inits = deepcopy(self.params)
591
+ # For JS, stairType needs to be code
592
+ inits['stairType'].valType = "code"
593
+ # Method needs to be code and upper
594
+ inits['switchMethod'].valType = "code"
595
+ inits['switchMethod'].val = inits['switchMethod'].val.upper()
596
+
597
+ code = (
598
+ "\nfunction %(name)sLoopBegin(%(name)sLoopScheduler, snapshot) {\n"
599
+ )
600
+ buff.writeIndentedLines(code % inits)
601
+
602
+ buff.setIndentLevel(1, relative=True)
603
+ code = (
604
+ "return async function() {\n"
605
+ )
606
+ buff.writeIndentedLines(code % inits)
607
+
608
+ buff.setIndentLevel(1, relative=True)
609
+ code = (
610
+ "// setup a MultiStairTrialHandler\n"
611
+ "%(name)sConditions = TrialHandler.importConditions(psychoJS.serverManager, %(conditionsFile)s);\n"
612
+ "%(name)s = new data.MultiStairHandler({stairType:MultiStairHandler.StaircaseType.%(stairType)s, \n"
613
+ )
614
+ buff.writeIndentedLines(code % inits)
615
+
616
+ buff.setIndentLevel(1, relative=True)
617
+ code = (
618
+ "psychoJS: psychoJS,\n"
619
+ "name: '%(name)s',\n"
620
+ "varName: '%(name)sVal',\n"
621
+ "nTrials: %(nReps)s,\n"
622
+ "conditions: %(name)sConditions,\n"
623
+ "method: TrialHandler.Method.%(switchMethod)s\n"
624
+ )
625
+ buff.writeIndentedLines(code % inits)
626
+
627
+ buff.setIndentLevel(-1, relative=True)
628
+ code = (
629
+ "});\n"
630
+ "psychoJS.experiment.addLoop(%(name)s); // add the loop to the experiment\n"
631
+ "currentLoop = %(name)s; // we're now the current loop\n"
632
+ "// Schedule all the trials in the trialList:\n"
633
+ "for (const thisQuestLoop of %(name)s) {\n"
634
+ )
635
+ buff.writeIndentedLines(code % inits)
636
+
637
+ buff.setIndentLevel(1, relative=True)
638
+ thisLoop = self.exp.flow.loopDict[self]
639
+ for thisChild in thisLoop:
640
+ if thisChild.getType() == 'Routine':
641
+ code = (
642
+ "const snapshot = %(name)s.getSnapshot();\n"
643
+ "{loopName}LoopScheduler.add(importConditions(snapshot));\n"
644
+ "{loopName}LoopScheduler.add({childName}RoutineBegin(snapshot));\n"
645
+ "{loopName}LoopScheduler.add({childName}RoutineEachFrame());\n"
646
+ "{loopName}LoopScheduler.add({childName}RoutineEnd());\n"
647
+ "{loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
648
+ .format(childName=thisChild.params['name'],
649
+ loopName=self.params['name'])
650
+ )
651
+ else: # for a LoopInitiator
652
+ code = (
653
+ "const snapshot = %(name)s.getSnapshot();\n"
654
+ "const {childName}LoopScheduler = new Scheduler(psychoJS);\n"
655
+ "{loopName}LoopScheduler.add(importConditions(snapshot));\n"
656
+ "{loopName}LoopScheduler.add({childName}LoopBegin({childName}LoopScheduler, snapshot));\n"
657
+ "{loopName}LoopScheduler.add({childName}LoopScheduler);\n"
658
+ "{loopName}LoopScheduler.add({childName}LoopEnd);\n"
659
+ "{loopName}LoopScheduler.add(endLoopIteration({loopName}LoopScheduler, snapshot));\n"
660
+ .format(childName=thisChild.params['name'],
661
+ loopName=self.params['name'])
662
+ )
663
+ buff.writeIndentedLines(code % inits)
664
+
665
+ buff.setIndentLevel(-1, relative=True)
666
+ code = (
667
+ "}"
668
+ "\n\n"
669
+ "return Scheduler.Event.NEXT;\n"
670
+ )
671
+ buff.writeIndentedLines(code % inits)
672
+
673
+ buff.setIndentLevel(-1, relative=True)
674
+ code = (
675
+ "}"
676
+ )
677
+ buff.writeIndentedLines(code % inits)
678
+
679
+ buff.setIndentLevel(-1, relative=True)
680
+ code = (
681
+ "}"
682
+ )
683
+ buff.writeIndentedLines(code % inits)
684
+
685
+ def writeLoopEndCode(self, buff):
686
+ # Just within the loop advance data line if loop is whole trials
687
+ if self.params['isTrials'].val:
688
+ buff.writeIndentedLines("thisExp.nextEntry()\n\n")
689
+ # end of the loop. dedent
690
+ buff.setIndentLevel(-1, relative=True)
691
+ buff.writeIndented("# all staircases completed\n")
692
+ buff.writeIndented("\n")
693
+ # save data
694
+ if self.params['isTrials'].val:
695
+ if self.exp.settings.params['Save excel file'].val:
696
+ code = "%(name)s.saveAsExcel(filename + '.xlsx')\n"
697
+ buff.writeIndented(code % self.params)
698
+ if self.exp.settings.params['Save csv file'].val:
699
+ code = ("%(name)s.saveAsText(filename + '%(name)s.csv', "
700
+ "delim=',')\n")
701
+ buff.writeIndented(code % self.params)
702
+
703
+ def writeLoopEndCodeJS(self, buff):
704
+ code = (
705
+ "\n"
706
+ "async function %(name)sLoopEnd() {\n"
707
+ )
708
+ buff.writeIndentedLines(code % self.params)
709
+
710
+ buff.setIndentLevel(1, relative=True)
711
+ code = (
712
+ "// terminate loop\n"
713
+ "psychoJS.experiment.removeLoop(%(name)s);\n"
714
+ "return Scheduler.Event.NEXT;\n"
715
+ )
716
+ buff.writeIndentedLines(code % self.params)
717
+
718
+ buff.setIndentLevel(-1, relative=True)
719
+ code = (
720
+ "}"
721
+ )
722
+ buff.writeIndentedLines(code % self.params)
723
+
724
+ def getType(self):
725
+ return 'MultiStairHandler'
726
+
727
+ def writeInitCode(self, buff):
728
+ # not needed - initialise the staircase only when needed
729
+ pass
730
+
731
+
732
+ class LoopInitiator:
733
+ """A simple class for inserting into the flow.
734
+ This is created automatically when the loop is created"""
735
+
736
+ def __init__(self, loop):
737
+ super(LoopInitiator, self).__init__()
738
+ self.loop = loop
739
+ self.exp = loop.exp
740
+ loop.initiator = self
741
+
742
+ @property
743
+ def _xml(self):
744
+ # Make root element
745
+ element = Element("LoopInitiator")
746
+ element.set("loopType", self.loop.__class__.__name__)
747
+ element.set("name", self.loop.params['name'].val)
748
+ # Add an element for each parameter
749
+ for key, param in sorted(self.loop.params.items()):
750
+ # Create node
751
+ paramNode = Element("Param")
752
+ paramNode.set("name", key)
753
+ # Assign values
754
+ if hasattr(param, 'updates'):
755
+ paramNode.set('updates', "{}".format(param.updates))
756
+ if hasattr(param, 'val'):
757
+ paramNode.set('val', u"{}".format(param.val).replace("\n", "&#10;"))
758
+ if hasattr(param, 'valType'):
759
+ paramNode.set('valType', param.valType)
760
+ element.append(paramNode)
761
+ return element
762
+
763
+ @property
764
+ def name(self):
765
+ return self.loop.name
766
+
767
+ def getType(self):
768
+ return 'LoopInitiator'
769
+
770
+ def writeInitCode(self, buff):
771
+ self.loop.writeInitCode(buff)
772
+
773
+ def writeInitCodeJS(self, buff):
774
+ self.loop.writeInitCodeJS(buff)
775
+
776
+ def writeMainCode(self, buff):
777
+ self.loop.writeLoopStartCode(buff)
778
+ # we are now the inner-most loop
779
+ self.exp.flow._loopList.append(self.loop)
780
+
781
+ def writeMainCodeJS(self, buff, modular):
782
+ self.loop.writeLoopStartCodeJS(buff, modular)
783
+ # we are now the inner-most loop
784
+ self.exp.flow._loopList.append(self.loop)
785
+
786
+ def writeExperimentEndCode(self, buff): # not needed
787
+ pass
788
+
789
+
790
+ class LoopTerminator:
791
+ """A simple class for inserting into the flow.
792
+ This is created automatically when the loop is created"""
793
+
794
+ def __init__(self, loop):
795
+ super(LoopTerminator, self).__init__()
796
+ self.loop = loop
797
+ self.exp = loop.exp
798
+ loop.terminator = self
799
+
800
+ @property
801
+ def _xml(self):
802
+ # Make root element
803
+ element = Element("LoopTerminator")
804
+ element.set("name", self.loop.params['name'].val)
805
+
806
+ return element
807
+
808
+ @property
809
+ def name(self):
810
+ return self.loop.name
811
+
812
+ def getType(self):
813
+ return 'LoopTerminator'
814
+
815
+ def writeInitCode(self, buff):
816
+ pass
817
+
818
+ def writeMainCode(self, buff):
819
+ self.loop.writeLoopEndCode(buff)
820
+ # _loopList[-1] will now be the inner-most loop
821
+ self.exp.flow._loopList.remove(self.loop)
822
+
823
+ def writeMainCodeJS(self, buff, modular):
824
+ self.loop.writeLoopEndCodeJS(buff)
825
+ # _loopList[-1] will now be the inner-most loop
826
+ self.exp.flow._loopList.remove(self.loop)
827
+
828
+ def writeExperimentEndCode(self, buff): # not needed
829
+ pass