psychopy 2025.1.0__py3-none-any.whl → 2025.2.1__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 (226) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/colors.py +10 -5
  113. psychopy/data/experiment.py +133 -23
  114. psychopy/data/routine.py +12 -0
  115. psychopy/data/staircase.py +42 -20
  116. psychopy/data/trial.py +20 -12
  117. psychopy/data/utils.py +43 -3
  118. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  120. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  121. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  122. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  123. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  124. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  125. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  126. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  127. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  128. psychopy/event.py +20 -15
  129. psychopy/experiment/_experiment.py +86 -10
  130. psychopy/experiment/components/__init__.py +3 -10
  131. psychopy/experiment/components/_base.py +9 -20
  132. psychopy/experiment/components/button/__init__.py +1 -1
  133. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  134. psychopy/experiment/components/camera/__init__.py +137 -359
  135. psychopy/experiment/components/keyboard/__init__.py +17 -24
  136. psychopy/experiment/components/microphone/__init__.py +61 -110
  137. psychopy/experiment/components/movie/__init__.py +2 -3
  138. psychopy/experiment/components/serialOut/__init__.py +192 -93
  139. psychopy/experiment/components/settings/__init__.py +45 -27
  140. psychopy/experiment/components/sound/__init__.py +82 -73
  141. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  142. psychopy/experiment/devices.py +303 -0
  143. psychopy/experiment/exports.py +20 -18
  144. psychopy/experiment/flow.py +7 -0
  145. psychopy/experiment/loops.py +47 -29
  146. psychopy/experiment/monitor.py +74 -0
  147. psychopy/experiment/params.py +48 -10
  148. psychopy/experiment/plugins.py +28 -108
  149. psychopy/experiment/py2js_transpiler.py +1 -1
  150. psychopy/experiment/routines/__init__.py +1 -1
  151. psychopy/experiment/routines/_base.py +59 -24
  152. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  153. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  154. psychopy/hardware/__init__.py +20 -57
  155. psychopy/hardware/button.py +15 -2
  156. psychopy/hardware/camera/__init__.py +2237 -1394
  157. psychopy/hardware/joystick/__init__.py +1 -1
  158. psychopy/hardware/keyboard.py +5 -8
  159. psychopy/hardware/listener.py +4 -1
  160. psychopy/hardware/manager.py +75 -35
  161. psychopy/hardware/microphone.py +53 -7
  162. psychopy/hardware/monitor.py +144 -0
  163. psychopy/hardware/photometer/__init__.py +156 -117
  164. psychopy/hardware/serialdevice.py +16 -2
  165. psychopy/hardware/soundsensor.py +4 -1
  166. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  167. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  168. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  169. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  170. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  171. psychopy/iohub/util/__init__.py +7 -8
  172. psychopy/localization/generateTranslationTemplate.py +208 -116
  173. psychopy/localization/messages.pot +4305 -3502
  174. psychopy/monitors/MonitorCenter.py +174 -74
  175. psychopy/plugins/__init__.py +6 -4
  176. psychopy/preferences/devices.py +80 -0
  177. psychopy/preferences/generateHints.py +2 -1
  178. psychopy/preferences/preferences.py +35 -11
  179. psychopy/scripts/psychopy-pkgutil.py +969 -0
  180. psychopy/scripts/psyexpCompile.py +1 -1
  181. psychopy/session.py +34 -38
  182. psychopy/sound/__init__.py +6 -260
  183. psychopy/sound/audioclip.py +164 -0
  184. psychopy/sound/backend_ptb.py +8 -0
  185. psychopy/sound/backend_pygame.py +10 -0
  186. psychopy/sound/backend_pysound.py +9 -0
  187. psychopy/sound/backends/__init__.py +0 -0
  188. psychopy/sound/microphone.py +3 -0
  189. psychopy/sound/sound.py +58 -0
  190. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  191. psychopy/tests/data/duplicateHeaders.csv +2 -0
  192. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  193. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  194. psychopy/tests/test_data/test_utils.py +5 -1
  195. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  196. psychopy/tests/test_hardware/test_ports.py +0 -12
  197. psychopy/tests/test_tools/test_stringtools.py +1 -1
  198. psychopy/tools/attributetools.py +12 -5
  199. psychopy/tools/fontmanager.py +17 -14
  200. psychopy/tools/gltools.py +4 -2
  201. psychopy/tools/movietools.py +43 -2
  202. psychopy/tools/stringtools.py +33 -8
  203. psychopy/tools/versionchooser.py +1 -1
  204. psychopy/validation/audio.py +5 -1
  205. psychopy/validation/visual.py +5 -1
  206. psychopy/visual/basevisual.py +8 -7
  207. psychopy/visual/circle.py +2 -2
  208. psychopy/visual/helpers.py +3 -1
  209. psychopy/visual/image.py +29 -109
  210. psychopy/visual/movies/__init__.py +1800 -313
  211. psychopy/visual/polygon.py +4 -0
  212. psychopy/visual/shape.py +2 -2
  213. psychopy/visual/window.py +35 -12
  214. psychopy/voicekey/__init__.py +41 -669
  215. psychopy/voicekey/labjack_vks.py +7 -48
  216. psychopy/voicekey/parallel_vks.py +7 -42
  217. psychopy/voicekey/vk_tools.py +114 -263
  218. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
  219. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
  220. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  221. psychopy/visual/movies/players/__init__.py +0 -62
  222. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  223. psychopy/voicekey/demo_vks.py +0 -12
  224. psychopy/voicekey/signal.py +0 -42
  225. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  226. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -18,151 +18,190 @@ __all__ = [
18
18
  'getAllPhotometerClasses'
19
19
  ]
20
20
 
21
+ from psychopy.hardware.base import BaseResponseDevice, BaseResponse
22
+ from psychopy import layout, logging
23
+
21
24
  from psychopy.plugins import PluginStub
22
25
 
23
- # Special handling for legacy classes which have been offloaded to optional
24
- # packages. This will change to allow more flexibility in the future to avoid
25
- # updating this package for additions to these sub-packages. We'll need a
26
- # photometer type to do that, but for now we're doing it like this.
27
- from psychopy.hardware.crs.colorcal import ColorCAL
28
- from psychopy.hardware.crs.optical import OptiCAL
29
26
 
30
- # Photo Resaerch Inc. spectroradiometers
31
- from psychopy.hardware.pr import PR655, PR650
27
+ class PhotometerResponse(BaseResponse):
28
+ """
29
+ Response from a photometer device. Value can be a single integer, could represent overall
30
+ luminance or the value of a single gun.
31
+ """
32
+ pass
32
33
 
33
- # Konica Minolta light-measuring devices
34
- from psychopy.hardware.minolta import LS100, CS100A
35
34
 
36
- # Gamma scientific devices
37
- from psychopy.hardware.gammasci import S470
35
+ class BasePhotometerDevice(BaseResponseDevice):
36
+ responseClass = PhotometerResponse
38
37
 
39
- # photometer interfaces will be stored here after being registered
40
- photometerInterfaces = {}
38
+ def getLum(self):
39
+ """
40
+ Get luminance according to pixel values.
41
+ """
42
+ # dispatch messages and return the most recent
43
+ self.dispatchMessages()
44
+ if self.responses:
45
+ resp = self.responses[-1]
46
+ return resp.value
47
+ else:
48
+ # if no messages, assume no luminance
49
+ return 0
41
50
 
42
51
 
43
- def addPhotometer(cls):
44
- """Register a photometer interface class.
52
+ class ScreenBufferPhotometerDevice(BasePhotometerDevice):
53
+ """
54
+ Samples pixel colors from the screen buffer to emulate a photometer. Useful only for teaching,
55
+ as the output will always behave as if the screen is perfectly calibrated, as there's no
56
+ physical measurement involved.
57
+
58
+ Parameters
59
+ ----------
60
+ win : psychopy.visual.Window
61
+ Window to pull pixel values from
62
+ pos : tuple, list
63
+ Position of the patch of pixels to pretend there is a photometer looking at
64
+ size : tuple, list
65
+ Size of the patch of pixels to pretend there is a photometer looking at
66
+ units : str
67
+ "Spatial units in which to interpret size and position"
68
+ """
69
+ def __init__(self, win, pos=None, size=None, units=None):
70
+ # initialize
71
+ BaseResponseDevice.__init__(self)
72
+ # store win
73
+ self.win = win
74
+ # default rect
75
+ self.rect = None
76
+ # make clock
77
+ from psychopy.core import Clock
78
+ self.clock = Clock()
79
+ # store position params
80
+ self.units = units
81
+ self.pos = pos
82
+ self.size = size
83
+
84
+ @property
85
+ def pos(self):
86
+ return getattr(self._pos, self.units)
87
+
88
+ @pos.setter
89
+ def pos(self, value):
90
+ self._pos = layout.Position(value, units=self.units, win=self.win)
91
+
92
+ @property
93
+ def size(self):
94
+ return getattr(self._size, self.units)
95
+
96
+ @size.setter
97
+ def size(self, value):
98
+ self._size = layout.Size(value, units=self.units, win=self.win)
99
+
100
+ def dispatchMessages(self):
101
+ """
102
+ When called, dispatch a single reading.
103
+ """
104
+ # get rect
105
+ left, bottom = self._pos.pix + self.win.size / 2
106
+ w, h = self._size.pix
107
+ left = int(left - w / 2)
108
+ bottom = int(bottom - h / 2)
109
+ w = int(w)
110
+ h = int(h)
111
+ # read front buffer luminances for specified area
112
+ pixels = self.win._getPixels(
113
+ buffer="front",
114
+ rect=(left, bottom, w, h),
115
+ makeLum=True
116
+ )
117
+ # dispatch a message
118
+ self.receiveMessage(
119
+ self.parseMessage(pixels.mean() / 255)
120
+ )
121
+
122
+ def parseMessage(self, message):
123
+ return PhotometerResponse(
124
+ t=self.clock.getTime(),
125
+ value=message,
126
+ device=self
127
+ )
128
+
129
+ def isSameDevice(self, other):
130
+ return isinstance(other, ScreenBufferPhotometerDevice)
131
+
132
+ @staticmethod
133
+ def getAvailableDevices():
134
+ # there's only ever one
135
+ return [{
136
+ 'deviceName': "Photometer Emulator",
137
+ 'deviceClass': "psychopy.hardware.photometer.ScreenBufferPhotometerDevice",
138
+ 'win': "session.win",
139
+ }]
140
+
141
+
142
+ # --- legacy methods ---
45
143
 
46
- Once a photometer class is registered, it will be discoverable when
47
- :func:`getAllPhotometers()` is called. This function is also used by the
48
- plugin interface to add new interfaces at runtime.
49
144
 
50
- This function will overwrite interface with the same `driverFor` name
51
- automatically.
145
+ def addPhotometer(cls):
146
+ """
147
+ DEPRECATED: Photometer classes are added on import, so this function is no longer needed.
52
148
 
53
149
  Parameters
54
150
  ----------
55
151
  cls : Any
56
152
  Class specifying a photometer interface.
57
-
58
153
  """
59
- global photometerInterfaces
60
-
61
- # photometers interfaces are IDed by the model they interface with
62
- if not hasattr(cls, 'driverFor') or cls.driverFor is None:
63
- raise AttributeError(
64
- "Photometer interface class does not define member `driverFor` and "
65
- "cannot be added.")
66
-
67
- # add interface references to dictionary
68
- if isinstance(cls.driverFor, (list, tuple)):
69
- # multiple devices sharing the same interface
70
- for devModel in cls.driverFor:
71
- if not isinstance(devModel, str): # items must be all strings
72
- raise TypeError(
73
- "Invalid item type for array `driverFor`. Items must all "
74
- "have type `str`.")
75
- photometerInterfaces[devModel] = cls
76
- elif isinstance(cls.driverFor, str):
77
- devModel = cls.driverFor
78
- photometerInterfaces[devModel] = cls
79
- else:
80
- raise TypeError(
81
- "Invalid type for `driverFor` member specified. Must be either "
82
- "`str`, `tuple` or `list`.")
154
+ logging.warning(
155
+ "`addPhotometer` is deprecated, photometer classes are added on import so this function "
156
+ "is not needed."
157
+ )
83
158
 
84
159
 
85
160
  def getAllPhotometers():
86
- """Gets all available photometers.
87
-
88
- The returned photometers may vary depending on which drivers are installed.
89
- Standalone PsychoPy ships with libraries for all supported photometers.
161
+ """
162
+ Legacy method to get available photometers. Will return subclasses of BasePhotometerDevice as
163
+ well as legacy handlers for previously supported devices.
90
164
 
91
165
  Returns
92
166
  -------
93
167
  dict
94
- A mapping of all photometer classes. Where the keys (`str`) are model
95
- names the interface works with and the values are references to the
96
- unbound interface class associated with it. Keys can have the same value
97
- the interface is common to multiple devices.
98
-
168
+ Device classes against the names by which to represent them.
99
169
  """
100
- # Given that we need to preserve legacy namespaces for the time being, we
101
- # need to import supported photometer classes from their extant namespaces.
102
- # In the future, all photometer classes will be identified by possessing a
103
- # common base class and being a member of this module. This is much like
104
- # how Builder components are discovered.
105
-
106
- # build a dictionary with names
107
- foundPhotometers = {}
108
-
109
- # Classes from extant namespaces. Even though these are optional, we need
110
- # to respect the namespaces for now.
111
- optionalPhotometers = (
112
- 'ColorCAL', 'OptiCAL', 'S470', 'PR650', 'PR655', 'LS100', 'CS100A')
113
- incPhotomList = []
114
- for photName in optionalPhotometers:
115
- try:
116
- photClass = globals()[photName]
117
- except (ImportError, AttributeError):
118
- continue
119
- if issubclass(photClass, PluginStub):
120
- continue
121
- incPhotomList.append(photClass)
122
-
123
- # iterate over all classes and register them as if they were plugins
124
- for photom in incPhotomList:
125
- addPhotometer(photom)
126
-
127
- # Merge with classes from plugins. Duplicate names will be overwritten by
128
- # the plugins.
129
- foundPhotometers.update(photometerInterfaces)
130
-
131
- return foundPhotometers.copy()
170
+ # get photometer classes the new way: by looking for subclasses of BasePhotometerDevice
171
+ found = BasePhotometerDevice.__subclasses__()
172
+ # import classes which used to be in PsychoPy
173
+ from psychopy.hardware.crs.colorcal import ColorCAL
174
+ from psychopy.hardware.crs.optical import OptiCAL
175
+ from psychopy.hardware.pr import PR655, PR650
176
+ from psychopy.hardware.minolta import LS100, CS100A
177
+ from psychopy.hardware.gammasci import S470
178
+ # include any which aren't PluginStub's
179
+ for cls in (
180
+ ColorCAL,
181
+ OptiCAL,
182
+ PR655,
183
+ PR650,
184
+ LS100,
185
+ CS100A,
186
+ S470
187
+ ):
188
+ if not issubclass(cls, PluginStub):
189
+ found.append(cls)
190
+
191
+ return {
192
+ cls.__name__: cls
193
+ for cls in found
194
+ }
132
195
 
133
196
 
134
197
  def getAllPhotometerClasses():
135
- """Get unique photometer interface classes presently available.
136
-
137
- This is used to preserve compatibility with the legacy
138
- :func:`~psychopy.hardware.getAllPhotometers()` function call.
198
+ """
199
+ Legacy method to get available photometers. Will return subclasses of BasePhotometerDevice as
200
+ well as legacy handlers for previously supported devices.
139
201
 
140
202
  Returns
141
203
  -------
142
- list
143
- Discovered unique photometer classes.
144
-
204
+ dict
205
+ Device classes against the names by which to represent them.
145
206
  """
146
- # iterate over known photometers
147
- photometers = getAllPhotometers()
148
-
149
- if not photometers: # do nothing if no photometers found
150
- return []
151
-
152
- interfaceIDs = [] # a store unique IDs for interfaces
153
- # Remove items the are duplicated, i.e. multiple IDs that have a common
154
- # interface.
155
- knownInterfaces = []
156
- for cls in photometers.values():
157
- clsID = id(cls)
158
- if clsID in interfaceIDs: # already added
159
- continue
160
-
161
- interfaceIDs.append(clsID)
162
- knownInterfaces.append(cls)
163
-
164
- return knownInterfaces
165
-
166
-
167
- if __name__ == "__main__":
168
- pass
207
+ return getAllPhotometers()
@@ -293,7 +293,21 @@ class SerialDevice(BaseDevice, AttributeGetSetMixin):
293
293
  t = time.time() - start
294
294
  resp = self.com.read()
295
295
  # get remaining chars
296
- resp += self.com.readall()
296
+ active = True
297
+ while active and t < timeout:
298
+ # get resp
299
+ thisResp = self.com.read_until(self.eol)
300
+ # concatenate it to what we already have
301
+ resp += thisResp
302
+ # decide whether to continue
303
+ if thisResp.endswith(self.eol) and not multiline:
304
+ # in single line mode, always stop after an eol
305
+ active = False
306
+ else:
307
+ # otherwise, continue so long as we have a response
308
+ active = len(thisResp)
309
+ # wait for more
310
+ self.pause()
297
311
  # if we timed out, return None
298
312
  if t > timeout:
299
313
  return
@@ -301,7 +315,7 @@ class SerialDevice(BaseDevice, AttributeGetSetMixin):
301
315
  resp = resp.decode('utf-8')
302
316
  # if multiline, split by eol
303
317
  if multiline:
304
- resp = resp.split(str(self.eol))
318
+ resp = resp.split(self.eol.decode("utf-8"))
305
319
 
306
320
  return resp
307
321
 
@@ -346,6 +346,9 @@ class MicrophoneSoundSensor(BaseSoundSensorGroup):
346
346
  self.getThreshold(channel=channel) * (max(self.dbRange) - min(self.dbRange))
347
347
  )
348
348
 
349
+ def getCurrentVolume(self):
350
+ return self.device.getCurrentVolume()
351
+
349
352
  def _setThreshold(self, threshold, channel=None):
350
353
  """
351
354
  No additional setup is needed for emulator as thresholding is emulated outside of the
@@ -367,7 +370,7 @@ class MicrophoneSoundSensor(BaseSoundSensorGroup):
367
370
  vol = max(vol)
368
371
  # transform volume to arbitrary units
369
372
  adjVol = int(
370
- (vol - min(self.dbRange)) / (max(self.dbRange) - min(self.dbRange)) * 255
373
+ (vol - min(self.dbRange)) / (max(self.dbRange) - min(self.dbRange))
371
374
  )
372
375
  # iterate through channels
373
376
  for channel in range(self.channels):
@@ -490,7 +490,8 @@ def validateDeviceConfiguration(
490
490
  """Validate the device configuration settings provided.
491
491
  """
492
492
  validation_module = importDeviceModule(relative_module_path)
493
- validation_file_path = getSupportedConfigSettings(validation_module)
493
+ validation_file_path = getSupportedConfigSettings(
494
+ validation_module, deviceClassName=device_class_name)
494
495
 
495
496
  # use a default config if we can't get the YAML file
496
497
  if not os.path.exists(validation_file_path):
@@ -9,6 +9,7 @@ try:
9
9
  from psychopy_eyetracker_gazepoint.gazepoint.gp3 import (
10
10
  __file__,
11
11
  EyeTracker,
12
+ GazepointSampleEvent,
12
13
  MonocularEyeSampleEvent,
13
14
  BinocularEyeSampleEvent,
14
15
  FixationStartEvent,
@@ -16,8 +17,7 @@ try:
16
17
  SaccadeStartEvent,
17
18
  SaccadeEndEvent,
18
19
  BlinkStartEvent,
19
- BlinkEndEvent,
20
- GazepointSampleEvent
20
+ BlinkEndEvent
21
21
  )
22
22
  except (ModuleNotFoundError, ImportError, NameError):
23
23
  logging.error(
@@ -9,6 +9,7 @@ try:
9
9
  from psychopy_eyetracker_gazepoint.gazepoint.gp3 import (
10
10
  __file__,
11
11
  EyeTracker,
12
+ GazepointSampleEvent,
12
13
  MonocularEyeSampleEvent,
13
14
  BinocularEyeSampleEvent,
14
15
  FixationStartEvent,
@@ -17,5 +17,15 @@ except (ModuleNotFoundError, ImportError, NameError):
17
17
  "'psychopy-eyetracker-gazepoint' to be installed. Please install this "
18
18
  "package and restart the session to enable support.")
19
19
 
20
+ # sample event for this device was moved to the plugin, but we're going to use
21
+ # the original class for compatibility with older plugin versions
22
+ try:
23
+ from psychopy_eyetracker_gazepoint.gazepoint.gp3.eyetracker import (
24
+ GazepointSampleEvent)
25
+ except (ModuleNotFoundError, ImportError, NameError):
26
+ # import it from the old iohub location
27
+ from ....eye_events import GazepointSampleEvent
28
+
29
+
20
30
  if __name__ == "__main__":
21
31
  pass
@@ -11,6 +11,7 @@ from .. import Computer, Device
11
11
 
12
12
  #>>>>>>>>>>>>>>>>>>>>>>>>
13
13
 
14
+ import sys
14
15
  import ctypes
15
16
  import ctypes.util
16
17
  import CoreFoundation
@@ -47,13 +48,10 @@ psychopy_numlock_key_mappings['.'] = 'num_decimal'
47
48
  carbon_path = ctypes.util.find_library('Carbon')
48
49
  carbon = ctypes.cdll.LoadLibrary(carbon_path)
49
50
 
50
- _objc = ctypes.PyDLL(objc._objc.__file__)
51
- _objc.PyObjCObject_New.restype = ctypes.py_object
52
- _objc.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
53
-
54
51
 
55
52
  def objcify(ptr):
56
- return _objc.PyObjCObject_New(ptr, 0, 1)
53
+ return objc.objc_object(c_void_p=ptr)
54
+
57
55
 
58
56
  kTISPropertyUnicodeKeyLayoutData_p = ctypes.c_void_p.in_dll(
59
57
  carbon, 'kTISPropertyUnicodeKeyLayoutData')
@@ -123,6 +121,11 @@ modifier_name_mappings = dict(
123
121
 
124
122
 
125
123
  class Keyboard(ioHubKeyboardDevice):
124
+ """Keyboard device class for iohub on Mac OS X using Quartz.
125
+
126
+ This class uses the Quartz API to monitor keyboard events on Mac OS X.
127
+
128
+ """
126
129
  _last_mod_names = []
127
130
  _OS_MODIFIERS = ([(0x00001, 'lctrl'), (0x02000, 'rctrl'),
128
131
  (0x00002, 'lshift'), (0x00004, 'rshift'),
@@ -168,14 +168,13 @@ def getSupportedConfigSettings(moduleName, deviceClassName=None):
168
168
  fileName = 'supported_config_settings_{0}.yaml'.format(
169
169
  deviceClassName.lower())
170
170
  yamlFile = yamlRoot / pathlib.Path(moduleName.__file__).parent / fileName
171
- if not yamlFile.exists():
172
- raise FileNotFoundError(
173
- "No config file found in module dir for: {0}".format(
174
- moduleName))
175
- logging.debug(
176
- "Found ioHub device configuration file: {0}".format(yamlFile))
177
-
178
- return str(yamlFile)
171
+ if yamlFile.exists():
172
+ logging.debug(
173
+ "Found ioHub device configuration file: {0}".format(yamlFile))
174
+ return str(yamlFile)
175
+ logging.debug(("No configuration matching device class name '{0}' found in "
176
+ "module dir {1}. Using default config file instead.").format(
177
+ deviceClassName, yamlRoot))
179
178
 
180
179
  # file name for yaml file name convention for single file
181
180
  yamlFile = yamlRoot / pathlib.Path('supported_config_settings.yaml')