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
@@ -372,28 +372,6 @@ class MainFrame(wx.Frame):
372
372
  calibBox = wx.StaticBoxSizer(boxLabel)
373
373
 
374
374
  photometerBox = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
375
- # com port entry number
376
- self.comPortLabel = wx.StaticText(parent, -1, " ", size=(150, 20))
377
- # photometer button
378
- # photom type choices should not need localization:
379
- self._photomTypeItems = list([p.longName for p in hardware.getAllPhotometers()] + ["Get more..."])
380
- self.ctrlPhotomType = wx.Choice(parent, -1, name="Type:",
381
- choices=self._photomTypeItems)
382
-
383
- _ports = list(hardware.getSerialPorts())
384
- self._photomChoices = [_translate("Scan all ports")] + _ports
385
- _size = self.ctrlPhotomType.GetSize() + [0, 5]
386
- self.ctrlPhotomPort = wx.ComboBox(parent, -1, name="Port:",
387
- value=self._photomChoices[0],
388
- choices=self._photomChoices,
389
- size=_size)
390
-
391
- self.ctrlPhotomType.Bind(wx.EVT_CHOICE, self.onChangePhotomType)
392
- self.btnFindPhotometer = wx.Button(parent, -1,
393
- _translate("Get Photometer"))
394
- self.Bind(wx.EVT_BUTTON,
395
- self.onBtnFindPhotometer, self.btnFindPhotometer)
396
-
397
375
  # gamma controls
398
376
  self.btnCalibrateGamma = wx.Button(
399
377
  parent, -1, _translate("Gamma Calibration..."))
@@ -419,12 +397,11 @@ class MainFrame(wx.Frame):
419
397
  parent, -1, _translate("Plot spectra"))
420
398
  self.Bind(wx.EVT_BUTTON,
421
399
  self.plotSpectra, self.btnPlotSpectra)
422
- photometerBox.AddMany([self.ctrlPhotomType, self.btnFindPhotometer,
423
- self.ctrlPhotomPort, (0, 0),
424
- self.comPortLabel, (0, 0),
425
- self.btnCalibrateGamma, (0, 0),
426
- self.btnTestGamma, self.btnPlotGamma,
427
- self.btnCalibrateColor, self.btnPlotSpectra])
400
+ photometerBox.AddMany([
401
+ self.btnCalibrateGamma, (0, 0),
402
+ self.btnTestGamma, self.btnPlotGamma,
403
+ self.btnCalibrateColor, self.btnPlotSpectra
404
+ ])
428
405
 
429
406
  # ----GAMMA------------
430
407
  # calibration grid
@@ -787,54 +764,56 @@ class MainFrame(wx.Frame):
787
764
  self.unSavedMonitor = True
788
765
 
789
766
  def onCalibGammaBtn(self, event):
790
- if NO_MEASUREMENTS:
791
- # recalculate from previous measure
792
- lumsPre = self.currentMon.getLumsPre()
793
- lumLevels = self.currentMon.getLevelsPre()
794
- else:
795
- # present a dialogue to get details for calibration
796
- calibDlg = GammaDlg(self, self.currentMon)
797
- if calibDlg.ShowModal() != wx.ID_OK:
798
- calibDlg.Destroy()
799
- return 1
800
- nPoints = int(calibDlg.ctrlNPoints.GetValue())
801
- stimSize = unicodeToFloat(calibDlg.ctrlStimSize.GetValue())
802
- useBits = calibDlg.ctrlUseBits.GetValue()
803
- calibDlg.Destroy()
804
- autoMode = calibDlg.methodChoiceBx.GetStringSelection()
805
- # lib starts at zero but here we allow 1
806
- screen = int(calibDlg.ctrlScrN.GetValue()) - 1
767
+ from psychopy.hardware import monitor, DeviceManager
768
+ from psychopy.visual import Window
807
769
 
808
- # run the calibration itself
809
- lumLevels = monitors.DACrange(nPoints)
810
- _size = self.currentMon.getSizePix()
811
- lumsPre = monitors.getLumSeries(photometer=self.photom,
812
- lumLevels=lumLevels,
813
- useBits=useBits,
814
- autoMode=autoMode,
815
- winSize=_size,
816
- stimSize=stimSize,
817
- monitor=self.currentMon,
818
- screen=screen)
819
-
820
- # allow user to type in values
821
- if autoMode == 'semi':
822
- inputDlg = GammaLumValsDlg(parent=self, levels=lumLevels)
823
- lumsPre = inputDlg.show() # will be [] if user cancels
824
- inputDlg.Destroy()
825
-
826
- # fit the gamma curves
827
- if lumsPre is None or len(lumsPre) > 1:
828
- self.onCopyCalib(1) # create a new dated calibration
829
- self.currentMon.setLumsPre(lumsPre) # save for future
830
- self.currentMon.setLevelsPre(lumLevels) # save for future
831
- self.btnPlotGamma.Enable(True)
832
- self.choiceLinearMethod.Enable()
833
-
834
- # do the fits
835
- self.doGammaFits(lumLevels, lumsPre)
770
+ # get calibration setup params
771
+ dlg = CalibrationSetupDlg(self)
772
+ if dlg.ShowModal() == wx.ID_OK:
773
+ params = dlg.getParams()
836
774
  else:
837
- logging.warning('No lum values captured/entered')
775
+ # abort if cancelled
776
+ return
777
+
778
+ # setup calibration procedure window
779
+ win = Window(
780
+ screen=int(params['screen']),
781
+ checkTiming=False,
782
+ fullscr=True
783
+ )
784
+ # setup calibration procedure device
785
+ device = None
786
+ for profile in DeviceManager.getAvailableDevices(params['photometer']):
787
+ # instantiate from profile
788
+ device = DeviceManager.addDevice(**profile)
789
+ break
790
+ # catch error if no device found
791
+ if device is None:
792
+ win.close()
793
+ wx.MessageBox(
794
+ "Could not find any device matching '{photometer}', aborting calibration.".format(**params),
795
+ parent=self,
796
+ style=wx.ICON_ERROR | wx.OK
797
+ )
798
+ return
799
+
800
+ # run calibration
801
+ try:
802
+ gammaGrid = monitor.calibrateGamma(
803
+ win,
804
+ profile['deviceName'],
805
+ patchSize=float(params['patchSize']),
806
+ nPoints=float(params['nPoints'])
807
+ )
808
+ except Exception as err:
809
+ win.close()
810
+ raise err
811
+ # if gamma was got, set values
812
+ if gammaGrid is not None:
813
+ self.gammaGrid.setData(numpy.array(gammaGrid))
814
+ # teardown
815
+ win.close()
816
+ DeviceManager.removeDevice(profile['deviceName'])
838
817
 
839
818
  def doGammaFits(self, levels, lums):
840
819
  linMethod = self.currentMon.getLinearizeMethod()
@@ -1102,6 +1081,127 @@ class MainFrame(wx.Frame):
1102
1081
  plotWindow.addCanvas(figureCanvas)
1103
1082
 
1104
1083
 
1084
+ class CalibrationSetupDlg(wx.Dialog):
1085
+ """
1086
+ Dialog getting params for a gamma calibration, which also runs said calibration
1087
+ """
1088
+ def __init__(self, parent):
1089
+ from psychopy.app.builder.validators import WarningManager
1090
+ from psychopy.experiment.params import Param
1091
+ from psychopy.app.builder.dialogs.paramCtrls import ParamCtrl, EVT_PARAM_CHANGED
1092
+
1093
+ wx.Dialog.__init__(
1094
+ self,
1095
+ parent,
1096
+ title=_translate("Calibrate gamma"),
1097
+ style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1098
+ )
1099
+ # setup sizer
1100
+ self.border = wx.BoxSizer(wx.VERTICAL)
1101
+ self.SetSizer(self.border)
1102
+ self.sizer = wx.BoxSizer(wx.VERTICAL)
1103
+ self.border.Add(
1104
+ self.sizer, border=12, proportion=1, flag=wx.EXPAND | wx.ALL
1105
+ )
1106
+ # create warnings handler
1107
+ self.warnings = WarningManager(self)
1108
+ # define params
1109
+ self.params = {}
1110
+ self.params['screen'] = Param(
1111
+ 1, valType="code", inputType="single",
1112
+ label=_translate("Screen"),
1113
+ hint=_translate(
1114
+ "Screen to run the calibration on"
1115
+ )
1116
+ )
1117
+ def getPhotometers():
1118
+ from psychopy.experiment.monitor import BasePhotometerDeviceBackend
1119
+ from psychopy.preferences import prefs
1120
+ # start with nothing
1121
+ devices = [(None, "")]
1122
+ # iterate through known photometer device backends
1123
+ for cls in BasePhotometerDeviceBackend.__subclasses__():
1124
+ # add their device class against their label
1125
+ devices.append(
1126
+ (cls.deviceClass, cls.backendLabel)
1127
+ )
1128
+
1129
+ return devices
1130
+ def getPhotometerValues():
1131
+ return [val[0] for val in getPhotometers()]
1132
+ def getPhotometerLabels():
1133
+ return [val[1] for val in getPhotometers()]
1134
+ self.params['photometer'] = Param(
1135
+ getPhotometerValues()[0], valType="str", inputType="choice",
1136
+ allowedVals=getPhotometerValues, allowedLabels=getPhotometerLabels,
1137
+ label=_translate("Photometer"),
1138
+ hint=_translate(
1139
+ "Photometer device, from the device manager, to use for this calibration"
1140
+ )
1141
+ )
1142
+ self.params['patchSize'] = Param(
1143
+ 0.3, valType="code", inputType="single",
1144
+ label=_translate("Patch size"),
1145
+ hint=_translate(
1146
+ "How much of the screen (0-1) the calibration patch should occupy"
1147
+ )
1148
+ )
1149
+ self.params['nPoints'] = Param(
1150
+ 8, valType="code", inputType="single",
1151
+ label=_translate("Calibration points"),
1152
+ hint=_translate(
1153
+ "How many calibration points to use"
1154
+ )
1155
+ )
1156
+ # add param ctrls
1157
+ self.paramCtrls = {}
1158
+ for key, param in self.params.items():
1159
+ # add label
1160
+ lbl = wx.StaticText(
1161
+ self, label=param.label
1162
+ )
1163
+ self.sizer.Add(
1164
+ lbl, border=6, flag=wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT
1165
+ )
1166
+ # add ctrl
1167
+ self.paramCtrls[key] = ParamCtrl(
1168
+ self,
1169
+ 'photometer',
1170
+ param,
1171
+ element=None,
1172
+ warnings=self.warnings
1173
+ )
1174
+ self.sizer.Add(
1175
+ self.paramCtrls[key], border=6, flag=wx.EXPAND | wx.ALL
1176
+ )
1177
+ # add
1178
+ self.paramCtrls['photometer'].Bind(EVT_PARAM_CHANGED, self.onPhotometerChosen)
1179
+ # add buttons
1180
+ btns = self.CreateStdDialogButtonSizer(flags=wx.OK | wx.CANCEL)
1181
+ self.border.Add(
1182
+ btns, border=6, flag=wx.EXPAND | wx.ALL
1183
+ )
1184
+ for btn in btns.GetChildren():
1185
+ if btn.Window is not None and btn.Window.Id == wx.ID_OK:
1186
+ self.okBtn = btn.Window
1187
+ # start off with OK disabled
1188
+ self.okBtn.Disable()
1189
+
1190
+ self.Layout()
1191
+ self.Fit()
1192
+
1193
+ def onPhotometerChosen(self, evt=None):
1194
+ self.okBtn.Enable(
1195
+ bool(self.paramCtrls['photometer'].getValue())
1196
+ )
1197
+
1198
+ def getParams(self):
1199
+ return {
1200
+ key: ctrl.getValue()
1201
+ for key, ctrl in self.paramCtrls.items()
1202
+ }
1203
+
1204
+
1105
1205
  class GammaLumValsDlg(wx.Dialog):
1106
1206
  """A dialogue to manually get the luminance values recorded for each level
1107
1207
  """
@@ -437,7 +437,7 @@ def scanPlugins():
437
437
  if not ep.group.startswith("psychopy"):
438
438
  continue
439
439
  # make sure we have an entry for this distribution
440
- if sys.version.startswith("3.8"):
440
+ if sys.version.startswith("3.8") or sys.version.startswith("3.9"):
441
441
  distName = dist.metadata['name']
442
442
  else:
443
443
  distName = dist.name
@@ -789,10 +789,10 @@ def loadPlugin(plugin):
789
789
  f"Registering entry point {ep.value} (from {plugin}) to {ep.group}:{ep.name}"
790
790
  )
791
791
  try:
792
- ep = ep.load() # load the entry point
792
+ mod = ep.load() # load the entry point
793
793
 
794
794
  # Raise a warning if the plugin is being loaded from a zip file.
795
- if '.zip' in inspect.getfile(ep):
795
+ if '.zip' in inspect.getfile(mod):
796
796
  logging.warning(
797
797
  "Plugin `{}` is being loaded from a zip file. This may "
798
798
  "cause issues with the plugin's functionality.".format(plugin))
@@ -801,7 +801,7 @@ def loadPlugin(plugin):
801
801
  msg = f"Skipping entry point {ep.value} (from {plugin}) to {ep.group}:{ep.name}"
802
802
  # append reason
803
803
  if isinstance(err, ImportError):
804
- msg += f" as {ep.value} cannot be imported."
804
+ msg += f" as {ep.value} cannot be imported ({err})."
805
805
  else:
806
806
  msg += f", reason: {err}"
807
807
  # log message
@@ -811,6 +811,8 @@ def loadPlugin(plugin):
811
811
  _failed_plugins_.append(plugin)
812
812
 
813
813
  continue
814
+ else:
815
+ ep = mod
814
816
 
815
817
  # If we get here, the entry point is valid and we can safely add it
816
818
  # to PsychoPy's namespace.
@@ -0,0 +1,80 @@
1
+ import importlib
2
+ import json
3
+ from pathlib import Path
4
+
5
+
6
+ class DeviceConfig(dict):
7
+ """
8
+ Dict linked to a JSON file, used to store configuration parameters for devices in the Device
9
+ Manager.
10
+
11
+ Parameters
12
+ ----------
13
+ file : str or pathlib.Path
14
+ JSON file this dict is linked to
15
+ """
16
+ def __init__(self, file):
17
+ # load file on init
18
+ self.load(file)
19
+
20
+ def copy(self):
21
+ """
22
+ Create a copy of this DeviceConfig, linked to the same file. Useful for making temporary
23
+ changes which can then be made permenant by calling `save`
24
+
25
+ Returns
26
+ -------
27
+ DeviceConfig
28
+ A new DeviceConfig loaded from the same file as this one.
29
+ """
30
+ return DeviceConfig(self.file)
31
+
32
+ def reload(self):
33
+ """
34
+ Load this DeviceConfig again from its file, overwriting any local changes. Useful if you
35
+ have made and saved changes in a copy and want to make sure this DeviceConfig object
36
+ is up to date with the file.
37
+ """
38
+ self.load(self.file)
39
+
40
+ def load(self, file):
41
+ """
42
+ Load configuration from a given file. Doing so will also set the file that this
43
+ DeviceConfig object is linked to.
44
+
45
+ Parameters
46
+ ----------
47
+ file : str or pathlib.Path
48
+ JSON file to load from
49
+ """
50
+ # start off empty
51
+ self.clear()
52
+ # store file path
53
+ self.file = Path(file)
54
+ # read file
55
+ data = json.loads(
56
+ self.file.read_text()
57
+ )
58
+ # apply
59
+ for key, val in data.items():
60
+ # get class from stored data
61
+ mod = ".".join(
62
+ val['__cls__'].split(".")[:-1]
63
+ )
64
+ name = val['__cls__'].split(".")[-1]
65
+ cls = getattr(importlib.import_module(mod), name)
66
+ # initialise class with profile from stored data
67
+ self[key] = cls.fromJSON(val)
68
+ # make sure device name and device key line up
69
+ self[key].name = key
70
+
71
+ def save(self):
72
+ """
73
+ Save the contents of this DeviceConfig object to its linked JSON file.
74
+ """
75
+ # save
76
+ self.file.write_text(
77
+ json.dumps({
78
+ key: device.toJSON() for key, device in self.items()
79
+ }, indent=True)
80
+ )
@@ -11,6 +11,7 @@ import sys
11
11
  from psychopy import core
12
12
 
13
13
  write_mode = 'w'
14
+ new_line = '\n'
14
15
 
15
16
  hintsFile = 'hints.py'
16
17
  comments_all = []
@@ -68,7 +69,7 @@ for specfile in specfiles:
68
69
 
69
70
  # Output hint.py
70
71
  try:
71
- fp = open(hintsFile, write_mode)
72
+ fp = open(hintsFile, write_mode, newline=new_line)
72
73
  fp.write('#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n')
73
74
  fp.write('# This file was generated by generateHints.py.\n')
74
75
  fp.write('# Following strings are used to localize hints in '
@@ -7,6 +7,7 @@ import sys
7
7
  import platform
8
8
  from pathlib import Path
9
9
  from psychopy import logging
10
+ from . import devices
10
11
  from .. import __version__
11
12
 
12
13
  from packaging.version import Version
@@ -71,7 +72,7 @@ class Preferences:
71
72
  self.paths = {} # this will remain a dictionary
72
73
  self.keys = {} # does not remain a dictionary
73
74
 
74
- self.getPaths()
75
+ # Only call loadAll, which will handle getPaths
75
76
  self.loadAll()
76
77
  # setting locale is now handled in psychopy.localization.init
77
78
  # as called upon import by the app
@@ -101,7 +102,7 @@ class Preferences:
101
102
  print(msg % userCfg)
102
103
  self.loadAll() # reloads, now getting all from .spec
103
104
 
104
- def getPaths(self):
105
+ def getPaths(self, userDir=None):
105
106
  """Get the paths to various directories and files used by PsychoPy.
106
107
 
107
108
  If the paths are not found, they are created. Usually, this is only
@@ -130,6 +131,7 @@ class Preferences:
130
131
  self.paths['resources'] = dirResources
131
132
  self.paths['assets'] = join(dirPsychoPy, "assets")
132
133
  self.paths['tests'] = join(dirPsychoPy, 'tests')
134
+ self.paths['scripts'] = join(dirPsychoPy, 'scripts')
133
135
  # path to libs/frameworks
134
136
  if 'PsychoPy.app/Contents' in exePath:
135
137
  self.paths['libs'] = exePath.replace("MacOS/python", "Frameworks")
@@ -139,16 +141,25 @@ class Preferences:
139
141
  # if there isn't an app folder at all then this is a lib-only psychopy
140
142
  # so don't try to load app prefs etc
141
143
  NO_APP = True
144
+ # get user dir
145
+ if userDir is not None and os.path.isdir(userDir):
146
+ self.paths['userPrefsDir'] = join(
147
+ userDir, '.psychopy3'
148
+ )
149
+ elif sys.platform == 'win32':
150
+ self.paths['userPrefsDir'] = join(
151
+ os.environ['APPDATA'], 'psychopy3'
152
+ )
153
+ else:
154
+ self.paths['userPrefsDir'] = join(
155
+ os.environ['HOME'], '.psychopy3'
156
+ )
157
+ # get system-appropriate spec file
142
158
  if sys.platform == 'win32':
143
159
  self.paths['prefsSpecFile'] = join(prefSpecDir, 'Windows.spec')
144
- self.paths['userPrefsDir'] = join(os.environ['APPDATA'],
145
- 'psychopy3')
146
160
  else:
147
- self.paths['prefsSpecFile'] = join(prefSpecDir,
148
- platform.system() + '.spec')
149
- self.paths['userPrefsDir'] = join(os.environ['HOME'],
150
- '.psychopy3')
151
-
161
+ self.paths['prefsSpecFile'] = join(
162
+ prefSpecDir, platform.system() + '.spec')
152
163
  # directory for files created by the app at runtime needed for operation
153
164
  self.paths['userCacheDir'] = join(self.paths['userPrefsDir'], 'cache')
154
165
 
@@ -175,7 +186,10 @@ class Preferences:
175
186
  except OSError as err:
176
187
  if err.errno != errno.EEXIST:
177
188
  raise
178
-
189
+ # make sure there's a device manager config file
190
+ deviceCfgFile = self.paths['deviceCfgFile'] = Path(self.paths['userPrefsDir']) / "devices.json"
191
+ if not deviceCfgFile.is_file():
192
+ deviceCfgFile.write_text("{}", encoding="utf-8")
179
193
  # site-packages root directory for user-installed packages
180
194
  userPkgRoot = Path(self.paths['packages'])
181
195
 
@@ -268,9 +282,10 @@ class Preferences:
268
282
  Path(self.paths['themes']) / file.name
269
283
  )
270
284
 
271
- def loadAll(self):
285
+ def loadAll(self, userDir=None):
272
286
  """Load the user prefs and the application data
273
287
  """
288
+ self.getPaths(userDir=userDir)
274
289
  self._validator = validate.Validator()
275
290
 
276
291
  # note: self.paths['userPrefsDir'] gets set in loadSitePrefs()
@@ -340,6 +355,15 @@ class Preferences:
340
355
  cfg.write()
341
356
 
342
357
  return cfg
358
+
359
+ @property
360
+ def devices(self):
361
+ if not hasattr(self, "_devices"):
362
+ self._devices = devices.DeviceConfig(
363
+ self.paths['deviceCfgFile']
364
+ )
365
+
366
+ return self._devices
343
367
 
344
368
  def saveUserPrefs(self):
345
369
  """Validate and save the various setting to the appropriate files