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,680 @@
1
+ """
2
+ Python support for `Brain Products GMBH <https://www.brainproducts.com>`_ hardware.
3
+
4
+ Here we have implemented support for the Remote Control Server application,
5
+ which allows you to control recordings, send annotations etc. all from Python.
6
+ """
7
+
8
+ import socket
9
+ import time
10
+ import threading
11
+ import weakref
12
+ from psychopy import logging
13
+
14
+ _appStates = {
15
+ 'AP:0': 'Closed',
16
+ 'AP:1': 'Open',
17
+ 'AP:-1': 'Errored',
18
+ }
19
+
20
+ _recordingStates = {
21
+ 'RS:0': 'Idle',
22
+ 'RS:1': 'Monitoring',
23
+ 'RS:2': 'Calibration',
24
+ 'RS:3': 'Impedance check',
25
+ 'RS:4': 'Recording', # the manual calls this Saving (recording)"
26
+ 'RS:5': 'Saving calibration', # the manual calls this "Saving calibration"
27
+ 'RS:6': 'Paused',
28
+ 'RS:7': 'Paused calibration',
29
+ 'RS:8': 'Paused impedance check',
30
+ }
31
+
32
+ _acquisitionStates = {
33
+ 'AQ:0': 'Stopped',
34
+ 'AQ:1': 'Running',
35
+ 'AQ:2': 'Warning',
36
+ 'AQ:3': 'Error',
37
+ }
38
+
39
+
40
+ class RemoteControlServer:
41
+ """
42
+ Provides a remote-control interface to BrainProducts Recorder.
43
+
44
+ Example usage::
45
+
46
+ import time
47
+ from psychopy import logging
48
+ from psychopy.hardware import brainproducts
49
+
50
+ logging.console.setLevel(logging.DEBUG)
51
+ rcs = brainproducts.RemoteControlServer()
52
+ rcs.open('testExp',
53
+ workspace='C:/Vision/Workfiles/Standard Workspace.rwksp',
54
+ participant='S0021')
55
+ rcs.openRecorder()
56
+ time.sleep(2)
57
+ rcs.mode = 'monitor' # or 'impedance', or 'default'
58
+ rcs.startRecording()
59
+ time.sleep(2)
60
+ rcs.sendAnnotation('124', 'STIM')
61
+ time.sleep(1)
62
+ rcs.pauseRecording()
63
+ time.sleep(1)
64
+ rcs.resumeRecording()
65
+ time.sleep(1)
66
+ rcs.stopRecording()
67
+ time.sleep(1)
68
+ rcs.mode = 'default' # stops monitoring mode
69
+
70
+ """
71
+
72
+ def __init__(self, host='127.0.0.1', port=6700, timeout=1.0,
73
+ testMode=False):
74
+ """To initialize the remote control recorder.
75
+
76
+ Parameters
77
+ ----------
78
+ host : string, optional
79
+ The IP address or hostname of the computer running RCS.
80
+ Defaults to ``127.0.0.1``.
81
+ port : int, optional
82
+ The port on which RCS is listening for a connection on the
83
+ EEG computer. This should usually not need to be changed.
84
+ Defaults to ``6700``.
85
+ timeout : float, optional
86
+ The timeout (in seconds) to wait for sending/receivign commands
87
+ testMode : bool, optional
88
+ If ``True``, the network connection to the RCS computer will
89
+ not actually be initialized.
90
+ Defaults to ``False``.
91
+ """
92
+ self._testMode = testMode
93
+
94
+ self.applicationState = None
95
+ self.recordingState = None
96
+ self.acquisitionState = None
97
+
98
+ self._host = host
99
+ self._port = port
100
+ self._recording = False
101
+ self._timeout = timeout
102
+
103
+ # various properties that are initially unknown
104
+ self._mode = 'default'
105
+ self._exp_name = None
106
+ self._participant = None
107
+ self._workspace = None
108
+ self._amplifier = None
109
+ self._overwriteProtection = None
110
+ self._RCSversion = None
111
+
112
+ self._bufferChars = '' # unprocessed stream from RCS
113
+ self._bufferList = [] # list of messages
114
+ self._socket = socket.socket(socket.AF_INET,
115
+ socket.SOCK_STREAM)
116
+ self._socket.settimeout(self._timeout)
117
+
118
+ try:
119
+ self._socket.connect((self._host, self._port))
120
+ except socket.error:
121
+ if not self._testMode:
122
+ msg = ('Could not connect to RCS at %s:%s. Make sure the '
123
+ 'Remote Control Server software is running and set '
124
+ 'to "Connect"' %
125
+ (self._host, self._port))
126
+ raise RuntimeError(msg)
127
+ else:
128
+ pass
129
+
130
+ self._listener = _ListenerThread(self)
131
+ self._listener.start()
132
+
133
+ def sendRaw(self, message, checkOutput='OK'):
134
+ """A helper function to send raw messages (strings) to the RCS.
135
+
136
+ This is normally only used for debugging purposes and is not
137
+ needed by most users.
138
+
139
+ Parameters
140
+ ----------
141
+ message : string
142
+ The string that will be sent
143
+ checkOutput : string (default='OK')
144
+ If a value is provided then this will be checked for by
145
+ this function. If no check is needed then set checkOutput=None
146
+ """
147
+ # Append \r if it's not already part of the message: RCS
148
+ # uses this as command separators.
149
+ if self._testMode:
150
+ return
151
+
152
+ # check for reply
153
+ if not message.endswith('\r') or not message.endswith('\r\n'):
154
+ message += '\r'
155
+ if type(message) != bytes:
156
+ message = message.encode('utf-8')
157
+ self._socket.sendall(message)
158
+
159
+ # did reply include OK message?
160
+ if not checkOutput:
161
+ return
162
+ # wait for message with expected output (means OK)
163
+ reply = self.waitForMessage(endswith=checkOutput)
164
+ if not reply:
165
+ logging.warning(
166
+ "RCS Didn't receive expected response from RCS to "
167
+ "the message {}. Current stack of recent responses:{}."
168
+ .format(message, self._listener.messages))
169
+ logging.flush()
170
+ else:
171
+ return True
172
+
173
+ def waitForMessage(self, containing='', endswith=''):
174
+ """Wait for a message, optionally one that meets certain criteria
175
+
176
+ Parameters
177
+ ----------
178
+ containing : str
179
+ A string the message must contain
180
+ endswith : str
181
+ A string the message must end with (ignoring newline characters)
182
+
183
+ Returns
184
+ -------
185
+ The (complete) message string if one was received or None if not
186
+ """
187
+ # check output
188
+ OK = False
189
+ t0 = time.time()
190
+ while time.time() - t0 < self._timeout and not OK:
191
+ for reply in self._listener.messages:
192
+ if reply.endswith(endswith) and containing in reply:
193
+ logging.debug("RCS received {}".format(repr(reply)))
194
+ self._listener.messages.remove(reply)
195
+ return reply
196
+
197
+ def waitForState(self, stateName, permitted, timeout=10):
198
+ """Helper function to wait for a particular state (or any attribute, for that matter)
199
+ to have a particular value. Beware this will wait indefinitely, so only call
200
+ if you are confident that the state will eventually arrive!
201
+
202
+ Parameters
203
+ ----------
204
+ stateName : str
205
+ Name of the state (e.g. "applicationState")
206
+ permitted : list
207
+ List of values that are permitted before returning
208
+
209
+ """
210
+ if type(permitted) is not list:
211
+ raise TypeError("permitted must be a list of permitted values")
212
+ t0 = time.time()
213
+ while getattr(self, stateName) not in permitted:
214
+ time.sleep(0.01)
215
+ if time.time()-t0 > timeout:
216
+ logging.warning(
217
+ f'RCS {stateName} not achieved: expected states {permitted} but state is {getattr(self, stateName)}'
218
+ )
219
+ return
220
+
221
+ def open(self, expName, participant, workspace):
222
+ """Opens a study/workspace on the RCS server
223
+
224
+ Parameters
225
+ ----------
226
+ expName : str
227
+ Name of the experiment. Will make up the first part of the
228
+ EEG filename.
229
+ participant : str
230
+ Participant identifier. Will make up the second part of the
231
+ EEG filename.
232
+ workspace : str
233
+ The full path to the workspace file (.rwksp), with forward slashes
234
+ as path separators. e.g. "c:/myFolder/mySetup.rwksp"
235
+ """
236
+ self.workspace = workspace
237
+ self.participant = participant
238
+ self.expName = expName
239
+ # all appears OK
240
+ logging.info(
241
+ 'RCS connected: {} - {}'.format(self.expName, self.participant))
242
+
243
+ def openRecorder(self):
244
+ """Opens the Recorder application from the Remote Control.
245
+
246
+ Neat, huh?!
247
+ """
248
+ msg = 'O'
249
+ self.sendRaw(msg, checkOutput="O:OK")
250
+ # after reporting OK it should also change the status
251
+ self.waitForState("applicationState", ["Open"])
252
+ self.waitForState("recordingState", ["Idle"])
253
+ # check that the RCS is using the correct messaging version
254
+ self.sendRaw("VM", checkOutput="VM:2")
255
+
256
+ def _updateState(self, msg):
257
+ # Update our state variables from a state message
258
+ if msg[:2] == 'AP':
259
+ self.applicationState = _appStates[msg]
260
+ logging.info('RCS Recorder app is now {}'
261
+ .format(self.applicationState.upper()))
262
+ elif msg[:2] == 'RS':
263
+ self.recordingState = _recordingStates[msg]
264
+ logging.info('RCS Recorder State is now {}'
265
+ .format(self.recordingState.upper()))
266
+ elif msg[:2] == 'AQ':
267
+ self.acquisitionState = _acquisitionStates[msg]
268
+ logging.info('RCS Acq is now {}'
269
+ .format(self.acquisitionState.upper()))
270
+ else:
271
+ raise RuntimeError("RCS._updateState was sent unknown message"
272
+ "'{}'".format(msg))
273
+
274
+ @property
275
+ def workspace(self):
276
+ """
277
+ Get/set the path to the workspace file. An absolute path is required.
278
+
279
+ Example Usage::
280
+
281
+ rcs.workspace = 'C:/Vision/Worksfiles/testing.rwksp'
282
+
283
+ """
284
+ return self._workspace
285
+
286
+ @workspace.setter
287
+ def workspace(self, path):
288
+ msg = '1:%s' % path
289
+ self.sendRaw(msg, checkOutput=msg + ':OK')
290
+
291
+ self._workspace = path
292
+
293
+ @property
294
+ def expName(self):
295
+ """
296
+ Get/set the name of the experiment or study (string)
297
+
298
+ The name will make up the first part of the EEG filename.
299
+
300
+ Example Usage::
301
+
302
+ rcs.expName = 'MyTestStudy'
303
+
304
+ """
305
+ return self._exp_name
306
+
307
+ @expName.setter
308
+ def expName(self, name):
309
+ msg = '2:%s' % name
310
+ self.sendRaw(msg, checkOutput=msg + ':OK')
311
+
312
+ self._exp_name = name
313
+
314
+ @property
315
+ def participant(self):
316
+ """
317
+ Get/set the participant identifier (a string or numeric).
318
+
319
+ This identifier will make up the center part of the EEG filename.
320
+
321
+ """
322
+ return self._participant
323
+
324
+ @participant.setter
325
+ def participant(self, participant):
326
+ msg = '3:{}'.format(participant)
327
+ self.sendRaw(msg, checkOutput=msg + ':OK')
328
+ # keep track of the change
329
+ self._participant = participant
330
+
331
+ @property
332
+ def mode(self):
333
+ """
334
+ Get/set the current mode.
335
+
336
+ Mode is a string that can be one of:
337
+
338
+ - 'default' or 'def' or None will exit special modes
339
+ - 'impedance' or 'imp' for impedance checking
340
+ - 'monitoring' or 'mon'
341
+ - 'test' or 'tes' to go into test view
342
+
343
+ """
344
+ return self._mode
345
+
346
+ @mode.setter
347
+ def mode(self, mode):
348
+ if mode in ['impedance', 'imp']:
349
+ if self.recordingState == "Recording":
350
+ finalRecordingState = "Paused impedance check"
351
+ else:
352
+ finalRecordingState = "Impedance check"
353
+ self._mode = 'impedance'
354
+ msg = 'I'
355
+ elif mode in ['monitor', 'mon']:
356
+ self._mode = 'monitor'
357
+ msg = 'M'
358
+ elif mode in ['test', 'tes']:
359
+ self._mode = 'test'
360
+ msg = 'T'
361
+ elif mode in ['default', 'def', None]:
362
+ self._mode = 'default'
363
+ msg = 'SV'
364
+ else:
365
+ msg = ('`mode` must be one of: impedance, imp, monitor, mon, test '
366
+ 'def, or default.')
367
+ raise ValueError(msg)
368
+
369
+ replyOK = self.sendRaw(msg, checkOutput=msg + ':OK')
370
+ if not replyOK:
371
+ raise IOError(f"Failed to set RCS into mode {mode}. RCS did not reply 'OK'")
372
+
373
+ # now wait for appropriate state changes to match our target mode
374
+ if mode in ['impedance', 'imp']:
375
+ self.waitForState("recordingState", [finalRecordingState])
376
+ self.waitForState("acquisitionState", ["Running"])
377
+ elif mode in ['monitor', 'mon']:
378
+ self.waitForState("recordingState", ["Monitoring"])
379
+ self.waitForState("acquisitionState", ["Running"])
380
+ elif mode in ['test', 'tes']:
381
+ <<<<<<< HEAD
382
+ self.waitForState("recordingState", ["Calibration"])
383
+ =======
384
+ self.waitForState("recordingState", ["Monitoring"])
385
+ >>>>>>> release
386
+ self.waitForState("acquisitionState", ["Running"])
387
+ elif mode in ['default', 'def', None]:
388
+ self.waitForState("recordingState", ["Idle"])
389
+ self.waitForState("acquisitionState", ["Stopped"])
390
+
391
+ @property
392
+ def timeout(self):
393
+ """What is a reasonable timeout in seconds (initially set to 0.5)
394
+
395
+ For some systems (e.g. when the RCS is the same machine) you might want
396
+ to set this to a lower value. For an unpredictable or slow network
397
+ connection you might want to set this to a higher value.
398
+ """
399
+ return self._timeout
400
+
401
+ @timeout.setter
402
+ def timeout(self, timeout):
403
+ self._socket.settimeout(timeout)
404
+ self._timeout = timeout
405
+
406
+ @property
407
+ def amplifier(self):
408
+ """Get/set the amplifier to use. Could be one of
409
+ " ['actiCHamp', 'BrainAmp Family',"
410
+ " 'LiveAmp', 'QuickAmp USB', 'Simulated Amplifier',"
411
+ " 'V-Amp / FirstAmp']
412
+
413
+ For Liveamp you should also provide the serial number,
414
+ comma separated from the amplifier type.
415
+
416
+ Examples:
417
+ rcs = RemoteControlServer()
418
+ rcs.amplifier = 'LiveAmp', 'LA-05490-0200'
419
+ # OR
420
+ rcs.amplifier = 'actiCHamp'
421
+ """
422
+ return self._amplifier
423
+
424
+ @amplifier.setter
425
+ def amplifier(self, amplifier):
426
+ # did we get a tuple/list of ampType, ampSN or just name?
427
+ serialNumber = None
428
+ if len(amplifier) == 2: # e.g. ('LiveAmp', '34834727')
429
+ amplifier, serialNumber = amplifier
430
+ elif len(amplifier) == 1: # e.g. ('actiCHamp')
431
+ amplifier = amplifier[0] # extract string from tuple/list
432
+ else:
433
+ assert type(amplifier) == str # hopefully then we got the name raw
434
+ # check for LiveAmp that we also have a SN
435
+ if amplifier == 'LiveAmp' and not serialNumber:
436
+ logging.warning("LiveAmp may need a serial number. Use\n"
437
+ " rcs.amplifier = 'LiveAmp', 'LA-serialNumberHere'")
438
+ logging.flush()
439
+ if amplifier in ['actiCHamp', 'BrainAmp Family',
440
+ 'LiveAmp', 'QuickAmp USB', 'Simulated Amplifier',
441
+ 'V-Amp / FirstAmp']:
442
+ msg = "SA:{}".format(amplifier)
443
+ self.sendRaw(msg, checkOutput=msg + ':OK')
444
+ else:
445
+ errMsg = (f"Unknown amplifier '{amplifier}'. The `amplifier` value "
446
+ "should be a LiveAmp serial number or one of "
447
+ "['actiCHamp', 'BrainAmp Family',"
448
+ " 'LiveAmp', 'QuickAmp USB', 'Simulated Amplifier',"
449
+ " 'V-Amp / FirstAmp']")
450
+ raise ValueError(errMsg)
451
+ if serialNumber:
452
+ # LiveAmp allows you to send the serial number
453
+ msg = "SN:{}".format(serialNumber)
454
+ self.sendRaw(msg, checkOutput=msg + ':OK')
455
+ self._amplifier = amplifier
456
+ self._amplifierSN = serialNumber
457
+
458
+ @property
459
+ def overwriteProtection(self):
460
+ """An attribute to get/set whether the overwrite protection is turned on.
461
+
462
+ When checking the attribute the state of `rcs.overwriteProtection` a call will be
463
+ made to the RCS and the report is based on the response. There is also a
464
+ variable `rcs._overwriteProtection` that is simply the stored state from the
465
+ most recent call and does not make any further communication with the RCS itself.
466
+
467
+ Usage example::
468
+
469
+ rcs.overwriteProtection = True # set it to be on
470
+ print(rcs.overwriteProtection) # print current state
471
+ """
472
+ reply = self.sendRaw("OW", checkOutput=None) # we'll check this one manually
473
+ # reply is OW:0:OK or OW:1:OK
474
+ if reply == 'OW:0:OK':
475
+ state = False
476
+ elif reply == 'OW:1:OK':
477
+ state = True
478
+ else:
479
+ raise IOError("Request for overwrite state received unknown"
480
+ "response '{}'".format(reply))
481
+ self._overwriteProtection = state
482
+ return self._overwriteProtection
483
+
484
+ @overwriteProtection.setter
485
+ def overwriteProtection(self, value):
486
+ if value not in [True, False]: # or 1, 0 not necess bool type
487
+ raise ValueError("RCS.overwriteProtection should be set to "
488
+ "True or False, not '{}'".format(value))
489
+ msg = "OW:{}".format(int(value))
490
+ self.sendRaw(msg, checkOutput=msg + ':OK')
491
+ self._overwriteProtection = bool(value)
492
+
493
+ @property
494
+ def version(self):
495
+ """Reports the version of the RCS application
496
+
497
+ Example usage::
498
+
499
+ print(rcs.version)
500
+
501
+ """
502
+ if not self._RCSversion:
503
+ # otherwise request info from RCS
504
+ msg = 'VS'
505
+ self.sendRaw(msg, checkOutput='')
506
+ reply = self.waitForMessage(containing='VS:')
507
+ if reply:
508
+ self._RCSversion = reply.strip().replace("VS:")
509
+ else:
510
+ logging.warning("Failed to retrieve the version of the RCS software")
511
+ logging.flush()
512
+ return self._RCSversion
513
+
514
+ def dcReset(self):
515
+ """Use this to reset any DC offset that might have accumulated
516
+ if you aren't using a high-pass filter"""
517
+ msg = 'D'
518
+ self.sendRaw(msg)
519
+
520
+ def startRecording(self):
521
+ """
522
+ Start recording EEG.
523
+
524
+ """
525
+ recordingType = self.recordingState
526
+ if recordingType not in ['Monitoring', 'Calibration', 'Impedance check']:
527
+ msg = ('To start recording, the RCS must be in one of "Monitoring", '
528
+ f'"Calibration" or "Impedance check" states, not {recordingType}')
529
+ raise RuntimeError(msg)
530
+ if self._recording:
531
+ msg = 'Recording is already in progress!'
532
+ raise RuntimeError(msg)
533
+
534
+ msg = 'S'
535
+ self.sendRaw(msg)
536
+
537
+ self.waitForState("recordingState", ["Recording", "Saving calibration"])
538
+ self._recording = True
539
+
540
+ def stopRecording(self):
541
+ """
542
+ Stop recording EEG.
543
+
544
+ """
545
+ if not self._recording:
546
+ msg = 'Recording has not yet been started!'
547
+ raise RuntimeError(msg)
548
+
549
+ msg = 'Q'
550
+ self.sendRaw(msg)
551
+ self.waitForState("recordingState", ["Recording", "Calibration"])
552
+ self._recording = False
553
+
554
+ def pauseRecording(self):
555
+ """
556
+ Pause recording EEG without ending the session.
557
+
558
+ """
559
+ msg = 'P'
560
+ self.sendRaw(msg)
561
+ self.waitForState("recordingState", ["Paused", "Paused calibration"])
562
+
563
+ def resumeRecording(self):
564
+ """
565
+ Resume a paused recording
566
+
567
+ """
568
+ msg = 'C'
569
+ self.sendRaw(msg)
570
+ self.waitForState("recordingState", ["Recording", "Saving calibration"])
571
+
572
+ def sendAnnotation(self, annotation, annType):
573
+ """Sends a message to be logged on the Recorder.
574
+
575
+ The timing of annotations may be imprecise and this
576
+ should not be trusted as a method of sending sync triggers.
577
+
578
+ Annotations can contain any ASCII characters except for ";"
579
+
580
+ Parameters
581
+ -----------------
582
+
583
+ annotation : string
584
+ The description text to be sent in the annotation.
585
+
586
+ annType : string
587
+ The category of the annotation which are user-defined
588
+ strings (e.g. stimulus, response)
589
+
590
+ Example usage::
591
+
592
+ rcs.sendAnnotation("face003", "stimulus")
593
+
594
+ """
595
+ msg = "AN:{};{}".format(annotation, annType)
596
+ self.sendRaw(msg)
597
+
598
+ def close(self):
599
+ """Closes the recording and deletes all associated workspace
600
+ variables (e.g. when a participant has been completed)
601
+ """
602
+ msg = 'X'
603
+ self.sendRaw(msg)
604
+ self.waitForState("recordingState", ["Idle"])
605
+ self.waitForState("acquisitionState", ["Stopped"])
606
+ self.waitForState("applicationState", ["Closed"])
607
+
608
+
609
+ class _ListenerThread(threading.Thread):
610
+ def __init__(self, parent):
611
+ self._socket = parent._socket # type: socket.socket
612
+ self.messages = []
613
+ self._buffer = ''
614
+ threading.Thread.__init__(self, daemon=True)
615
+ self._parentRef = weakref.ref(parent)
616
+ self._is_running = None
617
+
618
+ def run(self):
619
+ """Gets run repeatedly until terminates
620
+ """
621
+ if self._is_running is None:
622
+ self._is_running = True
623
+ while self._is_running:
624
+ try:
625
+ if self._socket._closed:
626
+ break
627
+ recvd = self._socket.recv(512).decode('utf-8')
628
+ self._buffer += recvd
629
+ self.processBuffer()
630
+ except socket.timeout:
631
+ time.sleep(0.1)
632
+ except OSError:
633
+ if self._socket._closed:
634
+ self._is_running = False
635
+
636
+ def processBuffer(self):
637
+
638
+ # check for whole messages:
639
+ nMessages = self._buffer.count('\r')
640
+ msgList = self._buffer.split('\r')
641
+ for msgN in range(nMessages):
642
+ thisMsg = msgList[msgN]
643
+ # remove message from buffer so we don't reuse
644
+ self._buffer = self._buffer.replace(thisMsg + '\r', '')
645
+ # check if the message is a change of state
646
+ if thisMsg[:2] in ['AP', 'RS', 'AQ']:
647
+ self._parentRef()._updateState(thisMsg)
648
+ else:
649
+ self.messages.append(thisMsg)
650
+
651
+ def clear(self):
652
+ self.messages = []
653
+ self._buffer = ''
654
+ while True:
655
+ try:
656
+ self._socket.recv(1)
657
+ except socket.timeout: # no chars left to clear
658
+ return
659
+
660
+
661
+ if __name__ == "__main__":
662
+ logging.console.setLevel(logging.DEBUG)
663
+ rcs = RemoteControlServer()
664
+ rcs.open('testExp',
665
+ workspace='C:/Vision/Workfiles/Standard Workspace.rwksp',
666
+ participant='S0021')
667
+ rcs.openRecorder()
668
+ time.sleep(2)
669
+ rcs.mode = 'monitor' # or 'impedance', or 'default'
670
+ rcs.startRecording()
671
+ time.sleep(2)
672
+ rcs.sendAnnotation('124', 'STIM')
673
+ time.sleep(1)
674
+ rcs.pauseRecording()
675
+ time.sleep(1)
676
+ rcs.resumeRecording()
677
+ time.sleep(1)
678
+ rcs.stopRecording()
679
+ time.sleep(1)
680
+ rcs.mode = 'default' # stops monitoring mode