psychopy 2025.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.
- psychopy/CHANGELOG.txt +3981 -0
- psychopy/GIT_SHA +1 -0
- psychopy/LICENSE.txt +11 -0
- psychopy/LICENSES.txt +55 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +148 -0
- psychopy/alerts/__init__.py +65 -0
- psychopy/alerts/_alerts.py +224 -0
- psychopy/alerts/_errorHandler.py +50 -0
- psychopy/alerts/alertsCatalogue/1000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/1100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/1500.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2110.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2115.yaml +21 -0
- psychopy/alerts/alertsCatalogue/2120.yaml +22 -0
- psychopy/alerts/alertsCatalogue/2150.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2155.yaml +21 -0
- psychopy/alerts/alertsCatalogue/2300.yaml +0 -0
- psychopy/alerts/alertsCatalogue/2500.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3110.yaml +25 -0
- psychopy/alerts/alertsCatalogue/3115.yaml +24 -0
- psychopy/alerts/alertsCatalogue/3200.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
- psychopy/alerts/alertsCatalogue/3400.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3600.yaml +0 -0
- psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4051.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4052.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4100.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4105.yaml +18 -0
- psychopy/alerts/alertsCatalogue/4115.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4120.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4125.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4160.yaml +25 -0
- psychopy/alerts/alertsCatalogue/4200.yaml +0 -0
- psychopy/alerts/alertsCatalogue/4205.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4210.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4305.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4310.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4315.yaml +21 -0
- psychopy/alerts/alertsCatalogue/4320.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4325.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4330.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4335.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4340.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4405.yaml +24 -0
- psychopy/alerts/alertsCatalogue/4505.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4510.yaml +25 -0
- psychopy/alerts/alertsCatalogue/4520.yaml +26 -0
- psychopy/alerts/alertsCatalogue/4530.yaml +23 -0
- psychopy/alerts/alertsCatalogue/4540.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4545.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4550.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4605.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4610.yaml +22 -0
- psychopy/alerts/alertsCatalogue/4615.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4705.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4710.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/5000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/5055.yaml +25 -0
- psychopy/alerts/alertsCatalogue/6000.yaml +0 -0
- psychopy/alerts/alertsCatalogue/6105.yaml +18 -0
- psychopy/alerts/alertsCatalogue/7105.yaml +19 -0
- psychopy/alerts/alertsCatalogue/8105.yaml +21 -0
- psychopy/alerts/alertsCatalogue/8110.yaml +21 -0
- psychopy/alerts/alertsCatalogue/9998.yaml +19 -0
- psychopy/alerts/alertsCatalogue/9999.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +123 -0
- psychopy/alerts/alertsCatalogue/alertTemplate.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +173 -0
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +34 -0
- psychopy/alerts/alerttools.py +322 -0
- psychopy/app/Resources/README.md +12 -0
- psychopy/app/Resources/__init__.py +0 -0
- psychopy/app/Resources/betasplash.png +0 -0
- psychopy/app/Resources/betasplash@2x.png +0 -0
- psychopy/app/Resources/builder.ico +0 -0
- psychopy/app/Resources/classic/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/classic/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/classic/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/classic/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/classic/README.txt +9 -0
- psychopy/app/Resources/classic/__init__.py +0 -0
- psychopy/app/Resources/classic/_layouts.ai +1138 -1
- psychopy/app/Resources/classic/add.png +0 -0
- psychopy/app/Resources/classic/add@2x.png +0 -0
- psychopy/app/Resources/classic/addExp32.png +0 -0
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/alerts.png +0 -0
- psychopy/app/Resources/classic/beta.png +0 -0
- psychopy/app/Resources/classic/browser.png +0 -0
- psychopy/app/Resources/classic/browser@2x.png +0 -0
- psychopy/app/Resources/classic/bug16.png +0 -0
- psychopy/app/Resources/classic/case.png +0 -0
- psychopy/app/Resources/classic/case@2x.png +0 -0
- psychopy/app/Resources/classic/circle_mask.png +0 -0
- psychopy/app/Resources/classic/circle_mask@2x.png +0 -0
- psychopy/app/Resources/classic/clear.png +0 -0
- psychopy/app/Resources/classic/clear@2x.png +0 -0
- psychopy/app/Resources/classic/coderclass16.png +0 -0
- psychopy/app/Resources/classic/coderfunc16.png +0 -0
- psychopy/app/Resources/classic/coderimport16.png +0 -0
- psychopy/app/Resources/classic/coderjs16.png +0 -0
- psychopy/app/Resources/classic/coderpython16.png +0 -0
- psychopy/app/Resources/classic/codervar16.png +0 -0
- psychopy/app/Resources/classic/cogwindow32.png +0 -0
- psychopy/app/Resources/classic/color32.png +0 -0
- psychopy/app/Resources/classic/compile32.png +0 -0
- psychopy/app/Resources/classic/compile_js.png +0 -0
- psychopy/app/Resources/classic/compile_js@2x.png +0 -0
- psychopy/app/Resources/classic/compile_py.png +0 -0
- psychopy/app/Resources/classic/compile_py@2x.png +0 -0
- psychopy/app/Resources/classic/copy16.png +0 -0
- psychopy/app/Resources/classic/currentFile.png +0 -0
- psychopy/app/Resources/classic/currentFile@2x.png +0 -0
- psychopy/app/Resources/classic/delete16.png +0 -0
- psychopy/app/Resources/classic/desktop.png +0 -0
- psychopy/app/Resources/classic/desktop@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/dirup16.png +0 -0
- psychopy/app/Resources/classic/docclose16.png +0 -0
- psychopy/app/Resources/classic/download.png +0 -0
- psychopy/app/Resources/classic/edit.png +0 -0
- psychopy/app/Resources/classic/edit@2x.png +0 -0
- psychopy/app/Resources/classic/editbtn.png +0 -0
- psychopy/app/Resources/classic/editbtn16.png +0 -0
- psychopy/app/Resources/classic/editbtn16@2x.png +0 -0
- psychopy/app/Resources/classic/editbtn@2x.png +0 -0
- psychopy/app/Resources/classic/email.png +0 -0
- psychopy/app/Resources/classic/email@2x.png +0 -0
- psychopy/app/Resources/classic/experiment.png +0 -0
- psychopy/app/Resources/classic/experiment@2x.png +0 -0
- psychopy/app/Resources/classic/expsettings.png +0 -0
- psychopy/app/Resources/classic/expsettings@2x.png +0 -0
- psychopy/app/Resources/classic/file.png +0 -0
- psychopy/app/Resources/classic/file@2x.png +0 -0
- psychopy/app/Resources/classic/fileaudio.png +0 -0
- psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
- psychopy/app/Resources/classic/filecss.png +0 -0
- psychopy/app/Resources/classic/filecss@2x.png +0 -0
- psychopy/app/Resources/classic/filecsv.png +0 -0
- psychopy/app/Resources/classic/filecsv@2x.png +0 -0
- psychopy/app/Resources/classic/filedesign.png +0 -0
- psychopy/app/Resources/classic/filedesign@2x.png +0 -0
- psychopy/app/Resources/classic/filefont.png +0 -0
- psychopy/app/Resources/classic/filefont@2x.png +0 -0
- psychopy/app/Resources/classic/filegit.png +0 -0
- psychopy/app/Resources/classic/filegit@2x.png +0 -0
- psychopy/app/Resources/classic/filehtml.png +0 -0
- psychopy/app/Resources/classic/filehtml@2x.png +0 -0
- psychopy/app/Resources/classic/fileimage.png +0 -0
- psychopy/app/Resources/classic/fileimage@2x.png +0 -0
- psychopy/app/Resources/classic/fileinfo.png +0 -0
- psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
- psychopy/app/Resources/classic/filejs.png +0 -0
- psychopy/app/Resources/classic/filejs@2x.png +0 -0
- psychopy/app/Resources/classic/filejson.png +0 -0
- psychopy/app/Resources/classic/filejson@2x.png +0 -0
- psychopy/app/Resources/classic/filenew.png +0 -0
- psychopy/app/Resources/classic/filenew32.png +0 -0
- psychopy/app/Resources/classic/filenew@2x.png +0 -0
- psychopy/app/Resources/classic/fileopen.png +0 -0
- psychopy/app/Resources/classic/fileopen32.png +0 -0
- psychopy/app/Resources/classic/fileopen@2x.png +0 -0
- psychopy/app/Resources/classic/filepkg.png +0 -0
- psychopy/app/Resources/classic/filepkg@2x.png +0 -0
- psychopy/app/Resources/classic/filepsyexp.png +0 -0
- psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/classic/filepy.png +0 -0
- psychopy/app/Resources/classic/filepy@2x.png +0 -0
- psychopy/app/Resources/classic/filesave.png +0 -0
- psychopy/app/Resources/classic/filesave32.png +0 -0
- psychopy/app/Resources/classic/filesave@2x.png +0 -0
- psychopy/app/Resources/classic/filesaveas.png +0 -0
- psychopy/app/Resources/classic/filesaveas32.png +0 -0
- psychopy/app/Resources/classic/filesaveas@2x.png +0 -0
- psychopy/app/Resources/classic/filetxt.png +0 -0
- psychopy/app/Resources/classic/filetxt@2x.png +0 -0
- psychopy/app/Resources/classic/fileunknown.png +0 -0
- psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
- psychopy/app/Resources/classic/filevideo.png +0 -0
- psychopy/app/Resources/classic/filevideo@2x.png +0 -0
- psychopy/app/Resources/classic/filter.png +0 -0
- psychopy/app/Resources/classic/filter@2x.png +0 -0
- psychopy/app/Resources/classic/find.png +0 -0
- psychopy/app/Resources/classic/find@2x.png +0 -0
- psychopy/app/Resources/classic/folder-open16.png +0 -0
- psychopy/app/Resources/classic/folder16.png +0 -0
- psychopy/app/Resources/classic/foldernew16.png +0 -0
- psychopy/app/Resources/classic/fork.png +0 -0
- psychopy/app/Resources/classic/fork@2x.png +0 -0
- psychopy/app/Resources/classic/github.png +0 -0
- psychopy/app/Resources/classic/github@2x.png +0 -0
- psychopy/app/Resources/classic/globe.png +0 -0
- psychopy/app/Resources/classic/globe@2x.png +0 -0
- psychopy/app/Resources/classic/globe_bug.png +0 -0
- psychopy/app/Resources/classic/globe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/globe_greensync.png +0 -0
- psychopy/app/Resources/classic/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/globe_info.png +0 -0
- psychopy/app/Resources/classic/globe_info@2x.png +0 -0
- psychopy/app/Resources/classic/globe_magnifier.png +0 -0
- psychopy/app/Resources/classic/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/globe_run.png +0 -0
- psychopy/app/Resources/classic/globe_run@2x.png +0 -0
- psychopy/app/Resources/classic/globe_user.png +0 -0
- psychopy/app/Resources/classic/globe_user@2x.png +0 -0
- psychopy/app/Resources/classic/goto16.png +0 -0
- psychopy/app/Resources/classic/greendot.png +0 -0
- psychopy/app/Resources/classic/greendot@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe.png +0 -0
- psychopy/app/Resources/classic/greenglobe@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_bug.png +0 -0
- psychopy/app/Resources/classic/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/classic/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_info.png +0 -0
- psychopy/app/Resources/classic/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/classic/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_run.png +0 -0
- psychopy/app/Resources/classic/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/classic/greenglobe_user.png +0 -0
- psychopy/app/Resources/classic/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/classic/greydot.png +0 -0
- psychopy/app/Resources/classic/greydot@2x.png +0 -0
- psychopy/app/Resources/classic/greytick.png +0 -0
- psychopy/app/Resources/classic/greytick@2x.png +0 -0
- psychopy/app/Resources/classic/invalid_img.png +0 -0
- psychopy/app/Resources/classic/jsPilot.png +0 -0
- psychopy/app/Resources/classic/jsPilot@2x.png +0 -0
- psychopy/app/Resources/classic/jsRun.png +0 -0
- psychopy/app/Resources/classic/jsRun@2x.png +0 -0
- psychopy/app/Resources/classic/libroot.png +0 -0
- psychopy/app/Resources/classic/libroot@2x.png +0 -0
- psychopy/app/Resources/classic/loop.png +0 -0
- psychopy/app/Resources/classic/loop@2x.png +0 -0
- psychopy/app/Resources/classic/monitor16.png +0 -0
- psychopy/app/Resources/classic/monitors.png +0 -0
- psychopy/app/Resources/classic/monitors16.png +0 -0
- psychopy/app/Resources/classic/monitors32.png +0 -0
- psychopy/app/Resources/classic/monitors@2x.png +0 -0
- psychopy/app/Resources/classic/orangedot.png +0 -0
- psychopy/app/Resources/classic/orangedot@2x.png +0 -0
- psychopy/app/Resources/classic/pavlovia.png +0 -0
- psychopy/app/Resources/classic/pavlovia16.png +0 -0
- psychopy/app/Resources/classic/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/classic/pavlovia@2x.png +0 -0
- psychopy/app/Resources/classic/pavsync.png +0 -0
- psychopy/app/Resources/classic/pavsync@2x.png +0 -0
- psychopy/app/Resources/classic/person_off.png +0 -0
- psychopy/app/Resources/classic/person_off@2x.png +0 -0
- psychopy/app/Resources/classic/person_on.png +0 -0
- psychopy/app/Resources/classic/person_on@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/classic/plugins32.png +0 -0
- psychopy/app/Resources/classic/plus.png +0 -0
- psychopy/app/Resources/classic/plus@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-app.png +0 -0
- psychopy/app/Resources/classic/preferences-app48.png +0 -0
- psychopy/app/Resources/classic/preferences-app@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-conn.png +0 -0
- psychopy/app/Resources/classic/preferences-conn48.png +0 -0
- psychopy/app/Resources/classic/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-debug.png +0 -0
- psychopy/app/Resources/classic/preferences-debug@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-general.png +0 -0
- psychopy/app/Resources/classic/preferences-general48.png +0 -0
- psychopy/app/Resources/classic/preferences-general@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware48.png +0 -0
- psychopy/app/Resources/classic/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/classic/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/classic/preferences-pilot.png +0 -0
- psychopy/app/Resources/classic/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/classic/preferences32.png +0 -0
- psychopy/app/Resources/classic/pyPilot.png +0 -0
- psychopy/app/Resources/classic/pyPilot@2x.png +0 -0
- psychopy/app/Resources/classic/pyRun.png +0 -0
- psychopy/app/Resources/classic/pyRun@2x.png +0 -0
- psychopy/app/Resources/classic/reddot.png +0 -0
- psychopy/app/Resources/classic/reddot@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe.png +0 -0
- psychopy/app/Resources/classic/redglobe@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_bug.png +0 -0
- psychopy/app/Resources/classic/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_greensync.png +0 -0
- psychopy/app/Resources/classic/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_info.png +0 -0
- psychopy/app/Resources/classic/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/classic/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_run.png +0 -0
- psychopy/app/Resources/classic/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/classic/redglobe_user.png +0 -0
- psychopy/app/Resources/classic/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/classic/redo.png +0 -0
- psychopy/app/Resources/classic/redo32.png +0 -0
- psychopy/app/Resources/classic/redo@2x.png +0 -0
- psychopy/app/Resources/classic/regex.png +0 -0
- psychopy/app/Resources/classic/regex@2x.png +0 -0
- psychopy/app/Resources/classic/removeExp32.png +0 -0
- psychopy/app/Resources/classic/rename16.png +0 -0
- psychopy/app/Resources/classic/restart.png +0 -0
- psychopy/app/Resources/classic/restart@2x.png +0 -0
- psychopy/app/Resources/classic/run.png +0 -0
- psychopy/app/Resources/classic/run@2x.png +0 -0
- psychopy/app/Resources/classic/runner.png +0 -0
- psychopy/app/Resources/classic/runner@2x.png +0 -0
- psychopy/app/Resources/classic/runnerPilot.png +0 -0
- psychopy/app/Resources/classic/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/classic/savebtn.png +0 -0
- psychopy/app/Resources/classic/savebtn16.png +0 -0
- psychopy/app/Resources/classic/savebtn16@2x.png +0 -0
- psychopy/app/Resources/classic/savebtn@2x.png +0 -0
- psychopy/app/Resources/classic/search.png +0 -0
- psychopy/app/Resources/classic/search@2x.png +0 -0
- psychopy/app/Resources/classic/showBuilder.png +0 -0
- psychopy/app/Resources/classic/showBuilder@2x.png +0 -0
- psychopy/app/Resources/classic/showCoder.png +0 -0
- psychopy/app/Resources/classic/showCoder@2x.png +0 -0
- psychopy/app/Resources/classic/showRunner.png +0 -0
- psychopy/app/Resources/classic/showRunner@2x.png +0 -0
- psychopy/app/Resources/classic/starred.png +0 -0
- psychopy/app/Resources/classic/starred@2x.png +0 -0
- psychopy/app/Resources/classic/start.png +0 -0
- psychopy/app/Resources/classic/start@2x.png +0 -0
- psychopy/app/Resources/classic/stdout.png +0 -0
- psychopy/app/Resources/classic/stop.png +0 -0
- psychopy/app/Resources/classic/stop32.png +0 -0
- psychopy/app/Resources/classic/stop@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlBot.png +0 -0
- psychopy/app/Resources/classic/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/classic/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlRight.png +0 -0
- psychopy/app/Resources/classic/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/classic/switchCtrlTop.png +0 -0
- psychopy/app/Resources/classic/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/classic/tick.png +0 -0
- psychopy/app/Resources/classic/tick@2x.png +0 -0
- psychopy/app/Resources/classic/undo.png +0 -0
- psychopy/app/Resources/classic/undo32.png +0 -0
- psychopy/app/Resources/classic/undo@2x.png +0 -0
- psychopy/app/Resources/classic/unstarred.png +0 -0
- psychopy/app/Resources/classic/unstarred@2x.png +0 -0
- psychopy/app/Resources/classic/user_none.png +0 -0
- psychopy/app/Resources/classic/view-refresh16.png +0 -0
- psychopy/app/Resources/classic/viewbtn.png +0 -0
- psychopy/app/Resources/classic/viewbtn16.png +0 -0
- psychopy/app/Resources/classic/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/classic/viewbtn@2x.png +0 -0
- psychopy/app/Resources/classic/windows16.png +0 -0
- psychopy/app/Resources/classic/windows16@2x.png +0 -0
- psychopy/app/Resources/coder.ico +0 -0
- psychopy/app/Resources/dark/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/dark/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/dark/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/dark/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/dark/README.txt +3 -0
- psychopy/app/Resources/dark/__init__.py +0 -0
- psychopy/app/Resources/dark/add.png +0 -0
- psychopy/app/Resources/dark/add@2x.png +0 -0
- psychopy/app/Resources/dark/addExp32.png +0 -0
- psychopy/app/Resources/dark/addExp32@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/alerts.png +0 -0
- psychopy/app/Resources/dark/alerts@2x.png +0 -0
- psychopy/app/Resources/dark/beta.png +0 -0
- psychopy/app/Resources/dark/beta@2x.png +0 -0
- psychopy/app/Resources/dark/browser.png +0 -0
- psychopy/app/Resources/dark/browser@2x.png +0 -0
- psychopy/app/Resources/dark/bug16.png +0 -0
- psychopy/app/Resources/dark/bug16@2x.png +0 -0
- psychopy/app/Resources/dark/case.png +0 -0
- psychopy/app/Resources/dark/case@2x.png +0 -0
- psychopy/app/Resources/dark/circle_mask.png +0 -0
- psychopy/app/Resources/dark/circle_mask@2x.png +0 -0
- psychopy/app/Resources/dark/clear.png +0 -0
- psychopy/app/Resources/dark/clear@2x.png +0 -0
- psychopy/app/Resources/dark/coderclass16.png +0 -0
- psychopy/app/Resources/dark/coderclass16@2x.png +0 -0
- psychopy/app/Resources/dark/coderfunc16.png +0 -0
- psychopy/app/Resources/dark/coderfunc16@2x.png +0 -0
- psychopy/app/Resources/dark/coderimport16.png +0 -0
- psychopy/app/Resources/dark/coderimport16@2x.png +0 -0
- psychopy/app/Resources/dark/coderjs.png +0 -0
- psychopy/app/Resources/dark/coderjs@2x.png +0 -0
- psychopy/app/Resources/dark/coderpython.png +0 -0
- psychopy/app/Resources/dark/coderpython@2x.png +0 -0
- psychopy/app/Resources/dark/codervar16.png +0 -0
- psychopy/app/Resources/dark/codervar16@2x.png +0 -0
- psychopy/app/Resources/dark/cogwindow32.png +0 -0
- psychopy/app/Resources/dark/cogwindow32@2x.png +0 -0
- psychopy/app/Resources/dark/color32.png +0 -0
- psychopy/app/Resources/dark/color32@2x.png +0 -0
- psychopy/app/Resources/dark/compile_js.png +0 -0
- psychopy/app/Resources/dark/compile_js@2x.png +0 -0
- psychopy/app/Resources/dark/compile_py.png +0 -0
- psychopy/app/Resources/dark/compile_py@2x.png +0 -0
- psychopy/app/Resources/dark/copy16.png +0 -0
- psychopy/app/Resources/dark/copy16@2x.png +0 -0
- psychopy/app/Resources/dark/currentFile16.png +0 -0
- psychopy/app/Resources/dark/currentFile16@2x.png +0 -0
- psychopy/app/Resources/dark/delete16.png +0 -0
- psychopy/app/Resources/dark/delete16@2x.png +0 -0
- psychopy/app/Resources/dark/desktop.png +0 -0
- psychopy/app/Resources/dark/desktop@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/dirup16.png +0 -0
- psychopy/app/Resources/dark/dirup16@2x.png +0 -0
- psychopy/app/Resources/dark/docclose16.png +0 -0
- psychopy/app/Resources/dark/docclose16@2x.png +0 -0
- psychopy/app/Resources/dark/download.png +0 -0
- psychopy/app/Resources/dark/download@2x.png +0 -0
- psychopy/app/Resources/dark/edit.png +0 -0
- psychopy/app/Resources/dark/edit@2x.png +0 -0
- psychopy/app/Resources/dark/editbtn16.png +0 -0
- psychopy/app/Resources/dark/editbtn16@2x.png +0 -0
- psychopy/app/Resources/dark/email.png +0 -0
- psychopy/app/Resources/dark/email@2x.png +0 -0
- psychopy/app/Resources/dark/experiment.png +0 -0
- psychopy/app/Resources/dark/experiment@2x.png +0 -0
- psychopy/app/Resources/dark/expsettings.png +0 -0
- psychopy/app/Resources/dark/expsettings@2x.png +0 -0
- psychopy/app/Resources/dark/file.png +0 -0
- psychopy/app/Resources/dark/file@16w.png +0 -0
- psychopy/app/Resources/dark/file@16w@2x.png +0 -0
- psychopy/app/Resources/dark/file@2x.png +0 -0
- psychopy/app/Resources/dark/fileaudio.png +0 -0
- psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
- psychopy/app/Resources/dark/filecss.png +0 -0
- psychopy/app/Resources/dark/filecss@2x.png +0 -0
- psychopy/app/Resources/dark/filecsv.png +0 -0
- psychopy/app/Resources/dark/filecsv@2x.png +0 -0
- psychopy/app/Resources/dark/filedesign.png +0 -0
- psychopy/app/Resources/dark/filedesign@2x.png +0 -0
- psychopy/app/Resources/dark/filefont.png +0 -0
- psychopy/app/Resources/dark/filefont@2x.png +0 -0
- psychopy/app/Resources/dark/filegit.png +0 -0
- psychopy/app/Resources/dark/filegit@2x.png +0 -0
- psychopy/app/Resources/dark/filehtml.png +0 -0
- psychopy/app/Resources/dark/filehtml@2x.png +0 -0
- psychopy/app/Resources/dark/fileimage.png +0 -0
- psychopy/app/Resources/dark/fileimage@2x.png +0 -0
- psychopy/app/Resources/dark/fileinfo.png +0 -0
- psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
- psychopy/app/Resources/dark/filejs.png +0 -0
- psychopy/app/Resources/dark/filejs@2x.png +0 -0
- psychopy/app/Resources/dark/filejson.png +0 -0
- psychopy/app/Resources/dark/filejson@2x.png +0 -0
- psychopy/app/Resources/dark/filenew.png +0 -0
- psychopy/app/Resources/dark/filenew32.png +0 -0
- psychopy/app/Resources/dark/filenew32@2x.png +0 -0
- psychopy/app/Resources/dark/filenew@2x.png +0 -0
- psychopy/app/Resources/dark/fileopen.png +0 -0
- psychopy/app/Resources/dark/fileopen32.png +0 -0
- psychopy/app/Resources/dark/fileopen32@2x.png +0 -0
- psychopy/app/Resources/dark/fileopen@2x.png +0 -0
- psychopy/app/Resources/dark/filepkg.png +0 -0
- psychopy/app/Resources/dark/filepkg@2x.png +0 -0
- psychopy/app/Resources/dark/filepsyexp.png +0 -0
- psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/dark/filepy.png +0 -0
- psychopy/app/Resources/dark/filepy@2x.png +0 -0
- psychopy/app/Resources/dark/filesave.png +0 -0
- psychopy/app/Resources/dark/filesave32.png +0 -0
- psychopy/app/Resources/dark/filesave32@2x.png +0 -0
- psychopy/app/Resources/dark/filesave@2x.png +0 -0
- psychopy/app/Resources/dark/filesaveas.png +0 -0
- psychopy/app/Resources/dark/filesaveas32.png +0 -0
- psychopy/app/Resources/dark/filesaveas32@2x.png +0 -0
- psychopy/app/Resources/dark/filesaveas@2x.png +0 -0
- psychopy/app/Resources/dark/filetxt.png +0 -0
- psychopy/app/Resources/dark/filetxt@2x.png +0 -0
- psychopy/app/Resources/dark/fileunknown.png +0 -0
- psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
- psychopy/app/Resources/dark/filevideo.png +0 -0
- psychopy/app/Resources/dark/filevideo@2x.png +0 -0
- psychopy/app/Resources/dark/filter.png +0 -0
- psychopy/app/Resources/dark/filter@2x.png +0 -0
- psychopy/app/Resources/dark/find.png +0 -0
- psychopy/app/Resources/dark/find@2x.png +0 -0
- psychopy/app/Resources/dark/folder-open16.png +0 -0
- psychopy/app/Resources/dark/folder-open16@2x.png +0 -0
- psychopy/app/Resources/dark/folder16.png +0 -0
- psychopy/app/Resources/dark/folder16@2x.png +0 -0
- psychopy/app/Resources/dark/foldernew16.png +0 -0
- psychopy/app/Resources/dark/foldernew16@2x.png +0 -0
- psychopy/app/Resources/dark/fork.png +0 -0
- psychopy/app/Resources/dark/fork@2x.png +0 -0
- psychopy/app/Resources/dark/github.png +0 -0
- psychopy/app/Resources/dark/github@2x.png +0 -0
- psychopy/app/Resources/dark/globe.png +0 -0
- psychopy/app/Resources/dark/globe@2x.png +0 -0
- psychopy/app/Resources/dark/globe_bug.png +0 -0
- psychopy/app/Resources/dark/globe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/globe_greensync.png +0 -0
- psychopy/app/Resources/dark/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/globe_info.png +0 -0
- psychopy/app/Resources/dark/globe_info@2x.png +0 -0
- psychopy/app/Resources/dark/globe_magnifier.png +0 -0
- psychopy/app/Resources/dark/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/globe_run.png +0 -0
- psychopy/app/Resources/dark/globe_run@2x.png +0 -0
- psychopy/app/Resources/dark/globe_user.png +0 -0
- psychopy/app/Resources/dark/globe_user@2x.png +0 -0
- psychopy/app/Resources/dark/goto.png +0 -0
- psychopy/app/Resources/dark/goto@2x.png +0 -0
- psychopy/app/Resources/dark/greendot.png +0 -0
- psychopy/app/Resources/dark/greendot@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe.png +0 -0
- psychopy/app/Resources/dark/greenglobe@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_bug.png +0 -0
- psychopy/app/Resources/dark/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/dark/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_info.png +0 -0
- psychopy/app/Resources/dark/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/dark/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_run.png +0 -0
- psychopy/app/Resources/dark/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/dark/greenglobe_user.png +0 -0
- psychopy/app/Resources/dark/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/dark/greydot.png +0 -0
- psychopy/app/Resources/dark/greydot@2x.png +0 -0
- psychopy/app/Resources/dark/greytick.png +0 -0
- psychopy/app/Resources/dark/greytick@2x.png +0 -0
- psychopy/app/Resources/dark/invalid_img.png +0 -0
- psychopy/app/Resources/dark/jsPilot.png +0 -0
- psychopy/app/Resources/dark/jsPilot@2x.png +0 -0
- psychopy/app/Resources/dark/jsRun.png +0 -0
- psychopy/app/Resources/dark/jsRun@2x.png +0 -0
- psychopy/app/Resources/dark/libroot16.png +0 -0
- psychopy/app/Resources/dark/libroot16@2x.png +0 -0
- psychopy/app/Resources/dark/loop.png +0 -0
- psychopy/app/Resources/dark/loop@2x.png +0 -0
- psychopy/app/Resources/dark/monitor16.png +0 -0
- psychopy/app/Resources/dark/monitor16@2x.png +0 -0
- psychopy/app/Resources/dark/monitors.png +0 -0
- psychopy/app/Resources/dark/monitors32.png +0 -0
- psychopy/app/Resources/dark/monitors32@2x.png +0 -0
- psychopy/app/Resources/dark/monitors@2x.png +0 -0
- psychopy/app/Resources/dark/orangedot.png +0 -0
- psychopy/app/Resources/dark/orangedot@2x.png +0 -0
- psychopy/app/Resources/dark/pavlovia.png +0 -0
- psychopy/app/Resources/dark/pavlovia16.png +0 -0
- psychopy/app/Resources/dark/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/dark/pavlovia@2x.png +0 -0
- psychopy/app/Resources/dark/pavsync.png +0 -0
- psychopy/app/Resources/dark/pavsync@2x.png +0 -0
- psychopy/app/Resources/dark/person_off.png +0 -0
- psychopy/app/Resources/dark/person_off@2x.png +0 -0
- psychopy/app/Resources/dark/person_on.png +0 -0
- psychopy/app/Resources/dark/person_on@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugins32.png +0 -0
- psychopy/app/Resources/dark/plugins32@2x.png +0 -0
- psychopy/app/Resources/dark/plus.png +0 -0
- psychopy/app/Resources/dark/plus@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-app.png +0 -0
- psychopy/app/Resources/dark/preferences-app48.png +0 -0
- psychopy/app/Resources/dark/preferences-app48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-app@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-conn.png +0 -0
- psychopy/app/Resources/dark/preferences-conn48.png +0 -0
- psychopy/app/Resources/dark/preferences-conn48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-debug.png +0 -0
- psychopy/app/Resources/dark/preferences-debug@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-general.png +0 -0
- psychopy/app/Resources/dark/preferences-general48.png +0 -0
- psychopy/app/Resources/dark/preferences-general48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-general@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware48.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard48@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/dark/preferences-pilot.png +0 -0
- psychopy/app/Resources/dark/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/dark/preferences32.png +0 -0
- psychopy/app/Resources/dark/preferences32@2x.png +0 -0
- psychopy/app/Resources/dark/pyPilot.png +0 -0
- psychopy/app/Resources/dark/pyPilot@2x.png +0 -0
- psychopy/app/Resources/dark/pyRun.png +0 -0
- psychopy/app/Resources/dark/pyRun@2x.png +0 -0
- psychopy/app/Resources/dark/reddot.png +0 -0
- psychopy/app/Resources/dark/reddot@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe.png +0 -0
- psychopy/app/Resources/dark/redglobe@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_bug.png +0 -0
- psychopy/app/Resources/dark/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_greensync.png +0 -0
- psychopy/app/Resources/dark/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_info.png +0 -0
- psychopy/app/Resources/dark/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/dark/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_run.png +0 -0
- psychopy/app/Resources/dark/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/dark/redglobe_user.png +0 -0
- psychopy/app/Resources/dark/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/dark/redo.png +0 -0
- psychopy/app/Resources/dark/redo32.png +0 -0
- psychopy/app/Resources/dark/redo32@2x.png +0 -0
- psychopy/app/Resources/dark/redo@2x.png +0 -0
- psychopy/app/Resources/dark/regex.png +0 -0
- psychopy/app/Resources/dark/regex@2x.png +0 -0
- psychopy/app/Resources/dark/removeExp32.png +0 -0
- psychopy/app/Resources/dark/removeExp32@2x.png +0 -0
- psychopy/app/Resources/dark/rename16.png +0 -0
- psychopy/app/Resources/dark/rename16@2x.png +0 -0
- psychopy/app/Resources/dark/restart.png +0 -0
- psychopy/app/Resources/dark/restart@2x.png +0 -0
- psychopy/app/Resources/dark/runner.png +0 -0
- psychopy/app/Resources/dark/runner@2x.png +0 -0
- psychopy/app/Resources/dark/runnerPilot.png +0 -0
- psychopy/app/Resources/dark/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/dark/savebtn16.png +0 -0
- psychopy/app/Resources/dark/savebtn16@2x.png +0 -0
- psychopy/app/Resources/dark/search.png +0 -0
- psychopy/app/Resources/dark/search@2x.png +0 -0
- psychopy/app/Resources/dark/showBuilder.png +0 -0
- psychopy/app/Resources/dark/showBuilder@2x.png +0 -0
- psychopy/app/Resources/dark/showCoder.png +0 -0
- psychopy/app/Resources/dark/showCoder@2x.png +0 -0
- psychopy/app/Resources/dark/showRunner.png +0 -0
- psychopy/app/Resources/dark/showRunner@2x.png +0 -0
- psychopy/app/Resources/dark/starred.png +0 -0
- psychopy/app/Resources/dark/starred@2x.png +0 -0
- psychopy/app/Resources/dark/start.png +0 -0
- psychopy/app/Resources/dark/start@2x.png +0 -0
- psychopy/app/Resources/dark/stdout.png +0 -0
- psychopy/app/Resources/dark/stdout@2x.png +0 -0
- psychopy/app/Resources/dark/stop.png +0 -0
- psychopy/app/Resources/dark/stop32.png +0 -0
- psychopy/app/Resources/dark/stop32@2x.png +0 -0
- psychopy/app/Resources/dark/stop@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlBot.png +0 -0
- psychopy/app/Resources/dark/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/dark/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlRight.png +0 -0
- psychopy/app/Resources/dark/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/dark/switchCtrlTop.png +0 -0
- psychopy/app/Resources/dark/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/dark/tick.png +0 -0
- psychopy/app/Resources/dark/tick@2x.png +0 -0
- psychopy/app/Resources/dark/undo.png +0 -0
- psychopy/app/Resources/dark/undo32.png +0 -0
- psychopy/app/Resources/dark/undo32@2x.png +0 -0
- psychopy/app/Resources/dark/undo@2x.png +0 -0
- psychopy/app/Resources/dark/unstarred.png +0 -0
- psychopy/app/Resources/dark/unstarred@2x.png +0 -0
- psychopy/app/Resources/dark/user_none.png +0 -0
- psychopy/app/Resources/dark/view-refresh16.png +0 -0
- psychopy/app/Resources/dark/view-refresh16@2x.png +0 -0
- psychopy/app/Resources/dark/viewbtn16.png +0 -0
- psychopy/app/Resources/dark/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/dark/windows16.png +0 -0
- psychopy/app/Resources/dark/windows16@2x.png +0 -0
- psychopy/app/Resources/light/FlowBottom_CompLeft.png +0 -0
- psychopy/app/Resources/light/FlowBottom_CompRight.png +0 -0
- psychopy/app/Resources/light/FlowTop_CompLeft.png +0 -0
- psychopy/app/Resources/light/FlowTop_CompRight.png +0 -0
- psychopy/app/Resources/light/README.txt +3 -0
- psychopy/app/Resources/light/__init__.py +0 -0
- psychopy/app/Resources/light/add.png +0 -0
- psychopy/app/Resources/light/add@2x.png +0 -0
- psychopy/app/Resources/light/addExp32.png +0 -0
- psychopy/app/Resources/light/addExp32@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/alerts.png +0 -0
- psychopy/app/Resources/light/alerts@2x.png +0 -0
- psychopy/app/Resources/light/beta.png +0 -0
- psychopy/app/Resources/light/beta@2x.png +0 -0
- psychopy/app/Resources/light/browser.png +0 -0
- psychopy/app/Resources/light/browser@2x.png +0 -0
- psychopy/app/Resources/light/bug16.png +0 -0
- psychopy/app/Resources/light/bug16@2x.png +0 -0
- psychopy/app/Resources/light/case.png +0 -0
- psychopy/app/Resources/light/case@2x.png +0 -0
- psychopy/app/Resources/light/circle_mask.png +0 -0
- psychopy/app/Resources/light/circle_mask@2x.png +0 -0
- psychopy/app/Resources/light/clear.png +0 -0
- psychopy/app/Resources/light/clear@2x.png +0 -0
- psychopy/app/Resources/light/coderclass16.png +0 -0
- psychopy/app/Resources/light/coderclass16@2x.png +0 -0
- psychopy/app/Resources/light/coderfunc16.png +0 -0
- psychopy/app/Resources/light/coderfunc16@2x.png +0 -0
- psychopy/app/Resources/light/coderimport16.png +0 -0
- psychopy/app/Resources/light/coderimport16@2x.png +0 -0
- psychopy/app/Resources/light/coderjs.png +0 -0
- psychopy/app/Resources/light/coderjs@2x.png +0 -0
- psychopy/app/Resources/light/coderpython.png +0 -0
- psychopy/app/Resources/light/coderpython@2x.png +0 -0
- psychopy/app/Resources/light/codervar16.png +0 -0
- psychopy/app/Resources/light/codervar16@2x.png +0 -0
- psychopy/app/Resources/light/cogwindow32.png +0 -0
- psychopy/app/Resources/light/cogwindow32@2x.png +0 -0
- psychopy/app/Resources/light/color16.png +0 -0
- psychopy/app/Resources/light/color16@2x.png +0 -0
- psychopy/app/Resources/light/color32.png +0 -0
- psychopy/app/Resources/light/color32@2x.png +0 -0
- psychopy/app/Resources/light/compile_js.png +0 -0
- psychopy/app/Resources/light/compile_js@2x.png +0 -0
- psychopy/app/Resources/light/compile_py.png +0 -0
- psychopy/app/Resources/light/compile_py@2x.png +0 -0
- psychopy/app/Resources/light/copy16.png +0 -0
- psychopy/app/Resources/light/copy16@2x.png +0 -0
- psychopy/app/Resources/light/currentFile16.png +0 -0
- psychopy/app/Resources/light/currentFile16@2x.png +0 -0
- psychopy/app/Resources/light/delete16.png +0 -0
- psychopy/app/Resources/light/delete16@2x.png +0 -0
- psychopy/app/Resources/light/delete8.png +0 -0
- psychopy/app/Resources/light/desktop.png +0 -0
- psychopy/app/Resources/light/desktop@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/dirup16.png +0 -0
- psychopy/app/Resources/light/dirup16@2x.png +0 -0
- psychopy/app/Resources/light/docclose16.png +0 -0
- psychopy/app/Resources/light/docclose16@2x.png +0 -0
- psychopy/app/Resources/light/download.png +0 -0
- psychopy/app/Resources/light/download@2x.png +0 -0
- psychopy/app/Resources/light/edit.png +0 -0
- psychopy/app/Resources/light/edit@2x.png +0 -0
- psychopy/app/Resources/light/editbtn16.png +0 -0
- psychopy/app/Resources/light/editbtn16@2x.png +0 -0
- psychopy/app/Resources/light/email.png +0 -0
- psychopy/app/Resources/light/email@2x.png +0 -0
- psychopy/app/Resources/light/experiment.png +0 -0
- psychopy/app/Resources/light/experiment@2x.png +0 -0
- psychopy/app/Resources/light/expsettings.png +0 -0
- psychopy/app/Resources/light/expsettings@2x.png +0 -0
- psychopy/app/Resources/light/file.png +0 -0
- psychopy/app/Resources/light/file@2x.png +0 -0
- psychopy/app/Resources/light/fileaudio.png +0 -0
- psychopy/app/Resources/light/fileaudio@2x.png +0 -0
- psychopy/app/Resources/light/filecss.png +0 -0
- psychopy/app/Resources/light/filecss@2x.png +0 -0
- psychopy/app/Resources/light/filecsv.png +0 -0
- psychopy/app/Resources/light/filecsv@2x.png +0 -0
- psychopy/app/Resources/light/filedesign.png +0 -0
- psychopy/app/Resources/light/filedesign@2x.png +0 -0
- psychopy/app/Resources/light/filefont.png +0 -0
- psychopy/app/Resources/light/filefont@2x.png +0 -0
- psychopy/app/Resources/light/filegit.png +0 -0
- psychopy/app/Resources/light/filegit@2x.png +0 -0
- psychopy/app/Resources/light/filehtml.png +0 -0
- psychopy/app/Resources/light/filehtml@2x.png +0 -0
- psychopy/app/Resources/light/fileimage.png +0 -0
- psychopy/app/Resources/light/fileimage@2x.png +0 -0
- psychopy/app/Resources/light/fileinfo.png +0 -0
- psychopy/app/Resources/light/fileinfo@2x.png +0 -0
- psychopy/app/Resources/light/filejs.png +0 -0
- psychopy/app/Resources/light/filejs@2x.png +0 -0
- psychopy/app/Resources/light/filejson.png +0 -0
- psychopy/app/Resources/light/filejson@2x.png +0 -0
- psychopy/app/Resources/light/filenew.png +0 -0
- psychopy/app/Resources/light/filenew32.png +0 -0
- psychopy/app/Resources/light/filenew32@2x.png +0 -0
- psychopy/app/Resources/light/filenew@2x.png +0 -0
- psychopy/app/Resources/light/fileopen.png +0 -0
- psychopy/app/Resources/light/fileopen32.png +0 -0
- psychopy/app/Resources/light/fileopen32@2x.png +0 -0
- psychopy/app/Resources/light/fileopen@2x.png +0 -0
- psychopy/app/Resources/light/filepkg.png +0 -0
- psychopy/app/Resources/light/filepkg@2x.png +0 -0
- psychopy/app/Resources/light/filepsyexp.png +0 -0
- psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/light/filepy.png +0 -0
- psychopy/app/Resources/light/filepy@2x.png +0 -0
- psychopy/app/Resources/light/filesave.png +0 -0
- psychopy/app/Resources/light/filesave32.png +0 -0
- psychopy/app/Resources/light/filesave32@2x.png +0 -0
- psychopy/app/Resources/light/filesave@2x.png +0 -0
- psychopy/app/Resources/light/filesaveas.png +0 -0
- psychopy/app/Resources/light/filesaveas32.png +0 -0
- psychopy/app/Resources/light/filesaveas32@2x.png +0 -0
- psychopy/app/Resources/light/filesaveas@2x.png +0 -0
- psychopy/app/Resources/light/filetxt.png +0 -0
- psychopy/app/Resources/light/filetxt@2x.png +0 -0
- psychopy/app/Resources/light/fileunknown.png +0 -0
- psychopy/app/Resources/light/fileunknown@2x.png +0 -0
- psychopy/app/Resources/light/filevideo.png +0 -0
- psychopy/app/Resources/light/filevideo@2x.png +0 -0
- psychopy/app/Resources/light/filter.png +0 -0
- psychopy/app/Resources/light/filter@2x.png +0 -0
- psychopy/app/Resources/light/find.png +0 -0
- psychopy/app/Resources/light/find@2x.png +0 -0
- psychopy/app/Resources/light/folder-open16.png +0 -0
- psychopy/app/Resources/light/folder-open16@2x.png +0 -0
- psychopy/app/Resources/light/folder16.png +0 -0
- psychopy/app/Resources/light/folder16@2x.png +0 -0
- psychopy/app/Resources/light/foldernew16.png +0 -0
- psychopy/app/Resources/light/foldernew16@2x.png +0 -0
- psychopy/app/Resources/light/fork.png +0 -0
- psychopy/app/Resources/light/fork@2x.png +0 -0
- psychopy/app/Resources/light/github.png +0 -0
- psychopy/app/Resources/light/github@2x.png +0 -0
- psychopy/app/Resources/light/globe.png +0 -0
- psychopy/app/Resources/light/globe@2x.png +0 -0
- psychopy/app/Resources/light/globe_bug.png +0 -0
- psychopy/app/Resources/light/globe_bug@2x.png +0 -0
- psychopy/app/Resources/light/globe_greensync.png +0 -0
- psychopy/app/Resources/light/globe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/globe_info.png +0 -0
- psychopy/app/Resources/light/globe_info@2x.png +0 -0
- psychopy/app/Resources/light/globe_magnifier.png +0 -0
- psychopy/app/Resources/light/globe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/globe_run.png +0 -0
- psychopy/app/Resources/light/globe_run@2x.png +0 -0
- psychopy/app/Resources/light/globe_user.png +0 -0
- psychopy/app/Resources/light/globe_user@2x.png +0 -0
- psychopy/app/Resources/light/goto16.png +0 -0
- psychopy/app/Resources/light/goto16@2x.png +0 -0
- psychopy/app/Resources/light/greendot.png +0 -0
- psychopy/app/Resources/light/greendot@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe.png +0 -0
- psychopy/app/Resources/light/greenglobe@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_bug.png +0 -0
- psychopy/app/Resources/light/greenglobe_bug@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_greensync.png +0 -0
- psychopy/app/Resources/light/greenglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_info.png +0 -0
- psychopy/app/Resources/light/greenglobe_info@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_magnifier.png +0 -0
- psychopy/app/Resources/light/greenglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_run.png +0 -0
- psychopy/app/Resources/light/greenglobe_run@2x.png +0 -0
- psychopy/app/Resources/light/greenglobe_user.png +0 -0
- psychopy/app/Resources/light/greenglobe_user@2x.png +0 -0
- psychopy/app/Resources/light/greydot.png +0 -0
- psychopy/app/Resources/light/greydot@2x.png +0 -0
- psychopy/app/Resources/light/greytick.png +0 -0
- psychopy/app/Resources/light/greytick@2x.png +0 -0
- psychopy/app/Resources/light/invalid_img.png +0 -0
- psychopy/app/Resources/light/jsPilot.png +0 -0
- psychopy/app/Resources/light/jsPilot@2x.png +0 -0
- psychopy/app/Resources/light/jsRun.png +0 -0
- psychopy/app/Resources/light/jsRun@2x.png +0 -0
- psychopy/app/Resources/light/libroot16.png +0 -0
- psychopy/app/Resources/light/libroot16@2x.png +0 -0
- psychopy/app/Resources/light/loop.png +0 -0
- psychopy/app/Resources/light/loop@2x.png +0 -0
- psychopy/app/Resources/light/monitor16.png +0 -0
- psychopy/app/Resources/light/monitor16@2x.png +0 -0
- psychopy/app/Resources/light/monitors.png +0 -0
- psychopy/app/Resources/light/monitors32.png +0 -0
- psychopy/app/Resources/light/monitors32@2x.png +0 -0
- psychopy/app/Resources/light/monitors@2x.png +0 -0
- psychopy/app/Resources/light/orangedot.png +0 -0
- psychopy/app/Resources/light/orangedot@2x.png +0 -0
- psychopy/app/Resources/light/pavlovia.png +0 -0
- psychopy/app/Resources/light/pavlovia16.png +0 -0
- psychopy/app/Resources/light/pavlovia16@2x.png +0 -0
- psychopy/app/Resources/light/pavlovia@2x.png +0 -0
- psychopy/app/Resources/light/pavsync.png +0 -0
- psychopy/app/Resources/light/pavsync@2x.png +0 -0
- psychopy/app/Resources/light/person_off.png +0 -0
- psychopy/app/Resources/light/person_off@2x.png +0 -0
- psychopy/app/Resources/light/person_on.png +0 -0
- psychopy/app/Resources/light/person_on@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugins32.png +0 -0
- psychopy/app/Resources/light/plugins32@2x.png +0 -0
- psychopy/app/Resources/light/plus.png +0 -0
- psychopy/app/Resources/light/plus@2x.png +0 -0
- psychopy/app/Resources/light/preferences-app.png +0 -0
- psychopy/app/Resources/light/preferences-app48.png +0 -0
- psychopy/app/Resources/light/preferences-app48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-app@2x.png +0 -0
- psychopy/app/Resources/light/preferences-conn.png +0 -0
- psychopy/app/Resources/light/preferences-conn48.png +0 -0
- psychopy/app/Resources/light/preferences-conn48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-conn@2x.png +0 -0
- psychopy/app/Resources/light/preferences-general.png +0 -0
- psychopy/app/Resources/light/preferences-general48.png +0 -0
- psychopy/app/Resources/light/preferences-general48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-general@2x.png +0 -0
- psychopy/app/Resources/light/preferences-hardware.png +0 -0
- psychopy/app/Resources/light/preferences-hardware48.png +0 -0
- psychopy/app/Resources/light/preferences-hardware48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-hardware@2x.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard48.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard48@2x.png +0 -0
- psychopy/app/Resources/light/preferences-keyboard@2x.png +0 -0
- psychopy/app/Resources/light/preferences-pilot.png +0 -0
- psychopy/app/Resources/light/preferences-pilot@2x.png +0 -0
- psychopy/app/Resources/light/preferences32.png +0 -0
- psychopy/app/Resources/light/preferences32@2x.png +0 -0
- psychopy/app/Resources/light/pyPilot.png +0 -0
- psychopy/app/Resources/light/pyPilot@2x.png +0 -0
- psychopy/app/Resources/light/pyRun.png +0 -0
- psychopy/app/Resources/light/pyRun@2x.png +0 -0
- psychopy/app/Resources/light/reddot.png +0 -0
- psychopy/app/Resources/light/reddot@2x.png +0 -0
- psychopy/app/Resources/light/redglobe.png +0 -0
- psychopy/app/Resources/light/redglobe@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_bug.png +0 -0
- psychopy/app/Resources/light/redglobe_bug@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_greensync.png +0 -0
- psychopy/app/Resources/light/redglobe_greensync@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_info.png +0 -0
- psychopy/app/Resources/light/redglobe_info@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_magnifier.png +0 -0
- psychopy/app/Resources/light/redglobe_magnifier@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_run.png +0 -0
- psychopy/app/Resources/light/redglobe_run@2x.png +0 -0
- psychopy/app/Resources/light/redglobe_user.png +0 -0
- psychopy/app/Resources/light/redglobe_user@2x.png +0 -0
- psychopy/app/Resources/light/redo.png +0 -0
- psychopy/app/Resources/light/redo32.png +0 -0
- psychopy/app/Resources/light/redo32@2x.png +0 -0
- psychopy/app/Resources/light/redo@2x.png +0 -0
- psychopy/app/Resources/light/regex.png +0 -0
- psychopy/app/Resources/light/regex@2x.png +0 -0
- psychopy/app/Resources/light/removeExp32.png +0 -0
- psychopy/app/Resources/light/removeExp32@2x.png +0 -0
- psychopy/app/Resources/light/rename16.png +0 -0
- psychopy/app/Resources/light/rename16@2x.png +0 -0
- psychopy/app/Resources/light/restart.png +0 -0
- psychopy/app/Resources/light/restart@2x.png +0 -0
- psychopy/app/Resources/light/runner.png +0 -0
- psychopy/app/Resources/light/runner@2x.png +0 -0
- psychopy/app/Resources/light/runnerPilot.png +0 -0
- psychopy/app/Resources/light/runnerPilot@2x.png +0 -0
- psychopy/app/Resources/light/savebtn16.png +0 -0
- psychopy/app/Resources/light/savebtn16@2x.png +0 -0
- psychopy/app/Resources/light/search.png +0 -0
- psychopy/app/Resources/light/search@2x.png +0 -0
- psychopy/app/Resources/light/showBuilder.png +0 -0
- psychopy/app/Resources/light/showBuilder@2x.png +0 -0
- psychopy/app/Resources/light/showCoder.png +0 -0
- psychopy/app/Resources/light/showCoder@2x.png +0 -0
- psychopy/app/Resources/light/showRunner.png +0 -0
- psychopy/app/Resources/light/showRunner@2x.png +0 -0
- psychopy/app/Resources/light/starred.png +0 -0
- psychopy/app/Resources/light/starred@2x.png +0 -0
- psychopy/app/Resources/light/start.png +0 -0
- psychopy/app/Resources/light/start@2x.png +0 -0
- psychopy/app/Resources/light/stdout.png +0 -0
- psychopy/app/Resources/light/stdout@2x.png +0 -0
- psychopy/app/Resources/light/stop.png +0 -0
- psychopy/app/Resources/light/stop32.png +0 -0
- psychopy/app/Resources/light/stop32@2x.png +0 -0
- psychopy/app/Resources/light/stop@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlBot.png +0 -0
- psychopy/app/Resources/light/switchCtrlBot@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlLeft.png +0 -0
- psychopy/app/Resources/light/switchCtrlLeft@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlRight.png +0 -0
- psychopy/app/Resources/light/switchCtrlRight@2x.png +0 -0
- psychopy/app/Resources/light/switchCtrlTop.png +0 -0
- psychopy/app/Resources/light/switchCtrlTop@2x.png +0 -0
- psychopy/app/Resources/light/tick.png +0 -0
- psychopy/app/Resources/light/tick@2x.png +0 -0
- psychopy/app/Resources/light/undo.png +0 -0
- psychopy/app/Resources/light/undo32.png +0 -0
- psychopy/app/Resources/light/undo32@2x.png +0 -0
- psychopy/app/Resources/light/undo@2x.png +0 -0
- psychopy/app/Resources/light/unstarred.png +0 -0
- psychopy/app/Resources/light/unstarred@2x.png +0 -0
- psychopy/app/Resources/light/user_none.png +0 -0
- psychopy/app/Resources/light/view-refresh16.png +0 -0
- psychopy/app/Resources/light/view-refresh16@2x.png +0 -0
- psychopy/app/Resources/light/viewbtn16.png +0 -0
- psychopy/app/Resources/light/viewbtn16@2x.png +0 -0
- psychopy/app/Resources/light/windows16.png +0 -0
- psychopy/app/Resources/light/windows16@2x.png +0 -0
- psychopy/app/Resources/moveComponentIcons.py +50 -0
- psychopy/app/Resources/psychopy.desktop +13 -0
- psychopy/app/Resources/psychopy.icns +0 -0
- psychopy/app/Resources/psychopy.ico +0 -0
- psychopy/app/Resources/psychopy.png +0 -0
- psychopy/app/Resources/psychopy.xml +9 -0
- psychopy/app/Resources/psychopy@2x.png +0 -0
- psychopy/app/Resources/routine_templates/Basic.psyexp +344 -0
- psychopy/app/Resources/routine_templates/Misc.psyexp +228 -0
- psychopy/app/Resources/routine_templates/Online.psyexp +200 -0
- psychopy/app/Resources/routine_templates/Trials.psyexp +735 -0
- psychopy/app/Resources/routine_templates/readme.md +14 -0
- psychopy/app/Resources/runner.ico +0 -0
- psychopy/app/Resources/splash.png +0 -0
- psychopy/app/Resources/splash@2x.png +0 -0
- psychopy/app/Resources/tips.txt +45 -0
- psychopy/app/Resources/tips_ar_001.txt +45 -0
- psychopy/app/Resources/tips_fr_FR.txt +45 -0
- psychopy/app/Resources/tips_ja_JP.txt +42 -0
- psychopy/app/Resources/tips_zh_CN.txt +45 -0
- psychopy/app/Resources/window.ico +0 -0
- psychopy/app/__init__.py +339 -0
- psychopy/app/__main__.py +3 -0
- psychopy/app/_psychopyApp.py +1298 -0
- psychopy/app/appData.spec +58 -0
- psychopy/app/builder/__init__.py +5 -0
- psychopy/app/builder/builder.py +4738 -0
- psychopy/app/builder/dialogs/__init__.py +1910 -0
- psychopy/app/builder/dialogs/dlgsCode.py +631 -0
- psychopy/app/builder/dialogs/dlgsConditions.py +669 -0
- psychopy/app/builder/dialogs/findDlg.py +275 -0
- psychopy/app/builder/dialogs/paramCtrls.py +1802 -0
- psychopy/app/builder/validators.py +589 -0
- psychopy/app/coder/__init__.py +8 -0
- psychopy/app/coder/codeEditorBase.py +460 -0
- psychopy/app/coder/coder.py +3100 -0
- psychopy/app/coder/fileBrowser.py +714 -0
- psychopy/app/coder/folding.py +128 -0
- psychopy/app/coder/psychoParser.py +139 -0
- psychopy/app/coder/repl.py +496 -0
- psychopy/app/coder/sourceTree.py +302 -0
- psychopy/app/colorpicker/__init__.py +594 -0
- psychopy/app/colorpicker/ui.py +336 -0
- psychopy/app/connections/__init__.py +10 -0
- psychopy/app/connections/news.py +104 -0
- psychopy/app/connections/sendusage.py +61 -0
- psychopy/app/connections/updates.py +642 -0
- psychopy/app/console.py +164 -0
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/dialogs.py +664 -0
- psychopy/app/errorDlg.py +238 -0
- psychopy/app/frametracker.py +8 -0
- psychopy/app/idle.py +181 -0
- psychopy/app/jobs.py +676 -0
- psychopy/app/linuxconfig/__init__.py +153 -0
- psychopy/app/linuxconfig/ui.py +88 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +11268 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -0
- psychopy/app/localizedStrings.py +386 -0
- psychopy/app/pavlovia_ui/__init__.py +22 -0
- psychopy/app/pavlovia_ui/_base.py +268 -0
- psychopy/app/pavlovia_ui/functions.py +176 -0
- psychopy/app/pavlovia_ui/menu.py +140 -0
- psychopy/app/pavlovia_ui/project.py +943 -0
- psychopy/app/pavlovia_ui/search.py +444 -0
- psychopy/app/pavlovia_ui/sync.py +137 -0
- psychopy/app/pavlovia_ui/user.py +264 -0
- psychopy/app/plugin_manager/__init__.py +5 -0
- psychopy/app/plugin_manager/dialog.py +402 -0
- psychopy/app/plugin_manager/output.py +132 -0
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +643 -0
- psychopy/app/plugin_manager/plugins.py +1363 -0
- psychopy/app/plugin_manager/utils.py +115 -0
- psychopy/app/preferencesDlg.py +771 -0
- psychopy/app/psychopyApp.py +207 -0
- psychopy/app/ribbon.py +1019 -0
- psychopy/app/runner/__init__.py +1 -0
- psychopy/app/runner/runner.py +1299 -0
- psychopy/app/runner/scriptProcess.py +363 -0
- psychopy/app/stdout/__init__.py +1 -0
- psychopy/app/stdout/stdOutRich.py +410 -0
- psychopy/app/sysInfoDlg.py +242 -0
- psychopy/app/themes/__init__.py +78 -0
- psychopy/app/themes/colors.py +201 -0
- psychopy/app/themes/css/contrast_black.css +112 -0
- psychopy/app/themes/css/contrast_white.css +115 -0
- psychopy/app/themes/css/dark.css +112 -0
- psychopy/app/themes/css/light.css +115 -0
- psychopy/app/themes/fonts.py +629 -0
- psychopy/app/themes/handlers.py +321 -0
- psychopy/app/themes/icons.py +267 -0
- psychopy/app/themes/spec/Classic.json +154 -0
- psychopy/app/themes/spec/ClassicDark.json +154 -0
- psychopy/app/themes/spec/GitHub.json +154 -0
- psychopy/app/themes/spec/HiVisDark.json +153 -0
- psychopy/app/themes/spec/HiVisLight.json +154 -0
- psychopy/app/themes/spec/MinimalDark.json +154 -0
- psychopy/app/themes/spec/MinimalLight.json +153 -0
- psychopy/app/themes/spec/PsychopyDark.json +231 -0
- psychopy/app/themes/spec/PsychopyLight.json +236 -0
- psychopy/app/themes/ui.py +74 -0
- psychopy/app/ui/__init__.py +184 -0
- psychopy/app/urls.py +31 -0
- psychopy/app/utils.py +1741 -0
- psychopy/app/viewer/__init__.py +0 -0
- psychopy/assets/Psychopy Window Favicon@16w.png +0 -0
- psychopy/assets/Psychopy Window Favicon@32w.png +0 -0
- psychopy/assets/USB-C.png +0 -0
- psychopy/assets/USB.png +0 -0
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/creditCard.png +0 -0
- psychopy/assets/default.mp3 +0 -0
- psychopy/assets/default.mp4 +0 -0
- psychopy/assets/default.png +0 -0
- psychopy/assets/fonts/Arvo-Bold.ttf +0 -0
- psychopy/assets/fonts/Arvo-BoldItalic.ttf +0 -0
- psychopy/assets/fonts/Arvo-Italic.ttf +0 -0
- psychopy/assets/fonts/Arvo-Regular.ttf +0 -0
- psychopy/assets/fonts/DejaVuSerif.ttf +0 -0
- psychopy/assets/fonts/IndieFlower-Regular.ttf +0 -0
- psychopy/assets/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- psychopy/assets/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
- psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/templates/instruct1.png +0 -0
- psychopy/assets/templates/instruct2.png +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/voicekeyThresholdStim.wav +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +619 -0
- psychopy/colors.py +1058 -0
- psychopy/compatibility.py +147 -0
- psychopy/constants.py +87 -0
- psychopy/contrib/__init__.py +0 -0
- psychopy/contrib/configobj/LICENSE +39 -0
- psychopy/contrib/configobj/__init__.py +2455 -0
- psychopy/contrib/configobj/_version.py +2 -0
- psychopy/contrib/configobj/validate.py +1458 -0
- psychopy/contrib/lazy_import.py +402 -0
- psychopy/contrib/mseq.py +273 -0
- psychopy/contrib/mseqSearch.py +183 -0
- psychopy/contrib/psi.py +100 -0
- psychopy/contrib/quest.py +484 -0
- psychopy/contrib/tesselate.py +188 -0
- psychopy/core.py +167 -0
- psychopy/data/__init__.py +45 -0
- psychopy/data/base.py +566 -0
- psychopy/data/counterbalance.py +210 -0
- psychopy/data/experiment.py +1049 -0
- psychopy/data/fit.py +248 -0
- psychopy/data/routine.py +94 -0
- psychopy/data/shelf.py +237 -0
- psychopy/data/staircase.py +2270 -0
- psychopy/data/trial.py +2505 -0
- psychopy/data/utils.py +858 -0
- psychopy/demos/__init__.py +0 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/README.md +5 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +354 -0
- psychopy/demos/builder/Design Templates/branchedExperiment/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Design Templates/psychophysicsStaircase/README.md +20 -0
- psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +286 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/README.md +14 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +340 -0
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/stairDefinitions.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/README.md +18 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/chooseBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/facesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/housesBlock.xlsx +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +219 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/face03.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house01.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house02.jpg +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/stims/house03.jpg +0 -0
- psychopy/demos/builder/Experiments/BART/README.md +30 -0
- psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
- psychopy/demos/builder/Experiments/BART/assets/background.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/bang.mp3 +0 -0
- psychopy/demos/builder/Experiments/BART/assets/bang.wav +0 -0
- psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/bart.psyexp +844 -0
- psychopy/demos/builder/Experiments/BART/spreadsheets/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +255 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/README.md +23 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/TIPI.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/bigFiveItems.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/demographics.xlsx +0 -0
- psychopy/demos/builder/Experiments/BigFiveInventory/mini_IPIP.xlsx +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +462 -0
- psychopy/demos/builder/Experiments/GoNoGo/go.png +0 -0
- psychopy/demos/builder/Experiments/GoNoGo/nogo.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/README.md +5 -0
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +494 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/goNoGo/readme.md +14 -0
- psychopy/demos/builder/Experiments/mentalRotation/F.png +0 -0
- psychopy/demos/builder/Experiments/mentalRotation/FR.png +0 -0
- psychopy/demos/builder/Experiments/mentalRotation/MentalRot.csv +33 -0
- psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +646 -0
- psychopy/demos/builder/Experiments/mentalRotation/README.md +23 -0
- psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +509 -0
- psychopy/demos/builder/Experiments/navon/README.md +26 -0
- psychopy/demos/builder/Experiments/navon/bigHsmallH.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigHsmallS.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigSsmallH.png +0 -0
- psychopy/demos/builder/Experiments/navon/bigSsmallS.png +0 -0
- psychopy/demos/builder/Experiments/navon/mask.png +0 -0
- psychopy/demos/builder/Experiments/navon/stimuli.pptx +0 -0
- psychopy/demos/builder/Experiments/navon/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/README.md +14 -0
- psychopy/demos/builder/Experiments/sternberg/mainTrials.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/pracTrials.xlsx +0 -0
- psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +649 -0
- psychopy/demos/builder/Experiments/stroop/README.md +15 -0
- psychopy/demos/builder/Experiments/stroop/stroop.psyexp +321 -0
- psychopy/demos/builder/Experiments/stroop/trialTypes.csv +7 -0
- psychopy/demos/builder/Experiments/stroopExtended/README.md +14 -0
- psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +418 -0
- psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +418 -0
- psychopy/demos/builder/Experiments/stroopExtended/trialTypes.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopExtended/trialTypesReverse.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/README.md +27 -0
- psychopy/demos/builder/Experiments/stroopVoice/conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +382 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +371 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +308 -0
- psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +391 -0
- psychopy/demos/builder/Feature Demos/gratings/readme.md +8 -0
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/noise/face.jpg +0 -0
- psychopy/demos/builder/Feature Demos/noise/noise.psyexp +477 -0
- psychopy/demos/builder/Feature Demos/noise/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/panorama/panImg.jpg +0 -0
- psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +177 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +434 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +451 -0
- psychopy/demos/builder/Feature Demos/progress/readme.md +1 -0
- psychopy/demos/builder/Feature Demos/sliders/README.md +8 -0
- psychopy/demos/builder/Feature Demos/sliders/fruitConditions.xlsx +0 -0
- psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +984 -0
- psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +199 -0
- psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +639 -0
- psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +415 -0
- psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +659 -0
- psychopy/demos/builder/Hardware/EGI_netstation/README.md +17 -0
- psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +339 -0
- psychopy/demos/builder/Hardware/EGI_netstation/trialTypesEEG.csv +7 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/readme.md +19 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/trials_params.xlsx +0 -0
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +830 -0
- psychopy/demos/builder/Hardware/camera/camera.psyexp +330 -0
- psychopy/demos/builder/Hardware/camera/readme.md +6 -0
- psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +459 -0
- psychopy/demos/builder/Hardware/eyetracking/readme.md +7 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/Bullseye_grey.mov +0 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +468 -0
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/readme.md +7 -0
- psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +211 -0
- psychopy/demos/builder/Hardware/fMRI/readme.md +7 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +484 -0
- psychopy/demos/builder/Hardware/microphone/phrases.xlsx +0 -0
- psychopy/demos/builder/Hardware/microphone/readme.md +9 -0
- psychopy/demos/builder/Hardware/pump/README.md +19 -0
- psychopy/demos/builder/Hardware/pump/pump.psyexp +646 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +350 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/readme.md +5 -0
- psychopy/demos/builder/Helper Tools/clockFace/README.md +3 -0
- psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +211 -0
- psychopy/demos/builder/Helper Tools/colors/README.md +5 -0
- psychopy/demos/builder/Helper Tools/colors/colors.psyexp +321 -0
- psychopy/demos/builder/Helper Tools/drawPolygon/README.md +5 -0
- psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +704 -0
- psychopy/demos/builder/Helper Tools/keyNameFinder/README.md +14 -0
- psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +225 -0
- psychopy/demos/builder/Helper Tools/spatialUnits/README.md +6 -0
- psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +206 -0
- psychopy/demos/builder/README.txt +43 -0
- psychopy/demos/builder/__init__.py +0 -0
- psychopy/demos/coder/__init__.py +0 -0
- psychopy/demos/coder/basic/hello_world.py +37 -0
- psychopy/demos/coder/csvFromPsydat.py +29 -0
- psychopy/demos/coder/experiment control/JND_staircase_analysis.py +85 -0
- psychopy/demos/coder/experiment control/JND_staircase_exp.py +110 -0
- psychopy/demos/coder/experiment control/TrialHandler.py +64 -0
- psychopy/demos/coder/experiment control/TrialHandler2.py +42 -0
- psychopy/demos/coder/experiment control/__init__.py +0 -0
- psychopy/demos/coder/experiment control/autoDraw_autoLog.py +50 -0
- psychopy/demos/coder/experiment control/experimentHandler.py +57 -0
- psychopy/demos/coder/experiment control/fMRI_launchScan.py +74 -0
- psychopy/demos/coder/experiment control/gammaMotionAnalysis.py +68 -0
- psychopy/demos/coder/experiment control/gammaMotionNull.py +169 -0
- psychopy/demos/coder/experiment control/logFiles.py +55 -0
- psychopy/demos/coder/experiment control/piloting.py +65 -0
- psychopy/demos/coder/experiment control/runtimeInfo.py +79 -0
- psychopy/demos/coder/hardware/CRS_BitsBox.py +75 -0
- psychopy/demos/coder/hardware/CRS_BitsPlusPlus.py +73 -0
- psychopy/demos/coder/hardware/RiftHeadTrackingExample.py +95 -0
- psychopy/demos/coder/hardware/RiftMinimal.py +44 -0
- psychopy/demos/coder/hardware/VSHD_Distortion.py +49 -0
- psychopy/demos/coder/hardware/__init__.py +0 -0
- psychopy/demos/coder/hardware/camera.py +90 -0
- psychopy/demos/coder/hardware/cedrusRB730.py +38 -0
- psychopy/demos/coder/hardware/crsBitsAdvancedDemo.py +780 -0
- psychopy/demos/coder/hardware/egi_netstation.py +45 -0
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/demos/coder/hardware/ioLab_bbox.py +62 -0
- psychopy/demos/coder/hardware/labjack_u3.py +61 -0
- psychopy/demos/coder/hardware/monitorDemo.py +28 -0
- psychopy/demos/coder/hardware/parallelPortOutput.py +49 -0
- psychopy/demos/coder/hardware/qmixPump.py +79 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +127 -0
- psychopy/demos/coder/input/GUI.py +52 -0
- psychopy/demos/coder/input/__init__.py +0 -0
- psychopy/demos/coder/input/customMouse.py +51 -0
- psychopy/demos/coder/input/joystick_universal.py +84 -0
- psychopy/demos/coder/input/keyNameFinder.py +39 -0
- psychopy/demos/coder/input/mic.png +0 -0
- psychopy/demos/coder/input/mouse.py +66 -0
- psychopy/demos/coder/iohub/delaytest.py +231 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/canal.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/fall.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/lake.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/party.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/images/swimming.jpg +0 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByConditionVariables.py +67 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/readTrialEventsByMessages.py +34 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/run.py +323 -0
- psychopy/demos/coder/iohub/eyetracking/gcCursor/trial_conditions.xlsx +0 -0
- psychopy/demos/coder/iohub/eyetracking/simple.py +147 -0
- psychopy/demos/coder/iohub/eyetracking/validation.py +251 -0
- psychopy/demos/coder/iohub/iodatastore/saveEventReport.py +56 -0
- psychopy/demos/coder/iohub/keyboard.py +189 -0
- psychopy/demos/coder/iohub/keyboardreactiontime.py +88 -0
- psychopy/demos/coder/iohub/launchHub.py +160 -0
- psychopy/demos/coder/iohub/mouse.py +112 -0
- psychopy/demos/coder/iohub/mouse_multi_window.py +124 -0
- psychopy/demos/coder/iohub/serial/_parseserial.py +53 -0
- psychopy/demos/coder/iohub/serial/customparser.py +84 -0
- psychopy/demos/coder/iohub/serial/pstbox.py +166 -0
- psychopy/demos/coder/iohub/wintab/_wintabgraphics.py +281 -0
- psychopy/demos/coder/iohub/wintab/pen_demo.py +260 -0
- psychopy/demos/coder/misc/encrypt_data.py +48 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/demos/coder/misc/makeMovie.py +28 -0
- psychopy/demos/coder/misc/rigidBodyTransform.py +76 -0
- psychopy/demos/coder/stimuli/Campaign.ttf +0 -0
- psychopy/demos/coder/stimuli/MovieStim.py +65 -0
- psychopy/demos/coder/stimuli/__init__.py +0 -0
- psychopy/demos/coder/stimuli/aperture.py +37 -0
- psychopy/demos/coder/stimuli/beach.jpg +0 -0
- psychopy/demos/coder/stimuli/bufferImageStim.py +78 -0
- psychopy/demos/coder/stimuli/clockface.py +43 -0
- psychopy/demos/coder/stimuli/colorPalette.py +134 -0
- psychopy/demos/coder/stimuli/compare_text_timing.py +193 -0
- psychopy/demos/coder/stimuli/counterphase.py +43 -0
- psychopy/demos/coder/stimuli/customTextures.py +57 -0
- psychopy/demos/coder/stimuli/dot_gabors.py +35 -0
- psychopy/demos/coder/stimuli/dots.py +35 -0
- psychopy/demos/coder/stimuli/elementArrays.py +92 -0
- psychopy/demos/coder/stimuli/embeddedOpenGL.py +35 -0
- psychopy/demos/coder/stimuli/face.jpg +0 -0
- psychopy/demos/coder/stimuli/face_jpg.py +48 -0
- psychopy/demos/coder/stimuli/gabor.py +30 -0
- psychopy/demos/coder/stimuli/imagesAndPatches.py +51 -0
- psychopy/demos/coder/stimuli/jwpIntro.mp4 +0 -0
- psychopy/demos/coder/stimuli/kanizsa.py +41 -0
- psychopy/demos/coder/stimuli/maskReveal.py +63 -0
- psychopy/demos/coder/stimuli/plaid.py +43 -0
- psychopy/demos/coder/stimuli/ratingScale.py +183 -0
- psychopy/demos/coder/stimuli/rotatingFlashingWedge.py +37 -0
- psychopy/demos/coder/stimuli/screensAndWindows.py +58 -0
- psychopy/demos/coder/stimuli/secondOrderGratings.py +66 -0
- psychopy/demos/coder/stimuli/shapeContains.py +50 -0
- psychopy/demos/coder/stimuli/shapes.py +63 -0
- psychopy/demos/coder/stimuli/soundStimuli.py +49 -0
- psychopy/demos/coder/stimuli/starField.py +48 -0
- psychopy/demos/coder/stimuli/stim3d.py +107 -0
- psychopy/demos/coder/stimuli/textBoxStim/textbox_glyph_placement.py +93 -0
- psychopy/demos/coder/stimuli/textBoxStim/textbox_simple.py +71 -0
- psychopy/demos/coder/stimuli/textStimuli.py +106 -0
- psychopy/demos/coder/stimuli/textbox_editable.py +64 -0
- psychopy/demos/coder/stimuli/variousVisualStims.py +47 -0
- psychopy/demos/coder/stimuli/visual_noise.py +31 -0
- psychopy/demos/coder/sysInfo.py +56 -0
- psychopy/demos/coder/timing/__init__.py +0 -0
- psychopy/demos/coder/timing/callOnFlip.py +39 -0
- psychopy/demos/coder/timing/clocksAndTimers.py +46 -0
- psychopy/demos/coder/timing/millikeyKeyboardTimingTest.py +323 -0
- psychopy/demos/coder/timing/timeByFrames.py +80 -0
- psychopy/demos/coder/timing/timeByFramesEx.py +107 -0
- psychopy/demos/coder/understanding psychopy/colors.py +70 -0
- psychopy/demos/coder/understanding psychopy/fontLayout.py +69 -0
- psychopy/demos/demo_migration.py +214 -0
- psychopy/demos/modernizeDemos.py +16 -0
- psychopy/demos/test_demo_migration.py +155 -0
- psychopy/devices/__init__.py +8 -0
- psychopy/event.py +1339 -0
- psychopy/exceptions.py +59 -0
- psychopy/experiment/__init__.py +51 -0
- psychopy/experiment/_experiment.py +1514 -0
- psychopy/experiment/blankTemplate.xltx +0 -0
- psychopy/experiment/components/__init__.py +402 -0
- psychopy/experiment/components/_base.py +1689 -0
- psychopy/experiment/components/aperture/__init__.py +153 -0
- psychopy/experiment/components/aperture/classic/aperture.png +0 -0
- psychopy/experiment/components/aperture/classic/aperture@2x.png +0 -0
- psychopy/experiment/components/aperture/dark/aperture.png +0 -0
- psychopy/experiment/components/aperture/dark/aperture@2x.png +0 -0
- psychopy/experiment/components/aperture/light/aperture.png +0 -0
- psychopy/experiment/components/aperture/light/aperture@2x.png +0 -0
- psychopy/experiment/components/brush/__init__.py +165 -0
- psychopy/experiment/components/brush/classic/brush.png +0 -0
- psychopy/experiment/components/brush/classic/brush@2x.png +0 -0
- psychopy/experiment/components/brush/dark/brush.png +0 -0
- psychopy/experiment/components/brush/dark/brush@2x.png +0 -0
- psychopy/experiment/components/brush/light/brush.png +0 -0
- psychopy/experiment/components/brush/light/brush@2x.png +0 -0
- psychopy/experiment/components/button/__init__.py +493 -0
- psychopy/experiment/components/button/classic/button.png +0 -0
- psychopy/experiment/components/button/classic/button@2x.png +0 -0
- psychopy/experiment/components/button/dark/button.png +0 -0
- psychopy/experiment/components/button/dark/button@2x.png +0 -0
- psychopy/experiment/components/button/light/button.png +0 -0
- psychopy/experiment/components/button/light/button@2x.png +0 -0
- psychopy/experiment/components/buttonBox/__init__.py +322 -0
- psychopy/experiment/components/buttonBox/classic/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/classic/buttonBox@2x.png +0 -0
- psychopy/experiment/components/buttonBox/dark/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/dark/buttonBox@2x.png +0 -0
- psychopy/experiment/components/buttonBox/light/buttonBox.png +0 -0
- psychopy/experiment/components/buttonBox/light/buttonBox@2x.png +0 -0
- psychopy/experiment/components/camera/__init__.py +381 -0
- psychopy/experiment/components/camera/classic/webcam.png +0 -0
- psychopy/experiment/components/camera/classic/webcam@2x.png +0 -0
- psychopy/experiment/components/camera/dark/webcam.png +0 -0
- psychopy/experiment/components/camera/dark/webcam@2x.png +0 -0
- psychopy/experiment/components/camera/light/webcam.png +0 -0
- psychopy/experiment/components/camera/light/webcam@2x.png +0 -0
- psychopy/experiment/components/code/__init__.py +299 -0
- psychopy/experiment/components/code/classic/code.png +0 -0
- psychopy/experiment/components/code/classic/code@2x.png +0 -0
- psychopy/experiment/components/code/dark/code.png +0 -0
- psychopy/experiment/components/code/dark/code@2x.png +0 -0
- psychopy/experiment/components/code/light/code.png +0 -0
- psychopy/experiment/components/code/light/code@2x.png +0 -0
- psychopy/experiment/components/dots/__init__.py +199 -0
- psychopy/experiment/components/dots/classic/dots.png +0 -0
- psychopy/experiment/components/dots/classic/dots@2x.png +0 -0
- psychopy/experiment/components/dots/dark/dots.png +0 -0
- psychopy/experiment/components/dots/dark/dots@2x.png +0 -0
- psychopy/experiment/components/dots/dots.xcf +0 -0
- psychopy/experiment/components/dots/light/dots.png +0 -0
- psychopy/experiment/components/dots/light/dots@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +194 -0
- psychopy/experiment/components/eyetracker_record/classic/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/classic/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/dark/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/dark/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/eyetracker_record/light/eyetracker_record.png +0 -0
- psychopy/experiment/components/eyetracker_record/light/eyetracker_record@2x.png +0 -0
- psychopy/experiment/components/form/__init__.py +227 -0
- psychopy/experiment/components/form/classic/form.png +0 -0
- psychopy/experiment/components/form/classic/form@2x.png +0 -0
- psychopy/experiment/components/form/dark/form.png +0 -0
- psychopy/experiment/components/form/dark/form@2x.png +0 -0
- psychopy/experiment/components/form/formItems.xltx +0 -0
- psychopy/experiment/components/form/light/form.png +0 -0
- psychopy/experiment/components/form/light/form@2x.png +0 -0
- psychopy/experiment/components/grating/__init__.py +203 -0
- psychopy/experiment/components/grating/classic/grating.png +0 -0
- psychopy/experiment/components/grating/classic/grating@2x.png +0 -0
- psychopy/experiment/components/grating/dark/grating.png +0 -0
- psychopy/experiment/components/grating/dark/grating@2x.png +0 -0
- psychopy/experiment/components/grating/light/grating.png +0 -0
- psychopy/experiment/components/grating/light/grating@2x.png +0 -0
- psychopy/experiment/components/image/__init__.py +195 -0
- psychopy/experiment/components/image/classic/image.png +0 -0
- psychopy/experiment/components/image/classic/image@2x.png +0 -0
- psychopy/experiment/components/image/dark/image.png +0 -0
- psychopy/experiment/components/image/dark/image@2x.png +0 -0
- psychopy/experiment/components/image/light/image.png +0 -0
- psychopy/experiment/components/image/light/image@2x.png +0 -0
- psychopy/experiment/components/joyButtons/__init__.py +453 -0
- psychopy/experiment/components/joyButtons/classic/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/classic/joybuttons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/dark/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/dark/joyButtons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/light/joyButtons.png +0 -0
- psychopy/experiment/components/joyButtons/light/joyButtons@2x.png +0 -0
- psychopy/experiment/components/joyButtons/virtualJoyButtons.py +34 -0
- psychopy/experiment/components/joystick/__init__.py +572 -0
- psychopy/experiment/components/joystick/classic/joystick.png +0 -0
- psychopy/experiment/components/joystick/classic/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/dark/joystick.png +0 -0
- psychopy/experiment/components/joystick/dark/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/light/joystick.png +0 -0
- psychopy/experiment/components/joystick/light/joystick@2x.png +0 -0
- psychopy/experiment/components/joystick/virtualJoystick.py +40 -0
- psychopy/experiment/components/keyboard/__init__.py +568 -0
- psychopy/experiment/components/keyboard/classic/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/classic/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard/dark/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/dark/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard/light/keyboard.png +0 -0
- psychopy/experiment/components/keyboard/light/keyboard@2x.png +0 -0
- psychopy/experiment/components/keyboard.xcf +0 -0
- psychopy/experiment/components/microphone/__init__.py +600 -0
- psychopy/experiment/components/microphone/classic/microphone.png +0 -0
- psychopy/experiment/components/microphone/classic/microphone@2x.png +0 -0
- psychopy/experiment/components/microphone/dark/microphone.png +0 -0
- psychopy/experiment/components/microphone/dark/microphone@2x.png +0 -0
- psychopy/experiment/components/microphone/light/microphone.png +0 -0
- psychopy/experiment/components/microphone/light/microphone@2x.png +0 -0
- psychopy/experiment/components/mouse/__init__.py +773 -0
- psychopy/experiment/components/mouse/classic/mouse.png +0 -0
- psychopy/experiment/components/mouse/classic/mouse@2x.png +0 -0
- psychopy/experiment/components/mouse/dark/mouse.png +0 -0
- psychopy/experiment/components/mouse/dark/mouse@2x.png +0 -0
- psychopy/experiment/components/mouse/light/mouse.png +0 -0
- psychopy/experiment/components/mouse/light/mouse@2x.png +0 -0
- psychopy/experiment/components/movie/__init__.py +348 -0
- psychopy/experiment/components/movie/classic/movie.png +0 -0
- psychopy/experiment/components/movie/classic/movie@2x.png +0 -0
- psychopy/experiment/components/movie/dark/movie.png +0 -0
- psychopy/experiment/components/movie/dark/movie@2x.png +0 -0
- psychopy/experiment/components/movie/light/movie.png +0 -0
- psychopy/experiment/components/movie/light/movie@2x.png +0 -0
- psychopy/experiment/components/panorama/__init__.py +456 -0
- psychopy/experiment/components/panorama/classic/panorama.png +0 -0
- psychopy/experiment/components/panorama/classic/panorama@2x.png +0 -0
- psychopy/experiment/components/panorama/dark/panorama.png +0 -0
- psychopy/experiment/components/panorama/dark/panorama@2x.png +0 -0
- psychopy/experiment/components/panorama/light/panorama.png +0 -0
- psychopy/experiment/components/panorama/light/panorama@2x.png +0 -0
- psychopy/experiment/components/parallelOut/__init__.py +178 -0
- psychopy/experiment/components/parallelOut/classic/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/classic/parallel@2x.png +0 -0
- psychopy/experiment/components/parallelOut/dark/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/dark/parallel@2x.png +0 -0
- psychopy/experiment/components/parallelOut/light/parallel.png +0 -0
- psychopy/experiment/components/parallelOut/light/parallel@2x.png +0 -0
- psychopy/experiment/components/polygon/__init__.py +332 -0
- psychopy/experiment/components/polygon/classic/polygon.png +0 -0
- psychopy/experiment/components/polygon/classic/polygon@2x.png +0 -0
- psychopy/experiment/components/polygon/dark/polygon.png +0 -0
- psychopy/experiment/components/polygon/dark/polygon@2x.png +0 -0
- psychopy/experiment/components/polygon/light/polygon.png +0 -0
- psychopy/experiment/components/polygon/light/polygon@2x.png +0 -0
- psychopy/experiment/components/progress/__init__.py +130 -0
- psychopy/experiment/components/progress/classic/progress.png +0 -0
- psychopy/experiment/components/progress/classic/progress@2x.png +0 -0
- psychopy/experiment/components/progress/dark/progress.png +0 -0
- psychopy/experiment/components/progress/dark/progress@2x.png +0 -0
- psychopy/experiment/components/progress/light/progress.png +0 -0
- psychopy/experiment/components/progress/light/progress@2x.png +0 -0
- psychopy/experiment/components/resourceManager/__init__.py +176 -0
- psychopy/experiment/components/resourceManager/classic/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/classic/resource_manager@2x.png +0 -0
- psychopy/experiment/components/resourceManager/dark/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/dark/resource_manager@2x.png +0 -0
- psychopy/experiment/components/resourceManager/light/resource_manager.png +0 -0
- psychopy/experiment/components/resourceManager/light/resource_manager@2x.png +0 -0
- psychopy/experiment/components/roi/__init__.py +316 -0
- psychopy/experiment/components/roi/classic/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/classic/eyetracker_roi@2x.png +0 -0
- psychopy/experiment/components/roi/dark/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/dark/eyetracker_roi@2X.png +0 -0
- psychopy/experiment/components/roi/light/eyetracker_roi.png +0 -0
- psychopy/experiment/components/roi/light/eyetracker_roi@2X.png +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +387 -0
- psychopy/experiment/components/routineSettings/classic/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/classic/routineSettings@2x.png +0 -0
- psychopy/experiment/components/routineSettings/dark/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/dark/routineSettings@2x.png +0 -0
- psychopy/experiment/components/routineSettings/light/routineSettings.png +0 -0
- psychopy/experiment/components/routineSettings/light/routineSettings@2x.png +0 -0
- psychopy/experiment/components/serialOut/__init__.py +304 -0
- psychopy/experiment/components/serialOut/classic/serial.png +0 -0
- psychopy/experiment/components/serialOut/classic/serial@2x.png +0 -0
- psychopy/experiment/components/serialOut/dark/serial.png +0 -0
- psychopy/experiment/components/serialOut/dark/serial@2x.png +0 -0
- psychopy/experiment/components/serialOut/light/serial.png +0 -0
- psychopy/experiment/components/serialOut/light/serial@2x.png +0 -0
- psychopy/experiment/components/settings/JS_htmlHeader.tmpl +23 -0
- psychopy/experiment/components/settings/JS_setupExp.tmpl +28 -0
- psychopy/experiment/components/settings/__init__.py +2254 -0
- psychopy/experiment/components/settings/classic/settings.png +0 -0
- psychopy/experiment/components/settings/classic/settings@2x.png +0 -0
- psychopy/experiment/components/settings/dark/settings.png +0 -0
- psychopy/experiment/components/settings/dark/settings@2x.png +0 -0
- psychopy/experiment/components/settings/eyetracking.py +108 -0
- psychopy/experiment/components/settings/light/settings.png +0 -0
- psychopy/experiment/components/settings/light/settings@2x.png +0 -0
- psychopy/experiment/components/slider/__init__.py +410 -0
- psychopy/experiment/components/slider/classic/slider.png +0 -0
- psychopy/experiment/components/slider/classic/slider@2x.png +0 -0
- psychopy/experiment/components/slider/dark/slider.png +0 -0
- psychopy/experiment/components/slider/dark/slider@2x.png +0 -0
- psychopy/experiment/components/slider/light/slider.png +0 -0
- psychopy/experiment/components/slider/light/slider@2x.png +0 -0
- psychopy/experiment/components/sound/__init__.py +441 -0
- psychopy/experiment/components/sound/classic/sound.png +0 -0
- psychopy/experiment/components/sound/classic/sound@2x.png +0 -0
- psychopy/experiment/components/sound/dark/sound.png +0 -0
- psychopy/experiment/components/sound/dark/sound@2x.png +0 -0
- psychopy/experiment/components/sound/light/sound.png +0 -0
- psychopy/experiment/components/sound/light/sound@2x.png +0 -0
- psychopy/experiment/components/soundsensor/__init__.py +324 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
- psychopy/experiment/components/static/__init__.py +293 -0
- psychopy/experiment/components/static/classic/static.png +0 -0
- psychopy/experiment/components/static/classic/static@2x.png +0 -0
- psychopy/experiment/components/static/dark/static.png +0 -0
- psychopy/experiment/components/static/dark/static@2x.png +0 -0
- psychopy/experiment/components/static/light/static.png +0 -0
- psychopy/experiment/components/static/light/static@2x.png +0 -0
- psychopy/experiment/components/text/__init__.py +190 -0
- psychopy/experiment/components/text/classic/text.png +0 -0
- psychopy/experiment/components/text/classic/text@2x.png +0 -0
- psychopy/experiment/components/text/dark/text.png +0 -0
- psychopy/experiment/components/text/dark/text@2x.png +0 -0
- psychopy/experiment/components/text/light/text.png +0 -0
- psychopy/experiment/components/text/light/text@2x.png +0 -0
- psychopy/experiment/components/textbox/__init__.py +336 -0
- psychopy/experiment/components/textbox/classic/textbox.png +0 -0
- psychopy/experiment/components/textbox/classic/textbox@2x.png +0 -0
- psychopy/experiment/components/textbox/dark/textbox.png +0 -0
- psychopy/experiment/components/textbox/dark/textbox@2x.png +0 -0
- psychopy/experiment/components/textbox/light/textbox.png +0 -0
- psychopy/experiment/components/textbox/light/textbox@2x.png +0 -0
- psychopy/experiment/components/unknown/__init__.py +87 -0
- psychopy/experiment/components/unknown/classic/unknown.png +0 -0
- psychopy/experiment/components/unknown/classic/unknown@2x.png +0 -0
- psychopy/experiment/components/unknown/dark/unknown.png +0 -0
- psychopy/experiment/components/unknown/dark/unknown@2x.png +0 -0
- psychopy/experiment/components/unknown/light/unknown.png +0 -0
- psychopy/experiment/components/unknown/light/unknown@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +86 -0
- psychopy/experiment/components/unknownPlugin/classic/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/classic/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/dark/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/dark/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/unknownPlugin/light/unknownPlugin.png +0 -0
- psychopy/experiment/components/unknownPlugin/light/unknownPlugin@2x.png +0 -0
- psychopy/experiment/components/utils.py +0 -0
- psychopy/experiment/components/variable/__init__.py +184 -0
- psychopy/experiment/components/variable/classic/variable.png +0 -0
- psychopy/experiment/components/variable/classic/variable@2x.png +0 -0
- psychopy/experiment/components/variable/dark/variable.png +0 -0
- psychopy/experiment/components/variable/dark/variable@2x.png +0 -0
- psychopy/experiment/components/variable/light/variable.png +0 -0
- psychopy/experiment/components/variable/light/variable@2x.png +0 -0
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/experiment.xsd +223 -0
- psychopy/experiment/exports.py +390 -0
- psychopy/experiment/flow.py +547 -0
- psychopy/experiment/localization.py +11 -0
- psychopy/experiment/loopTemplate.xltx +0 -0
- psychopy/experiment/loops.py +1051 -0
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +541 -0
- psychopy/experiment/plugins.py +42 -0
- psychopy/experiment/py2js.py +183 -0
- psychopy/experiment/py2js_transpiler.py +613 -0
- psychopy/experiment/questPlusTemplate.xltx +0 -0
- psychopy/experiment/questTemplate.xltx +0 -0
- psychopy/experiment/routines/__init__.py +108 -0
- psychopy/experiment/routines/_base.py +1172 -0
- psychopy/experiment/routines/audioValidator/__init__.py +229 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +320 -0
- psychopy/experiment/routines/counterbalance/classic/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/classic/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/counterbalanceItems.xltx +0 -0
- psychopy/experiment/routines/counterbalance/dark/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/dark/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/counterbalance/light/counterbalance.png +0 -0
- psychopy/experiment/routines/counterbalance/light/counterbalance@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/__init__.py +334 -0
- psychopy/experiment/routines/eyetracker_calibrate/classic/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/dark/eyetracker_calib@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/light/eyetracker_calib@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/__init__.py +354 -0
- psychopy/experiment/routines/eyetracker_validate/classic/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/dark/eyetracker_valid@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid.png +0 -0
- psychopy/experiment/routines/eyetracker_validate/light/eyetracker_valid@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/__init__.py +236 -0
- psychopy/experiment/routines/pavlovia_survey/classic/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/classic/survey@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/dark/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/dark/survey@2x.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/light/survey.png +0 -0
- psychopy/experiment/routines/pavlovia_survey/light/survey@2x.png +0 -0
- psychopy/experiment/routines/unknown/__init__.py +30 -0
- psychopy/experiment/routines/unknown/classic/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/classic/unknown.png +0 -0
- psychopy/experiment/routines/unknown/dark/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/dark/eyetracker_record@2x.png +0 -0
- psychopy/experiment/routines/unknown/dark/unknown.png +0 -0
- psychopy/experiment/routines/unknown/dark/unknown@2x.png +0 -0
- psychopy/experiment/routines/unknown/light/eyetracker_record.png +0 -0
- psychopy/experiment/routines/unknown/light/eyetracker_record@2x.png +0 -0
- psychopy/experiment/routines/unknown/light/unknown.png +0 -0
- psychopy/experiment/routines/unknown/light/unknown@2x.png +0 -0
- psychopy/experiment/routines/utils.py +0 -0
- psychopy/experiment/routines/visualValidator/__init__.py +395 -0
- psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
- psychopy/experiment/staircaseTemplate.xltx +0 -0
- psychopy/experiment/utils.py +44 -0
- psychopy/filters.py +11 -0
- psychopy/gamma.py +9 -0
- psychopy/gui/__init__.py +47 -0
- psychopy/gui/qtgui.py +882 -0
- psychopy/gui/util.py +74 -0
- psychopy/gui/wxgui.py +420 -0
- psychopy/hardware/__init__.py +178 -0
- psychopy/hardware/base.py +466 -0
- psychopy/hardware/bbtk/__init__.py +24 -0
- psychopy/hardware/brainproducts.py +25 -0
- psychopy/hardware/button.py +238 -0
- psychopy/hardware/buttonbox/__init__.py +122 -0
- psychopy/hardware/camera/__init__.py +3657 -0
- psychopy/hardware/cedrus.py +24 -0
- psychopy/hardware/crs/__init__.py +46 -0
- psychopy/hardware/crs/bits.py +37 -0
- psychopy/hardware/crs/colorcal.py +18 -0
- psychopy/hardware/crs/optical.py +33 -0
- psychopy/hardware/crs/shaders.py +34 -0
- psychopy/hardware/emotiv.py +33 -0
- psychopy/hardware/emulator.py +46 -0
- psychopy/hardware/exceptions.py +86 -0
- psychopy/hardware/eyetracker.py +132 -0
- psychopy/hardware/forp.py +37 -0
- psychopy/hardware/gammasci.py +26 -0
- psychopy/hardware/iolab.py +24 -0
- psychopy/hardware/joystick/__init__.py +1196 -0
- psychopy/hardware/joystick/_base.py +251 -0
- psychopy/hardware/joystick/backend_glfw.py +306 -0
- psychopy/hardware/joystick/backend_pyglet.py +309 -0
- psychopy/hardware/joystick/mappings.py +287 -0
- psychopy/hardware/keyboard.py +975 -0
- psychopy/hardware/knownDevices.json +52 -0
- psychopy/hardware/labhackers.py +27 -0
- psychopy/hardware/labjacks.py +26 -0
- psychopy/hardware/lightsensor.py +789 -0
- psychopy/hardware/listener.py +330 -0
- psychopy/hardware/manager.py +992 -0
- psychopy/hardware/microphone.py +1630 -0
- psychopy/hardware/minolta.py +33 -0
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/mouse/__init__.py +885 -0
- psychopy/hardware/photometer/__init__.py +207 -0
- psychopy/hardware/pr.py +33 -0
- psychopy/hardware/qmix.py +32 -0
- psychopy/hardware/serialdevice.py +387 -0
- psychopy/hardware/soundsensor.py +476 -0
- psychopy/hardware/spatial/__init__.py +231 -0
- psychopy/hardware/speaker.py +351 -0
- psychopy/hardware/triggerbox/__init__.py +56 -0
- psychopy/hardware/triggerbox/base.py +170 -0
- psychopy/hardware/triggerbox/parallel.py +362 -0
- psychopy/info.py +719 -0
- psychopy/iohub/__init__.py +50 -0
- psychopy/iohub/client/__init__.py +1481 -0
- psychopy/iohub/client/connect.py +260 -0
- psychopy/iohub/client/eyetracker/__init__.py +4 -0
- psychopy/iohub/client/eyetracker/validation/__init__.py +8 -0
- psychopy/iohub/client/eyetracker/validation/posgrid.py +274 -0
- psychopy/iohub/client/eyetracker/validation/procedure.py +1292 -0
- psychopy/iohub/client/eyetracker/validation/trigger.py +240 -0
- psychopy/iohub/client/keyboard.py +513 -0
- psychopy/iohub/client/wintab.py +378 -0
- psychopy/iohub/constants.py +1176 -0
- psychopy/iohub/datastore/__init__.py +439 -0
- psychopy/iohub/datastore/default_datastore.yaml +8 -0
- psychopy/iohub/datastore/util.py +868 -0
- psychopy/iohub/default_config.yaml +17 -0
- psychopy/iohub/devices/__init__.py +1047 -0
- psychopy/iohub/devices/computer.py +607 -0
- psychopy/iohub/devices/default_device.yaml +15 -0
- psychopy/iohub/devices/deviceConfigValidation.py +519 -0
- psychopy/iohub/devices/display/__init__.py +755 -0
- psychopy/iohub/devices/display/default_display.yaml +118 -0
- psychopy/iohub/devices/display/supported_config_settings.yaml +69 -0
- psychopy/iohub/devices/eventfilters.py +3572 -0
- psychopy/iohub/devices/experiment/__init__.py +316 -0
- psychopy/iohub/devices/experiment/default_experiment.yaml +74 -0
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +26 -0
- psychopy/iohub/devices/eyetracker/__init__.py +474 -0
- psychopy/iohub/devices/eyetracker/calibration/__init__.py +1 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +391 -0
- psychopy/iohub/devices/eyetracker/default_eyetracker.yaml +18 -0
- psychopy/iohub/devices/eyetracker/eye_events.py +1751 -0
- psychopy/iohub/devices/eyetracker/filters/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/filters/parser.py +913 -0
- psychopy/iohub/devices/eyetracker/hw/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/calibration.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +31 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/__init__.py +8 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/calibration.py +10 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/default_eyetracker.yaml +102 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +443 -0
- psychopy/iohub/devices/eyetracker/hw/mouse/supported_config_settings.yaml +110 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +27 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/eyetracker.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/bisector.py +20 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/constants.py +19 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/data_parse.py +22 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/eyetracker.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/pupil_core/pupil_remote.py +18 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/__init__.py +4 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/__init__.py +28 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/calibration.py +22 -0
- psychopy/iohub/devices/eyetracker/hw/sr_research/eyelink/eyetracker.py +38 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/__init__.py +29 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/calibration.py +19 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/eyetracker.py +17 -0
- psychopy/iohub/devices/eyetracker/hw/tobii/tobiiwrapper.py +19 -0
- psychopy/iohub/devices/eyetracker/supported_config_settings.yaml +44 -0
- psychopy/iohub/devices/keyboard/__init__.py +309 -0
- psychopy/iohub/devices/keyboard/darwin.py +431 -0
- psychopy/iohub/devices/keyboard/darwinkey.py +94 -0
- psychopy/iohub/devices/keyboard/default_keyboard.yaml +94 -0
- psychopy/iohub/devices/keyboard/linux2.py +102 -0
- psychopy/iohub/devices/keyboard/supported_config_settings.yaml +39 -0
- psychopy/iohub/devices/keyboard/win32.py +356 -0
- psychopy/iohub/devices/mouse/__init__.py +709 -0
- psychopy/iohub/devices/mouse/darwin.py +414 -0
- psychopy/iohub/devices/mouse/default_mouse.yaml +92 -0
- psychopy/iohub/devices/mouse/linux2.py +172 -0
- psychopy/iohub/devices/mouse/supported_config_settings.yaml +39 -0
- psychopy/iohub/devices/mouse/win32.py +284 -0
- psychopy/iohub/devices/pyXHook.py +619 -0
- psychopy/iohub/devices/serial/__init__.py +779 -0
- psychopy/iohub/devices/serial/default_pstbox.yaml +108 -0
- psychopy/iohub/devices/serial/default_serial.yaml +108 -0
- psychopy/iohub/devices/serial/supported_config_settings.yaml +85 -0
- psychopy/iohub/devices/serial/supported_config_settings_pstbox.yaml +85 -0
- psychopy/iohub/devices/supported_config_settings.yaml +36 -0
- psychopy/iohub/devices/wintab/__init__.py +580 -0
- psychopy/iohub/devices/wintab/default_wintab.yaml +89 -0
- psychopy/iohub/devices/wintab/supported_config_settings.yaml +42 -0
- psychopy/iohub/devices/wintab/win32.py +722 -0
- psychopy/iohub/devices/xlib.py +6795 -0
- psychopy/iohub/errors.py +61 -0
- psychopy/iohub/lazy_import.py +582 -0
- psychopy/iohub/net.py +349 -0
- psychopy/iohub/server.py +1075 -0
- psychopy/iohub/start_iohub_process.py +121 -0
- psychopy/iohub/util/__init__.py +832 -0
- psychopy/iohub/util/visualangle.py +126 -0
- psychopy/layout.py +916 -0
- psychopy/liaison.py +471 -0
- psychopy/locale_setup.py +41 -0
- psychopy/localization/__init__.py +22 -0
- psychopy/localization/_localization.py +155 -0
- psychopy/localization/generateTranslationTemplate.py +257 -0
- psychopy/localization/mappings.txt +170 -0
- psychopy/localization/messages.pot +9950 -0
- psychopy/localization/readme.txt +68 -0
- psychopy/logging.py +415 -0
- psychopy/microphone.py +28 -0
- psychopy/misc.py +40 -0
- psychopy/monitors/MonitorCenter.py +1354 -0
- psychopy/monitors/__init__.py +30 -0
- psychopy/monitors/calibData.py +78 -0
- psychopy/monitors/calibTools.py +1301 -0
- psychopy/monitors/getLumSeries.py +50 -0
- psychopy/monitors/psychopy-icon.svg +395 -0
- psychopy/monitors/psychopy.ico +0 -0
- psychopy/parallel/__init__.py +206 -0
- psychopy/parallel/_dlportio.py +150 -0
- psychopy/parallel/_inpout.py +120 -0
- psychopy/parallel/_linux.py +105 -0
- psychopy/piloting.py +61 -0
- psychopy/platform_specific/__init__.py +46 -0
- psychopy/platform_specific/darwin.py +326 -0
- psychopy/platform_specific/linux.py +77 -0
- psychopy/platform_specific/win32.py +124 -0
- psychopy/plugins/__init__.py +1397 -0
- psychopy/plugins/util.py +104 -0
- psychopy/preferences/Darwin.spec +294 -0
- psychopy/preferences/FreeBSD.spec +294 -0
- psychopy/preferences/Linux.spec +294 -0
- psychopy/preferences/Windows.spec +294 -0
- psychopy/preferences/__init__.py +20 -0
- psychopy/preferences/baseNoArch.spec +290 -0
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +93 -0
- psychopy/preferences/generateSpec.py +80 -0
- psychopy/preferences/hints.py +367 -0
- psychopy/preferences/preferences.py +448 -0
- psychopy/projects/__init__.py +11 -0
- psychopy/projects/gitignore.py +45 -0
- psychopy/projects/pavlovia.py +1674 -0
- psychopy/projects/sshkeys.py +107 -0
- psychopy/psychojs.zip +0 -0
- psychopy/pylintrc +69 -0
- psychopy/scripts/__init__.py +0 -0
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +229 -0
- psychopy/session.py +1651 -0
- psychopy/sound/__init__.py +50 -0
- psychopy/sound/_base.py +399 -0
- psychopy/sound/audioclip.py +1325 -0
- psychopy/sound/audiodevice.py +857 -0
- psychopy/sound/backend_ptb.py +406 -0
- psychopy/sound/backend_pygame.py +323 -0
- psychopy/sound/backend_pyo.py +64 -0
- psychopy/sound/backend_pysound.py +324 -0
- psychopy/sound/backend_sounddevice.py +44 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/exceptions.py +115 -0
- psychopy/sound/microphone.py +357 -0
- psychopy/sound/sound.py +58 -0
- psychopy/sound/transcribe.py +1321 -0
- psychopy/tests/README.md +344 -0
- psychopy/tests/__init__.py +47 -0
- psychopy/tests/data/Electronic_Chime-KevanGC-495939803.wav +0 -0
- psychopy/tests/data/TestCircle_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestCircle_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq10_s1.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq1_s1.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s0.5.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s0.png +0 -0
- psychopy/tests/data/TestForm_scrolling_nq3_s1.png +0 -0
- psychopy/tests/data/TestImage_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestImage_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestImage_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestImage_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestImage_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestImage_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestImage_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestImage_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestImage_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestImage_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestImage_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestImage_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestImage_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestImage_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_center center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_left center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_horizontal_right center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_minmax_0.png +0 -0
- psychopy/tests/data/TestProgress_testValue_minmax_1.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_bottom center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_center center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_center center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_top center_0.3.png +0 -0
- psychopy/tests/data/TestProgress_testValue_vertical_top center_0.6.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestProgress_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestROI_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestROI_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestROI_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestROI_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestROI_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestROI_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestROI_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestROI_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestROI_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestROI_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestROI_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestROI_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestROI_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestROI_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestSession_exp1.png +0 -0
- psychopy/tests/data/TestSession_exp2.png +0 -0
- psychopy/tests/data/TestShape_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestShape_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestShape_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestShape_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestShape_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestShape_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestShape_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestShape_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestShape_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestShape_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestShape_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestShape_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestShape_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestShape_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h128_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w128h64_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h128_top_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_center_center.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_top_left.png +0 -0
- psychopy/tests/data/TestTarget_w64h64_top_right.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_0p6.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_0p8.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p2.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p4.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p6.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_1p8.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_2p0.png +0 -0
- psychopy/tests/data/TestTextbox_testLetterSpacing_None.png +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_-3ovr8_0ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_-3ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_0ovr8_3ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_testSpeechpoint_3ovr8_0ovr8.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h128_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w128h64_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h128_top_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_center_center.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_top_left.png +0 -0
- psychopy/tests/data/Test_textbox_w64h64_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h128_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w128h64_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h128_top_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_bottom_right.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_center_center.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_top_left.png +0 -0
- psychopy/tests/data/Test_uax14_textbox_w64h64_top_right.png +0 -0
- psychopy/tests/data/TextComponent_disabled.psyexp +63 -0
- psychopy/tests/data/TextComponent_not_disabled.psyexp +63 -0
- psychopy/tests/data/aperture1_deg.png +0 -0
- psychopy/tests/data/aperture1_degFlat.png +0 -0
- psychopy/tests/data/aperture1_degFlatPos.png +0 -0
- psychopy/tests/data/aperture1_norm.png +0 -0
- psychopy/tests/data/aperture1_normHexbackground.png +0 -0
- psychopy/tests/data/aperture1_normNoShade.png +0 -0
- psychopy/tests/data/aperture1_pix.png +0 -0
- psychopy/tests/data/aperture1_stencil.png +0 -0
- psychopy/tests/data/aperture2_deg.png +0 -0
- psychopy/tests/data/aperture2_degFlat.png +0 -0
- psychopy/tests/data/aperture2_degFlatPos.png +0 -0
- psychopy/tests/data/aperture2_norm.png +0 -0
- psychopy/tests/data/aperture2_normHexbackground.png +0 -0
- psychopy/tests/data/aperture2_normNoShade.png +0 -0
- psychopy/tests/data/aperture2_pix.png +0 -0
- psychopy/tests/data/aperture2_stencil.png +0 -0
- psychopy/tests/data/beatandrcos_cm.png +0 -0
- psychopy/tests/data/beatandrcos_deg.png +0 -0
- psychopy/tests/data/beatandrcos_degFlat.png +0 -0
- psychopy/tests/data/beatandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/beatandrcos_height.png +0 -0
- psychopy/tests/data/beatandrcos_norm.png +0 -0
- psychopy/tests/data/beatandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/beatandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/beatandrcos_pix.png +0 -0
- psychopy/tests/data/beatandrcos_stencil.png +0 -0
- psychopy/tests/data/blend_add_cm.png +0 -0
- psychopy/tests/data/blend_add_deg.png +0 -0
- psychopy/tests/data/blend_add_degFlat.png +0 -0
- psychopy/tests/data/blend_add_degFlatPos.png +0 -0
- psychopy/tests/data/blend_add_height.png +0 -0
- psychopy/tests/data/blend_add_norm.png +0 -0
- psychopy/tests/data/blend_add_normAddBlend.png +0 -0
- psychopy/tests/data/blend_add_normHexbackground.png +0 -0
- psychopy/tests/data/blend_add_normNoShade.png +0 -0
- psychopy/tests/data/blend_add_pix.png +0 -0
- psychopy/tests/data/blend_add_stencil.png +0 -0
- psychopy/tests/data/broken2020_2_5_resources/broken_resources.psyexp +84 -0
- psychopy/tests/data/broken2020_2_5_resources/psychopy72.png +0 -0
- psychopy/tests/data/bufferimg_gabor_cm.png +0 -0
- psychopy/tests/data/bufferimg_gabor_deg.png +0 -0
- psychopy/tests/data/bufferimg_gabor_degFlat.png +0 -0
- psychopy/tests/data/bufferimg_gabor_degFlatPos.png +0 -0
- psychopy/tests/data/bufferimg_gabor_height.png +0 -0
- psychopy/tests/data/bufferimg_gabor_norm.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normAddBlend.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normHexbackground.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normNoShade.png +0 -0
- psychopy/tests/data/bufferimg_gabor_pix.png +0 -0
- psychopy/tests/data/bufferimg_gabor_stencil.png +0 -0
- psychopy/tests/data/circleHex_cm.png +0 -0
- psychopy/tests/data/circleHex_deg.png +0 -0
- psychopy/tests/data/circleHex_degFlat.png +0 -0
- psychopy/tests/data/circleHex_degFlatPos.png +0 -0
- psychopy/tests/data/circleHex_height.png +0 -0
- psychopy/tests/data/circleHex_norm.png +0 -0
- psychopy/tests/data/circleHex_normAddBlend.png +0 -0
- psychopy/tests/data/circleHex_normHexbackground.png +0 -0
- psychopy/tests/data/circleHex_normNoShade.png +0 -0
- psychopy/tests/data/circleHex_pix.png +0 -0
- psychopy/tests/data/circleHex_stencil.png +0 -0
- psychopy/tests/data/corrFullRandom.csv +16 -0
- psychopy/tests/data/corrFullRandom.tsv +6 -0
- psychopy/tests/data/corrFullRandomTH2.csv +16 -0
- psychopy/tests/data/corrMultiKeyExpWide.csv +3 -0
- psychopy/tests/data/corrMultiKeyTrials.csv +8 -0
- psychopy/tests/data/corrMultiKeyTrials.xlsx +0 -0
- psychopy/tests/data/corrRandom.csv +16 -0
- psychopy/tests/data/corrRandom.tsv +6 -0
- psychopy/tests/data/corrRandomTH2.csv +16 -0
- psychopy/tests/data/corrXlsx.xlsx +0 -0
- psychopy/tests/data/correctScript/js/correctCodeComponent.js +181 -0
- psychopy/tests/data/correctScript/js/correctImageComponent.js +204 -0
- psychopy/tests/data/correctScript/js/correctKeyboardComponent.js +225 -0
- psychopy/tests/data/correctScript/js/correctMouseComponent.js +221 -0
- psychopy/tests/data/correctScript/js/correctMovieComponent.js +210 -0
- psychopy/tests/data/correctScript/js/correctPolygonComponent.js +205 -0
- psychopy/tests/data/correctScript/js/correctSliderComponent.js +209 -0
- psychopy/tests/data/correctScript/js/correctSoundComponent.js +205 -0
- psychopy/tests/data/correctScript/js/correctTextComponent.js +207 -0
- psychopy/tests/data/correctScript/python/correctApertureComponent.py +157 -0
- psychopy/tests/data/correctScript/python/correctCodeComponent.py +135 -0
- psychopy/tests/data/correctScript/python/correctDotsComponent.py +161 -0
- psychopy/tests/data/correctScript/python/correctEnvGratingComponent.py +167 -0
- psychopy/tests/data/correctScript/python/correctFormComponent.py +150 -0
- psychopy/tests/data/correctScript/python/correctGratingComponent.py +158 -0
- psychopy/tests/data/correctScript/python/correctImageComponent.py +160 -0
- psychopy/tests/data/correctScript/python/correctJoyButtonsComponent.py +204 -0
- psychopy/tests/data/correctScript/python/correctJoystickComponent.py +214 -0
- psychopy/tests/data/correctScript/python/correctKeyboardComponent.py +169 -0
- psychopy/tests/data/correctScript/python/correctMicrophoneComponent.py +163 -0
- psychopy/tests/data/correctScript/python/correctMouseComponent.py +174 -0
- psychopy/tests/data/correctScript/python/correctMovieComponent.py +160 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +169 -0
- psychopy/tests/data/correctScript/python/correctParallelOutComponent.py +156 -0
- psychopy/tests/data/correctScript/python/correctPatchComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctPolygonComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctQmixPumpComponent.py +174 -0
- psychopy/tests/data/correctScript/python/correctRatingScaleComponent.py +152 -0
- psychopy/tests/data/correctScript/python/correctSliderComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctSoundComponent.py +157 -0
- psychopy/tests/data/correctScript/python/correctStaticComponent.py +176 -0
- psychopy/tests/data/correctScript/python/correctTextComponent.py +159 -0
- psychopy/tests/data/correctScript/python/correctVariableComponent.py +139 -0
- psychopy/tests/data/correctScript/python/correctcedrusButtonBoxComponent.py +199 -0
- psychopy/tests/data/correctScript/python/correctioLabsButtonBoxComponent.py +187 -0
- psychopy/tests/data/dataTest.xlsx +0 -0
- psychopy/tests/data/dots_cm.png +0 -0
- psychopy/tests/data/dots_deg.png +0 -0
- psychopy/tests/data/dots_degFlat.png +0 -0
- psychopy/tests/data/dots_degFlatPos.png +0 -0
- psychopy/tests/data/dots_height.png +0 -0
- psychopy/tests/data/dots_norm.png +0 -0
- psychopy/tests/data/dots_normAddBlend.png +0 -0
- psychopy/tests/data/dots_normHexbackground.png +0 -0
- psychopy/tests/data/dots_normNoShade.png +0 -0
- psychopy/tests/data/dots_pix.png +0 -0
- psychopy/tests/data/dots_stencil.png +0 -0
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/data/elarray1_cm.png +0 -0
- psychopy/tests/data/elarray1_deg.png +0 -0
- psychopy/tests/data/elarray1_degFlat.png +0 -0
- psychopy/tests/data/elarray1_degFlatPos.png +0 -0
- psychopy/tests/data/elarray1_height.png +0 -0
- psychopy/tests/data/elarray1_norm.png +0 -0
- psychopy/tests/data/elarray1_normAddBlend.png +0 -0
- psychopy/tests/data/elarray1_normHexbackground.png +0 -0
- psychopy/tests/data/elarray1_pix.png +0 -0
- psychopy/tests/data/elarray1_stencil.png +0 -0
- psychopy/tests/data/envelopeandrcos_cm.png +0 -0
- psychopy/tests/data/envelopeandrcos_deg.png +0 -0
- psychopy/tests/data/envelopeandrcos_degFlat.png +0 -0
- psychopy/tests/data/envelopeandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/envelopeandrcos_height.png +0 -0
- psychopy/tests/data/envelopeandrcos_norm.png +0 -0
- psychopy/tests/data/envelopeandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/envelopeandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/envelopeandrcos_pix.png +0 -0
- psychopy/tests/data/envelopeandrcos_stencil.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_cm.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_deg.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_degFlat.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_degFlatPos.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_height.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_norm.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normAddBlend.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normHexbackground.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_pix.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_stencil.png +0 -0
- psychopy/tests/data/filltext.png +0 -0
- psychopy/tests/data/form_font_demographics.png +0 -0
- psychopy/tests/data/form_font_demographics.xlsx +0 -0
- psychopy/tests/data/form_font_languages.png +0 -0
- psychopy/tests/data/form_font_languages.xlsx +0 -0
- psychopy/tests/data/form_font_nonstandard.png +0 -0
- psychopy/tests/data/futureParams.psyexp +150 -0
- psychopy/tests/data/gabor1_cm.png +0 -0
- psychopy/tests/data/gabor1_deg.png +0 -0
- psychopy/tests/data/gabor1_degFlat.png +0 -0
- psychopy/tests/data/gabor1_degFlatPos.png +0 -0
- psychopy/tests/data/gabor1_height.png +0 -0
- psychopy/tests/data/gabor1_norm.png +0 -0
- psychopy/tests/data/gabor1_normAddBlend.png +0 -0
- psychopy/tests/data/gabor1_normHexbackground.png +0 -0
- psychopy/tests/data/gabor1_normNoShade.png +0 -0
- psychopy/tests/data/gabor1_pix.png +0 -0
- psychopy/tests/data/gabor1_stencil.png +0 -0
- psychopy/tests/data/gabor2_cm.png +0 -0
- psychopy/tests/data/gabor2_deg.png +0 -0
- psychopy/tests/data/gabor2_degFlat.png +0 -0
- psychopy/tests/data/gabor2_degFlatPos.png +0 -0
- psychopy/tests/data/gabor2_height.png +0 -0
- psychopy/tests/data/gabor2_norm.png +0 -0
- psychopy/tests/data/gabor2_normAddBlend.png +0 -0
- psychopy/tests/data/gabor2_normHexbackground.png +0 -0
- psychopy/tests/data/gabor2_normNoShade.png +0 -0
- psychopy/tests/data/gabor2_pix.png +0 -0
- psychopy/tests/data/gabor2_stencil.png +0 -0
- psychopy/tests/data/ghost_stroop.psyexp +131 -0
- psychopy/tests/data/ghost_trialTypes.xlsx +0 -0
- psychopy/tests/data/green_48000.flac.dist +0 -0
- psychopy/tests/data/greyscale.jpg +0 -0
- psychopy/tests/data/greyscale2.png +0 -0
- psychopy/tests/data/greyscale2_cm.png +0 -0
- psychopy/tests/data/greyscale2_deg.png +0 -0
- psychopy/tests/data/greyscale2_degFlat.png +0 -0
- psychopy/tests/data/greyscale2_degFlatPos.png +0 -0
- psychopy/tests/data/greyscale2_height.png +0 -0
- psychopy/tests/data/greyscale2_norm.png +0 -0
- psychopy/tests/data/greyscale2_normAddBlend.png +0 -0
- psychopy/tests/data/greyscale2_normHexbackground.png +0 -0
- psychopy/tests/data/greyscale2_normNoShade.png +0 -0
- psychopy/tests/data/greyscale2_pix.png +0 -0
- psychopy/tests/data/greyscale2_stencil.png +0 -0
- psychopy/tests/data/greyscaleLowContr_cm.png +0 -0
- psychopy/tests/data/greyscaleLowContr_deg.png +0 -0
- psychopy/tests/data/greyscaleLowContr_degFlat.png +0 -0
- psychopy/tests/data/greyscaleLowContr_degFlatPos.png +0 -0
- psychopy/tests/data/greyscaleLowContr_height.png +0 -0
- psychopy/tests/data/greyscaleLowContr_norm.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normAddBlend.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normHexbackground.png +0 -0
- psychopy/tests/data/greyscaleLowContr_normNoShade.png +0 -0
- psychopy/tests/data/greyscaleLowContr_pix.png +0 -0
- psychopy/tests/data/greyscaleLowContr_stencil.png +0 -0
- psychopy/tests/data/greyscale_cm.png +0 -0
- psychopy/tests/data/greyscale_deg.png +0 -0
- psychopy/tests/data/greyscale_degFlat.png +0 -0
- psychopy/tests/data/greyscale_degFlatPos.png +0 -0
- psychopy/tests/data/greyscale_height.png +0 -0
- psychopy/tests/data/greyscale_norm.png +0 -0
- psychopy/tests/data/greyscale_normAddBlend.png +0 -0
- psychopy/tests/data/greyscale_normHexbackground.png +0 -0
- psychopy/tests/data/greyscale_normNoShade.png +0 -0
- psychopy/tests/data/greyscale_pix.png +0 -0
- psychopy/tests/data/greyscale_stencil.png +0 -0
- psychopy/tests/data/imageAndGauss_cm.png +0 -0
- psychopy/tests/data/imageAndGauss_deg.png +0 -0
- psychopy/tests/data/imageAndGauss_degFlat.png +0 -0
- psychopy/tests/data/imageAndGauss_degFlatPos.png +0 -0
- psychopy/tests/data/imageAndGauss_height.png +0 -0
- psychopy/tests/data/imageAndGauss_norm.png +0 -0
- psychopy/tests/data/imageAndGauss_normAddBlend.png +0 -0
- psychopy/tests/data/imageAndGauss_normHexbackground.png +0 -0
- psychopy/tests/data/imageAndGauss_normNoShade.png +0 -0
- psychopy/tests/data/imageAndGauss_pix.png +0 -0
- psychopy/tests/data/imageAndGauss_stencil.png +0 -0
- psychopy/tests/data/movFrame1_cm.png +0 -0
- psychopy/tests/data/movFrame1_deg.png +0 -0
- psychopy/tests/data/movFrame1_degFlat.png +0 -0
- psychopy/tests/data/movFrame1_degFlatPos.png +0 -0
- psychopy/tests/data/movFrame1_height.png +0 -0
- psychopy/tests/data/movFrame1_norm.png +0 -0
- psychopy/tests/data/movFrame1_normAddBlend.png +0 -0
- psychopy/tests/data/movFrame1_normHexbackground.png +0 -0
- psychopy/tests/data/movFrame1_normNoShade.png +0 -0
- psychopy/tests/data/movFrame1_pix.png +0 -0
- psychopy/tests/data/movFrame1_stencil.png +0 -0
- psychopy/tests/data/multiKeypressExperiment.psydat +357 -0
- psychopy/tests/data/multiKeypressTrialhandler.psydat +252 -0
- psychopy/tests/data/multiStairConds.xlsx +0 -0
- psychopy/tests/data/multiStairQuestPlus.xlsx +0 -0
- psychopy/tests/data/newstyle.psydat +6155 -0
- psychopy/tests/data/noiseAndRcos_cm.png +0 -0
- psychopy/tests/data/noiseAndRcos_deg.png +0 -0
- psychopy/tests/data/noiseAndRcos_degFlat.png +0 -0
- psychopy/tests/data/noiseAndRcos_degFlatPos.png +0 -0
- psychopy/tests/data/noiseAndRcos_height.png +0 -0
- psychopy/tests/data/noiseAndRcos_norm.png +0 -0
- psychopy/tests/data/noiseAndRcos_normAddBlend.png +0 -0
- psychopy/tests/data/noiseAndRcos_normHexbackground.png +0 -0
- psychopy/tests/data/noiseAndRcos_normNoShade.png +0 -0
- psychopy/tests/data/noiseAndRcos_pix.png +0 -0
- psychopy/tests/data/noiseAndRcos_stencil.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_cm.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_deg.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_degFlat.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_degFlatPos.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_height.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_norm.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normAddBlend.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normHexbackground.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normNoShade.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_pix.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_stencil.png +0 -0
- psychopy/tests/data/numpyImage_cm.png +0 -0
- psychopy/tests/data/numpyImage_deg.png +0 -0
- psychopy/tests/data/numpyImage_degFlat.png +0 -0
- psychopy/tests/data/numpyImage_degFlatPos.png +0 -0
- psychopy/tests/data/numpyImage_height.png +0 -0
- psychopy/tests/data/numpyImage_norm.png +0 -0
- psychopy/tests/data/numpyImage_normAddBlend.png +0 -0
- psychopy/tests/data/numpyImage_normHexbackground.png +0 -0
- psychopy/tests/data/numpyImage_normNoShade.png +0 -0
- psychopy/tests/data/numpyImage_pix.png +0 -0
- psychopy/tests/data/numpyImage_stencil.png +0 -0
- psychopy/tests/data/numpyLowContr_cm.png +0 -0
- psychopy/tests/data/numpyLowContr_deg.png +0 -0
- psychopy/tests/data/numpyLowContr_degFlat.png +0 -0
- psychopy/tests/data/numpyLowContr_degFlatPos.png +0 -0
- psychopy/tests/data/numpyLowContr_height.png +0 -0
- psychopy/tests/data/numpyLowContr_norm.png +0 -0
- psychopy/tests/data/numpyLowContr_normAddBlend.png +0 -0
- psychopy/tests/data/numpyLowContr_normHexbackground.png +0 -0
- psychopy/tests/data/numpyLowContr_normNoShade.png +0 -0
- psychopy/tests/data/numpyLowContr_pix.png +0 -0
- psychopy/tests/data/numpyLowContr_stencil.png +0 -0
- psychopy/tests/data/oldstyle.psydat +1534 -0
- psychopy/tests/data/oldstyle_stair.psydat +263 -0
- psychopy/tests/data/ratingscale1_cm.png +0 -0
- psychopy/tests/data/ratingscale1_deg.png +0 -0
- psychopy/tests/data/ratingscale1_degFlat.png +0 -0
- psychopy/tests/data/ratingscale1_degFlatPos.png +0 -0
- psychopy/tests/data/ratingscale1_height.png +0 -0
- psychopy/tests/data/ratingscale1_norm.png +0 -0
- psychopy/tests/data/ratingscale1_normAddBlend.png +0 -0
- psychopy/tests/data/ratingscale1_normHexbackground.png +0 -0
- psychopy/tests/data/ratingscale1_normNoShade.png +0 -0
- psychopy/tests/data/ratingscale1_pix.png +0 -0
- psychopy/tests/data/ratingscale1_stencil.png +0 -0
- psychopy/tests/data/red_16000.flac.dist +0 -0
- psychopy/tests/data/retroListParam.psyexp +82 -0
- psychopy/tests/data/right_to_left_unidcode.xlsx +0 -0
- psychopy/tests/data/sample.meshwarp.data +11522 -0
- psychopy/tests/data/shape2_1_cm.png +0 -0
- psychopy/tests/data/shape2_1_deg.png +0 -0
- psychopy/tests/data/shape2_1_degFlat.png +0 -0
- psychopy/tests/data/shape2_1_degFlatPos.png +0 -0
- psychopy/tests/data/shape2_1_height.png +0 -0
- psychopy/tests/data/shape2_1_norm.png +0 -0
- psychopy/tests/data/shape2_1_normAddBlend.png +0 -0
- psychopy/tests/data/shape2_1_normHexbackground.png +0 -0
- psychopy/tests/data/shape2_1_normNoShade.png +0 -0
- psychopy/tests/data/shape2_1_pix.png +0 -0
- psychopy/tests/data/shape2_1_stencil.png +0 -0
- psychopy/tests/data/shape2_2_cm.png +0 -0
- psychopy/tests/data/shape2_2_deg.png +0 -0
- psychopy/tests/data/shape2_2_degFlat.png +0 -0
- psychopy/tests/data/shape2_2_degFlatPos.png +0 -0
- psychopy/tests/data/shape2_2_height.png +0 -0
- psychopy/tests/data/shape2_2_norm.png +0 -0
- psychopy/tests/data/shape2_2_normAddBlend.png +0 -0
- psychopy/tests/data/shape2_2_normHexbackground.png +0 -0
- psychopy/tests/data/shape2_2_normNoShade.png +0 -0
- psychopy/tests/data/shape2_2_pix.png +0 -0
- psychopy/tests/data/shape2_2_stencil.png +0 -0
- psychopy/tests/data/simpleimage1_cm.png +0 -0
- psychopy/tests/data/simpleimage1_deg.png +0 -0
- psychopy/tests/data/simpleimage1_degFlat.png +0 -0
- psychopy/tests/data/simpleimage1_degFlatPos.png +0 -0
- psychopy/tests/data/simpleimage1_height.png +0 -0
- psychopy/tests/data/simpleimage1_norm.png +0 -0
- psychopy/tests/data/simpleimage1_normAddBlend.png +0 -0
- psychopy/tests/data/simpleimage1_normHexbackground.png +0 -0
- psychopy/tests/data/simpleimage1_normNoShade.png +0 -0
- psychopy/tests/data/simpleimage1_pix.png +0 -0
- psychopy/tests/data/simpleimage1_stencil.png +0 -0
- psychopy/tests/data/test001EntryImporting.psyexp +66 -0
- psychopy/tests/data/testMovie.mp4 +0 -0
- psychopy/tests/data/test_basic_run.py +1 -0
- psychopy/tests/data/test_circle_radius_.1height.png +0 -0
- psychopy/tests/data/test_circle_radius_.2height.png +0 -0
- psychopy/tests/data/test_circle_radius_10pix.png +0 -0
- psychopy/tests/data/test_circle_radius_20pix.png +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_form_combinations_choice_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_choice_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_description_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_free text_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_heading_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_radio_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_rating_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigItem.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigItemOverflow.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigResp.png +0 -0
- psychopy/tests/data/test_form_combinations_slider_bigRespOverflow.png +0 -0
- psychopy/tests/data/test_get_resources/blue.png +0 -0
- psychopy/tests/data/test_get_resources/groupA.csv +3 -0
- psychopy/tests/data/test_get_resources/groupB.csv +3 -0
- psychopy/tests/data/test_get_resources/groups.csv +3 -0
- psychopy/tests/data/test_get_resources/handledbyrm_constrloop.psyexp +143 -0
- psychopy/tests/data/test_get_resources/handledbyrm_noloop.psyexp +114 -0
- psychopy/tests/data/test_get_resources/handledbyrm_recurloop.psyexp +138 -0
- psychopy/tests/data/test_get_resources/handledbyrm_strloop.psyexp +126 -0
- psychopy/tests/data/test_get_resources/handledbystatic_constrloop.psyexp +141 -0
- psychopy/tests/data/test_get_resources/handledbystatic_noloop.psyexp +112 -0
- psychopy/tests/data/test_get_resources/handledbystatic_recurloop.psyexp +136 -0
- psychopy/tests/data/test_get_resources/handledbystatic_strloop.psyexp +124 -0
- psychopy/tests/data/test_get_resources/unhandled_constrloop.psyexp +128 -0
- psychopy/tests/data/test_get_resources/unhandled_noloop.psyexp +99 -0
- psychopy/tests/data/test_get_resources/unhandled_recurloop.psyexp +123 -0
- psychopy/tests/data/test_get_resources/unhandled_strloop.psyexp +111 -0
- psychopy/tests/data/test_get_resources/white.png +0 -0
- psychopy/tests/data/test_get_resources/yellow.png +0 -0
- psychopy/tests/data/test_image_aspect_default_None.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xFull_yNone.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xNone_yFull.png +0 -0
- psychopy/tests/data/test_image_aspect_default_xNone_yNone.png +0 -0
- psychopy/tests/data/test_image_flip_anchor_horiz.png +0 -0
- psychopy/tests/data/test_image_flip_anchor_vert.png +0 -0
- psychopy/tests/data/test_loaded_namespace/test_counterbalance.psyexp +142 -0
- psychopy/tests/data/test_loaded_namespace/test_custom_missing.psyexp +129 -0
- psychopy/tests/data/test_loaded_namespace/test_missing_counterbalance.psyexp +116 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_exp.psyexp +181 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_missing.psyexp +140 -0
- psychopy/tests/data/test_loaded_namespace/test_mix_name_calibration.psyexp +164 -0
- psychopy/tests/data/test_loops/testLoopsBlocks.psyexp +161 -0
- psychopy/tests/data/test_loops/testStaircase.psyexp +105 -0
- psychopy/tests/data/test_loops/test_current_loop_attr.psyexp +185 -0
- psychopy/tests/data/test_panorama/panoramaTestImage.png +0 -0
- psychopy/tests/data/test_panorama/panoramaTestImage.svg +1 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-0.3_0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_-1.0_0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_-1.0.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.0_1.0.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_-0.3.png +0 -0
- psychopy/tests/data/test_panorama/testPanorama_mvmt_0.3_0.3.png +0 -0
- psychopy/tests/data/test_resources.psyexp +491 -0
- psychopy/tests/data/test_session/outside_root/externalExp.psyexp +116 -0
- psychopy/tests/data/test_session/root/annotation/annotation.psyexp +161 -0
- psychopy/tests/data/test_session/root/error/error.psyexp +93 -0
- psychopy/tests/data/test_session/root/exp1/exp1.psyexp +133 -0
- psychopy/tests/data/test_session/root/exp2/exp2.psyexp +133 -0
- psychopy/tests/data/test_session/root/frameRate/frameRate.psyexp +114 -0
- psychopy/tests/data/test_session/root/invUseVersion/invUseVersion.psyexp +133 -0
- psychopy/tests/data/test_session/root/testClockFormat/testClockFormat.psyexp +122 -0
- psychopy/tests/data/test_session/root/testCtrls/testCtrls.psyexp +115 -0
- psychopy/tests/data/test_session/root/testEditExpInfo/testEditExpInfo.psyexp +135 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testNamedButtonBox/testNamedButtonBox.psyexp +145 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/data/test_slider_horiz_accute_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_accute_vert.png +0 -0
- psychopy/tests/data/test_slider_horiz_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_obtuse_horiz.png +0 -0
- psychopy/tests/data/test_slider_horiz_obtuse_vert.png +0 -0
- psychopy/tests/data/test_slider_horiz_vert.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_blanks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_clustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_morelabels.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_morelabelsclustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_moreticks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_moreticksclustered.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_nolabels.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_noticks.png +0 -0
- psychopy/tests/data/test_slider_ticklabelloc_simple.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_False_flip_False.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_False_flip_True.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_True_flip_False.png +0 -0
- psychopy/tests/data/test_slider_triangle_horiz_True_flip_True.png +0 -0
- psychopy/tests/data/test_sounds/default_16000.wav +0 -0
- psychopy/tests/data/test_sounds/default_192000.wav +0 -0
- psychopy/tests/data/test_sounds/default_22050.wav +0 -0
- psychopy/tests/data/test_sounds/default_32000.wav +0 -0
- psychopy/tests/data/test_sounds/default_44100.wav +0 -0
- psychopy/tests/data/test_sounds/default_48000.wav +0 -0
- psychopy/tests/data/test_sounds/default_8000.wav +0 -0
- psychopy/tests/data/test_sounds/default_96000.wav +0 -0
- psychopy/tests/data/test_static_component_script.psyexp +76 -0
- psychopy/tests/data/test_win_bg_large_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_large_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_small_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_small_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_tall_fill_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_200_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_256_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_500_default.png +0 -0
- psychopy/tests/data/test_win_bg_wide_fill_default.png +0 -0
- psychopy/tests/data/test_win_bgcolor_blue.png +0 -0
- psychopy/tests/data/test_win_bgcolor_green.png +0 -0
- psychopy/tests/data/test_win_bgcolor_red.png +0 -0
- psychopy/tests/data/testimage.jpg +0 -0
- psychopy/tests/data/testimagegray.jpg +0 -0
- psychopy/tests/data/testpixels.png +0 -0
- psychopy/tests/data/testwedges.png +0 -0
- psychopy/tests/data/text1_cm.png +0 -0
- psychopy/tests/data/text1_deg.png +0 -0
- psychopy/tests/data/text1_degFlat.png +0 -0
- psychopy/tests/data/text1_degFlatPos.png +0 -0
- psychopy/tests/data/text1_height.png +0 -0
- psychopy/tests/data/text1_norm.png +0 -0
- psychopy/tests/data/text1_normAddBlend.png +0 -0
- psychopy/tests/data/text1_normHexbackground.png +0 -0
- psychopy/tests/data/text1_normNoShade.png +0 -0
- psychopy/tests/data/text1_pix.png +0 -0
- psychopy/tests/data/text1_stencil.png +0 -0
- psychopy/tests/data/text2_cm.png +0 -0
- psychopy/tests/data/text2_deg.png +0 -0
- psychopy/tests/data/text2_degFlat.png +0 -0
- psychopy/tests/data/text2_degFlatPos.png +0 -0
- psychopy/tests/data/text2_norm.png +0 -0
- psychopy/tests/data/text2_normNoShade.png +0 -0
- psychopy/tests/data/text2_pix.png +0 -0
- psychopy/tests/data/textbox_charcolors_default_roygbiv.png +0 -0
- psychopy/tests/data/textbox_charcolors_default_white_hello_black_there.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_center.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_left.png +0 -0
- psychopy/tests/data/textbox_default_align_bottom_right.png +0 -0
- psychopy/tests/data/textbox_default_align_center.png +0 -0
- psychopy/tests/data/textbox_default_align_center_center.png +0 -0
- psychopy/tests/data/textbox_default_align_center_left.png +0 -0
- psychopy/tests/data/textbox_default_align_center_right.png +0 -0
- psychopy/tests/data/textbox_default_align_centre.png +0 -0
- psychopy/tests/data/textbox_default_align_centre_centre.png +0 -0
- psychopy/tests/data/textbox_default_align_more_than_two_words.png +0 -0
- psychopy/tests/data/textbox_default_align_someword.png +0 -0
- psychopy/tests/data/textbox_default_align_top_center.png +0 -0
- psychopy/tests/data/textbox_default_align_top_left.png +0 -0
- psychopy/tests/data/textbox_default_align_top_right.png +0 -0
- psychopy/tests/data/textbox_default_colors_WOB.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar1.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar2.png +0 -0
- psychopy/tests/data/textbox_default_colors_exemplar3.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke1.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke2.png +0 -0
- psychopy/tests/data/textbox_default_colors_tyke3.png +0 -0
- psychopy/tests/data/textbox_default_cutoff_top.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_1.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_2.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_3.png +0 -0
- psychopy/tests/data/textbox_default_exemplar_4.png +0 -0
- psychopy/tests/data/textbox_default_tyke_1.png +0 -0
- psychopy/tests/data/textbox_default_tyke_2.png +0 -0
- psychopy/tests/data/textbox_typing_blank.png +0 -0
- psychopy/tests/data/textbox_typing_longKoeran.png +0 -0
- psychopy/tests/data/textbox_typing_longWord.png +0 -0
- psychopy/tests/data/textbox_typing_navDel.png +0 -0
- psychopy/tests/data/textbox_typing_navLR.png +0 -0
- psychopy/tests/data/textbox_typing_newline.png +0 -0
- psychopy/tests/data/textbox_typing_pangram.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_bottom_right.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_center_right.png +0 -0
- psychopy/tests/data/textbox_uax14_align_centre.png +0 -0
- psychopy/tests/data/textbox_uax14_align_centre_centre.png +0 -0
- psychopy/tests/data/textbox_uax14_align_more_than_two_words.png +0 -0
- psychopy/tests/data/textbox_uax14_align_someword.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_center.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_left.png +0 -0
- psychopy/tests/data/textbox_uax14_align_top_right.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_WOB.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar1.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar2.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_exemplar3.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke1.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke2.png +0 -0
- psychopy/tests/data/textbox_uax14_colors_tyke3.png +0 -0
- psychopy/tests/data/textbox_uax14_cutoff_top.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_1.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_2.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_3.png +0 -0
- psychopy/tests/data/textbox_uax14_exemplar_4.png +0 -0
- psychopy/tests/data/textbox_uax14_tyke_1.png +0 -0
- psychopy/tests/data/textbox_uax14_tyke_2.png +0 -0
- psychopy/tests/data/trialTypes.csv +1 -0
- psychopy/tests/data/trialTypes.docx +0 -0
- psychopy/tests/data/trialTypes.pkl +0 -0
- psychopy/tests/data/trialTypes.tsv +7 -0
- psychopy/tests/data/trialTypes.xls +0 -0
- psychopy/tests/data/trialTypes.xlsx +0 -0
- psychopy/tests/data/trialTypes_eu.csv +7 -0
- psychopy/tests/data/trialsBlankCols.xlsx +0 -0
- psychopy/tests/data/wedge1_cm.png +0 -0
- psychopy/tests/data/wedge1_deg.png +0 -0
- psychopy/tests/data/wedge1_degFlat.png +0 -0
- psychopy/tests/data/wedge1_degFlatPos.png +0 -0
- psychopy/tests/data/wedge1_height.png +0 -0
- psychopy/tests/data/wedge1_norm.png +0 -0
- psychopy/tests/data/wedge1_normAddBlend.png +0 -0
- psychopy/tests/data/wedge1_normHexbackground.png +0 -0
- psychopy/tests/data/wedge1_normNoShade.png +0 -0
- psychopy/tests/data/wedge1_pix.png +0 -0
- psychopy/tests/data/wedge1_stencil.png +0 -0
- psychopy/tests/data/wedge2_cm.png +0 -0
- psychopy/tests/data/wedge2_deg.png +0 -0
- psychopy/tests/data/wedge2_degFlat.png +0 -0
- psychopy/tests/data/wedge2_degFlatPos.png +0 -0
- psychopy/tests/data/wedge2_height.png +0 -0
- psychopy/tests/data/wedge2_norm.png +0 -0
- psychopy/tests/data/wedge2_normAddBlend.png +0 -0
- psychopy/tests/data/wedge2_normHexbackground.png +0 -0
- psychopy/tests/data/wedge2_normNoShade.png +0 -0
- psychopy/tests/data/wedge2_pix.png +0 -0
- psychopy/tests/data/wedge2_stencil.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale-2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_-1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_1_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_-2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_2_pos-0.4_0.png +0 -0
- psychopy/tests/data/winScalePos_ori0_scale2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale-2_2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale1_-1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale1_1_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale2_-2_pos0_0.png +0 -0
- psychopy/tests/data/winScalePos_ori45_scale2_2_pos0_0.png +0 -0
- psychopy/tests/doc/run_btn.png +0 -0
- psychopy/tests/doc/test-suite-bugs.jpg +0 -0
- psychopy/tests/doc/user-bugs.jpg +0 -0
- psychopy/tests/dummy_xorg.conf +21 -0
- psychopy/tests/run.py +50 -0
- psychopy/tests/test_Installation.py +38 -0
- psychopy/tests/test_alerts/test_alerts.py +37 -0
- psychopy/tests/test_alerts/test_alerttools.py +132 -0
- psychopy/tests/test_app/__init__.py +0 -0
- psychopy/tests/test_app/conftest.py +40 -0
- psychopy/tests/test_app/test_builder/__init__.py +0 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +166 -0
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +133 -0
- psychopy/tests/test_app/test_builder/test_ComponentDialogs.py +98 -0
- psychopy/tests/test_app/test_command_line.py +65 -0
- psychopy/tests/test_app/test_runner/__init__.py +0 -0
- psychopy/tests/test_app/test_runner/test_RunnerFrame.py +166 -0
- psychopy/tests/test_app/test_speed.py +147 -0
- psychopy/tests/test_app/test_themes/test_icons.py +79 -0
- psychopy/tests/test_codecov.py +29 -0
- psychopy/tests/test_colors/test_Color.py +41 -0
- psychopy/tests/test_data/dataPrev.xlsx +0 -0
- psychopy/tests/test_data/test_ExperimentHandler.py +171 -0
- psychopy/tests/test_data/test_MultiStairHandler.py +174 -0
- psychopy/tests/test_data/test_StairHandlers.py +1241 -0
- psychopy/tests/test_data/test_TrialHandler.py +335 -0
- psychopy/tests/test_data/test_TrialHandler2.py +584 -0
- psychopy/tests/test_data/test_TrialHandlerExt.py +359 -0
- psychopy/tests/test_data/test_fitFunctions.py +101 -0
- psychopy/tests/test_data/test_utils.py +173 -0
- psychopy/tests/test_data/test_xlsx.py +92 -0
- psychopy/tests/test_demos/test_builder_demos.py +52 -0
- psychopy/tests/test_experiment/__init__.py +0 -0
- psychopy/tests/test_experiment/known_py_diffs.txt +47 -0
- psychopy/tests/test_experiment/needs_wx/__init__.py +0 -0
- psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +14780 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +130 -0
- psychopy/tests/test_experiment/needs_wx/test_Experiment.py +406 -0
- psychopy/tests/test_experiment/needs_wx/test_components.py +170 -0
- psychopy/tests/test_experiment/test_component_compile_js.py +59 -0
- psychopy/tests/test_experiment/test_component_compile_python.py +165 -0
- psychopy/tests/test_experiment/test_components/__init__.py +1 -0
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +218 -0
- psychopy/tests/test_experiment/test_components/test_CodeComponent.py +70 -0
- psychopy/tests/test_experiment/test_components/test_GratingComponent.py +7 -0
- psychopy/tests/test_experiment/test_components/test_ImageComponent.py +8 -0
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_MouseComponent.py +136 -0
- psychopy/tests/test_experiment/test_components/test_PolygonComponent.py +65 -0
- psychopy/tests/test_experiment/test_components/test_ResourceManagerComponent.py +50 -0
- psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
- psychopy/tests/test_experiment/test_components/test_SettingsComponent.py +80 -0
- psychopy/tests/test_experiment/test_components/test_StaticComponent.py +51 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_all_components.py +104 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +477 -0
- psychopy/tests/test_experiment/test_experiment.py +199 -0
- psychopy/tests/test_experiment/test_loops.py +103 -0
- psychopy/tests/test_experiment/test_params.py +363 -0
- psychopy/tests/test_experiment/test_py2js.py +230 -0
- psychopy/tests/test_experiment/test_routines/__init__.py +1 -0
- psychopy/tests/test_experiment/test_routines/test_EyetrackerCalibrationRoutine.py +9 -0
- psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +9 -0
- psychopy/tests/test_experiment/test_routines/test_all_routines.py +23 -0
- psychopy/tests/test_experiment/test_routines/test_base_routine.py +154 -0
- psychopy/tests/test_experiment/test_routines/test_standalone_routines.py +36 -0
- psychopy/tests/test_gui/test_DlgFromDictQt.py +82 -0
- psychopy/tests/test_gui/test_DlgFromDictWx.py +82 -0
- psychopy/tests/test_hardware/__init__.py +0 -0
- psychopy/tests/test_hardware/test_device_manager.py +36 -0
- psychopy/tests/test_hardware/test_emulator.py +106 -0
- psychopy/tests/test_hardware/test_gammasci.py +35 -0
- psychopy/tests/test_hardware/test_keyboard.py +241 -0
- psychopy/tests/test_hardware/test_keyboard_events.py +362 -0
- psychopy/tests/test_hardware/test_photodiode.py +66 -0
- psychopy/tests/test_hardware/test_ports.py +129 -0
- psychopy/tests/test_iohub/__init__.py +1 -0
- psychopy/tests/test_iohub/test_computer.py +124 -0
- psychopy/tests/test_iohub/test_event_get_and_clear.py +94 -0
- psychopy/tests/test_iohub/test_keyboard.py +91 -0
- psychopy/tests/test_iohub/test_launch.py +19 -0
- psychopy/tests/test_iohub/testutil.py +54 -0
- psychopy/tests/test_liaison/test_Liaison.py +275 -0
- psychopy/tests/test_misc/__init__.py +0 -0
- psychopy/tests/test_misc/memory_usage.py +164 -0
- psychopy/tests/test_misc/test_GammaFun.py +15 -0
- psychopy/tests/test_misc/test_clock.py +63 -0
- psychopy/tests/test_misc/test_color.py +205 -0
- psychopy/tests/test_misc/test_core.py +451 -0
- psychopy/tests/test_misc/test_event.py +308 -0
- psychopy/tests/test_misc/test_info.py +18 -0
- psychopy/tests/test_misc/test_layout.py +49 -0
- psychopy/tests/test_misc/test_locale.py +38 -0
- psychopy/tests/test_misc/test_web.py +22 -0
- psychopy/tests/test_monitors/test_monitors.py +91 -0
- psychopy/tests/test_plugins/__init__.py +0 -0
- psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
- psychopy/tests/test_preferences/__init__.py +0 -0
- psychopy/tests/test_preferences/test_prefs.py +28 -0
- psychopy/tests/test_session/test_Session.py +257 -0
- psychopy/tests/test_sound/__init__.py +0 -0
- psychopy/tests/test_sound/test_audioclip.py +331 -0
- psychopy/tests/test_sound/test_hamming.py +45 -0
- psychopy/tests/test_sound/test_sound.py +138 -0
- psychopy/tests/test_sound/test_sound_pygame.py +67 -0
- psychopy/tests/test_tools/test_animationtools.py +51 -0
- psychopy/tests/test_tools/test_arraytools.py +242 -0
- psychopy/tests/test_tools/test_attributetools.py +257 -0
- psychopy/tests/test_tools/test_colorspacetools.py +150 -0
- psychopy/tests/test_tools/test_environmenttools.py +52 -0
- psychopy/tests/test_tools/test_fileerrortools.py +97 -0
- psychopy/tests/test_tools/test_filetools.py +104 -0
- psychopy/tests/test_tools/test_imagetools.py +57 -0
- psychopy/tests/test_tools/test_mathtools.py +688 -0
- psychopy/tests/test_tools/test_stringtools.py +143 -0
- psychopy/tests/test_tools/test_versionchooser.py +209 -0
- psychopy/tests/test_tools/test_viewtools.py +168 -0
- psychopy/tests/test_validators/__init__.py +0 -0
- psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
- psychopy/tests/test_visual/__init__.py +0 -0
- psychopy/tests/test_visual/measure_parity.py +243 -0
- psychopy/tests/test_visual/test_all_stimuli.py +655 -0
- psychopy/tests/test_visual/test_basevisual.py +564 -0
- psychopy/tests/test_visual/test_brush.py +68 -0
- psychopy/tests/test_visual/test_button.py +15 -0
- psychopy/tests/test_visual/test_circle.py +74 -0
- psychopy/tests/test_visual/test_contains_overlaps.py +242 -0
- psychopy/tests/test_visual/test_custommouse.py +47 -0
- psychopy/tests/test_visual/test_dots.py +155 -0
- psychopy/tests/test_visual/test_form.py +414 -0
- psychopy/tests/test_visual/test_framepacking.py +41 -0
- psychopy/tests/test_visual/test_gamma.py +152 -0
- psychopy/tests/test_visual/test_glfw_backend.py +14 -0
- psychopy/tests/test_visual/test_image.py +219 -0
- psychopy/tests/test_visual/test_panorama.py +41 -0
- psychopy/tests/test_visual/test_progress.py +96 -0
- psychopy/tests/test_visual/test_projections.py +215 -0
- psychopy/tests/test_visual/test_projections_interactive.py +163 -0
- psychopy/tests/test_visual/test_roi.py +105 -0
- psychopy/tests/test_visual/test_shape.py +23 -0
- psychopy/tests/test_visual/test_slider.py +328 -0
- psychopy/tests/test_visual/test_target.py +35 -0
- psychopy/tests/test_visual/test_textbox.py +508 -0
- psychopy/tests/test_visual/test_winFlipTiming.py +95 -0
- psychopy/tests/test_visual/test_winScalePos.py +78 -0
- psychopy/tests/test_visual/test_window.py +124 -0
- psychopy/tests/utils.py +372 -0
- psychopy/tools/LineBreak.txt +3597 -0
- psychopy/tools/__init__.py +9 -0
- psychopy/tools/animationtools.py +49 -0
- psychopy/tools/apptools.py +32 -0
- psychopy/tools/arraytools.py +561 -0
- psychopy/tools/attributetools.py +263 -0
- psychopy/tools/audiotools.py +361 -0
- psychopy/tools/colorspacetools.py +717 -0
- psychopy/tools/coordinatetools.py +104 -0
- psychopy/tools/environmenttools.py +62 -0
- psychopy/tools/fileerrortools.py +66 -0
- psychopy/tools/filetools.py +406 -0
- psychopy/tools/fontmanager.py +1065 -0
- psychopy/tools/gltools.py +7367 -0
- psychopy/tools/imagetools.py +65 -0
- psychopy/tools/linebreak.py +322 -0
- psychopy/tools/mathtools.py +4910 -0
- psychopy/tools/monitorunittools.py +276 -0
- psychopy/tools/movietools.py +1111 -0
- psychopy/tools/pkgtools.py +654 -0
- psychopy/tools/plottools.py +25 -0
- psychopy/tools/rifttools.py +76 -0
- psychopy/tools/stereotools.py +9 -0
- psychopy/tools/stimulustools.py +198 -0
- psychopy/tools/stringtools.py +466 -0
- psychopy/tools/systemtools.py +1331 -0
- psychopy/tools/typetools.py +52 -0
- psychopy/tools/unittools.py +16 -0
- psychopy/tools/versionchooser.py +604 -0
- psychopy/tools/viewtools.py +1068 -0
- psychopy/tools/wizard.py +804 -0
- psychopy/validation/__init__.py +6 -0
- psychopy/validation/audio.py +78 -0
- psychopy/validation/visual.py +119 -0
- psychopy/visual/__init__.py +121 -0
- psychopy/visual/aperture.py +333 -0
- psychopy/visual/backends/__init__.py +77 -0
- psychopy/visual/backends/_base.py +481 -0
- psychopy/visual/backends/gamma.py +349 -0
- psychopy/visual/backends/glfwbackend.py +24 -0
- psychopy/visual/backends/pygamebackend.py +343 -0
- psychopy/visual/backends/pygletbackend.py +970 -0
- psychopy/visual/basevisual.py +2027 -0
- psychopy/visual/brush.py +204 -0
- psychopy/visual/bufferimage.py +311 -0
- psychopy/visual/button.py +194 -0
- psychopy/visual/circle.py +165 -0
- psychopy/visual/custommouse.py +263 -0
- psychopy/visual/dot.py +734 -0
- psychopy/visual/dropdown.py +165 -0
- psychopy/visual/elementarray.py +802 -0
- psychopy/visual/filters.py +419 -0
- psychopy/visual/form.py +1195 -0
- psychopy/visual/globalVars.py +24 -0
- psychopy/visual/grating.py +581 -0
- psychopy/visual/helpers.py +336 -0
- psychopy/visual/image.py +438 -0
- psychopy/visual/line.py +227 -0
- psychopy/visual/movie.py +12 -0
- psychopy/visual/movie2.py +21 -0
- psychopy/visual/movie3.py +18 -0
- psychopy/visual/movies/__init__.py +2334 -0
- psychopy/visual/movies/frame.py +258 -0
- psychopy/visual/movies/metadata.py +242 -0
- psychopy/visual/movies/players/_base.py +364 -0
- psychopy/visual/nnlvs.py +830 -0
- psychopy/visual/noise.py +32 -0
- psychopy/visual/panorama.py +313 -0
- psychopy/visual/patch.py +21 -0
- psychopy/visual/pie.py +237 -0
- psychopy/visual/polygon.py +226 -0
- psychopy/visual/progress.py +313 -0
- psychopy/visual/radial.py +29 -0
- psychopy/visual/ratingscale.py +18 -0
- psychopy/visual/rect.py +195 -0
- psychopy/visual/rift.py +2660 -0
- psychopy/visual/roi.py +141 -0
- psychopy/visual/secondorder.py +27 -0
- psychopy/visual/shaders.py +787 -0
- psychopy/visual/shape.py +897 -0
- psychopy/visual/simpleimage.py +303 -0
- psychopy/visual/slider.py +1222 -0
- psychopy/visual/stim3d.py +2109 -0
- psychopy/visual/target.py +278 -0
- psychopy/visual/text.py +769 -0
- psychopy/visual/textbox/__init__.py +1280 -0
- psychopy/visual/textbox/fontmanager.py +574 -0
- psychopy/visual/textbox/parsedtext.py +317 -0
- psychopy/visual/textbox/textgrid.py +278 -0
- psychopy/visual/textbox/textureatlas.py +248 -0
- psychopy/visual/textbox2/__init__.py +4 -0
- psychopy/visual/textbox2/textbox2.py +1970 -0
- psychopy/visual/vlcmoviestim.py +1294 -0
- psychopy/visual/window.py +3910 -0
- psychopy/visual/windowframepack.py +86 -0
- psychopy/visual/windowwarp.py +457 -0
- psychopy/voicekey/__init__.py +56 -0
- psychopy/voicekey/labjack_vks.py +9 -0
- psychopy/voicekey/parallel_vks.py +9 -0
- psychopy/voicekey/vk_tools.py +131 -0
- psychopy/web.py +289 -0
- psychopy-2025.2.4.dist-info/METADATA +160 -0
- psychopy-2025.2.4.dist-info/RECORD +3127 -0
- psychopy-2025.2.4.dist-info/WHEEL +4 -0
- psychopy-2025.2.4.dist-info/entry_points.txt +5 -0
- psychopy-2025.2.4.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,3657 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Classes and functions for reading and writing camera streams.
|
|
4
|
+
|
|
5
|
+
A camera may be used to document participant responses on video or used by the
|
|
6
|
+
experimenter to create movie stimuli or instructions.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Part of the PsychoPy library
|
|
11
|
+
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2025 Open Science Tools Ltd.
|
|
12
|
+
# Distributed under the terms of the GNU General Public License (GPL).
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'VIDEO_DEVICE_ROOT_LINUX',
|
|
16
|
+
'CAMERA_UNKNOWN_VALUE',
|
|
17
|
+
'CAMERA_NULL_VALUE',
|
|
18
|
+
# 'CAMERA_MODE_VIDEO',
|
|
19
|
+
# 'CAMERA_MODE_CV',
|
|
20
|
+
# 'CAMERA_MODE_PHOTO',
|
|
21
|
+
'CAMERA_TEMP_FILE_VIDEO',
|
|
22
|
+
'CAMERA_TEMP_FILE_AUDIO',
|
|
23
|
+
'CAMERA_API_AVFOUNDATION',
|
|
24
|
+
'CAMERA_API_DIRECTSHOW',
|
|
25
|
+
'CAMERA_API_VIDEO4LINUX2',
|
|
26
|
+
'CAMERA_API_ANY',
|
|
27
|
+
'CAMERA_API_UNKNOWN',
|
|
28
|
+
'CAMERA_API_NULL',
|
|
29
|
+
'CAMERA_LIB_FFPYPLAYER',
|
|
30
|
+
'CAMERA_LIB_OPENCV',
|
|
31
|
+
'CAMERA_LIB_UNKNOWN',
|
|
32
|
+
'CAMERA_LIB_NULL',
|
|
33
|
+
'CameraError',
|
|
34
|
+
'CameraNotReadyError',
|
|
35
|
+
'CameraNotFoundError',
|
|
36
|
+
'CameraFormatNotSupportedError',
|
|
37
|
+
'CameraFrameRateNotSupportedError',
|
|
38
|
+
'CameraFrameSizeNotSupportedError',
|
|
39
|
+
'FormatNotFoundError',
|
|
40
|
+
'PlayerNotAvailableError',
|
|
41
|
+
'CameraInterfaceFFmpeg',
|
|
42
|
+
'CameraInterfaceOpenCV',
|
|
43
|
+
'Camera',
|
|
44
|
+
'CameraInfo',
|
|
45
|
+
'getCameras',
|
|
46
|
+
'getCameraDescriptions',
|
|
47
|
+
'getOpenCameras',
|
|
48
|
+
'closeAllOpenCameras',
|
|
49
|
+
'renderVideo'
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
import platform
|
|
53
|
+
import inspect
|
|
54
|
+
import os
|
|
55
|
+
import os.path
|
|
56
|
+
import sys
|
|
57
|
+
import math
|
|
58
|
+
import uuid
|
|
59
|
+
import threading
|
|
60
|
+
import queue
|
|
61
|
+
import time
|
|
62
|
+
import numpy as np
|
|
63
|
+
import ctypes
|
|
64
|
+
import collections
|
|
65
|
+
|
|
66
|
+
from psychopy import core
|
|
67
|
+
from psychopy.constants import NOT_STARTED
|
|
68
|
+
from psychopy.hardware import DeviceManager
|
|
69
|
+
from psychopy.hardware.base import BaseDevice
|
|
70
|
+
from psychopy.visual.movies.frame import MovieFrame, NULL_MOVIE_FRAME_INFO
|
|
71
|
+
from psychopy.sound.microphone import Microphone
|
|
72
|
+
from psychopy.hardware.microphone import MicrophoneDevice
|
|
73
|
+
from psychopy.tools import systemtools as st
|
|
74
|
+
import psychopy.tools.movietools as movietools
|
|
75
|
+
import psychopy.logging as logging
|
|
76
|
+
from psychopy.localization import _translate
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------------------
|
|
79
|
+
# Constants
|
|
80
|
+
#
|
|
81
|
+
|
|
82
|
+
VIDEO_DEVICE_ROOT_LINUX = '/dev'
|
|
83
|
+
CAMERA_UNKNOWN_VALUE = u'Unknown' # fields where we couldn't get a value
|
|
84
|
+
CAMERA_NULL_VALUE = u'Null' # fields where we couldn't get a value
|
|
85
|
+
|
|
86
|
+
# camera operating modes
|
|
87
|
+
CAMERA_MODE_VIDEO = u'video'
|
|
88
|
+
CAMERA_MODE_CV = u'cv'
|
|
89
|
+
# CAMERA_MODE_PHOTO = u'photo' # planned
|
|
90
|
+
|
|
91
|
+
# camera status
|
|
92
|
+
CAMERA_STATUS_OK = 'ok'
|
|
93
|
+
CAMERA_STATUS_PAUSED = 'paused'
|
|
94
|
+
CAMERA_STATUS_EOF = 'eof'
|
|
95
|
+
|
|
96
|
+
# camera API flags, these specify which API camera settings were queried with
|
|
97
|
+
CAMERA_API_AVFOUNDATION = u'AVFoundation' # mac
|
|
98
|
+
CAMERA_API_DIRECTSHOW = u'DirectShow' # windows
|
|
99
|
+
CAMERA_API_VIDEO4LINUX2 = u'Video4Linux2' # linux
|
|
100
|
+
CAMERA_API_ANY = u'Any' # any API (OpenCV only)
|
|
101
|
+
CAMERA_API_UNKNOWN = u'Unknown' # unknown API
|
|
102
|
+
CAMERA_API_NULL = u'Null' # empty field
|
|
103
|
+
|
|
104
|
+
# camera libraries for playback nad recording
|
|
105
|
+
CAMERA_LIB_FFPYPLAYER = u'ffpyplayer'
|
|
106
|
+
CAMERA_LIB_OPENCV = u'opencv'
|
|
107
|
+
CAMERA_LIB_UNKNOWN = u'unknown'
|
|
108
|
+
CAMERA_LIB_NULL = u'null'
|
|
109
|
+
|
|
110
|
+
# special values
|
|
111
|
+
CAMERA_FRAMERATE_NOMINAL_NTSC = '30.000030'
|
|
112
|
+
CAMERA_FRAMERATE_NTSC = 30.000030
|
|
113
|
+
|
|
114
|
+
# FourCC and pixel format mappings, mostly used with AVFoundation to determine
|
|
115
|
+
# the FFMPEG decoder which is most suitable for it. Please expand this if you
|
|
116
|
+
# know any more!
|
|
117
|
+
pixelFormatTbl = {
|
|
118
|
+
'yuvs': 'yuyv422', # 4:2:2
|
|
119
|
+
'420v': 'nv12', # 4:2:0
|
|
120
|
+
'2vuy': 'uyvy422' # QuickTime 4:2:2
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Camera standards to help with selection. Some standalone cameras sometimes
|
|
124
|
+
# support an insane number of formats, this will help narrow them down.
|
|
125
|
+
standardResolutions = {
|
|
126
|
+
'vga': (640, 480),
|
|
127
|
+
'svga': (800, 600),
|
|
128
|
+
'xga': (1024, 768),
|
|
129
|
+
'wxga': (1280, 768),
|
|
130
|
+
'wxga+': (1440, 900),
|
|
131
|
+
'sxga': (1280, 1024),
|
|
132
|
+
'wsxga+': (1680, 1050),
|
|
133
|
+
'uxga': (1600, 1200),
|
|
134
|
+
'wuxga': (1920, 1200),
|
|
135
|
+
'wqxga': (2560, 1600),
|
|
136
|
+
'wquxga': (3840, 2400),
|
|
137
|
+
'720p': (1280, 720), # also known as HD
|
|
138
|
+
'1080p': (1920, 1080),
|
|
139
|
+
'2160p': (3840, 2160),
|
|
140
|
+
'uhd': (3840, 2160),
|
|
141
|
+
'dci': (4096, 2160)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------------------
|
|
145
|
+
# Keep track of open capture interfaces so we can close them at shutdown in the
|
|
146
|
+
# event that the user forrgets or the program crashes.
|
|
147
|
+
#
|
|
148
|
+
|
|
149
|
+
_openCaptureInterfaces = set()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------------------
|
|
153
|
+
# Exceptions
|
|
154
|
+
#
|
|
155
|
+
|
|
156
|
+
class CameraError(Exception):
|
|
157
|
+
"""Base class for errors around the camera."""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class CameraNotReadyError(CameraError):
|
|
161
|
+
"""Camera is not ready."""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class CameraNotFoundError(CameraError):
|
|
165
|
+
"""Raised when a camera cannot be found on the system."""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class CameraFormatNotSupportedError(CameraError):
|
|
169
|
+
"""Raised when a camera cannot use the settings requested by the user."""
|
|
170
|
+
|
|
171
|
+
class CameraFrameRateNotSupportedError(CameraFormatNotSupportedError):
|
|
172
|
+
"""Raised when a camera cannot use the frame rate settings requested by the
|
|
173
|
+
user."""
|
|
174
|
+
|
|
175
|
+
class CameraFrameSizeNotSupportedError(CameraFormatNotSupportedError):
|
|
176
|
+
"""Raised when a camera cannot use the frame size settings requested by the
|
|
177
|
+
user."""
|
|
178
|
+
|
|
179
|
+
class FormatNotFoundError(CameraError):
|
|
180
|
+
"""Cannot find a suitable pixel format for the camera."""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class PlayerNotAvailableError(Exception):
|
|
184
|
+
"""Raised when a player object is not available but is required."""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------------------
|
|
188
|
+
# Classes
|
|
189
|
+
#
|
|
190
|
+
|
|
191
|
+
class CameraInfo:
|
|
192
|
+
"""Information about a specific operating mode for a camera attached to the
|
|
193
|
+
system.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
index : int
|
|
198
|
+
Index of the camera. This is the enumeration for the camera which is
|
|
199
|
+
used to identify and select it by the `cameraLib`. This value may differ
|
|
200
|
+
between operating systems and the `cameraLib` being used.
|
|
201
|
+
name : str
|
|
202
|
+
Camera name retrieved by the OS. This may be a human-readable name
|
|
203
|
+
(i.e. DirectShow on Windows), an index on MacOS or a path (e.g.,
|
|
204
|
+
`/dev/video0` on Linux). If the `cameraLib` does not support this
|
|
205
|
+
feature, then this value will be generated.
|
|
206
|
+
frameSize : ArrayLike
|
|
207
|
+
Resolution of the frame `(w, h)` in pixels.
|
|
208
|
+
frameRate : ArrayLike
|
|
209
|
+
Allowable framerate for this camera mode.
|
|
210
|
+
pixelFormat : str
|
|
211
|
+
Pixel format for the stream. If `u'Null'`, then `codecFormat` is being
|
|
212
|
+
used to configure the camera.
|
|
213
|
+
codecFormat : str
|
|
214
|
+
Codec format for the stream. If `u'Null'`, then `pixelFormat` is being
|
|
215
|
+
used to configure the camera. Usually this value is used for high-def
|
|
216
|
+
stream formats.
|
|
217
|
+
cameraLib : str
|
|
218
|
+
Library used to access the camera. This can be either, 'ffpyplayer',
|
|
219
|
+
'opencv'.
|
|
220
|
+
cameraAPI : str
|
|
221
|
+
API used to access the camera. This relates to the external interface
|
|
222
|
+
being used by `cameraLib` to access the camera. This value can be:
|
|
223
|
+
'AVFoundation', 'DirectShow' or 'Video4Linux2'.
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
__slots__ = [
|
|
227
|
+
'_index',
|
|
228
|
+
'_name',
|
|
229
|
+
'_frameSize',
|
|
230
|
+
'_frameRate',
|
|
231
|
+
'_pixelFormat',
|
|
232
|
+
'_codecFormat',
|
|
233
|
+
'_cameraLib',
|
|
234
|
+
'_cameraAPI' # API in use, e.g. DirectShow on Windows
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
def __init__(self,
|
|
238
|
+
index=-1,
|
|
239
|
+
name=CAMERA_NULL_VALUE,
|
|
240
|
+
frameSize=(-1, -1),
|
|
241
|
+
frameRate=-1.0,
|
|
242
|
+
pixelFormat=CAMERA_UNKNOWN_VALUE,
|
|
243
|
+
codecFormat=CAMERA_UNKNOWN_VALUE,
|
|
244
|
+
cameraLib=CAMERA_NULL_VALUE,
|
|
245
|
+
cameraAPI=CAMERA_API_NULL):
|
|
246
|
+
|
|
247
|
+
self.index = index
|
|
248
|
+
self.name = name
|
|
249
|
+
self.frameSize = frameSize
|
|
250
|
+
self.frameRate = frameRate
|
|
251
|
+
self.pixelFormat = pixelFormat
|
|
252
|
+
self.codecFormat = codecFormat
|
|
253
|
+
self.cameraLib = cameraLib
|
|
254
|
+
self.cameraAPI = cameraAPI
|
|
255
|
+
|
|
256
|
+
def __repr__(self):
|
|
257
|
+
return (f"CameraInfo(index={repr(self.index)}, "
|
|
258
|
+
f"name={repr(self.name)}, "
|
|
259
|
+
f"frameSize={repr(self.frameSize)}, "
|
|
260
|
+
f"frameRate={self.frameRate}, "
|
|
261
|
+
f"pixelFormat={repr(self.pixelFormat)}, "
|
|
262
|
+
f"codecFormat={repr(self.codecFormat)}, "
|
|
263
|
+
f"cameraLib={repr(self.cameraLib)}, "
|
|
264
|
+
f"cameraAPI={repr(self.cameraAPI)})")
|
|
265
|
+
|
|
266
|
+
def __str__(self):
|
|
267
|
+
return self.description()
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def index(self):
|
|
271
|
+
"""Camera index (`int`). This is the enumerated index of this camera.
|
|
272
|
+
"""
|
|
273
|
+
return self._index
|
|
274
|
+
|
|
275
|
+
@index.setter
|
|
276
|
+
def index(self, value):
|
|
277
|
+
self._index = int(value)
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def name(self):
|
|
281
|
+
"""Camera name (`str`). This is the camera name retrieved by the OS.
|
|
282
|
+
"""
|
|
283
|
+
return self._name
|
|
284
|
+
|
|
285
|
+
@name.setter
|
|
286
|
+
def name(self, value):
|
|
287
|
+
self._name = str(value)
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def frameSize(self):
|
|
291
|
+
"""Resolution (w, h) in pixels (`ArrayLike` or `None`).
|
|
292
|
+
"""
|
|
293
|
+
return self._frameSize
|
|
294
|
+
|
|
295
|
+
@frameSize.setter
|
|
296
|
+
def frameSize(self, value):
|
|
297
|
+
if value is None:
|
|
298
|
+
self._frameSize = None
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
assert len(value) == 2, "Value for `frameSize` must have length 2."
|
|
302
|
+
assert all([isinstance(i, int) for i in value]), (
|
|
303
|
+
"Values for `frameSize` must be integers.")
|
|
304
|
+
|
|
305
|
+
self._frameSize = value
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def frameRate(self):
|
|
309
|
+
"""Frame rate (`float`) or range (`ArrayLike`).
|
|
310
|
+
|
|
311
|
+
Depends on the backend being used. If a range is provided, then the
|
|
312
|
+
first value is the maximum and the second value is the minimum frame
|
|
313
|
+
rate.
|
|
314
|
+
"""
|
|
315
|
+
return self._frameRate
|
|
316
|
+
|
|
317
|
+
@frameRate.setter
|
|
318
|
+
def frameRate(self, value):
|
|
319
|
+
# assert len(value) == 2, "Value for `frameRateRange` must have length 2."
|
|
320
|
+
# assert all([isinstance(i, int) for i in value]), (
|
|
321
|
+
# "Values for `frameRateRange` must be integers.")
|
|
322
|
+
# assert value[0] <= value[1], (
|
|
323
|
+
# "Value for `frameRateRange` must be `min` <= `max`.")
|
|
324
|
+
|
|
325
|
+
self._frameRate = value
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def pixelFormat(self):
|
|
329
|
+
"""Video pixel format (`str`). An empty string indicates this field is
|
|
330
|
+
not initialized.
|
|
331
|
+
"""
|
|
332
|
+
return self._pixelFormat
|
|
333
|
+
|
|
334
|
+
@pixelFormat.setter
|
|
335
|
+
def pixelFormat(self, value):
|
|
336
|
+
self._pixelFormat = str(value)
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def codecFormat(self):
|
|
340
|
+
"""Codec format, may be used instead of `pixelFormat` for some
|
|
341
|
+
configurations. Default is `''`.
|
|
342
|
+
"""
|
|
343
|
+
return self._codecFormat
|
|
344
|
+
|
|
345
|
+
@codecFormat.setter
|
|
346
|
+
def codecFormat(self, value):
|
|
347
|
+
self._codecFormat = str(value)
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def cameraLib(self):
|
|
351
|
+
"""Camera library these settings are targeted towards (`str`).
|
|
352
|
+
"""
|
|
353
|
+
return self._cameraLib
|
|
354
|
+
|
|
355
|
+
@cameraLib.setter
|
|
356
|
+
def cameraLib(self, value):
|
|
357
|
+
self._cameraLib = str(value)
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def cameraAPI(self):
|
|
361
|
+
"""Camera API in use to obtain this information (`str`).
|
|
362
|
+
"""
|
|
363
|
+
return self._cameraAPI
|
|
364
|
+
|
|
365
|
+
@cameraAPI.setter
|
|
366
|
+
def cameraAPI(self, value):
|
|
367
|
+
self._cameraAPI = str(value)
|
|
368
|
+
|
|
369
|
+
def frameSizeAsFormattedString(self):
|
|
370
|
+
"""Get image size as as formatted string.
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
str
|
|
375
|
+
Size formatted as `'WxH'` (e.g. `'480x320'`).
|
|
376
|
+
|
|
377
|
+
"""
|
|
378
|
+
return '{width}x{height}'.format(
|
|
379
|
+
width=self.frameSize[0],
|
|
380
|
+
height=self.frameSize[1])
|
|
381
|
+
|
|
382
|
+
def description(self):
|
|
383
|
+
"""Get a description as a string.
|
|
384
|
+
|
|
385
|
+
For all backends, this value is guaranteed to be valid after the camera
|
|
386
|
+
has been opened. Some backends may be able to provide this information
|
|
387
|
+
before the camera is opened.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
str
|
|
392
|
+
Description of the camera format as a human readable string.
|
|
393
|
+
|
|
394
|
+
"""
|
|
395
|
+
codecFormat = self._codecFormat
|
|
396
|
+
pixelFormat = self._pixelFormat
|
|
397
|
+
codec = codecFormat if not pixelFormat else pixelFormat
|
|
398
|
+
|
|
399
|
+
if self.frameSize is None:
|
|
400
|
+
frameSize = (-1, -1)
|
|
401
|
+
else:
|
|
402
|
+
frameSize = self.frameSize
|
|
403
|
+
|
|
404
|
+
return "[{name}] {width}x{height}@{frameRate}fps, {codec}".format(
|
|
405
|
+
#index=self.index,
|
|
406
|
+
name=self.name,
|
|
407
|
+
width=str(frameSize[0]),
|
|
408
|
+
height=str(frameSize[1]),
|
|
409
|
+
frameRate=str(self.frameRate),
|
|
410
|
+
codec=codec
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class CameraDevice(BaseDevice):
|
|
415
|
+
"""Class providing an interface with a camera attached to the system.
|
|
416
|
+
|
|
417
|
+
This interface handles the opening, closing, and reading of camera streams.
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
device : Any
|
|
422
|
+
Camera device to open a stream with. The type of this value is dependent
|
|
423
|
+
on the platform and the camera library being used. This can be an integer
|
|
424
|
+
index, a string representing the camera device name.
|
|
425
|
+
captureLib : str
|
|
426
|
+
Camera library to use for opening the camera stream. This can be either
|
|
427
|
+
'ffpyplayer' or 'opencv'. If `None`, the default recommend library is
|
|
428
|
+
used.
|
|
429
|
+
frameSize : tuple
|
|
430
|
+
Frame size of the camera stream. This is a tuple of the form
|
|
431
|
+
`(width, height)`.
|
|
432
|
+
frameRate : float
|
|
433
|
+
Frame rate of the camera stream. This is the number of frames per
|
|
434
|
+
second that the camera will capture. If `None`, the default frame rate
|
|
435
|
+
is used. The default value is 30.0.
|
|
436
|
+
pixelFormat : str or None
|
|
437
|
+
Pixel format of the camera stream. This is the format in which the
|
|
438
|
+
camera will capture frames. If `None`, the default pixel format is used.
|
|
439
|
+
The default value is `None`.
|
|
440
|
+
codecFormat : str or None
|
|
441
|
+
Codec format of the camera stream. This is the codec that will be used
|
|
442
|
+
to encode the camera stream. If `None`, the default codec format is
|
|
443
|
+
used. The default value is `None`.
|
|
444
|
+
captureAPI: str
|
|
445
|
+
Camera API to use for opening the camera stream. This can be either
|
|
446
|
+
'AVFoundation', 'DirectShow', or 'Video4Linux2'. If `None`, the default
|
|
447
|
+
camera API is used based on the platform. The default value is `None`.
|
|
448
|
+
decoderOpts : dict or None
|
|
449
|
+
Decoder options for the camera stream. This is a dictionary of options
|
|
450
|
+
that will be passed to the decoder when opening the camera stream. If
|
|
451
|
+
`None`, the default decoder options are used. The default value is an
|
|
452
|
+
empty dictionary.
|
|
453
|
+
bufferSecs : float
|
|
454
|
+
Number of seconds to buffer frames from the capture stream. This allows
|
|
455
|
+
frames to be buffered in memory until they are needed. This allows
|
|
456
|
+
the camera stream to be read asynchronously and prevents frames from
|
|
457
|
+
being dropped if the main thread is busy. The default value is 5.0
|
|
458
|
+
seconds.
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
def __init__(self, device, captureLib='ffpyplayer', frameSize=(640, 480),
|
|
462
|
+
frameRate=30.0, pixelFormat=None, codecFormat=None,
|
|
463
|
+
captureAPI=None, decoderOpts=None, bufferSecs=5.0):
|
|
464
|
+
|
|
465
|
+
BaseDevice.__init__(self)
|
|
466
|
+
|
|
467
|
+
# transform some of the params
|
|
468
|
+
pixelFormat = pixelFormat if pixelFormat is not None else ''
|
|
469
|
+
codecFormat = codecFormat if codecFormat is not None else ''
|
|
470
|
+
|
|
471
|
+
# if device is an integer, get name from index
|
|
472
|
+
foundProfile = None
|
|
473
|
+
if isinstance(device, int):
|
|
474
|
+
for profile in self.getAvailableDevices(False):
|
|
475
|
+
if profile['device'] == device:
|
|
476
|
+
foundProfile = profile
|
|
477
|
+
device = profile['deviceName']
|
|
478
|
+
break
|
|
479
|
+
elif isinstance(device, str):
|
|
480
|
+
# if device is a string, use it as the device name
|
|
481
|
+
for profile in self.getAvailableDevices(False):
|
|
482
|
+
# find a device which best matches the settings
|
|
483
|
+
if profile['deviceName'] != device:
|
|
484
|
+
continue
|
|
485
|
+
|
|
486
|
+
# check if all the other params match
|
|
487
|
+
paramsMatch = all([
|
|
488
|
+
profile['deviceName'] == device,
|
|
489
|
+
profile['captureLib'] == captureLib if captureLib else True,
|
|
490
|
+
profile['frameSize'] == frameSize if frameSize else True,
|
|
491
|
+
profile['frameRate'] == frameRate if frameRate else True,
|
|
492
|
+
profile['pixelFormat'] == pixelFormat if pixelFormat else True,
|
|
493
|
+
profile['codecFormat'] == codecFormat if codecFormat else True,
|
|
494
|
+
profile['captureAPI'] == captureAPI if captureAPI else True
|
|
495
|
+
])
|
|
496
|
+
|
|
497
|
+
if not paramsMatch:
|
|
498
|
+
continue
|
|
499
|
+
|
|
500
|
+
foundProfile = profile
|
|
501
|
+
device = profile['device']
|
|
502
|
+
|
|
503
|
+
break
|
|
504
|
+
|
|
505
|
+
if foundProfile is None:
|
|
506
|
+
raise CameraNotFoundError(
|
|
507
|
+
"Cannot find camera with index or name '{}'.".format(device))
|
|
508
|
+
|
|
509
|
+
self._device = device
|
|
510
|
+
|
|
511
|
+
# camera settings from profile
|
|
512
|
+
self._frameSize = foundProfile['frameSize']
|
|
513
|
+
self._frameRate = foundProfile['frameRate']
|
|
514
|
+
self._pixelFormat = foundProfile['pixelFormat']
|
|
515
|
+
self._codecFormat = foundProfile['codecFormat']
|
|
516
|
+
self._captureLib = foundProfile['captureLib']
|
|
517
|
+
self._captureAPI = foundProfile['captureAPI']
|
|
518
|
+
|
|
519
|
+
# capture interface
|
|
520
|
+
self._capture = None # camera stream capture object
|
|
521
|
+
self._decoderOpts = decoderOpts if decoderOpts is not None else {}
|
|
522
|
+
self._bufferSecs = bufferSecs # number of seconds to buffer frames
|
|
523
|
+
self._absRecStreamStartTime = -1.0 # absolute recording start time
|
|
524
|
+
self._absRecExpStartTime = -1.0
|
|
525
|
+
|
|
526
|
+
# stream properties
|
|
527
|
+
self._metadata = {} # metadata about the camera stream
|
|
528
|
+
|
|
529
|
+
# recording properties
|
|
530
|
+
self._frameStore = [] # store frames read from the camera stream
|
|
531
|
+
self._isRecording = False # `True` if the camera is recording and frames will be captured
|
|
532
|
+
|
|
533
|
+
# camera API to use with FFMPEG
|
|
534
|
+
if captureAPI is None:
|
|
535
|
+
if platform.system() == 'Windows':
|
|
536
|
+
self._cameraAPI = CAMERA_API_DIRECTSHOW
|
|
537
|
+
elif platform.system() == 'Darwin':
|
|
538
|
+
self._cameraAPI = CAMERA_API_AVFOUNDATION
|
|
539
|
+
elif platform.system() == 'Linux':
|
|
540
|
+
self._cameraAPI = CAMERA_API_VIDEO4LINUX2
|
|
541
|
+
else:
|
|
542
|
+
raise RuntimeError(
|
|
543
|
+
"Unsupported platform: {}. Supported platforms are: {}".format(
|
|
544
|
+
platform.system(), ', '.join(self._supportedPlatforms)))
|
|
545
|
+
else:
|
|
546
|
+
self._cameraAPI = captureAPI
|
|
547
|
+
|
|
548
|
+
# store device info
|
|
549
|
+
profile = self.getDeviceProfile()
|
|
550
|
+
if profile:
|
|
551
|
+
self.info = CameraInfo(
|
|
552
|
+
name=profile['deviceName'],
|
|
553
|
+
frameSize=profile['frameSize'],
|
|
554
|
+
frameRate=profile['frameRate'],
|
|
555
|
+
pixelFormat=profile['pixelFormat'],
|
|
556
|
+
codecFormat=profile['codecFormat'],
|
|
557
|
+
cameraLib=profile['captureLib'],
|
|
558
|
+
cameraAPI=profile['captureAPI']
|
|
559
|
+
)
|
|
560
|
+
else:
|
|
561
|
+
self.info = CameraInfo()
|
|
562
|
+
|
|
563
|
+
def isSameDevice(self, other):
|
|
564
|
+
"""
|
|
565
|
+
Determine whether this object represents the same physical device as a given other object.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
other : BaseDevice, dict
|
|
570
|
+
Other device object to compare against, or a dict of params.
|
|
571
|
+
|
|
572
|
+
Returns
|
|
573
|
+
-------
|
|
574
|
+
bool
|
|
575
|
+
True if the two objects represent the same physical device
|
|
576
|
+
"""
|
|
577
|
+
if isinstance(other, CameraDevice):
|
|
578
|
+
return other._device == self._device
|
|
579
|
+
elif isinstance(other, Camera):
|
|
580
|
+
return getattr(other, "_capture", None) == self
|
|
581
|
+
elif isinstance(other, dict) and "device" in other:
|
|
582
|
+
return other['deviceName'] == self._device
|
|
583
|
+
else:
|
|
584
|
+
return False
|
|
585
|
+
|
|
586
|
+
@staticmethod
|
|
587
|
+
def getAvailableDevices(best=True):
|
|
588
|
+
"""
|
|
589
|
+
Get all available devices of this type.
|
|
590
|
+
|
|
591
|
+
Parameters
|
|
592
|
+
----------
|
|
593
|
+
best : bool
|
|
594
|
+
If True, return only the best available frame rate/resolution for each device, rather
|
|
595
|
+
than returning all. Best available spec is chosen as the highest resolution with a
|
|
596
|
+
frame rate above 30fps (or just highest resolution, if none are over 30fps).
|
|
597
|
+
|
|
598
|
+
Returns
|
|
599
|
+
-------
|
|
600
|
+
list[dict]
|
|
601
|
+
List of dictionaries containing the parameters needed to initialise each device.
|
|
602
|
+
"""
|
|
603
|
+
profiles = []
|
|
604
|
+
# iterate through cameras
|
|
605
|
+
for cams in CameraDevice.getCameras().values():
|
|
606
|
+
# if requested, filter for best spec for each device
|
|
607
|
+
if best:
|
|
608
|
+
allCams = cams.copy()
|
|
609
|
+
lastBest = {
|
|
610
|
+
'pixels': 0,
|
|
611
|
+
'frameRate': 0
|
|
612
|
+
}
|
|
613
|
+
bestResolution = None
|
|
614
|
+
minFrameRate = max(28, min([cam.frameRate for cam in allCams]))
|
|
615
|
+
for cam in allCams:
|
|
616
|
+
# summarise spec of this cam
|
|
617
|
+
current = {
|
|
618
|
+
'pixels': cam.frameSize[0] * cam.frameSize[1],
|
|
619
|
+
'frameRate': cam.frameRate
|
|
620
|
+
}
|
|
621
|
+
# store best frame rate as a fallback
|
|
622
|
+
if bestResolution is None or current['pixels'] > lastBest['pixels']:
|
|
623
|
+
bestResolution = cam
|
|
624
|
+
# if it's better than the last, set it as the only cam
|
|
625
|
+
if current['pixels'] > lastBest['pixels'] and current['frameRate'] >= minFrameRate:
|
|
626
|
+
cams = [cam]
|
|
627
|
+
# if no cameras meet frame rate requirement, use one with best resolution
|
|
628
|
+
cams = [bestResolution]
|
|
629
|
+
# iterate through all (possibly filtered) cameras
|
|
630
|
+
for cam in cams:
|
|
631
|
+
# construct a dict profile from the CameraInfo object
|
|
632
|
+
profiles.append({
|
|
633
|
+
'deviceName': cam.name,
|
|
634
|
+
'deviceClass': "psychopy.hardware.camera.CameraDevice",
|
|
635
|
+
'device': cam.index,
|
|
636
|
+
'captureLib': cam.cameraLib,
|
|
637
|
+
'frameSize': cam.frameSize,
|
|
638
|
+
'frameRate': cam.frameRate,
|
|
639
|
+
'pixelFormat': cam.pixelFormat,
|
|
640
|
+
'codecFormat': cam.codecFormat,
|
|
641
|
+
'captureAPI': cam.cameraAPI
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
return profiles
|
|
645
|
+
|
|
646
|
+
@staticmethod
|
|
647
|
+
def getCameras(cameraLib=None):
|
|
648
|
+
"""Get a list of devices this interface can open.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
cameraLib : str or None
|
|
653
|
+
Camera library to use for opening the camera stream. This can be
|
|
654
|
+
either 'ffpyplayer' or 'opencv'. If `None`, the default recommend
|
|
655
|
+
library is used.
|
|
656
|
+
|
|
657
|
+
Returns
|
|
658
|
+
-------
|
|
659
|
+
dict
|
|
660
|
+
List of objects which represent cameras that can be opened by this
|
|
661
|
+
interface. Pass any of these values to `device` to open a stream.
|
|
662
|
+
|
|
663
|
+
"""
|
|
664
|
+
if cameraLib is None:
|
|
665
|
+
cameraLib = CAMERA_LIB_FFPYPLAYER
|
|
666
|
+
|
|
667
|
+
if cameraLib == CAMERA_LIB_FFPYPLAYER:
|
|
668
|
+
global _cameraGetterFuncTbl
|
|
669
|
+
systemName = platform.system() # get the system name
|
|
670
|
+
|
|
671
|
+
# lookup the function for the given platform
|
|
672
|
+
getCamerasFunc = _cameraGetterFuncTbl.get(systemName, None)
|
|
673
|
+
if getCamerasFunc is None: # if unsupported
|
|
674
|
+
raise OSError(
|
|
675
|
+
"Cannot get cameras, unsupported platform '{}'.".format(
|
|
676
|
+
systemName))
|
|
677
|
+
|
|
678
|
+
return getCamerasFunc()
|
|
679
|
+
|
|
680
|
+
def _clearFrameStore(self):
|
|
681
|
+
"""Clear the frame store.
|
|
682
|
+
"""
|
|
683
|
+
self._frameStore.clear()
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def device(self):
|
|
687
|
+
"""Camera device this interface is using (`Any`).
|
|
688
|
+
|
|
689
|
+
This is the camera device that was passed to the constructor. It may be
|
|
690
|
+
a `CameraInfo` object or a string representing the camera device.
|
|
691
|
+
|
|
692
|
+
"""
|
|
693
|
+
return self._device
|
|
694
|
+
|
|
695
|
+
@property
|
|
696
|
+
def cameraLib(self):
|
|
697
|
+
"""Camera library this interface is using (`str`).
|
|
698
|
+
|
|
699
|
+
This is the camera library that was passed to the constructor. It may be
|
|
700
|
+
'ffpyplayer' or 'opencv'. If `None`, the default recommend library is
|
|
701
|
+
used.
|
|
702
|
+
|
|
703
|
+
"""
|
|
704
|
+
return self.info.captureLib if self.info else None
|
|
705
|
+
|
|
706
|
+
@property
|
|
707
|
+
def frameSize(self):
|
|
708
|
+
"""Frame size of the camera stream (`tuple`).
|
|
709
|
+
|
|
710
|
+
This is the frame size of the camera stream. It is a tuple of the form
|
|
711
|
+
`(width, height)`. If the camera stream is not open, this will return
|
|
712
|
+
`None`.
|
|
713
|
+
|
|
714
|
+
"""
|
|
715
|
+
return self.info.frameSize if self.info else None
|
|
716
|
+
|
|
717
|
+
@property
|
|
718
|
+
def frameRate(self):
|
|
719
|
+
"""Frame rate of the camera stream (`float`).
|
|
720
|
+
|
|
721
|
+
This is the frame rate of the camera stream. If the camera stream is
|
|
722
|
+
not open, this will return `None`.
|
|
723
|
+
|
|
724
|
+
"""
|
|
725
|
+
return self.info.frameRate if self.info else None
|
|
726
|
+
|
|
727
|
+
@property
|
|
728
|
+
def frameInterval(self):
|
|
729
|
+
"""Frame interval of the camera stream (`float`).
|
|
730
|
+
|
|
731
|
+
This is the time between frames in seconds. It is calculated as
|
|
732
|
+
`1.0 / frameRate`. If the camera stream is not open, this will return
|
|
733
|
+
`None`.
|
|
734
|
+
|
|
735
|
+
"""
|
|
736
|
+
return self._frameInterval
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def pixelFormat(self):
|
|
740
|
+
"""Pixel format of the camera stream (`str`).
|
|
741
|
+
|
|
742
|
+
This is the pixel format of the camera stream. If the camera stream is
|
|
743
|
+
not open, this will return `None`.
|
|
744
|
+
|
|
745
|
+
"""
|
|
746
|
+
return self.info.pixelFormat if self.info else None
|
|
747
|
+
|
|
748
|
+
@property
|
|
749
|
+
def codecFormat(self):
|
|
750
|
+
"""Codec format of the camera stream (`str`).
|
|
751
|
+
|
|
752
|
+
This is the codec format of the camera stream. If the camera stream is
|
|
753
|
+
not open, this will return `None`.
|
|
754
|
+
|
|
755
|
+
"""
|
|
756
|
+
return self.info.codecFormat if self.info else None
|
|
757
|
+
|
|
758
|
+
@property
|
|
759
|
+
def cameraAPI(self):
|
|
760
|
+
"""Camera API used to access the camera stream (`str`).
|
|
761
|
+
|
|
762
|
+
This is the camera API used to access the camera stream. If the camera
|
|
763
|
+
stream is not open, this will return `None`.
|
|
764
|
+
|
|
765
|
+
"""
|
|
766
|
+
return self.info.cameraAPI if self.info else None
|
|
767
|
+
|
|
768
|
+
@property
|
|
769
|
+
def bufferSecs(self):
|
|
770
|
+
"""Number of seconds to buffer frames from the camera stream (`float`).
|
|
771
|
+
|
|
772
|
+
This is the number of seconds to buffer frames from the camera stream.
|
|
773
|
+
This allows frames to be buffered in memory until they are needed. This
|
|
774
|
+
allows the camera stream to be read asynchronously and prevents frames
|
|
775
|
+
from being dropped if the main thread is busy.
|
|
776
|
+
|
|
777
|
+
"""
|
|
778
|
+
return self._bufferSecs
|
|
779
|
+
|
|
780
|
+
def getMetadata(self):
|
|
781
|
+
"""Get metadata about the camera stream.
|
|
782
|
+
|
|
783
|
+
Returns
|
|
784
|
+
-------
|
|
785
|
+
dict
|
|
786
|
+
Dictionary containing metadata about the camera stream. Returns an
|
|
787
|
+
empty dictionary if no metadata is available.
|
|
788
|
+
|
|
789
|
+
"""
|
|
790
|
+
if self._capture is None:
|
|
791
|
+
return {}
|
|
792
|
+
|
|
793
|
+
# get metadata from the capture stream
|
|
794
|
+
return self._capture.get_metadata() if self._capture else {}
|
|
795
|
+
|
|
796
|
+
@property
|
|
797
|
+
def frameSizeBytes(self):
|
|
798
|
+
"""Size of the image in bytes (`int`).
|
|
799
|
+
|
|
800
|
+
This is the size of the image in bytes. It is calculated as
|
|
801
|
+
`width * height * 3`, where `width` and `height` are the dimensions of
|
|
802
|
+
the camera stream. If the camera stream is not open, this will return
|
|
803
|
+
`0`.
|
|
804
|
+
|
|
805
|
+
"""
|
|
806
|
+
if self._frameSize is None:
|
|
807
|
+
return 0
|
|
808
|
+
|
|
809
|
+
return self._frameSizeBytes
|
|
810
|
+
|
|
811
|
+
@property
|
|
812
|
+
def frameCount(self):
|
|
813
|
+
"""Number of frames read from the camera stream (`int`).
|
|
814
|
+
|
|
815
|
+
This is the number of frames read from the camera stream since the last
|
|
816
|
+
time the camera was opened. If the camera stream is not open, this will
|
|
817
|
+
return `0`.
|
|
818
|
+
|
|
819
|
+
"""
|
|
820
|
+
return self._frameCount
|
|
821
|
+
|
|
822
|
+
@property
|
|
823
|
+
def streamTime(self):
|
|
824
|
+
"""Current stream time in seconds (`float`).
|
|
825
|
+
|
|
826
|
+
This is the current stream time in seconds. It is calculated as the
|
|
827
|
+
difference between the current time and the absolute recording start
|
|
828
|
+
time. If the camera stream is not open, this will return `-1.0`.
|
|
829
|
+
|
|
830
|
+
"""
|
|
831
|
+
if self._cameraAPI == CAMERA_API_AVFOUNDATION:
|
|
832
|
+
return time.time() if self._capture is not None else -1.0
|
|
833
|
+
else:
|
|
834
|
+
return self._capture.get_pts() if self._capture is not None else -1.0
|
|
835
|
+
|
|
836
|
+
def _toNumpyView(self, frame):
|
|
837
|
+
"""Convert a frame to a Numpy view.
|
|
838
|
+
|
|
839
|
+
This function converts a frame to a Numpy view. The frame is returned as
|
|
840
|
+
a Numpy array. The resulting array will be in the correct format to
|
|
841
|
+
upload to OpenGL as a texture.
|
|
842
|
+
|
|
843
|
+
Parameters
|
|
844
|
+
----------
|
|
845
|
+
frame : Any
|
|
846
|
+
The frame to convert.
|
|
847
|
+
|
|
848
|
+
Returns
|
|
849
|
+
-------
|
|
850
|
+
numpy.ndarray
|
|
851
|
+
The converted frame in RGB format.
|
|
852
|
+
|
|
853
|
+
"""
|
|
854
|
+
return np.asarray(frame, dtype=np.uint8)
|
|
855
|
+
|
|
856
|
+
# --------------------------------------------------------------------------
|
|
857
|
+
# Platform-specific camera frame aquisition methods
|
|
858
|
+
#
|
|
859
|
+
# These methods are used to open, close, and read frames from the camera
|
|
860
|
+
# stream. They are platform-specific and are called depending on the
|
|
861
|
+
# camera library being used.
|
|
862
|
+
#
|
|
863
|
+
|
|
864
|
+
# --------------------------------------------------------------------------
|
|
865
|
+
# FFPyPlayer-specific methods
|
|
866
|
+
#
|
|
867
|
+
|
|
868
|
+
def _openFFPyPlayer(self):
|
|
869
|
+
"""Open the camera stream using FFmpeg (ffpyplayer).
|
|
870
|
+
|
|
871
|
+
This method should be called to open the camera stream using FFmpeg.
|
|
872
|
+
It should initialize the camera and prepare it for reading frames.
|
|
873
|
+
|
|
874
|
+
"""
|
|
875
|
+
# configure the camera stream reader
|
|
876
|
+
ff_opts = {} # ffmpeg options
|
|
877
|
+
lib_opts = {} # ffpyplayer options
|
|
878
|
+
_camera = CAMERA_NULL_VALUE
|
|
879
|
+
_frameRate = CAMERA_NULL_VALUE
|
|
880
|
+
|
|
881
|
+
# setup commands for FFMPEG
|
|
882
|
+
if self._captureAPI == CAMERA_API_DIRECTSHOW: # windows
|
|
883
|
+
ff_opts['f'] = 'dshow'
|
|
884
|
+
_camera = 'video={}'.format(self.info.name)
|
|
885
|
+
_frameRate = self._frameRate
|
|
886
|
+
if self._pixelFormat:
|
|
887
|
+
ff_opts['pixel_format'] = self._pixelFormat
|
|
888
|
+
if self._codecFormat:
|
|
889
|
+
ff_opts['vcodec'] = self._codecFormat
|
|
890
|
+
elif self._captureAPI == CAMERA_API_AVFOUNDATION: # darwin
|
|
891
|
+
ff_opts['f'] = 'avfoundation'
|
|
892
|
+
ff_opts['i'] = _camera = self._device
|
|
893
|
+
|
|
894
|
+
# handle pixel formats using FourCC
|
|
895
|
+
global pixelFormatTbl
|
|
896
|
+
ffmpegPixFmt = pixelFormatTbl.get(self._pixelFormat, None)
|
|
897
|
+
|
|
898
|
+
if ffmpegPixFmt is None:
|
|
899
|
+
raise FormatNotFoundError(
|
|
900
|
+
"Cannot find suitable FFMPEG pixel format for '{}'. Try a "
|
|
901
|
+
"different format or camera.".format(
|
|
902
|
+
self._pixelFormat))
|
|
903
|
+
|
|
904
|
+
self._pixelFormat = ffmpegPixFmt
|
|
905
|
+
|
|
906
|
+
# this needs to be exactly specified if using NTSC
|
|
907
|
+
if math.isclose(CAMERA_FRAMERATE_NTSC, self._frameRate):
|
|
908
|
+
_frameRate = CAMERA_FRAMERATE_NOMINAL_NTSC
|
|
909
|
+
else:
|
|
910
|
+
_frameRate = str(self._frameRate)
|
|
911
|
+
|
|
912
|
+
# need these since hardware acceleration is not possible on Mac yet
|
|
913
|
+
lib_opts['fflags'] = 'nobuffer'
|
|
914
|
+
lib_opts['flags'] = 'low_delay'
|
|
915
|
+
lib_opts['pixel_format'] = self._pixelFormat
|
|
916
|
+
lib_opts['use_wallclock_as_timestamps'] = '1'
|
|
917
|
+
# ff_opts['framedrop'] = True
|
|
918
|
+
# ff_opts['fast'] = True
|
|
919
|
+
elif self._captureAPI == CAMERA_API_VIDEO4LINUX2:
|
|
920
|
+
raise OSError(
|
|
921
|
+
"Sorry, camera does not support Linux at this time. However, "
|
|
922
|
+
"it will in future versions.")
|
|
923
|
+
|
|
924
|
+
else:
|
|
925
|
+
raise RuntimeError("Unsupported camera API specified.")
|
|
926
|
+
|
|
927
|
+
# set library options
|
|
928
|
+
camWidth, camHeight = self._frameSize
|
|
929
|
+
logging.debug(
|
|
930
|
+
"Using camera mode {}x{} at {} fps".format(
|
|
931
|
+
camWidth, camHeight, _frameRate))
|
|
932
|
+
|
|
933
|
+
# configure the real-time buffer size, we compute using RGB8 since this
|
|
934
|
+
# is uncompressed and represents the largest size we can expect
|
|
935
|
+
self._frameSizeBytes = int(camWidth * camHeight * 3)
|
|
936
|
+
framesToBufferCount = int(self._bufferSecs * self._frameRate)
|
|
937
|
+
_bufferSize = int(self._frameSizeBytes * framesToBufferCount)
|
|
938
|
+
logging.debug(
|
|
939
|
+
"Setting real-time buffer size to {} bytes "
|
|
940
|
+
"for {} seconds of video ({} frames @ {} fps)".format(
|
|
941
|
+
_bufferSize,
|
|
942
|
+
self._bufferSecs,
|
|
943
|
+
framesToBufferCount,
|
|
944
|
+
self._frameRate)
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
# common settings across libraries
|
|
948
|
+
ff_opts['low_delay'] = True # low delay for real-time playback
|
|
949
|
+
# ff_opts['framedrop'] = True
|
|
950
|
+
# ff_opts['use_wallclock_as_timestamps'] = True
|
|
951
|
+
ff_opts['fast'] = True
|
|
952
|
+
# ff_opts['sync'] = 'ext'
|
|
953
|
+
ff_opts['rtbufsize'] = str(_bufferSize) # set the buffer size
|
|
954
|
+
ff_opts['an'] = True
|
|
955
|
+
# ff_opts['infbuf'] = True # enable infinite buffering
|
|
956
|
+
|
|
957
|
+
# for ffpyplayer, we need to set the video size and framerate
|
|
958
|
+
lib_opts['video_size'] = '{width}x{height}'.format(
|
|
959
|
+
width=camWidth, height=camHeight)
|
|
960
|
+
lib_opts['framerate'] = str(_frameRate)
|
|
961
|
+
ff_opts['loglevel'] = 'error'
|
|
962
|
+
ff_opts['nostdin'] = True
|
|
963
|
+
|
|
964
|
+
# open the media player
|
|
965
|
+
from ffpyplayer.player import MediaPlayer
|
|
966
|
+
self._capture = MediaPlayer(
|
|
967
|
+
_camera,
|
|
968
|
+
ff_opts=ff_opts,
|
|
969
|
+
lib_opts=lib_opts)
|
|
970
|
+
|
|
971
|
+
# compute the frame interval, needed for generating timestamps
|
|
972
|
+
self._frameInterval = 1.0 / self._frameRate
|
|
973
|
+
|
|
974
|
+
# get metadata from the capture stream
|
|
975
|
+
tStart = time.time() # start time for the stream
|
|
976
|
+
metadataTimeout = 5.0 # timeout for metadata retrieval
|
|
977
|
+
while time.time() - tStart < metadataTimeout: # wait for metadata
|
|
978
|
+
streamMetadata = self._capture.get_metadata()
|
|
979
|
+
if streamMetadata['src_vid_size'] != (0, 0):
|
|
980
|
+
break
|
|
981
|
+
time.sleep(0.001) # wait for metadata to be available
|
|
982
|
+
else:
|
|
983
|
+
msg = (
|
|
984
|
+
"Failed to obtain stream metadata (possibly caused by a device "
|
|
985
|
+
"already in use by other application)."
|
|
986
|
+
)
|
|
987
|
+
logging.error(msg)
|
|
988
|
+
raise CameraNotReadyError(msg)
|
|
989
|
+
|
|
990
|
+
self._metadata = streamMetadata # store the metadata for later use
|
|
991
|
+
|
|
992
|
+
# check if the camera metadata matches the requested settings
|
|
993
|
+
if streamMetadata['src_vid_size'] != tuple(self._frameSize):
|
|
994
|
+
raise CameraFrameSizeNotSupportedError(
|
|
995
|
+
"Camera does not support the requested frame size "
|
|
996
|
+
"{size}. Supported sizes are: {supportedSizes}".format(
|
|
997
|
+
size=self._frameSize,
|
|
998
|
+
supportedSizes=streamMetadata['src_vid_size']))
|
|
999
|
+
|
|
1000
|
+
# pause the camera stream
|
|
1001
|
+
self._capture.set_pause(True)
|
|
1002
|
+
|
|
1003
|
+
def _closeFFPyPlayer(self):
|
|
1004
|
+
"""Close the camera stream opened with FFmpeg (ffpyplayer).
|
|
1005
|
+
|
|
1006
|
+
This method should be called to close the camera stream and release any
|
|
1007
|
+
resources associated with it.
|
|
1008
|
+
|
|
1009
|
+
"""
|
|
1010
|
+
if self._capture is not None:
|
|
1011
|
+
# self._capture.set_pause(True) # pause the stream
|
|
1012
|
+
self._capture.close_player()
|
|
1013
|
+
|
|
1014
|
+
def _getFramesFFPyPlayer(self):
|
|
1015
|
+
"""Get the most recent frames from the camera stream opened with FFmpeg
|
|
1016
|
+
(ffpyplayer).
|
|
1017
|
+
|
|
1018
|
+
Returns
|
|
1019
|
+
-------
|
|
1020
|
+
numpy.ndarray
|
|
1021
|
+
Most recent frames from the camera stream. Returns `None` if no
|
|
1022
|
+
frames are available.
|
|
1023
|
+
|
|
1024
|
+
"""
|
|
1025
|
+
if self._capture is None:
|
|
1026
|
+
raise PlayerNotAvailableError(
|
|
1027
|
+
"Camera stream is not open. Call `open()` first.")
|
|
1028
|
+
|
|
1029
|
+
# read all buffered frames from the camera stream until we get nothing
|
|
1030
|
+
recentFrames = []
|
|
1031
|
+
while 1:
|
|
1032
|
+
frame, status = self._capture.get_frame()
|
|
1033
|
+
|
|
1034
|
+
if status == CAMERA_STATUS_EOF or status == CAMERA_STATUS_PAUSED:
|
|
1035
|
+
break
|
|
1036
|
+
|
|
1037
|
+
if frame is None: # ditto
|
|
1038
|
+
break
|
|
1039
|
+
|
|
1040
|
+
img, curPts = frame
|
|
1041
|
+
if curPts < self._absRecStreamStartTime and self._isRecording:
|
|
1042
|
+
del img # free the memory used by the frame
|
|
1043
|
+
# if the frame is before the recording start time, skip it
|
|
1044
|
+
continue
|
|
1045
|
+
|
|
1046
|
+
self._frameCount += 1 # increment the frame count
|
|
1047
|
+
|
|
1048
|
+
recentFrames.append((
|
|
1049
|
+
img,
|
|
1050
|
+
curPts-self._absRecStreamStartTime,
|
|
1051
|
+
curPts))
|
|
1052
|
+
|
|
1053
|
+
return recentFrames
|
|
1054
|
+
|
|
1055
|
+
# --------------------------------------------------------------------------
|
|
1056
|
+
# OpenCV-specific methods
|
|
1057
|
+
#
|
|
1058
|
+
|
|
1059
|
+
def _convertFrameToRGBOpenCV(self, frame):
|
|
1060
|
+
"""Convert a frame to RGB format using OpenCV.
|
|
1061
|
+
|
|
1062
|
+
This function converts a frame to RGB format. The frame is returned as
|
|
1063
|
+
a Numpy array. The resulting array will be in the correct format to
|
|
1064
|
+
upload to OpenGL as a texture.
|
|
1065
|
+
|
|
1066
|
+
Parameters
|
|
1067
|
+
----------
|
|
1068
|
+
frame : numpy.ndarray
|
|
1069
|
+
The frame to convert.
|
|
1070
|
+
|
|
1071
|
+
Returns
|
|
1072
|
+
-------
|
|
1073
|
+
numpy.ndarray
|
|
1074
|
+
The converted frame in RGB format.
|
|
1075
|
+
|
|
1076
|
+
"""
|
|
1077
|
+
import cv2
|
|
1078
|
+
|
|
1079
|
+
# this can be done in the shader to save CPU use, will figure out later
|
|
1080
|
+
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
1081
|
+
|
|
1082
|
+
def _openOpenCV(self):
|
|
1083
|
+
"""Open the camera stream using OpenCV.
|
|
1084
|
+
|
|
1085
|
+
This method should be called to open the camera stream using OpenCV.
|
|
1086
|
+
It should initialize the camera and prepare it for reading frames.
|
|
1087
|
+
|
|
1088
|
+
"""
|
|
1089
|
+
pass
|
|
1090
|
+
|
|
1091
|
+
def _closeOpenCV(self):
|
|
1092
|
+
"""Close the camera stream opened with OpenCV.
|
|
1093
|
+
|
|
1094
|
+
This method should be called to close the camera stream and release any
|
|
1095
|
+
resources associated with it.
|
|
1096
|
+
|
|
1097
|
+
"""
|
|
1098
|
+
pass
|
|
1099
|
+
|
|
1100
|
+
def _getFramesOpenCV(self):
|
|
1101
|
+
"""Get the most recent frames from the camera stream opened with OpenCV.
|
|
1102
|
+
|
|
1103
|
+
Returns
|
|
1104
|
+
-------
|
|
1105
|
+
numpy.ndarray
|
|
1106
|
+
Most recent frames from the camera stream. Returns `None` if no
|
|
1107
|
+
frames are available.
|
|
1108
|
+
|
|
1109
|
+
"""
|
|
1110
|
+
if self._capture is None:
|
|
1111
|
+
raise PlayerNotAvailableError(
|
|
1112
|
+
"Camera stream is not open. Call `open()` first.")
|
|
1113
|
+
|
|
1114
|
+
pass
|
|
1115
|
+
|
|
1116
|
+
# --------------------------------------------------------------------------
|
|
1117
|
+
# Public methods for camera stream management
|
|
1118
|
+
#
|
|
1119
|
+
|
|
1120
|
+
def __hash__(self):
|
|
1121
|
+
"""Hash on the camera device name and library used."""
|
|
1122
|
+
return hash((self._device, self._captureLib))
|
|
1123
|
+
|
|
1124
|
+
def open(self):
|
|
1125
|
+
"""Open the camera stream.
|
|
1126
|
+
|
|
1127
|
+
This method should be called to open the camera stream. It should
|
|
1128
|
+
initialize the camera and prepare it for reading frames.
|
|
1129
|
+
|
|
1130
|
+
"""
|
|
1131
|
+
if self._captureLib == 'ffpyplayer':
|
|
1132
|
+
self._openFFPyPlayer()
|
|
1133
|
+
|
|
1134
|
+
global _openCaptureInterfaces
|
|
1135
|
+
_openCaptureInterfaces.add(self)
|
|
1136
|
+
|
|
1137
|
+
def close(self):
|
|
1138
|
+
"""Close the camera stream.
|
|
1139
|
+
|
|
1140
|
+
This method should be called to close the camera stream and release any
|
|
1141
|
+
resources associated with it.
|
|
1142
|
+
|
|
1143
|
+
"""
|
|
1144
|
+
if self.isRecording:
|
|
1145
|
+
self.stop() # stop the recording if it is in progress
|
|
1146
|
+
logging.warning(
|
|
1147
|
+
"CameraDevice.close() called while recording. Stopping.")
|
|
1148
|
+
|
|
1149
|
+
if self._captureLib == 'ffpyplayer':
|
|
1150
|
+
self._closeFFPyPlayer()
|
|
1151
|
+
|
|
1152
|
+
self._capture = None # reset the capture object
|
|
1153
|
+
|
|
1154
|
+
global _openCaptureInterfaces
|
|
1155
|
+
if self in _openCaptureInterfaces:
|
|
1156
|
+
_openCaptureInterfaces.remove(self)
|
|
1157
|
+
|
|
1158
|
+
@property
|
|
1159
|
+
def isOpen(self):
|
|
1160
|
+
"""Check if the camera stream is open.
|
|
1161
|
+
|
|
1162
|
+
Returns
|
|
1163
|
+
-------
|
|
1164
|
+
bool
|
|
1165
|
+
`True` if the camera stream is open, `False` otherwise.
|
|
1166
|
+
|
|
1167
|
+
"""
|
|
1168
|
+
return self._capture is not None
|
|
1169
|
+
|
|
1170
|
+
def record(self):
|
|
1171
|
+
"""Start recording camera frames to memory.
|
|
1172
|
+
|
|
1173
|
+
This method should be called to start recording the camera stream.
|
|
1174
|
+
Frame timestamps will be generated based on the current time when
|
|
1175
|
+
this method is called. The frames will be stored and made available
|
|
1176
|
+
through the `getFrames()` method.
|
|
1177
|
+
|
|
1178
|
+
To get precise audio synchronization:
|
|
1179
|
+
|
|
1180
|
+
1. Start the microphone recording
|
|
1181
|
+
2. Store samples somehwere keeping track of the absolute time of the
|
|
1182
|
+
first audio sample.
|
|
1183
|
+
3. Call this method to start the camera recording and store the
|
|
1184
|
+
returned start time.
|
|
1185
|
+
4. When the recording is stopped, compute the offset between the
|
|
1186
|
+
absolute start time of the audio recording and the absolute start
|
|
1187
|
+
time of the camera recording. Compute the postion of the first
|
|
1188
|
+
audio sample in the audio buffer by multiplying the offset by the
|
|
1189
|
+
sample rate of the audio recording. This will give you the
|
|
1190
|
+
position of the first audio sample in the audio buffer
|
|
1191
|
+
corresponding to the very beginning of the first camera frame.
|
|
1192
|
+
|
|
1193
|
+
Returns
|
|
1194
|
+
-------
|
|
1195
|
+
float
|
|
1196
|
+
The absolute start time of the recording in seconds. Use this value
|
|
1197
|
+
to syncronize audio recording with the capture stream.
|
|
1198
|
+
|
|
1199
|
+
"""
|
|
1200
|
+
if not self.isOpen:
|
|
1201
|
+
raise RuntimeError("Camera stream is not open. Call `open()` first.")
|
|
1202
|
+
|
|
1203
|
+
self._frameCount = 0 # reset the frame count
|
|
1204
|
+
self._clearFrameStore() # clear the frame store
|
|
1205
|
+
self._capture.set_pause(False) # start the capture stream
|
|
1206
|
+
|
|
1207
|
+
# need to use a different timebase on macOS, due to a bug
|
|
1208
|
+
if self._cameraAPI == CAMERA_API_AVFOUNDATION:
|
|
1209
|
+
self._absRecStreamStartTime = time.time()
|
|
1210
|
+
else:
|
|
1211
|
+
self._absRecStreamStartTime = self._capture.get_pts() # get the absolute start time
|
|
1212
|
+
|
|
1213
|
+
self._absRecExpStartTime = core.getTime() # experiment start time in seconds
|
|
1214
|
+
self._isRecording = True
|
|
1215
|
+
|
|
1216
|
+
return self._absRecStreamStartTime
|
|
1217
|
+
|
|
1218
|
+
def start(self):
|
|
1219
|
+
"""Start recording the camera stream.
|
|
1220
|
+
|
|
1221
|
+
Alias for `record()`. This method is provided for compatibility with
|
|
1222
|
+
other camera interfaces that may use `start()` to begin recording.
|
|
1223
|
+
|
|
1224
|
+
"""
|
|
1225
|
+
return self.record() # start recording and return the start time
|
|
1226
|
+
|
|
1227
|
+
def stop(self):
|
|
1228
|
+
"""Stop recording the camera stream.
|
|
1229
|
+
|
|
1230
|
+
This method should be called to stop recording the camera stream. It
|
|
1231
|
+
will stop capturing frames from the camera and clear the frame store.
|
|
1232
|
+
|
|
1233
|
+
"""
|
|
1234
|
+
self._capture.set_pause(True) # pause the capture stream
|
|
1235
|
+
|
|
1236
|
+
if self._cameraAPI == CAMERA_API_AVFOUNDATION:
|
|
1237
|
+
absStopTime = time.time()
|
|
1238
|
+
else:
|
|
1239
|
+
absStopTime = self._capture.get_pts()
|
|
1240
|
+
|
|
1241
|
+
self._isRecording = False
|
|
1242
|
+
|
|
1243
|
+
return absStopTime
|
|
1244
|
+
|
|
1245
|
+
@property
|
|
1246
|
+
def isRecording(self):
|
|
1247
|
+
"""Check if the camera stream is currently recording (`bool`).
|
|
1248
|
+
|
|
1249
|
+
Returns
|
|
1250
|
+
-------
|
|
1251
|
+
bool
|
|
1252
|
+
`True` if the camera stream is currently recording, `False`
|
|
1253
|
+
otherwise.
|
|
1254
|
+
|
|
1255
|
+
"""
|
|
1256
|
+
return self._isRecording
|
|
1257
|
+
|
|
1258
|
+
def getFrames(self):
|
|
1259
|
+
"""Get the most recent frames from the camera stream.
|
|
1260
|
+
|
|
1261
|
+
This method returns frame captured since the last call to this method.
|
|
1262
|
+
If no frames are available or `record()` has not been previously called,
|
|
1263
|
+
it returns an empty list.
|
|
1264
|
+
|
|
1265
|
+
You must call this method periodically at an interval of at least
|
|
1266
|
+
`bufferSecs` seconds or risk losing frames.
|
|
1267
|
+
|
|
1268
|
+
Returns
|
|
1269
|
+
-------
|
|
1270
|
+
list
|
|
1271
|
+
List of frames from the camera stream. Returns an empty list if no
|
|
1272
|
+
frames are available.
|
|
1273
|
+
|
|
1274
|
+
"""
|
|
1275
|
+
if self._captureLib == 'ffpyplayer':
|
|
1276
|
+
return self._getFramesFFPyPlayer()
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
# class name alias for legacy support
|
|
1280
|
+
CameraInterface = CameraDevice
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
# keep track of camera devices that are opened
|
|
1284
|
+
_openCameras = {}
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
class Camera:
|
|
1288
|
+
"""Class for displaying and recording video from a USB/PCI connected camera.
|
|
1289
|
+
|
|
1290
|
+
This class is capable of opening, recording, and saving camera video streams
|
|
1291
|
+
to disk. Camera stream reading/writing is done in a separate thread,
|
|
1292
|
+
allowing capture to occur in the background while the main thread is free to
|
|
1293
|
+
perform other tasks. This allows for capture to occur at higher frame rates
|
|
1294
|
+
than the display refresh rate. Audio recording is also supported if a
|
|
1295
|
+
microphone interface is provided, where recording will be synchronized with
|
|
1296
|
+
the video stream (as best as possible). Video and audio can be saved to disk
|
|
1297
|
+
either as a single file or as separate files.
|
|
1298
|
+
|
|
1299
|
+
GNU/Linux is supported only by the OpenCV backend (`cameraLib='opencv'`).
|
|
1300
|
+
|
|
1301
|
+
Parameters
|
|
1302
|
+
----------
|
|
1303
|
+
device : str or int
|
|
1304
|
+
Camera to open a stream with. If the ID is not valid, an error will be
|
|
1305
|
+
raised when `open()` is called. Value can be a string or number. String
|
|
1306
|
+
values are platform-dependent: a DirectShow URI or camera name on
|
|
1307
|
+
Windows, or a camera name/index on MacOS. Specifying a number (>=0) is a
|
|
1308
|
+
platform-independent means of selecting a camera. PsychoPy enumerates
|
|
1309
|
+
possible camera devices and makes them selectable without explicitly
|
|
1310
|
+
having the name of the cameras attached to the system. Use caution when
|
|
1311
|
+
specifying an integer, as the same index may not reference the same
|
|
1312
|
+
camera every time.
|
|
1313
|
+
mic : :class:`~psychopy.sound.microphone.Microphone` or None
|
|
1314
|
+
Microphone to record audio samples from during recording. The microphone
|
|
1315
|
+
input device must not be in use when `record()` is called. The audio
|
|
1316
|
+
track will be merged with the video upon calling `save()`. Make sure
|
|
1317
|
+
that `Microphone.maxRecordingSize` is specified to a reasonable value to
|
|
1318
|
+
prevent the audio track from being truncated. Specifying a microphone
|
|
1319
|
+
adds some latency to starting and stopping camera recording due to the
|
|
1320
|
+
added overhead involved with synchronizing the audio and video streams.
|
|
1321
|
+
frameRate : int or None
|
|
1322
|
+
Frame rate to record the camera stream at. If `None`, the camera's
|
|
1323
|
+
default frame rate will be used.
|
|
1324
|
+
frameSize : tuple or None
|
|
1325
|
+
Size (width, height) of the camera stream frames to record. If `None`,
|
|
1326
|
+
the camera's default frame size will be used.
|
|
1327
|
+
cameraLib : str
|
|
1328
|
+
Interface library (backend) to use for accessing the camera. May either
|
|
1329
|
+
be `ffpyplayer` or `opencv`. If `None`, the default library for the
|
|
1330
|
+
recommended by the PsychoPy developers will be used. Switching camera
|
|
1331
|
+
libraries could help resolve issues with camera compatibility. More
|
|
1332
|
+
camera libraries may be installed via extension packages.
|
|
1333
|
+
bufferSecs : float
|
|
1334
|
+
Size of the real-time camera stream buffer specified in seconds. This
|
|
1335
|
+
will tell the library to allocate a buffer that can hold enough
|
|
1336
|
+
frames to cover the specified number of seconds of video. This should
|
|
1337
|
+
be large enough to cover the time it takes to process frames in the
|
|
1338
|
+
main thread.
|
|
1339
|
+
win : :class:`~psychopy.visual.Window` or None
|
|
1340
|
+
Optional window associated with this camera. Some functionality may
|
|
1341
|
+
require an OpenGL context for presenting frames to the screen. If you
|
|
1342
|
+
are not planning to display the camera stream, this parameter can be
|
|
1343
|
+
safely ignored.
|
|
1344
|
+
name : str
|
|
1345
|
+
Label for the camera for logging purposes.
|
|
1346
|
+
keepFrames : int
|
|
1347
|
+
Number of frames to keep in memory for the camera stream. Calling
|
|
1348
|
+
`getVideoFrames()` will return the most recent `keepFrames` frames from
|
|
1349
|
+
the camera stream. If `keepFrames` is set to `0`, no frames will be kept
|
|
1350
|
+
in memory and the camera stream will not be buffered. This is useful if
|
|
1351
|
+
the user desires to access raw frame data from the camera stream.
|
|
1352
|
+
latencyBias : float
|
|
1353
|
+
Latency bias to correct for asychrony between the camera and the
|
|
1354
|
+
microphone. This is the amount of time in seconds to add to the
|
|
1355
|
+
microphone recording start time to shift the audio track to match
|
|
1356
|
+
corresponding events in the video stream. This is needed for some
|
|
1357
|
+
cameras whose drivers do not accurately report timestamps for camera
|
|
1358
|
+
frames. Positive values will shift the audio track forward in time, and
|
|
1359
|
+
negative values will shift backwards.
|
|
1360
|
+
usageMode : str
|
|
1361
|
+
Usage mode hint for the camera aquisition. This with enable
|
|
1362
|
+
optimizations for specific applications that will improve performance
|
|
1363
|
+
and reduce memory usage. The default value is 'video', which is suitable
|
|
1364
|
+
for recording video streams with audio efficently. The 'cv' mode is for
|
|
1365
|
+
computer vision applications where frames from the camera stream are
|
|
1366
|
+
processed in real-time (e.g. object detection, tracking, etc.) and the
|
|
1367
|
+
video is not being saved to disk. Audio will not be recorded in this
|
|
1368
|
+
mode even if a microphone is provided.
|
|
1369
|
+
|
|
1370
|
+
Examples
|
|
1371
|
+
--------
|
|
1372
|
+
Opening a camera stream and closing it::
|
|
1373
|
+
|
|
1374
|
+
camera = Camera(device=0)
|
|
1375
|
+
camera.open() # exception here on invalid camera
|
|
1376
|
+
camera.close()
|
|
1377
|
+
|
|
1378
|
+
Recording 5 seconds of video and saving it to disk::
|
|
1379
|
+
|
|
1380
|
+
cam = Camera(0)
|
|
1381
|
+
cam.open()
|
|
1382
|
+
cam.record() # starts recording
|
|
1383
|
+
|
|
1384
|
+
while cam.recordingTime < 5.0: # record for 5 seconds
|
|
1385
|
+
if event.getKeys('q'):
|
|
1386
|
+
break
|
|
1387
|
+
cam.update()
|
|
1388
|
+
|
|
1389
|
+
cam.stop() # stops recording
|
|
1390
|
+
cam.save('myVideo.mp4')
|
|
1391
|
+
cam.close()
|
|
1392
|
+
|
|
1393
|
+
Providing a microphone as follows enables audio recording::
|
|
1394
|
+
|
|
1395
|
+
mic = Microphone(0)
|
|
1396
|
+
cam = Camera(0, mic=mic)
|
|
1397
|
+
|
|
1398
|
+
Overriding the default frame rate and size (if `cameraLib` supports it)::
|
|
1399
|
+
|
|
1400
|
+
cam = Camera(0, frameRate=30, frameSize=(640, 480), cameraLib=u'opencv')
|
|
1401
|
+
|
|
1402
|
+
"""
|
|
1403
|
+
def __init__(self, device=0, mic=None, cameraLib=u'ffpyplayer',
|
|
1404
|
+
frameRate=None, frameSize=None, bufferSecs=4, win=None,
|
|
1405
|
+
name='cam', keepFrames=5, usageMode='video'):
|
|
1406
|
+
# add attributes for setters
|
|
1407
|
+
self.__dict__.update(
|
|
1408
|
+
{'_device': None,
|
|
1409
|
+
'_captureThread': None,
|
|
1410
|
+
'_mic': None,
|
|
1411
|
+
'_outFile': None,
|
|
1412
|
+
'_mode': u'video',
|
|
1413
|
+
'_frameRate': None,
|
|
1414
|
+
'_frameRateFrac': None,
|
|
1415
|
+
'_frameSize': None,
|
|
1416
|
+
'_size': None,
|
|
1417
|
+
'_cameraLib': u''})
|
|
1418
|
+
|
|
1419
|
+
self._cameraLib = cameraLib
|
|
1420
|
+
|
|
1421
|
+
# handle device
|
|
1422
|
+
self._capture = None
|
|
1423
|
+
if isinstance(device, CameraDevice):
|
|
1424
|
+
# if given a device object, use it
|
|
1425
|
+
self._capture = device
|
|
1426
|
+
elif device is None:
|
|
1427
|
+
# if given None, get the first available device
|
|
1428
|
+
for name, obj in DeviceManager.getInitialisedDevices(CameraDevice).items():
|
|
1429
|
+
self._capture = obj
|
|
1430
|
+
break
|
|
1431
|
+
# if there are none, set one up
|
|
1432
|
+
if self._capture is None:
|
|
1433
|
+
for profile in CameraDevice.getAvailableDevices():
|
|
1434
|
+
self._capture = DeviceManager.addDevice(**profile)
|
|
1435
|
+
break
|
|
1436
|
+
elif isinstance(device, str):
|
|
1437
|
+
if DeviceManager.getDevice(device):
|
|
1438
|
+
self._capture = DeviceManager.getDevice(device)
|
|
1439
|
+
else:
|
|
1440
|
+
# get available devices
|
|
1441
|
+
availableDevices = CameraDevice.getAvailableDevices()
|
|
1442
|
+
# if given a device name, try to find it
|
|
1443
|
+
for profile in availableDevices:
|
|
1444
|
+
if profile['deviceName'] != device:
|
|
1445
|
+
continue
|
|
1446
|
+
paramsMatch = all([
|
|
1447
|
+
profile.get(key) == value
|
|
1448
|
+
for key, value in {
|
|
1449
|
+
'deviceName': device,
|
|
1450
|
+
'captureLib': cameraLib,
|
|
1451
|
+
'frameRate': frameRate if frameRate is not None else True, # get first
|
|
1452
|
+
'frameSize': frameSize if frameSize is not None else True
|
|
1453
|
+
}.items() if value is not None
|
|
1454
|
+
])
|
|
1455
|
+
if not paramsMatch:
|
|
1456
|
+
continue
|
|
1457
|
+
|
|
1458
|
+
device = profile['device']
|
|
1459
|
+
break
|
|
1460
|
+
|
|
1461
|
+
# anything else, try to initialise a new device from params
|
|
1462
|
+
self._capture = CameraDevice(
|
|
1463
|
+
device=device,
|
|
1464
|
+
captureLib=cameraLib,
|
|
1465
|
+
frameRate=frameRate,
|
|
1466
|
+
frameSize=frameSize,
|
|
1467
|
+
pixelFormat=None, # use default pixel format
|
|
1468
|
+
codecFormat=None, # use default codec format
|
|
1469
|
+
captureAPI=None # use default capture API
|
|
1470
|
+
)
|
|
1471
|
+
else:
|
|
1472
|
+
# anything else, try to initialise a new device from params
|
|
1473
|
+
self._capture = CameraDevice(
|
|
1474
|
+
device=device,
|
|
1475
|
+
captureLib=cameraLib,
|
|
1476
|
+
frameRate=frameRate,
|
|
1477
|
+
frameSize=frameSize,
|
|
1478
|
+
pixelFormat=None, # use default pixel format
|
|
1479
|
+
codecFormat=None, # use default codec format
|
|
1480
|
+
captureAPI=None # use default capture API
|
|
1481
|
+
)
|
|
1482
|
+
# from here on in the init, use the device index as `device`
|
|
1483
|
+
device = self._capture.device
|
|
1484
|
+
# get info from device
|
|
1485
|
+
self._cameraInfo = self._capture.info
|
|
1486
|
+
|
|
1487
|
+
# handle microphone
|
|
1488
|
+
self.mic = None
|
|
1489
|
+
if isinstance(mic, MicrophoneDevice):
|
|
1490
|
+
# if given a device object, use it
|
|
1491
|
+
self.mic = mic
|
|
1492
|
+
elif isinstance(mic, Microphone):
|
|
1493
|
+
# if given a Microphone, use its device
|
|
1494
|
+
self.mic = mic.device
|
|
1495
|
+
elif mic is None:
|
|
1496
|
+
# if given None, get the first available device
|
|
1497
|
+
for name, obj in DeviceManager.getInitialisedDevices(MicrophoneDevice).items():
|
|
1498
|
+
self.mic = obj
|
|
1499
|
+
break
|
|
1500
|
+
# if there are none, set one up
|
|
1501
|
+
if self.mic is None:
|
|
1502
|
+
for profile in MicrophoneDevice.getAvailableDevices():
|
|
1503
|
+
self.mic = DeviceManager.addDevice(**profile)
|
|
1504
|
+
break
|
|
1505
|
+
elif isinstance(mic, str) and DeviceManager.getDevice(mic) is not None:
|
|
1506
|
+
# if given a device name, get the device
|
|
1507
|
+
self.mic = DeviceManager.getDevice(mic)
|
|
1508
|
+
else:
|
|
1509
|
+
# anything else, try to initialise a new device from params
|
|
1510
|
+
self.mic = MicrophoneDevice(
|
|
1511
|
+
index=mic
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
# current camera frame since the start of recording
|
|
1515
|
+
self.status = NOT_STARTED
|
|
1516
|
+
self._bufferSecs = float(bufferSecs)
|
|
1517
|
+
self._lastFrame = None # use None to avoid imports for ImageStim
|
|
1518
|
+
self._keepFrames = keepFrames # number of frames to keep in memory
|
|
1519
|
+
self._frameCount = 0 # number of frames read from the camera stream
|
|
1520
|
+
self._frameStore = collections.deque(maxlen=keepFrames)
|
|
1521
|
+
self._usageMode = usageMode # usage mode for the camera
|
|
1522
|
+
self._unsaved = False # is there any footage not saved?
|
|
1523
|
+
|
|
1524
|
+
# other information
|
|
1525
|
+
self.name = name
|
|
1526
|
+
# timestamp data
|
|
1527
|
+
self._streamTime = 0.0
|
|
1528
|
+
# store win (unused but needs to be set/got safely for parity with JS)
|
|
1529
|
+
self._win = None
|
|
1530
|
+
|
|
1531
|
+
# recording properties
|
|
1532
|
+
self._isStarted = False # is the stream started?
|
|
1533
|
+
self._audioReady = False
|
|
1534
|
+
self._videoReady = False
|
|
1535
|
+
|
|
1536
|
+
self._latencyBias = 0.0 # latency bias in seconds
|
|
1537
|
+
|
|
1538
|
+
self._absVideoRecStartTime = -1.0
|
|
1539
|
+
self._absVideoRecStopTime = -1.0
|
|
1540
|
+
self._absAudioRecStartTime = -1.0
|
|
1541
|
+
self._absAudioRecStopTime = -1.0
|
|
1542
|
+
|
|
1543
|
+
# computed timestamps for when
|
|
1544
|
+
self._absAudioActualRecStartTime = -1.0
|
|
1545
|
+
|
|
1546
|
+
self._absAudioRecStartPos = -1.0 # in samples
|
|
1547
|
+
self._absAudioRecStopPos = -1.0
|
|
1548
|
+
|
|
1549
|
+
self._curPTS = 0.0 # current display timestamp
|
|
1550
|
+
self._isRecording = False
|
|
1551
|
+
self._generatePTS = False # use genreated PTS values for frames
|
|
1552
|
+
|
|
1553
|
+
# movie writer instance, this runs in a separate thread
|
|
1554
|
+
self._movieWriter = None
|
|
1555
|
+
self._tempVideoFile = None # temporary video file for recording
|
|
1556
|
+
|
|
1557
|
+
# thread for polling the microphone
|
|
1558
|
+
self._audioTrack = None # audio track from the recent recording
|
|
1559
|
+
# keep track of the last video file saved
|
|
1560
|
+
self._lastVideoFile = None
|
|
1561
|
+
|
|
1562
|
+
# OpenGL stuff, just declare these attributes for now
|
|
1563
|
+
self._pixbuffId = None
|
|
1564
|
+
self._textureId = None
|
|
1565
|
+
self._interpolate = True # use bilinear interpolation by default
|
|
1566
|
+
self._texFilterNeedsUpdate = True # flag to update texture filtering
|
|
1567
|
+
self._texBufferSizeBytes = None # size of the texture buffer
|
|
1568
|
+
|
|
1569
|
+
# computer vison mode
|
|
1570
|
+
self._objClassfiers = {} # list of classifiers for CV mode
|
|
1571
|
+
|
|
1572
|
+
# keep track of files to merge
|
|
1573
|
+
self._filesToMerge = [] # list of tuples (videoFile, audioFile)
|
|
1574
|
+
|
|
1575
|
+
self.setWin(win) # sets up OpenGL stuff if needed
|
|
1576
|
+
|
|
1577
|
+
def authorize(self):
|
|
1578
|
+
"""Get permission to access the camera. Not implemented locally yet.
|
|
1579
|
+
"""
|
|
1580
|
+
pass # NOP
|
|
1581
|
+
|
|
1582
|
+
@property
|
|
1583
|
+
def latencyBias(self):
|
|
1584
|
+
"""Latency bias in seconds (`float`).
|
|
1585
|
+
|
|
1586
|
+
This is the latency bias that is applied to the timestamps of the frames
|
|
1587
|
+
in the camera stream. This is useful for synchronizing the camera stream
|
|
1588
|
+
with other devices such as microphones or audio interfaces. The default
|
|
1589
|
+
value is `0.0`, which means no latency bias is applied.
|
|
1590
|
+
|
|
1591
|
+
"""
|
|
1592
|
+
return self._latencyBias
|
|
1593
|
+
|
|
1594
|
+
@latencyBias.setter
|
|
1595
|
+
def latencyBias(self, value):
|
|
1596
|
+
"""Set the latency bias in seconds (`float`).
|
|
1597
|
+
|
|
1598
|
+
This is the latency bias that is applied to the timestamps of the frames
|
|
1599
|
+
in the camera stream. This is useful for synchronizing the camera stream
|
|
1600
|
+
with other devices such as microphones or audio interfaces. The default
|
|
1601
|
+
value is `0.0`, which means no latency bias is applied.
|
|
1602
|
+
|
|
1603
|
+
Parameters
|
|
1604
|
+
----------
|
|
1605
|
+
value : float
|
|
1606
|
+
Latency bias in seconds.
|
|
1607
|
+
|
|
1608
|
+
"""
|
|
1609
|
+
if not isinstance(value, (int, float)):
|
|
1610
|
+
raise TypeError("Latency bias must be a number.")
|
|
1611
|
+
|
|
1612
|
+
self._latencyBias = float(value)
|
|
1613
|
+
|
|
1614
|
+
@property
|
|
1615
|
+
def streamTime(self):
|
|
1616
|
+
"""Current stream time in seconds (`float`).
|
|
1617
|
+
|
|
1618
|
+
This is the current absolute time in seconds from the time the PC was
|
|
1619
|
+
booted. This is not the same as the recording time, which is the time
|
|
1620
|
+
since the recording started. This is useful for generating timestamps
|
|
1621
|
+
across multiple cameras or devices using the same time source.
|
|
1622
|
+
|
|
1623
|
+
"""
|
|
1624
|
+
return self._capture.streamTime
|
|
1625
|
+
|
|
1626
|
+
@property
|
|
1627
|
+
def recordingTime(self):
|
|
1628
|
+
"""Time in seconds since the recording started (`float`).
|
|
1629
|
+
|
|
1630
|
+
This is the time since the recording started. This is useful for
|
|
1631
|
+
generating timestamps for frames in the recording. If the recording has
|
|
1632
|
+
not started, this will return `0.0`.
|
|
1633
|
+
|
|
1634
|
+
"""
|
|
1635
|
+
if self._absRecStreamStartTime < 0:
|
|
1636
|
+
return 0.0
|
|
1637
|
+
|
|
1638
|
+
if self._cameraAPI == CAMERA_API_AVFOUNDATION:
|
|
1639
|
+
return time.time() - self._absRecStreamStartTime
|
|
1640
|
+
|
|
1641
|
+
# for other APIs, use the PTS value
|
|
1642
|
+
curPts = self._capture.get_pts()
|
|
1643
|
+
if curPts is None:
|
|
1644
|
+
return 0.0
|
|
1645
|
+
|
|
1646
|
+
# return the difference between the current PTS and the absolute start time
|
|
1647
|
+
return self._capture.get_pts() - self._absRecStreamStartTime
|
|
1648
|
+
|
|
1649
|
+
@property
|
|
1650
|
+
def isReady(self):
|
|
1651
|
+
"""Is the camera ready (`bool`)?
|
|
1652
|
+
|
|
1653
|
+
The camera is ready when the following conditions are met. First, we've
|
|
1654
|
+
created a player interface and opened it. Second, we have received
|
|
1655
|
+
metadata about the stream. At this point we can assume that the camera
|
|
1656
|
+
is 'hot' and the stream is being read.
|
|
1657
|
+
|
|
1658
|
+
This is a legacy property used to support older versions of PsychoPy.
|
|
1659
|
+
The `isOpened` property should be used instead.
|
|
1660
|
+
|
|
1661
|
+
"""
|
|
1662
|
+
return self.isStarted
|
|
1663
|
+
|
|
1664
|
+
@property
|
|
1665
|
+
def frameSize(self):
|
|
1666
|
+
"""Size of the video frame obtained from recent metadata (`float` or
|
|
1667
|
+
`None`).
|
|
1668
|
+
|
|
1669
|
+
Only valid after an `open()` and successive `_enqueueFrame()` call as
|
|
1670
|
+
metadata needs to be obtained from the stream. Returns `None` if not
|
|
1671
|
+
valid.
|
|
1672
|
+
"""
|
|
1673
|
+
if self._cameraInfo is None:
|
|
1674
|
+
return None
|
|
1675
|
+
|
|
1676
|
+
return self._cameraInfo.frameSize
|
|
1677
|
+
|
|
1678
|
+
@property
|
|
1679
|
+
def frameRate(self):
|
|
1680
|
+
"""Frame rate of the video stream (`float` or `None`).
|
|
1681
|
+
|
|
1682
|
+
Only valid after an `open()` and successive `_enqueueFrame()` call as
|
|
1683
|
+
metadata needs to be obtained from the stream. Returns `None` if not
|
|
1684
|
+
valid.
|
|
1685
|
+
|
|
1686
|
+
"""
|
|
1687
|
+
if self._cameraInfo is None:
|
|
1688
|
+
return None
|
|
1689
|
+
|
|
1690
|
+
return self._cameraInfo.frameRate
|
|
1691
|
+
|
|
1692
|
+
@property
|
|
1693
|
+
def frameInterval(self):
|
|
1694
|
+
"""Frame interval in seconds (`float`).
|
|
1695
|
+
|
|
1696
|
+
This is the time between frames in the video stream. This is computed
|
|
1697
|
+
from the frame rate of the video stream. If the frame rate is not set,
|
|
1698
|
+
this will return `None`.
|
|
1699
|
+
|
|
1700
|
+
"""
|
|
1701
|
+
if self._cameraInfo is None or self._cameraInfo.frameRate is None:
|
|
1702
|
+
return -1.0
|
|
1703
|
+
|
|
1704
|
+
return 1.0 / self._cameraInfo.frameRate
|
|
1705
|
+
|
|
1706
|
+
def _assertCameraReady(self):
|
|
1707
|
+
"""Assert that the camera is ready. Raises a `CameraNotReadyError` if
|
|
1708
|
+
the camera is not ready.
|
|
1709
|
+
"""
|
|
1710
|
+
if not self.isReady:
|
|
1711
|
+
raise CameraNotReadyError("Camera is not ready.")
|
|
1712
|
+
|
|
1713
|
+
@property
|
|
1714
|
+
def isRecording(self):
|
|
1715
|
+
"""`True` if the video is presently recording (`bool`)."""
|
|
1716
|
+
# Status flags as properties are pretty useful for users since they are
|
|
1717
|
+
# self documenting and prevent the user from touching the status flag
|
|
1718
|
+
# attribute directly.
|
|
1719
|
+
#
|
|
1720
|
+
return self._isRecording
|
|
1721
|
+
|
|
1722
|
+
@property
|
|
1723
|
+
def isStarted(self):
|
|
1724
|
+
"""`True` if the stream has started (`bool`). This status is given after
|
|
1725
|
+
`open()` has been called on this object.
|
|
1726
|
+
"""
|
|
1727
|
+
if hasattr(self, "_isStarted"):
|
|
1728
|
+
return self._isStarted
|
|
1729
|
+
|
|
1730
|
+
@property
|
|
1731
|
+
def isNotStarted(self):
|
|
1732
|
+
"""`True` if the stream may not have started yet (`bool`). This status
|
|
1733
|
+
is given before `open()` or after `close()` has been called on this
|
|
1734
|
+
object.
|
|
1735
|
+
"""
|
|
1736
|
+
return not self.isStarted
|
|
1737
|
+
|
|
1738
|
+
@property
|
|
1739
|
+
def isStopped(self):
|
|
1740
|
+
"""`True` if the recording has stopped (`bool`). This does not mean that
|
|
1741
|
+
the stream has stopped, `getVideoFrame()` will still yield frames until
|
|
1742
|
+
`close()` is called.
|
|
1743
|
+
"""
|
|
1744
|
+
return not self._isRecording
|
|
1745
|
+
|
|
1746
|
+
@property
|
|
1747
|
+
def metadata(self):
|
|
1748
|
+
"""Video metadata retrieved during the last frame update
|
|
1749
|
+
(`MovieMetadata`).
|
|
1750
|
+
"""
|
|
1751
|
+
return self.getMetadata()
|
|
1752
|
+
|
|
1753
|
+
def getMetadata(self):
|
|
1754
|
+
"""Get stream metadata.
|
|
1755
|
+
|
|
1756
|
+
Returns
|
|
1757
|
+
-------
|
|
1758
|
+
MovieMetadata vor None
|
|
1759
|
+
Metadata about the video stream, retrieved during the last frame
|
|
1760
|
+
update (`_enqueueFrame` call). If no metadata is available,
|
|
1761
|
+
returns `None`. This is useful for getting information about the
|
|
1762
|
+
video stream such as frame size, frame rate, pixel format, etc.
|
|
1763
|
+
|
|
1764
|
+
"""
|
|
1765
|
+
return self._capture.getMetadata() if self._capture else None
|
|
1766
|
+
|
|
1767
|
+
_getCamerasCache = {}
|
|
1768
|
+
|
|
1769
|
+
@staticmethod
|
|
1770
|
+
def getCameras(cameraLib='ffpyplayer'):
|
|
1771
|
+
"""Get information about installed cameras on this system.
|
|
1772
|
+
|
|
1773
|
+
Returns
|
|
1774
|
+
-------
|
|
1775
|
+
dict
|
|
1776
|
+
Mapping of camera information objects.
|
|
1777
|
+
|
|
1778
|
+
"""
|
|
1779
|
+
# not pluggable yet, needs to be made available via extensions
|
|
1780
|
+
return CameraDevice.getCameras(
|
|
1781
|
+
cameraLib=cameraLib)
|
|
1782
|
+
|
|
1783
|
+
@staticmethod
|
|
1784
|
+
def getAvailableDevices():
|
|
1785
|
+
devices = []
|
|
1786
|
+
for dev in st.getCameras():
|
|
1787
|
+
for spec in dev:
|
|
1788
|
+
devices.append({
|
|
1789
|
+
'device': spec['index'],
|
|
1790
|
+
'name': spec['device_name'],
|
|
1791
|
+
'frameRate': spec['frameRate'],
|
|
1792
|
+
'frameSize': spec['frameSize'],
|
|
1793
|
+
'pixelFormat': spec['pixelFormat'],
|
|
1794
|
+
'codecFormat': spec['codecFormat'],
|
|
1795
|
+
'cameraAPI': spec['cameraAPI']
|
|
1796
|
+
})
|
|
1797
|
+
|
|
1798
|
+
return devices
|
|
1799
|
+
|
|
1800
|
+
@staticmethod
|
|
1801
|
+
def getCameraDescriptions(collapse=False):
|
|
1802
|
+
"""Get a mapping or list of camera descriptions.
|
|
1803
|
+
|
|
1804
|
+
Camera descriptions are a compact way of representing camera settings
|
|
1805
|
+
and formats. Description strings can be used to specify which camera
|
|
1806
|
+
device and format to use with it to the `Camera` class.
|
|
1807
|
+
|
|
1808
|
+
Descriptions have the following format (example)::
|
|
1809
|
+
|
|
1810
|
+
'[Live! Cam Sync 1080p] 160x120@30fps, mjpeg'
|
|
1811
|
+
|
|
1812
|
+
This shows a specific camera format for the 'Live! Cam Sync 1080p'
|
|
1813
|
+
webcam which supports 160x120 frame size at 30 frames per second. The
|
|
1814
|
+
last value is the codec or pixel format used to decode the stream.
|
|
1815
|
+
Different pixel formats and codecs vary in performance.
|
|
1816
|
+
|
|
1817
|
+
Parameters
|
|
1818
|
+
----------
|
|
1819
|
+
collapse : bool
|
|
1820
|
+
Return camera information as string descriptions instead of
|
|
1821
|
+
`CameraInfo` objects. This provides a more compact way of
|
|
1822
|
+
representing camera formats in a (reasonably) human-readable format.
|
|
1823
|
+
|
|
1824
|
+
Returns
|
|
1825
|
+
-------
|
|
1826
|
+
dict or list
|
|
1827
|
+
Mapping (`dict`) of camera descriptions, where keys are camera names
|
|
1828
|
+
(`str`) and values are a `list` of format description strings
|
|
1829
|
+
associated with the camera. If `collapse=True`, all descriptions
|
|
1830
|
+
will be returned in a single flat list. This might be more useful
|
|
1831
|
+
for specifying camera formats from a single GUI list control.
|
|
1832
|
+
|
|
1833
|
+
"""
|
|
1834
|
+
return getCameraDescriptions(collapse=collapse)
|
|
1835
|
+
|
|
1836
|
+
@property
|
|
1837
|
+
def device(self):
|
|
1838
|
+
"""Camera to use (`str` or `None`).
|
|
1839
|
+
|
|
1840
|
+
String specifying the name of the camera to open a stream with. This
|
|
1841
|
+
must be set prior to calling `start()`. If the name is not valid, an
|
|
1842
|
+
error will be raised when `start()` is called.
|
|
1843
|
+
|
|
1844
|
+
"""
|
|
1845
|
+
return self._device
|
|
1846
|
+
|
|
1847
|
+
@device.setter
|
|
1848
|
+
def device(self, value):
|
|
1849
|
+
if value in (None, "None", "none", "Default", "default"):
|
|
1850
|
+
value = 0
|
|
1851
|
+
|
|
1852
|
+
self._device = value
|
|
1853
|
+
|
|
1854
|
+
@property
|
|
1855
|
+
def _hasPlayer(self):
|
|
1856
|
+
"""`True` if we have an active media player instance.
|
|
1857
|
+
"""
|
|
1858
|
+
# deprecated - remove in future versions and use `isStarted` instead
|
|
1859
|
+
return self.isStarted
|
|
1860
|
+
|
|
1861
|
+
@property
|
|
1862
|
+
def mic(self):
|
|
1863
|
+
"""Microphone to record audio samples from during recording
|
|
1864
|
+
(:class:`~psychopy.sound.microphone.Microphone` or `None`).
|
|
1865
|
+
|
|
1866
|
+
If `None`, no audio will be recorded. Cannot be set after opening a
|
|
1867
|
+
camera stream.
|
|
1868
|
+
"""
|
|
1869
|
+
return self._mic
|
|
1870
|
+
|
|
1871
|
+
@mic.setter
|
|
1872
|
+
def mic(self, value):
|
|
1873
|
+
if self.isStarted:
|
|
1874
|
+
raise CameraError("Cannot set microphone after starting camera.")
|
|
1875
|
+
|
|
1876
|
+
self._mic = value
|
|
1877
|
+
|
|
1878
|
+
@property
|
|
1879
|
+
def _hasAudio(self):
|
|
1880
|
+
"""`True` if we have a microphone object for audio recording.
|
|
1881
|
+
"""
|
|
1882
|
+
return self._mic is not None
|
|
1883
|
+
|
|
1884
|
+
@property
|
|
1885
|
+
def win(self):
|
|
1886
|
+
"""Window which frames are being presented (`psychopy.visual.Window` or
|
|
1887
|
+
`None`).
|
|
1888
|
+
"""
|
|
1889
|
+
return self._win
|
|
1890
|
+
|
|
1891
|
+
@win.setter
|
|
1892
|
+
def win(self, value):
|
|
1893
|
+
self._win = value
|
|
1894
|
+
|
|
1895
|
+
@property
|
|
1896
|
+
def frameCount(self):
|
|
1897
|
+
"""Number of frames captured in the present recording (`int`).
|
|
1898
|
+
"""
|
|
1899
|
+
if not self._isRecording:
|
|
1900
|
+
return 0
|
|
1901
|
+
|
|
1902
|
+
totalFramesBuffered = (
|
|
1903
|
+
len(self._captureFrames) + self._captureThread.framesWaiting)
|
|
1904
|
+
|
|
1905
|
+
return totalFramesBuffered
|
|
1906
|
+
|
|
1907
|
+
@property
|
|
1908
|
+
def keepFrames(self):
|
|
1909
|
+
"""Number of frames to keep in memory for the camera stream (`int`).
|
|
1910
|
+
"""
|
|
1911
|
+
return self._keepFrames
|
|
1912
|
+
|
|
1913
|
+
@keepFrames.setter
|
|
1914
|
+
def keepFrames(self, value):
|
|
1915
|
+
if value < 0:
|
|
1916
|
+
raise ValueError("`keepFrames` must be a non-negative integer.")
|
|
1917
|
+
|
|
1918
|
+
self._keepFrames = value
|
|
1919
|
+
oldFrames = self._frameStore
|
|
1920
|
+
oldStoreSize = len(self._frameStore)
|
|
1921
|
+
|
|
1922
|
+
if oldStoreSize == self._keepFrames:
|
|
1923
|
+
# nothing to do, size is the same
|
|
1924
|
+
return
|
|
1925
|
+
|
|
1926
|
+
# change the size of the frame store
|
|
1927
|
+
self._frameStore = collections.deque(maxlen=self._keepFrames)
|
|
1928
|
+
|
|
1929
|
+
if oldStoreSize > self._keepFrames:
|
|
1930
|
+
logging.warning(
|
|
1931
|
+
"Reducing `keepFrames` from {} to {} will discard the oldest "
|
|
1932
|
+
"frames in the buffer.".format(oldStoreSize, self._keepFrames))
|
|
1933
|
+
|
|
1934
|
+
# add back frames
|
|
1935
|
+
if oldStoreSize > 0:
|
|
1936
|
+
# copy the last `keepFrames` frames to the new store
|
|
1937
|
+
for i in range(oldStoreSize - self._keepFrames, oldStoreSize):
|
|
1938
|
+
self._frameStore.append(oldFrames[i])
|
|
1939
|
+
|
|
1940
|
+
@property
|
|
1941
|
+
def recordingTime(self):
|
|
1942
|
+
"""Current recording timestamp (`float`).
|
|
1943
|
+
|
|
1944
|
+
This returns the timestamp of the last frame captured in the recording.
|
|
1945
|
+
|
|
1946
|
+
This value increases monotonically from the last `record()` call. It
|
|
1947
|
+
will reset once `stop()` is called. This value is invalid outside
|
|
1948
|
+
`record()` and `stop()` calls.
|
|
1949
|
+
|
|
1950
|
+
"""
|
|
1951
|
+
return self.frameCount * self._capture.frameInterval
|
|
1952
|
+
|
|
1953
|
+
@property
|
|
1954
|
+
def recordingBytes(self):
|
|
1955
|
+
"""Current size of the recording in bytes (`int`).
|
|
1956
|
+
"""
|
|
1957
|
+
if not self._isRecording:
|
|
1958
|
+
return 0
|
|
1959
|
+
|
|
1960
|
+
return -1
|
|
1961
|
+
|
|
1962
|
+
def _assertMediaPlayer(self):
|
|
1963
|
+
"""Assert that we have a media player instance open.
|
|
1964
|
+
|
|
1965
|
+
This will raise a `RuntimeError` if there is no player open. Use this
|
|
1966
|
+
function to ensure that a player is present before running subsequent
|
|
1967
|
+
code.
|
|
1968
|
+
"""
|
|
1969
|
+
if self._capture is not None:
|
|
1970
|
+
return
|
|
1971
|
+
|
|
1972
|
+
raise PlayerNotAvailableError('Media player not initialized.')
|
|
1973
|
+
|
|
1974
|
+
@property
|
|
1975
|
+
def isReady(self):
|
|
1976
|
+
"""`True` if the video and audio capture devices are in a ready state
|
|
1977
|
+
(`bool`).
|
|
1978
|
+
|
|
1979
|
+
When this is `True`, the audio and video streams are properly started.
|
|
1980
|
+
|
|
1981
|
+
"""
|
|
1982
|
+
return self._audioReady and self._videoReady
|
|
1983
|
+
|
|
1984
|
+
def open(self):
|
|
1985
|
+
"""Open the camera stream and begin decoding frames (if available).
|
|
1986
|
+
|
|
1987
|
+
This function returns when the camera is ready to start getting
|
|
1988
|
+
frames.
|
|
1989
|
+
|
|
1990
|
+
Call `record()` to start recording frames to memory. Captured frames
|
|
1991
|
+
came be saved to disk using `save()`.
|
|
1992
|
+
|
|
1993
|
+
"""
|
|
1994
|
+
if self._hasPlayer:
|
|
1995
|
+
raise RuntimeError('Cannot open `MediaPlayer`, already opened.')
|
|
1996
|
+
|
|
1997
|
+
# Camera interface to use, these are hard coded but support for each is
|
|
1998
|
+
# provided by an extension.
|
|
1999
|
+
# desc = self._cameraInfo.description()
|
|
2000
|
+
|
|
2001
|
+
self._capture.open()
|
|
2002
|
+
|
|
2003
|
+
if self.win is not None:
|
|
2004
|
+
# if we have a window, setup texture buffers for displaying
|
|
2005
|
+
self._setupTextureBuffers()
|
|
2006
|
+
|
|
2007
|
+
# open the mic when the camera opens
|
|
2008
|
+
if hasattr(self.mic, "open"):
|
|
2009
|
+
self.mic.open()
|
|
2010
|
+
|
|
2011
|
+
self._isStarted = True
|
|
2012
|
+
|
|
2013
|
+
def record(self, clearLastRecording=True, waitForStart=False):
|
|
2014
|
+
"""Start recording frames.
|
|
2015
|
+
|
|
2016
|
+
This function will start recording frames and audio (if available). The
|
|
2017
|
+
value of `lastFrame` will be updated as new frames arrive and the
|
|
2018
|
+
`frameCount` will increase. You can access image data for the most
|
|
2019
|
+
recent frame to be captured using `lastFrame`.
|
|
2020
|
+
|
|
2021
|
+
If this is called before `open()` the camera stream will be opened
|
|
2022
|
+
automatically. This is not recommended as it may incur a longer than
|
|
2023
|
+
expected delay in the recording start time.
|
|
2024
|
+
|
|
2025
|
+
Warnings
|
|
2026
|
+
--------
|
|
2027
|
+
If a recording has been previously made without calling `save()` it will
|
|
2028
|
+
be discarded if `record()` is called again unless
|
|
2029
|
+
`clearLastRecording=False`.
|
|
2030
|
+
|
|
2031
|
+
Parameters
|
|
2032
|
+
----------
|
|
2033
|
+
clearLastRecording : bool
|
|
2034
|
+
Clear the frame buffer before starting the recording. If `True`,
|
|
2035
|
+
the frame buffer will be cleared before starting the recording. If
|
|
2036
|
+
`False`, the frame buffer will be kept and new frames will be added
|
|
2037
|
+
to the buffer. Default is `True`. This is deprecated and will
|
|
2038
|
+
eventually be removed in a future version of PsychoPy. The recording
|
|
2039
|
+
is always cleared when `record()` is called, so this parameter is
|
|
2040
|
+
ignored.
|
|
2041
|
+
waitForStart : bool
|
|
2042
|
+
Capture video only when the camera and microphone are ready. This
|
|
2043
|
+
will result in a longer delay before the recording starts, but will
|
|
2044
|
+
ensure the microphone is actually recording valid samples. In some
|
|
2045
|
+
cases this will result in a delay of up to 1 second before the
|
|
2046
|
+
recording starts.
|
|
2047
|
+
|
|
2048
|
+
"""
|
|
2049
|
+
if self.isNotStarted:
|
|
2050
|
+
self.open() # open the camera stream if we call record() first
|
|
2051
|
+
logging.warning(
|
|
2052
|
+
"Called `Camera.record()` before opening the camera stream, "
|
|
2053
|
+
"opening now. This is not recommended as it may incur a longer "
|
|
2054
|
+
"than expected delay in the recording start time."
|
|
2055
|
+
)
|
|
2056
|
+
|
|
2057
|
+
if self._isRecording:
|
|
2058
|
+
logging.warning(
|
|
2059
|
+
"Called `Camera.record()` while already recording, stopping "
|
|
2060
|
+
"the previous recording first."
|
|
2061
|
+
)
|
|
2062
|
+
self.stop()
|
|
2063
|
+
|
|
2064
|
+
# clear previous frames
|
|
2065
|
+
if clearLastRecording:
|
|
2066
|
+
self._frameStore.clear() # clear frames from last recording
|
|
2067
|
+
|
|
2068
|
+
self._capture._clearFrameStore()
|
|
2069
|
+
|
|
2070
|
+
# reset the movie writer
|
|
2071
|
+
self._openMovieFileWriter()
|
|
2072
|
+
|
|
2073
|
+
# reset audio flags
|
|
2074
|
+
self._audioReady = self._videoReady = False
|
|
2075
|
+
|
|
2076
|
+
# reset the last frame
|
|
2077
|
+
self._lastFrame = None
|
|
2078
|
+
|
|
2079
|
+
# start camera recording
|
|
2080
|
+
self._absVideoRecStartTime = self._capture.record()
|
|
2081
|
+
|
|
2082
|
+
# start microphone recording
|
|
2083
|
+
if self._usageMode == CAMERA_MODE_VIDEO:
|
|
2084
|
+
if self.mic is not None:
|
|
2085
|
+
audioStartTime = self.mic.start(
|
|
2086
|
+
waitForStart=int(waitForStart), # wait until the mic is ready
|
|
2087
|
+
)
|
|
2088
|
+
self._absAudioRecStartTime = self._capture.streamTime
|
|
2089
|
+
if waitForStart:
|
|
2090
|
+
self._absAudioActualRecStartTime = audioStartTime # time it will be ready
|
|
2091
|
+
else:
|
|
2092
|
+
self._absAudioActualRecStartTime = self._absAudioRecStartTime
|
|
2093
|
+
|
|
2094
|
+
self._isRecording = True # set recording flag
|
|
2095
|
+
# do an initial poll to avoid frame dropping
|
|
2096
|
+
self.update()
|
|
2097
|
+
# mark that there's unsaved footage
|
|
2098
|
+
self._unsaved = True
|
|
2099
|
+
|
|
2100
|
+
def start(self, waitForStart=True):
|
|
2101
|
+
"""Start the camera stream.
|
|
2102
|
+
|
|
2103
|
+
This will start the camera stream and begin decoding frames. If the
|
|
2104
|
+
camera is already started, this will do nothing. Use `record()` to start
|
|
2105
|
+
recording frames to memory.
|
|
2106
|
+
|
|
2107
|
+
"""
|
|
2108
|
+
return self.record(clearLastRecording=False, waitForStart=waitForStart)
|
|
2109
|
+
|
|
2110
|
+
def stop(self):
|
|
2111
|
+
"""Stop recording frames and audio (if available).
|
|
2112
|
+
"""
|
|
2113
|
+
# poll any remaining frames and stop
|
|
2114
|
+
self.update()
|
|
2115
|
+
|
|
2116
|
+
# stop the camera stream
|
|
2117
|
+
self._absVideoRecStopTime = self._capture.stop()
|
|
2118
|
+
|
|
2119
|
+
# stop audio recording if we have a microphone
|
|
2120
|
+
if self.hasMic and not self.mic._stream._closed:
|
|
2121
|
+
_, overflows = self.mic.poll()
|
|
2122
|
+
|
|
2123
|
+
if overflows > 0:
|
|
2124
|
+
logging.warning(
|
|
2125
|
+
"Audio recording overflowed {} times before stopping, "
|
|
2126
|
+
"some audio samples may be lost.".format(overflows))
|
|
2127
|
+
audioStopTime, _, _, _ = self.mic.stop(
|
|
2128
|
+
blockUntilStopped=0)
|
|
2129
|
+
|
|
2130
|
+
self._audioReady = self._videoReady = False # reset camera ready flags
|
|
2131
|
+
self._isRecording = False
|
|
2132
|
+
|
|
2133
|
+
self._closeMovieFileWriter()
|
|
2134
|
+
|
|
2135
|
+
def close(self):
|
|
2136
|
+
"""Close the camera.
|
|
2137
|
+
|
|
2138
|
+
This will close the camera stream and free up any resources used by the
|
|
2139
|
+
device. If the camera is currently recording, this will stop the
|
|
2140
|
+
recording, but will not discard any frames. You may still call `save()`
|
|
2141
|
+
to save the frames to disk.
|
|
2142
|
+
|
|
2143
|
+
"""
|
|
2144
|
+
self._closeMovieFileWriter()
|
|
2145
|
+
|
|
2146
|
+
self._capture.close() # close the camera stream
|
|
2147
|
+
self._capture = None # clear the capture object
|
|
2148
|
+
|
|
2149
|
+
if self.mic is not None:
|
|
2150
|
+
self.mic.close()
|
|
2151
|
+
|
|
2152
|
+
self._isStarted = False
|
|
2153
|
+
|
|
2154
|
+
def _mergeAudioVideoTracks(self, videoTrackFile, audioTrackFile,
|
|
2155
|
+
filename, writerOpts=None):
|
|
2156
|
+
"""Use FFMPEG to merge audio and video tracks into a single file.
|
|
2157
|
+
|
|
2158
|
+
Parameters
|
|
2159
|
+
----------
|
|
2160
|
+
videoTrackFile : str
|
|
2161
|
+
Path to the video track file to merge.
|
|
2162
|
+
audioTrackFile : str
|
|
2163
|
+
Path to the audio track file to merge.
|
|
2164
|
+
filename : str
|
|
2165
|
+
Path to the output file to save the merged audio and video tracks.
|
|
2166
|
+
writerOpts : dict or None
|
|
2167
|
+
Options to pass to the movie writer. If `None`, default options
|
|
2168
|
+
will be used. This is useful for specifying the codec, bitrate,
|
|
2169
|
+
etc. for the output file.
|
|
2170
|
+
|
|
2171
|
+
Returns
|
|
2172
|
+
-------
|
|
2173
|
+
str
|
|
2174
|
+
Path to the output file with merged audio and video tracks.
|
|
2175
|
+
|
|
2176
|
+
"""
|
|
2177
|
+
import subprocess as sp
|
|
2178
|
+
|
|
2179
|
+
# check if the video and audio track files exist
|
|
2180
|
+
if not os.path.exists(videoTrackFile):
|
|
2181
|
+
raise FileNotFoundError(
|
|
2182
|
+
"Video track file `{}` does not exist.".format(videoTrackFile))
|
|
2183
|
+
if not os.path.exists(audioTrackFile):
|
|
2184
|
+
raise FileNotFoundError(
|
|
2185
|
+
"Audio track file `{}` does not exist.".format(audioTrackFile))
|
|
2186
|
+
|
|
2187
|
+
# check if the output file already exists
|
|
2188
|
+
if os.path.exists(filename):
|
|
2189
|
+
logging.warning(
|
|
2190
|
+
"Output file `{}` already exists, it will be overwritten.".format(filename))
|
|
2191
|
+
os.remove(filename)
|
|
2192
|
+
|
|
2193
|
+
# build the command to merge audio and video tracks
|
|
2194
|
+
cmd = [
|
|
2195
|
+
'ffmpeg',
|
|
2196
|
+
'-loglevel', 'error', # suppress output except errors
|
|
2197
|
+
'-nostdin', # do not read from stdin
|
|
2198
|
+
'-y', # overwrite output file if it exists
|
|
2199
|
+
'-i', videoTrackFile, # input video track
|
|
2200
|
+
'-i', audioTrackFile, # input audio track
|
|
2201
|
+
'-c:v', 'copy', # copy video codec
|
|
2202
|
+
'-c:a', 'aac', # use AAC for audio codec
|
|
2203
|
+
'-strict', 'experimental', # allow experimental codecs
|
|
2204
|
+
'-threads', 'auto', # use all available threads
|
|
2205
|
+
'-shortest' # stop when the shortest input ends
|
|
2206
|
+
]
|
|
2207
|
+
# add output file
|
|
2208
|
+
cmd.append(filename)
|
|
2209
|
+
|
|
2210
|
+
# apply any writer options if provided
|
|
2211
|
+
if writerOpts is not None:
|
|
2212
|
+
for key, value in writerOpts.items():
|
|
2213
|
+
if isinstance(value, str):
|
|
2214
|
+
cmd.append('-' + key)
|
|
2215
|
+
cmd.append(value)
|
|
2216
|
+
elif isinstance(value, bool) and value:
|
|
2217
|
+
cmd.append('-' + key)
|
|
2218
|
+
elif isinstance(value, (int, float)):
|
|
2219
|
+
cmd.append('-' + key)
|
|
2220
|
+
cmd.append(str(value))
|
|
2221
|
+
|
|
2222
|
+
logging.debug(
|
|
2223
|
+
"Merging audio and video tracks with command: {}".format(' '.join(cmd))
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
# run the command to merge audio and video tracks
|
|
2227
|
+
try:
|
|
2228
|
+
proc = sp.Popen(
|
|
2229
|
+
cmd,
|
|
2230
|
+
stdout=sp.PIPE,
|
|
2231
|
+
stderr=sp.PIPE,
|
|
2232
|
+
stdin=sp.DEVNULL if hasattr(sp, 'DEVNULL') else None,
|
|
2233
|
+
universal_newlines=True, # use text mode for output
|
|
2234
|
+
text=True
|
|
2235
|
+
)
|
|
2236
|
+
proc.wait() # wait for the process to finish
|
|
2237
|
+
if proc.returncode != 0:
|
|
2238
|
+
logging.error(
|
|
2239
|
+
"FFMPEG returned non-zero exit code {} for command: {}".format(
|
|
2240
|
+
proc.returncode, cmd
|
|
2241
|
+
)
|
|
2242
|
+
)
|
|
2243
|
+
# wait for the process to finish
|
|
2244
|
+
except sp.CalledProcessError as e:
|
|
2245
|
+
logging.error(
|
|
2246
|
+
"Failed to merge audio and video tracks: {}".format(e))
|
|
2247
|
+
return None
|
|
2248
|
+
|
|
2249
|
+
logging.info(
|
|
2250
|
+
"Merged audio and video tracks into `{}`".format(filename))
|
|
2251
|
+
|
|
2252
|
+
return filename
|
|
2253
|
+
|
|
2254
|
+
def save(self, filename, useThreads=True, mergeAudio=True, writerOpts=None):
|
|
2255
|
+
"""Save the last recording to file.
|
|
2256
|
+
|
|
2257
|
+
This will write frames to `filename` acquired since the last call of
|
|
2258
|
+
`record()` and subsequent `stop()`. If `record()` is called again before
|
|
2259
|
+
`save()`, the previous recording will be deleted and lost.
|
|
2260
|
+
|
|
2261
|
+
This is a slow operation and will block for some time depending on the
|
|
2262
|
+
length of the video. This can be sped up by setting `useThreads=True` if
|
|
2263
|
+
supported.
|
|
2264
|
+
|
|
2265
|
+
Parameters
|
|
2266
|
+
----------
|
|
2267
|
+
filename : str
|
|
2268
|
+
File to save the resulting video to, should include the extension.
|
|
2269
|
+
useThreads : bool
|
|
2270
|
+
Use threading where possible to speed up the saving process.
|
|
2271
|
+
mergeAudio : bool
|
|
2272
|
+
Merge the audio track from the microphone with the video into a
|
|
2273
|
+
single file if `True`. If `False`, the audio track will be saved
|
|
2274
|
+
to a separate file with the same name as `filename`, but with a
|
|
2275
|
+
`.wav` extension. This is useful if you want to process the audio
|
|
2276
|
+
track separately, or merge it with the video later on as the process
|
|
2277
|
+
is computationally expensive and memory consuming. Default is
|
|
2278
|
+
`True`.
|
|
2279
|
+
writerOpts : dict or None
|
|
2280
|
+
Options to pass to the movie writer. If `None`, default options
|
|
2281
|
+
will be used.
|
|
2282
|
+
|
|
2283
|
+
"""
|
|
2284
|
+
# stop if still recording
|
|
2285
|
+
if self._isRecording:
|
|
2286
|
+
self.stop()
|
|
2287
|
+
logging.warning(
|
|
2288
|
+
"Called `Camera.save()` while recording, stopping the "
|
|
2289
|
+
"recording first."
|
|
2290
|
+
)
|
|
2291
|
+
|
|
2292
|
+
# if there's nothing to unsaved, do nothing
|
|
2293
|
+
if not self._unsaved:
|
|
2294
|
+
return
|
|
2295
|
+
|
|
2296
|
+
# check if we have an active movie writer
|
|
2297
|
+
if self._movieWriter is not None:
|
|
2298
|
+
self._movieWriter.close() # close the movie writer
|
|
2299
|
+
|
|
2300
|
+
# check if we have a temp movie file
|
|
2301
|
+
videoTrackFile = self._tempVideoFile
|
|
2302
|
+
|
|
2303
|
+
# write the temporary audio track to file if we have one
|
|
2304
|
+
tStart = time.time() # start time for the operation
|
|
2305
|
+
if self.mic is not None:
|
|
2306
|
+
audioTrack = self.mic.getRecording()
|
|
2307
|
+
|
|
2308
|
+
if audioTrack is not None:
|
|
2309
|
+
logging.debug(
|
|
2310
|
+
"Saving audio track to file `{}`...".format(filename))
|
|
2311
|
+
|
|
2312
|
+
# trim off samples before the recording started
|
|
2313
|
+
audioTrack = audioTrack.trimmed(
|
|
2314
|
+
direction='start',
|
|
2315
|
+
duration=self._absAudioRecStartPos,
|
|
2316
|
+
units='samples')
|
|
2317
|
+
|
|
2318
|
+
if mergeAudio:
|
|
2319
|
+
logging.debug("Merging audio track with video track...")
|
|
2320
|
+
# save it to a temp file
|
|
2321
|
+
import tempfile
|
|
2322
|
+
tempAudioFile = tempfile.NamedTemporaryFile(
|
|
2323
|
+
suffix='.wav', delete=False)
|
|
2324
|
+
audioTrackFile = tempAudioFile.name
|
|
2325
|
+
tempAudioFile.close() # close the file so we can use it later
|
|
2326
|
+
audioTrack.save(audioTrackFile)
|
|
2327
|
+
|
|
2328
|
+
# # composite audio a video tracks using MoviePy (huge thanks to
|
|
2329
|
+
# # that team)
|
|
2330
|
+
# from moviepy.video.io.VideoFileClip import VideoFileClip
|
|
2331
|
+
# from moviepy.audio.io.AudioFileClip import AudioFileClip
|
|
2332
|
+
# from moviepy.audio.AudioClip import CompositeAudioClip
|
|
2333
|
+
|
|
2334
|
+
# videoClip = VideoFileClip(videoTrackFile)
|
|
2335
|
+
# audioClip = AudioFileClip(audioTrackFile)
|
|
2336
|
+
# videoClip.audio = CompositeAudioClip([audioClip])
|
|
2337
|
+
|
|
2338
|
+
# # default options for the writer, needed or we can crash
|
|
2339
|
+
# moviePyOpts = {
|
|
2340
|
+
# 'logger': None
|
|
2341
|
+
# }
|
|
2342
|
+
|
|
2343
|
+
# if writerOpts is not None: # make empty dict if not provided
|
|
2344
|
+
# moviePyOpts.update(writerOpts)
|
|
2345
|
+
|
|
2346
|
+
# # transcode with the format the user wants
|
|
2347
|
+
# videoClip.write_videofile(
|
|
2348
|
+
# filename,
|
|
2349
|
+
# **moviePyOpts) # expand out options
|
|
2350
|
+
|
|
2351
|
+
# videoClip.close() # close the video clip
|
|
2352
|
+
# audioClip.close()
|
|
2353
|
+
|
|
2354
|
+
# merge audio and video tracks using FFMPEG
|
|
2355
|
+
mergedVideo = self._mergeAudioVideoTracks(
|
|
2356
|
+
videoTrackFile,
|
|
2357
|
+
audioTrackFile,
|
|
2358
|
+
filename,
|
|
2359
|
+
writerOpts=writerOpts)
|
|
2360
|
+
|
|
2361
|
+
os.remove(audioTrackFile) # remove the temp file
|
|
2362
|
+
|
|
2363
|
+
else:
|
|
2364
|
+
tAudioStart = time.time() # start time for audio saving
|
|
2365
|
+
# just save the audio file seperatley
|
|
2366
|
+
# check if the filename has an extension
|
|
2367
|
+
if '.' not in filename:
|
|
2368
|
+
audioTrackFile = filename + '.wav'
|
|
2369
|
+
else:
|
|
2370
|
+
# if it has an extension, use the same name but with .wav
|
|
2371
|
+
# extension
|
|
2372
|
+
rootName, _ = os.path.splitext(filename)
|
|
2373
|
+
audioTrackFile = rootName + '.wav'
|
|
2374
|
+
|
|
2375
|
+
audioTrack.save(audioTrackFile)
|
|
2376
|
+
|
|
2377
|
+
logging.info(
|
|
2378
|
+
"Saved recorded audio track to `{}` (took {:.6f} seconds)".format(
|
|
2379
|
+
audioTrackFile, time.time() - tAudioStart))
|
|
2380
|
+
|
|
2381
|
+
# just copy the video from the temp file to the final file
|
|
2382
|
+
import shutil
|
|
2383
|
+
shutil.copyfile(videoTrackFile, filename)
|
|
2384
|
+
|
|
2385
|
+
else:
|
|
2386
|
+
# just copy the video file to the destination
|
|
2387
|
+
import shutil
|
|
2388
|
+
shutil.copyfile(videoTrackFile, filename)
|
|
2389
|
+
|
|
2390
|
+
os.remove(videoTrackFile) # remove the temp file
|
|
2391
|
+
|
|
2392
|
+
logging.info(
|
|
2393
|
+
"Saved recorded video to `{}` (took {:.6f} seconds)".format(
|
|
2394
|
+
filename, time.time() - tStart))
|
|
2395
|
+
|
|
2396
|
+
self._frameStore.clear() # clear the frame store
|
|
2397
|
+
# mark that there's no longer unsaved footage
|
|
2398
|
+
self._unsaved = False
|
|
2399
|
+
|
|
2400
|
+
self._lastVideoFile = filename # store the last video file saved
|
|
2401
|
+
|
|
2402
|
+
return self._lastVideoFile
|
|
2403
|
+
|
|
2404
|
+
def _upload(self):
|
|
2405
|
+
"""Upload video file to an online repository. Not implemented locally,
|
|
2406
|
+
needed for auto translate to JS.
|
|
2407
|
+
"""
|
|
2408
|
+
pass # NOP
|
|
2409
|
+
|
|
2410
|
+
def _download(self):
|
|
2411
|
+
"""Download video file to an online repository. Not implemented locally,
|
|
2412
|
+
needed for auto translate to JS.
|
|
2413
|
+
"""
|
|
2414
|
+
pass # NOP
|
|
2415
|
+
|
|
2416
|
+
@property
|
|
2417
|
+
def lastClip(self):
|
|
2418
|
+
"""File path to the last recording (`str` or `None`).
|
|
2419
|
+
|
|
2420
|
+
This value is only valid if a previous recording has been saved
|
|
2421
|
+
successfully (`save()` was called), otherwise it will be set to `None`.
|
|
2422
|
+
|
|
2423
|
+
"""
|
|
2424
|
+
return self.getLastClip()
|
|
2425
|
+
|
|
2426
|
+
def getLastClip(self):
|
|
2427
|
+
"""File path to the last saved recording.
|
|
2428
|
+
|
|
2429
|
+
This value is only valid if a previous recording has been saved to disk
|
|
2430
|
+
(`save()` was called).
|
|
2431
|
+
|
|
2432
|
+
Returns
|
|
2433
|
+
-------
|
|
2434
|
+
str or None
|
|
2435
|
+
Path to the file the most recent call to `save()` created. Returns
|
|
2436
|
+
`None` if no file is ready.
|
|
2437
|
+
|
|
2438
|
+
"""
|
|
2439
|
+
return self._lastVideoFile
|
|
2440
|
+
|
|
2441
|
+
@property
|
|
2442
|
+
def lastFrame(self):
|
|
2443
|
+
"""Most recent frame pulled from the camera (`VideoFrame`) since the
|
|
2444
|
+
last call of `getVideoFrame`.
|
|
2445
|
+
"""
|
|
2446
|
+
return self._lastFrame
|
|
2447
|
+
|
|
2448
|
+
@property
|
|
2449
|
+
def frameCount(self):
|
|
2450
|
+
"""Total number of frames captured in the current recording (`int`).
|
|
2451
|
+
|
|
2452
|
+
This is the total number of frames captured since the last call to
|
|
2453
|
+
`record()`. This value is reset when `record()` is called again.
|
|
2454
|
+
|
|
2455
|
+
"""
|
|
2456
|
+
return self._frameCount
|
|
2457
|
+
|
|
2458
|
+
@property
|
|
2459
|
+
def hasMic(self):
|
|
2460
|
+
"""`True` if the camera has a microphone attached (`bool`).
|
|
2461
|
+
|
|
2462
|
+
This is `True` if the camera has a microphone attached and is ready to
|
|
2463
|
+
record audio. If the camera does not have a microphone, this will be
|
|
2464
|
+
`False`.
|
|
2465
|
+
|
|
2466
|
+
"""
|
|
2467
|
+
return self.mic is not None
|
|
2468
|
+
|
|
2469
|
+
def _convertFrameToRGBFFPyPlayer(self, frame):
|
|
2470
|
+
"""Convert a frame to RGB format.
|
|
2471
|
+
|
|
2472
|
+
This function converts a frame to RGB format. The frame is returned as
|
|
2473
|
+
a Numpy array. The resulting array will be in the correct format to
|
|
2474
|
+
upload to OpenGL as a texture.
|
|
2475
|
+
|
|
2476
|
+
Parameters
|
|
2477
|
+
----------
|
|
2478
|
+
frame : FFPyPlayer frame
|
|
2479
|
+
The frame to convert.
|
|
2480
|
+
|
|
2481
|
+
Returns
|
|
2482
|
+
-------
|
|
2483
|
+
numpy.ndarray
|
|
2484
|
+
The converted frame in RGB format.
|
|
2485
|
+
|
|
2486
|
+
"""
|
|
2487
|
+
from ffpyplayer.pic import SWScale
|
|
2488
|
+
if frame.get_pixel_format() == 'rgb24': # already converted
|
|
2489
|
+
return frame
|
|
2490
|
+
|
|
2491
|
+
rgbImg = SWScale(
|
|
2492
|
+
self._metadata.size[0], self._metadata.size[1], # width, height
|
|
2493
|
+
frame.get_pixel_format(),
|
|
2494
|
+
ofmt='rgb24').scale(frame)
|
|
2495
|
+
|
|
2496
|
+
return rgbImg
|
|
2497
|
+
|
|
2498
|
+
def update(self):
|
|
2499
|
+
"""Acquire the newest data from the camera and audio streams.
|
|
2500
|
+
|
|
2501
|
+
This must be called periodically to ensure that stream buffers are
|
|
2502
|
+
flushed before they overflow to prevent data loss. Furthermore,
|
|
2503
|
+
calling this too infrequently may result also result in more frames
|
|
2504
|
+
needing to be processed at once, which may result in performance issues.
|
|
2505
|
+
|
|
2506
|
+
Returns
|
|
2507
|
+
-------
|
|
2508
|
+
int
|
|
2509
|
+
Number of frames captured since the last call to this method. This
|
|
2510
|
+
will be `0` if no new frames were captured since the last call,
|
|
2511
|
+
indicating that the poll function is getting called too
|
|
2512
|
+
frequently or that the camera is not producing new frames (i.e.
|
|
2513
|
+
paused or closed). If `-1` is returned, it indicates that the
|
|
2514
|
+
either or both the camera and microphone are not in a ready state
|
|
2515
|
+
albiet both interfaces are open. This can happen if `update()` is
|
|
2516
|
+
called very shortly after `record()`.
|
|
2517
|
+
|
|
2518
|
+
Examples
|
|
2519
|
+
--------
|
|
2520
|
+
Capture camera frames in a loop::
|
|
2521
|
+
|
|
2522
|
+
while cam.recordingTime < 10.0: # record for 10 seconds
|
|
2523
|
+
numFrames = cam.update() # update the camera stream
|
|
2524
|
+
if numFrames > 0:
|
|
2525
|
+
frame = cam.getVideoFrame() # get the most recent frame
|
|
2526
|
+
# do something with the frame, e.g. display it
|
|
2527
|
+
else:
|
|
2528
|
+
# return last frame or placeholder frame if nothing new
|
|
2529
|
+
|
|
2530
|
+
"""
|
|
2531
|
+
# poll camera for new frames
|
|
2532
|
+
newFrames = self._capture.getFrames() # get new frames from the camera
|
|
2533
|
+
|
|
2534
|
+
if not self._videoReady and newFrames:
|
|
2535
|
+
# if we have new frames, we can set the video ready flag
|
|
2536
|
+
self._videoReady = True
|
|
2537
|
+
|
|
2538
|
+
if self.hasMic and not self.mic._stream._closed:
|
|
2539
|
+
# poll the microphone for audio samples
|
|
2540
|
+
audioPos, overflows = self.mic.poll()
|
|
2541
|
+
|
|
2542
|
+
if (not self._audioReady) and self._videoReady:
|
|
2543
|
+
nNewFrames = len(newFrames)
|
|
2544
|
+
# determine which video frame the audio starts at that we aquired
|
|
2545
|
+
keepFrames = []
|
|
2546
|
+
for i, frame in enumerate(newFrames):
|
|
2547
|
+
_, _, streamTime = frame
|
|
2548
|
+
if streamTime >= self._absAudioActualRecStartTime:
|
|
2549
|
+
keepFrames.append(frame)
|
|
2550
|
+
|
|
2551
|
+
# If we arrived at the audio start time and there is a video
|
|
2552
|
+
# frame captured after that, we can compute the exact position
|
|
2553
|
+
# of the sample in the audio track that corresponds to that
|
|
2554
|
+
# frame. This will allow us to align the audio and video streams
|
|
2555
|
+
# when saving the video file.
|
|
2556
|
+
if keepFrames:
|
|
2557
|
+
_, _, streamTime = keepFrames[0]
|
|
2558
|
+
|
|
2559
|
+
# delta between the first video frame's capture timestamp
|
|
2560
|
+
# and the time the mic reported itself as ready. Used to
|
|
2561
|
+
# align the audio and video streams
|
|
2562
|
+
frameSyncFudge = (
|
|
2563
|
+
streamTime - self._absAudioActualRecStartTime)
|
|
2564
|
+
|
|
2565
|
+
# compute exact time the first audio sample was recorded
|
|
2566
|
+
# from the audio position and actual recording start time
|
|
2567
|
+
absFirstAudioSampleTime = \
|
|
2568
|
+
self._absAudioActualRecStartTime - (
|
|
2569
|
+
audioPos / self.mic.sampleRateHz)
|
|
2570
|
+
|
|
2571
|
+
# compute how many samples we will discard from the audio
|
|
2572
|
+
# track to align it with the video stream
|
|
2573
|
+
self._absAudioRecStartPos = \
|
|
2574
|
+
((streamTime - absFirstAudioSampleTime) + \
|
|
2575
|
+
frameSyncFudge + self._latencyBias) * self.mic.sampleRateHz
|
|
2576
|
+
self._absAudioRecStartPos = int(self._absAudioRecStartPos)
|
|
2577
|
+
|
|
2578
|
+
# convert to samples
|
|
2579
|
+
self._audioReady = True
|
|
2580
|
+
|
|
2581
|
+
newFrames = keepFrames # keep only frames after the audio start time
|
|
2582
|
+
|
|
2583
|
+
else:
|
|
2584
|
+
self._audioReady = True # no mic, so we just set the flag
|
|
2585
|
+
|
|
2586
|
+
if not self.isReady:
|
|
2587
|
+
# if the camera is not ready, return -1 to indicate that we are not
|
|
2588
|
+
# ready to process frames yet
|
|
2589
|
+
return -1
|
|
2590
|
+
|
|
2591
|
+
if not newFrames:
|
|
2592
|
+
# if no new frames were captured, return 0 to indicate that we have
|
|
2593
|
+
# no new frames to process
|
|
2594
|
+
return 0
|
|
2595
|
+
|
|
2596
|
+
# put last frames into the frame store
|
|
2597
|
+
nNewFrames = len(newFrames)
|
|
2598
|
+
if nNewFrames > self._frameStore.maxlen:
|
|
2599
|
+
logging.warning(
|
|
2600
|
+
"Frame store overflowed, some frames may have been lost. "
|
|
2601
|
+
"Consider increasing the `keepFrames` parameter when creating "
|
|
2602
|
+
"the camera object or polling the camera more frequently."
|
|
2603
|
+
)
|
|
2604
|
+
|
|
2605
|
+
self._frameCount += nNewFrames # update total frames count
|
|
2606
|
+
# push all frames into the frame store
|
|
2607
|
+
for colorData, pts, streamTime in newFrames:
|
|
2608
|
+
# if camera is in CV mode, convert the frame to RGB
|
|
2609
|
+
if self._usageMode == CAMERA_MODE_CV:
|
|
2610
|
+
colorData = self._convertFrameToRGBFFPyPlayer(colorData)
|
|
2611
|
+
# add the frame to the frame store
|
|
2612
|
+
self._frameStore.append((colorData, pts, streamTime))
|
|
2613
|
+
|
|
2614
|
+
# if we have frames, update the last frame
|
|
2615
|
+
colorData, pts, streamTime = newFrames[-1]
|
|
2616
|
+
self._lastFrame = (
|
|
2617
|
+
self._convertFrameToRGBFFPyPlayer(colorData), # convert to RGB, nop if already
|
|
2618
|
+
pts, # presentation timestamp
|
|
2619
|
+
streamTime
|
|
2620
|
+
)
|
|
2621
|
+
|
|
2622
|
+
self._pixelTransfer() # transfer frames to the GPU if we have a window
|
|
2623
|
+
|
|
2624
|
+
# write frames out to video file
|
|
2625
|
+
if self._usageMode == CAMERA_MODE_VIDEO:
|
|
2626
|
+
for frame in newFrames:
|
|
2627
|
+
self._submitFrameToFile(frame)
|
|
2628
|
+
elif self._usageMode == CAMERA_MODE_CV:
|
|
2629
|
+
pass
|
|
2630
|
+
|
|
2631
|
+
return nNewFrames # return number of frames we got
|
|
2632
|
+
|
|
2633
|
+
def poll(self):
|
|
2634
|
+
"""Poll the camera for new frames.
|
|
2635
|
+
|
|
2636
|
+
Alias for `update()`.
|
|
2637
|
+
"""
|
|
2638
|
+
return self.update()
|
|
2639
|
+
|
|
2640
|
+
def getVideoFrames(self):
|
|
2641
|
+
"""Get the most recent frame from the stream (if available).
|
|
2642
|
+
|
|
2643
|
+
Returns
|
|
2644
|
+
-------
|
|
2645
|
+
list of tuple
|
|
2646
|
+
List of recent video frames. This will return a list of frame images
|
|
2647
|
+
as numpy arrays, their presentation timestamp in the recording, and
|
|
2648
|
+
the absolute stream time in seconds. Frames will be converted
|
|
2649
|
+
to RGB format if they are not already. The number of frames returned
|
|
2650
|
+
will be limited by the `keepFrames` parameter set when creating the
|
|
2651
|
+
camera object. If no frames are available, an empty list will be
|
|
2652
|
+
returned.
|
|
2653
|
+
|
|
2654
|
+
"""
|
|
2655
|
+
self.update()
|
|
2656
|
+
|
|
2657
|
+
recentFrames = [
|
|
2658
|
+
self._convertFrameToRGBFFPyPlayer(frame) for frame in self._frameStore]
|
|
2659
|
+
|
|
2660
|
+
return recentFrames
|
|
2661
|
+
|
|
2662
|
+
def getRecentVideoFrame(self):
|
|
2663
|
+
"""Get the most recent video frame from the camera.
|
|
2664
|
+
|
|
2665
|
+
Returns
|
|
2666
|
+
-------
|
|
2667
|
+
VideoFrame or None
|
|
2668
|
+
Most recent video frame. Returns `None` if no frame was available,
|
|
2669
|
+
or we timed out.
|
|
2670
|
+
|
|
2671
|
+
"""
|
|
2672
|
+
self.update()
|
|
2673
|
+
|
|
2674
|
+
return self._lastFrame[0] if self._lastFrame else None
|
|
2675
|
+
|
|
2676
|
+
# --------------------------------------------------------------------------
|
|
2677
|
+
# Audio track
|
|
2678
|
+
#
|
|
2679
|
+
|
|
2680
|
+
def getAudioTrack(self):
|
|
2681
|
+
"""Get the audio track data.
|
|
2682
|
+
|
|
2683
|
+
Returns
|
|
2684
|
+
-------
|
|
2685
|
+
AudioClip or None
|
|
2686
|
+
Audio track data from the microphone if available, or `None` if
|
|
2687
|
+
no microphone is set or no audio was recorded.
|
|
2688
|
+
|
|
2689
|
+
"""
|
|
2690
|
+
return self.mic.getRecording() if self.mic else None
|
|
2691
|
+
|
|
2692
|
+
# --------------------------------------------------------------------------
|
|
2693
|
+
# Video rendering
|
|
2694
|
+
#
|
|
2695
|
+
# These methods are used to render live video frames to a window. If a
|
|
2696
|
+
# window is set, this class will automamatically create the nessisary
|
|
2697
|
+
# OpenGL texture buffers and transfers the most recent video frame to the
|
|
2698
|
+
# GPU when `update` is called. The `ImageStim` class can access these
|
|
2699
|
+
# buffers for rendering by setting this class as the `image`.
|
|
2700
|
+
#
|
|
2701
|
+
|
|
2702
|
+
@property
|
|
2703
|
+
def win(self):
|
|
2704
|
+
"""Window to render the video frames to (`psychopy.visual.Window` or
|
|
2705
|
+
`None`).
|
|
2706
|
+
|
|
2707
|
+
If `None`, no rendering will be done and the video frames will not be
|
|
2708
|
+
displayed. If a window is set, the video frames will be rendered to the
|
|
2709
|
+
window using OpenGL textures.
|
|
2710
|
+
|
|
2711
|
+
"""
|
|
2712
|
+
return self._win
|
|
2713
|
+
|
|
2714
|
+
@win.setter
|
|
2715
|
+
def win(self, value):
|
|
2716
|
+
"""Set the window to render the video frames to.
|
|
2717
|
+
|
|
2718
|
+
This will set the window to render the video frames to. If the window
|
|
2719
|
+
is not `None`, it will automatically create OpenGL texture buffers for
|
|
2720
|
+
rendering the video frames. If the window is `None`, no rendering will
|
|
2721
|
+
be done and the video frames will not be displayed.
|
|
2722
|
+
|
|
2723
|
+
Parameters
|
|
2724
|
+
----------
|
|
2725
|
+
value : psychopy.visual.Window or None
|
|
2726
|
+
Window to render the video frames to. If `None`, no rendering will
|
|
2727
|
+
be done and the video frames will not be displayed.
|
|
2728
|
+
|
|
2729
|
+
"""
|
|
2730
|
+
self.setWin(value)
|
|
2731
|
+
|
|
2732
|
+
def setWin(self, win):
|
|
2733
|
+
"""Set the window to render the video frames to.
|
|
2734
|
+
|
|
2735
|
+
Parameters
|
|
2736
|
+
----------
|
|
2737
|
+
win : psychopy.visual.Window
|
|
2738
|
+
Window to render the video frames to. If `None`, no rendering will
|
|
2739
|
+
be done and the video frames will not be displayed.
|
|
2740
|
+
|
|
2741
|
+
"""
|
|
2742
|
+
self._win = win
|
|
2743
|
+
|
|
2744
|
+
# if we have a window, setup texture buffers for displaying
|
|
2745
|
+
if self._win is not None:
|
|
2746
|
+
self._setupTextureBuffers()
|
|
2747
|
+
return
|
|
2748
|
+
|
|
2749
|
+
# if we don't have a window, free any texture buffers
|
|
2750
|
+
self._freeTextureBuffers() # free any existing buffers
|
|
2751
|
+
|
|
2752
|
+
@property
|
|
2753
|
+
def interpolate(self):
|
|
2754
|
+
"""Whether the video texture should be filtered using linear or nearest
|
|
2755
|
+
neighbor interpolation (`bool`).
|
|
2756
|
+
|
|
2757
|
+
If `True`, the video texture will be filtered using linear interpolation.
|
|
2758
|
+
If `False`, the video texture will be filtered using nearest neighbor
|
|
2759
|
+
interpolation (pass-through). Default is `True`.
|
|
2760
|
+
|
|
2761
|
+
"""
|
|
2762
|
+
return self._interpolate
|
|
2763
|
+
|
|
2764
|
+
@interpolate.setter
|
|
2765
|
+
def interpolate(self, value):
|
|
2766
|
+
"""Set whether the video texture should be filtered using linear or
|
|
2767
|
+
nearest neighbor interpolation.
|
|
2768
|
+
|
|
2769
|
+
Parameters
|
|
2770
|
+
----------
|
|
2771
|
+
value : bool
|
|
2772
|
+
If `True`, the video texture will be filtered using linear
|
|
2773
|
+
interpolation. If `False`, the video texture will be filtered using
|
|
2774
|
+
nearest neighbor interpolation (pass-through). Default is `True`.
|
|
2775
|
+
|
|
2776
|
+
"""
|
|
2777
|
+
self.setTextureFilter(value)
|
|
2778
|
+
|
|
2779
|
+
def setTextureFilter(self, smooth=True):
|
|
2780
|
+
"""Set whether the video texture should be filtered using linear or
|
|
2781
|
+
nearest neighbor interpolation.
|
|
2782
|
+
|
|
2783
|
+
Parameters
|
|
2784
|
+
----------
|
|
2785
|
+
smooth : bool
|
|
2786
|
+
If `True`, the video texture will be filtered using linear
|
|
2787
|
+
interpolation. If `False`, the video texture will be filtered using
|
|
2788
|
+
nearest neighbor interpolation (pass-through.) Default is `True`.
|
|
2789
|
+
|
|
2790
|
+
"""
|
|
2791
|
+
self._interpolate = bool(smooth)
|
|
2792
|
+
self._texFilterNeedsUpdate = True # flag to update texture filtering
|
|
2793
|
+
|
|
2794
|
+
def _freeTextureBuffers(self):
|
|
2795
|
+
"""Free any texture buffers used by the camera.
|
|
2796
|
+
|
|
2797
|
+
This is used to free up any texture buffers used by the camera. This
|
|
2798
|
+
is called when the camera is closed or when the window is closed.
|
|
2799
|
+
"""
|
|
2800
|
+
import pyglet.gl as GL # needed for OpenGL texture management
|
|
2801
|
+
|
|
2802
|
+
try:
|
|
2803
|
+
# delete buffers and textures if previously created
|
|
2804
|
+
if self._pixbuffId is not None and self._pixbuffId.value > 0:
|
|
2805
|
+
GL.glDeleteBuffers(1, self._pixbuffId)
|
|
2806
|
+
# delete the old texture if present
|
|
2807
|
+
if self._textureId is not None and self._textureId.value > 0:
|
|
2808
|
+
GL.glDeleteTextures(1, self._textureId)
|
|
2809
|
+
except (TypeError, AttributeError):
|
|
2810
|
+
pass
|
|
2811
|
+
|
|
2812
|
+
# clear the IDs
|
|
2813
|
+
self._pixbuffId = GL.GLuint(0)
|
|
2814
|
+
self._textureId = GL.GLuint(0)
|
|
2815
|
+
|
|
2816
|
+
def _setupTextureBuffers(self):
|
|
2817
|
+
"""Setup texture buffers for the camera.
|
|
2818
|
+
|
|
2819
|
+
This allocates OpenGL texture buffers for video frames to be written
|
|
2820
|
+
to which then can be rendered to the screen. This is only called if the
|
|
2821
|
+
camera is opened and a window is set.
|
|
2822
|
+
|
|
2823
|
+
"""
|
|
2824
|
+
if self.win is None:
|
|
2825
|
+
return
|
|
2826
|
+
|
|
2827
|
+
self._freeTextureBuffers() # free any existing buffers
|
|
2828
|
+
|
|
2829
|
+
import pyglet.gl as GL
|
|
2830
|
+
|
|
2831
|
+
# get the size of the movie frame and compute the buffer size
|
|
2832
|
+
vidWidth, vidHeight = self.frameSize
|
|
2833
|
+
nBufferBytes = self._texBufferSizeBytes = (
|
|
2834
|
+
vidWidth * vidHeight * 3)
|
|
2835
|
+
|
|
2836
|
+
# Create the pixel buffer object which will serve as the texture memory
|
|
2837
|
+
# store. Pixel data will be copied to this buffer each frame.
|
|
2838
|
+
GL.glGenBuffers(1, ctypes.byref(self._pixbuffId))
|
|
2839
|
+
GL.glBindBuffer(GL.GL_PIXEL_UNPACK_BUFFER, self._pixbuffId)
|
|
2840
|
+
GL.glBufferData(
|
|
2841
|
+
GL.GL_PIXEL_UNPACK_BUFFER,
|
|
2842
|
+
nBufferBytes * ctypes.sizeof(GL.GLubyte),
|
|
2843
|
+
None,
|
|
2844
|
+
GL.GL_STREAM_DRAW) # one-way app -> GL
|
|
2845
|
+
GL.glBindBuffer(GL.GL_PIXEL_UNPACK_BUFFER, 0)
|
|
2846
|
+
|
|
2847
|
+
# Create a texture which will hold the data streamed to the pixel
|
|
2848
|
+
# buffer. Only one texture needs to be allocated.
|
|
2849
|
+
GL.glEnable(GL.GL_TEXTURE_2D)
|
|
2850
|
+
GL.glGenTextures(1, ctypes.byref(self._textureId))
|
|
2851
|
+
GL.glBindTexture(GL.GL_TEXTURE_2D, self._textureId)
|
|
2852
|
+
GL.glTexImage2D(
|
|
2853
|
+
GL.GL_TEXTURE_2D,
|
|
2854
|
+
0,
|
|
2855
|
+
GL.GL_RGB8,
|
|
2856
|
+
vidWidth, vidHeight, # frame dims in pixels
|
|
2857
|
+
0,
|
|
2858
|
+
GL.GL_RGB,
|
|
2859
|
+
GL.GL_UNSIGNED_BYTE,
|
|
2860
|
+
None)
|
|
2861
|
+
|
|
2862
|
+
# setup texture filtering
|
|
2863
|
+
if self._interpolate:
|
|
2864
|
+
texFilter = GL.GL_LINEAR
|
|
2865
|
+
else:
|
|
2866
|
+
texFilter = GL.GL_NEAREST
|
|
2867
|
+
|
|
2868
|
+
GL.glTexParameteri(
|
|
2869
|
+
GL.GL_TEXTURE_2D,
|
|
2870
|
+
GL.GL_TEXTURE_MAG_FILTER,
|
|
2871
|
+
texFilter)
|
|
2872
|
+
GL.glTexParameteri(
|
|
2873
|
+
GL.GL_TEXTURE_2D,
|
|
2874
|
+
GL.GL_TEXTURE_MIN_FILTER,
|
|
2875
|
+
texFilter)
|
|
2876
|
+
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP)
|
|
2877
|
+
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP)
|
|
2878
|
+
GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
|
|
2879
|
+
GL.glDisable(GL.GL_TEXTURE_2D)
|
|
2880
|
+
|
|
2881
|
+
GL.glFlush() # make sure all buffers are ready
|
|
2882
|
+
|
|
2883
|
+
def _pixelTransfer(self):
|
|
2884
|
+
"""Copy pixel data from video frame to texture.
|
|
2885
|
+
|
|
2886
|
+
This is called when a new frame is available. The pixel data is copied
|
|
2887
|
+
from the video frame to the texture store on the GPU.
|
|
2888
|
+
|
|
2889
|
+
"""
|
|
2890
|
+
if self.win is None:
|
|
2891
|
+
return # no window to render to
|
|
2892
|
+
|
|
2893
|
+
import pyglet.gl as GL
|
|
2894
|
+
|
|
2895
|
+
# get the size of the movie frame and compute the buffer size
|
|
2896
|
+
vidWidth, vidHeight = self.frameSize
|
|
2897
|
+
|
|
2898
|
+
# compute the buffer size
|
|
2899
|
+
nBufferBytes = self._texBufferSizeBytes
|
|
2900
|
+
|
|
2901
|
+
# bind pixel unpack buffer
|
|
2902
|
+
GL.glBindBuffer(GL.GL_PIXEL_UNPACK_BUFFER, self._pixbuffId)
|
|
2903
|
+
|
|
2904
|
+
# Free last storage buffer before mapping and writing new frame
|
|
2905
|
+
# data. This allows the GPU to process the extant buffer in VRAM
|
|
2906
|
+
# uploaded last cycle without being stalled by the CPU accessing it.
|
|
2907
|
+
GL.glBufferData(
|
|
2908
|
+
GL.GL_PIXEL_UNPACK_BUFFER,
|
|
2909
|
+
nBufferBytes * ctypes.sizeof(GL.GLubyte),
|
|
2910
|
+
None,
|
|
2911
|
+
GL.GL_STREAM_DRAW)
|
|
2912
|
+
|
|
2913
|
+
# Map the buffer to client memory, `GL_WRITE_ONLY` to tell the
|
|
2914
|
+
# driver to optimize for a one-way write operation if it can.
|
|
2915
|
+
bufferPtr = GL.glMapBuffer(
|
|
2916
|
+
GL.GL_PIXEL_UNPACK_BUFFER,
|
|
2917
|
+
GL.GL_WRITE_ONLY)
|
|
2918
|
+
|
|
2919
|
+
# map the video frame to a memoryview
|
|
2920
|
+
# suggested by Alex Forrence (aforren1) originally in PR #6439
|
|
2921
|
+
videoBuffer = self._lastFrame[0].to_memoryview()[0].memview
|
|
2922
|
+
videoFrameArray = np.frombuffer(videoBuffer, dtype=np.uint8)
|
|
2923
|
+
|
|
2924
|
+
# copy the frame data to the buffer
|
|
2925
|
+
ctypes.memmove(bufferPtr,
|
|
2926
|
+
videoFrameArray.ctypes.data,
|
|
2927
|
+
nBufferBytes)
|
|
2928
|
+
|
|
2929
|
+
# Very important that we unmap the buffer data after copying, but
|
|
2930
|
+
# keep the buffer bound for setting the texture.
|
|
2931
|
+
GL.glUnmapBuffer(GL.GL_PIXEL_UNPACK_BUFFER)
|
|
2932
|
+
|
|
2933
|
+
# bind the texture in OpenGL
|
|
2934
|
+
GL.glEnable(GL.GL_TEXTURE_2D)
|
|
2935
|
+
GL.glActiveTexture(GL.GL_TEXTURE0)
|
|
2936
|
+
GL.glBindTexture(GL.GL_TEXTURE_2D, self._textureId)
|
|
2937
|
+
|
|
2938
|
+
# copy the PBO to the texture (blocks on AMD for some reason)
|
|
2939
|
+
GL.glTexSubImage2D(
|
|
2940
|
+
GL.GL_TEXTURE_2D, 0, 0, 0,
|
|
2941
|
+
vidWidth, vidHeight,
|
|
2942
|
+
GL.GL_RGB,
|
|
2943
|
+
GL.GL_UNSIGNED_BYTE,
|
|
2944
|
+
0) # point to the presently bound buffer
|
|
2945
|
+
|
|
2946
|
+
# update texture filtering only if needed
|
|
2947
|
+
if self._texFilterNeedsUpdate:
|
|
2948
|
+
if self._interpolate:
|
|
2949
|
+
texFilter = GL.GL_LINEAR
|
|
2950
|
+
else:
|
|
2951
|
+
texFilter = GL.GL_NEAREST
|
|
2952
|
+
|
|
2953
|
+
GL.glTexParameteri(
|
|
2954
|
+
GL.GL_TEXTURE_2D,
|
|
2955
|
+
GL.GL_TEXTURE_MAG_FILTER,
|
|
2956
|
+
texFilter)
|
|
2957
|
+
GL.glTexParameteri(
|
|
2958
|
+
GL.GL_TEXTURE_2D,
|
|
2959
|
+
GL.GL_TEXTURE_MIN_FILTER,
|
|
2960
|
+
texFilter)
|
|
2961
|
+
|
|
2962
|
+
self._texFilterNeedsUpdate = False
|
|
2963
|
+
|
|
2964
|
+
# important to unbind the PBO
|
|
2965
|
+
GL.glBindBuffer(GL.GL_PIXEL_UNPACK_BUFFER, 0)
|
|
2966
|
+
GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
|
|
2967
|
+
GL.glDisable(GL.GL_TEXTURE_2D)
|
|
2968
|
+
|
|
2969
|
+
@property
|
|
2970
|
+
def colorTexture(self):
|
|
2971
|
+
"""OpenGL texture ID for the most recent video frame (`int` or `None`).
|
|
2972
|
+
|
|
2973
|
+
This is the OpenGL texture ID that can be used to render the most
|
|
2974
|
+
recent video frame to a window. If no window is set, this will be `None`.
|
|
2975
|
+
"""
|
|
2976
|
+
if self._textureId is None or self._textureId.value <= 0:
|
|
2977
|
+
return None
|
|
2978
|
+
|
|
2979
|
+
return self._textureId
|
|
2980
|
+
|
|
2981
|
+
@property
|
|
2982
|
+
def colorTextureSizeBytes(self):
|
|
2983
|
+
"""Size of the texture buffer used for rendering video frames
|
|
2984
|
+
(`int` or `None`).
|
|
2985
|
+
|
|
2986
|
+
This returns the size of the texture buffer in bytes used for rendering
|
|
2987
|
+
video frames. This is only valid if the camera is opened.
|
|
2988
|
+
|
|
2989
|
+
"""
|
|
2990
|
+
if self._cameraInfo is None:
|
|
2991
|
+
return None
|
|
2992
|
+
|
|
2993
|
+
return self._texBufferSizeBytes
|
|
2994
|
+
|
|
2995
|
+
# --------------------------------------------------------------------------
|
|
2996
|
+
# Movie writer platform-specific methods
|
|
2997
|
+
#
|
|
2998
|
+
# These are used to write frames to a movie file. We used to use the
|
|
2999
|
+
# `MovieFileWriter` class for this, but for now were implimenting this
|
|
3000
|
+
# directly in the camera class. This may change in the future.
|
|
3001
|
+
#
|
|
3002
|
+
|
|
3003
|
+
def _openMovieFileWriterFFPyPlayer(self, filename, encoderOpts=None):
|
|
3004
|
+
"""Open a movie file writer using the FFPyPlayer library.
|
|
3005
|
+
|
|
3006
|
+
Parameters
|
|
3007
|
+
----------
|
|
3008
|
+
filename : str
|
|
3009
|
+
File to save the resulting video to, should include the extension.
|
|
3010
|
+
encoderOpts : dict or None
|
|
3011
|
+
Options to pass to the encoder. This is a dictionary of options
|
|
3012
|
+
specific to the encoder library being used. See the documentation
|
|
3013
|
+
for `~psychopy.tools.movietools.MovieFileWriter` for more details.
|
|
3014
|
+
|
|
3015
|
+
"""
|
|
3016
|
+
from ffpyplayer.writer import MediaWriter
|
|
3017
|
+
|
|
3018
|
+
encoderOpts = encoderOpts or {}
|
|
3019
|
+
|
|
3020
|
+
# options to configure the writer
|
|
3021
|
+
frameWidth, frameHeight = self.frameSize
|
|
3022
|
+
|
|
3023
|
+
writerOptions = {
|
|
3024
|
+
'pix_fmt_in': 'yuv420p', # default for now using mp4
|
|
3025
|
+
'width_in': frameWidth,
|
|
3026
|
+
'height_in': frameHeight,
|
|
3027
|
+
'codec': 'libx264',
|
|
3028
|
+
'frame_rate': (int(self._capture.frameRate), 1)}
|
|
3029
|
+
|
|
3030
|
+
self._curPTS = 0.0 # current pts for the movie writer
|
|
3031
|
+
|
|
3032
|
+
self._generatePTS = False # whether to generate PTS for the movie writer
|
|
3033
|
+
if filename.endswith('.mp4'):
|
|
3034
|
+
self._generatePTS = True # generate PTS for mp4 files
|
|
3035
|
+
logging.debug(
|
|
3036
|
+
"MP4 format detected, PTS will be generated for the movie " \
|
|
3037
|
+
"writer.")
|
|
3038
|
+
|
|
3039
|
+
self._movieWriter = MediaWriter(
|
|
3040
|
+
filename,
|
|
3041
|
+
[writerOptions],
|
|
3042
|
+
fmt='mp4',
|
|
3043
|
+
overwrite=True, # overwrite existing file
|
|
3044
|
+
libOpts=encoderOpts)
|
|
3045
|
+
|
|
3046
|
+
def _submitFrameToFileFFPyPlayer(self, frames):
|
|
3047
|
+
"""Submit a frame to the movie file writer thread using FFPyPlayer.
|
|
3048
|
+
|
|
3049
|
+
This is used to submit frames to the movie file writer thread. It is
|
|
3050
|
+
called by the camera interface when a new frame is captured.
|
|
3051
|
+
|
|
3052
|
+
Parameters
|
|
3053
|
+
----------
|
|
3054
|
+
frames : list of tuples
|
|
3055
|
+
Color data and presentation timestamps to submit to the movie file
|
|
3056
|
+
writer thread.
|
|
3057
|
+
|
|
3058
|
+
Returns
|
|
3059
|
+
-------
|
|
3060
|
+
int
|
|
3061
|
+
Number of bytes written the the movie file.
|
|
3062
|
+
|
|
3063
|
+
"""
|
|
3064
|
+
if self._movieWriter is None:
|
|
3065
|
+
raise RuntimeError(
|
|
3066
|
+
"Attempting to call `_submitFrameToFileFFPyPlayer()` before "
|
|
3067
|
+
"`_openMovieFileWriterFFPyPlayer()`.")
|
|
3068
|
+
|
|
3069
|
+
from ffpyplayer.pic import SWScale
|
|
3070
|
+
|
|
3071
|
+
if not isinstance(frames, list):
|
|
3072
|
+
frames = [frames] # ensure frames is a list
|
|
3073
|
+
|
|
3074
|
+
# write frames to the movie file writer
|
|
3075
|
+
bytesOut = 0
|
|
3076
|
+
for colorData, pts, _ in frames:
|
|
3077
|
+
# do color conversion if needed
|
|
3078
|
+
frameWidth, frameHeight = colorData.get_size()
|
|
3079
|
+
sws = SWScale(
|
|
3080
|
+
frameWidth, frameHeight,
|
|
3081
|
+
colorData.get_pixel_format(),
|
|
3082
|
+
ofmt='yuv420p')
|
|
3083
|
+
|
|
3084
|
+
if self._generatePTS:
|
|
3085
|
+
pts = self._curPTS # use current for PTS
|
|
3086
|
+
self._curPTS += self._capture.frameInterval # increment dts by frame interval
|
|
3087
|
+
|
|
3088
|
+
bytesOut = self._movieWriter.write_frame(
|
|
3089
|
+
img=sws.scale(colorData),
|
|
3090
|
+
pts=pts,
|
|
3091
|
+
stream=0)
|
|
3092
|
+
|
|
3093
|
+
return bytesOut
|
|
3094
|
+
|
|
3095
|
+
def _closeMovieFileWriterFFPyPlayer(self):
|
|
3096
|
+
"""Close the movie file writer using the FFPyPlayer library.
|
|
3097
|
+
|
|
3098
|
+
This will close the movie file writer and free up any resources used by
|
|
3099
|
+
the writer. If the writer is not open, this will do nothing.
|
|
3100
|
+
"""
|
|
3101
|
+
if self._movieWriter is not None:
|
|
3102
|
+
logging.debug(
|
|
3103
|
+
"Closing movie file writer using FFPyPlayer...")
|
|
3104
|
+
self._movieWriter.close()
|
|
3105
|
+
else:
|
|
3106
|
+
logging.debug(
|
|
3107
|
+
"Attempting to call `_closeMovieFileWriterFFPyPlayer()` "
|
|
3108
|
+
"without an open movie file writer.")
|
|
3109
|
+
|
|
3110
|
+
#
|
|
3111
|
+
# Movie file writer methods
|
|
3112
|
+
#
|
|
3113
|
+
# These methods are used to open and close a movie file writer to save
|
|
3114
|
+
# frames to disk. We don't expose these methods to the user directly, but
|
|
3115
|
+
# they are used internally.
|
|
3116
|
+
#
|
|
3117
|
+
|
|
3118
|
+
def _openMovieFileWriter(self, encoderLib=None, encoderOpts=None):
|
|
3119
|
+
"""Open a movie file writer to save frames to disk.
|
|
3120
|
+
|
|
3121
|
+
This will open a movie file writer to save frames to disk. The frames
|
|
3122
|
+
will be saved to a temporary file and then merged with the audio
|
|
3123
|
+
track (if available) when `save()` is called.
|
|
3124
|
+
|
|
3125
|
+
Parameters
|
|
3126
|
+
----------
|
|
3127
|
+
encoderLib : str or None
|
|
3128
|
+
Encoder library to use for saving the video. This can be either
|
|
3129
|
+
`'ffpyplayer'` or `'opencv'`. If `None`, the same library that was
|
|
3130
|
+
used to open the camera stream. Default is `None`.
|
|
3131
|
+
encoderOpts : dict or None
|
|
3132
|
+
Options to pass to the encoder. This is a dictionary of options
|
|
3133
|
+
specific to the encoder library being used. See the documentation
|
|
3134
|
+
for `~psychopy.tools.movietools.MovieFileWriter` for more details.
|
|
3135
|
+
|
|
3136
|
+
Returns
|
|
3137
|
+
-------
|
|
3138
|
+
str
|
|
3139
|
+
Path to the temporary file that will be used to save the video. The
|
|
3140
|
+
file will be deleted when the movie file writer is closed or when
|
|
3141
|
+
`save()` is called.
|
|
3142
|
+
|
|
3143
|
+
"""
|
|
3144
|
+
if self._movieWriter is not None:
|
|
3145
|
+
return self._tempVideoFile # already open, return temp file
|
|
3146
|
+
|
|
3147
|
+
if encoderLib is None:
|
|
3148
|
+
encoderLib = self._cameraLib
|
|
3149
|
+
logging.debug(
|
|
3150
|
+
"Using encoder library '{}' to save video.".format(encoderLib))
|
|
3151
|
+
|
|
3152
|
+
# check if we have a temporary file to write to
|
|
3153
|
+
import tempfile
|
|
3154
|
+
# create a temporary file to write the video to
|
|
3155
|
+
tempVideoFile = tempfile.NamedTemporaryFile(
|
|
3156
|
+
suffix='.mp4', delete=True)
|
|
3157
|
+
self._tempVideoFile = tempVideoFile.name
|
|
3158
|
+
tempVideoFile.close()
|
|
3159
|
+
|
|
3160
|
+
logging.debug("Using temporary file '{}' for video.".format(self._tempVideoFile))
|
|
3161
|
+
|
|
3162
|
+
# check if the encoder library name string is valid
|
|
3163
|
+
if encoderLib not in ('ffpyplayer'):
|
|
3164
|
+
raise ValueError(
|
|
3165
|
+
"Invalid value for parameter `encoderLib`, expected one of "
|
|
3166
|
+
"`'ffpyplayer'` or `'opencv'`.")
|
|
3167
|
+
|
|
3168
|
+
if encoderLib == 'ffpyplayer':
|
|
3169
|
+
self._openMovieFileWriterFFPyPlayer(
|
|
3170
|
+
self._tempVideoFile, encoderOpts=encoderOpts)
|
|
3171
|
+
else:
|
|
3172
|
+
raise ValueError(
|
|
3173
|
+
"Invalid value for parameter `encoderLib`, expected one of "
|
|
3174
|
+
"`'ffpyplayer'` or `'opencv'`.")
|
|
3175
|
+
|
|
3176
|
+
return self._tempVideoFile
|
|
3177
|
+
|
|
3178
|
+
def _submitFrameToFile(self, frames, pts=None):
|
|
3179
|
+
"""Submit a frame to the movie file writer thread.
|
|
3180
|
+
|
|
3181
|
+
This is used to submit frames to the movie file writer thread. It is
|
|
3182
|
+
called by the camera interface when a new frame is captured.
|
|
3183
|
+
|
|
3184
|
+
Parameters
|
|
3185
|
+
----------
|
|
3186
|
+
frames : MovieFrame
|
|
3187
|
+
Frame to submit to the movie file writer thread.
|
|
3188
|
+
pts : float or None
|
|
3189
|
+
Presentation timestamp for the frame. If `None`, timestamps will be
|
|
3190
|
+
generated automatically by the movie file writer. This is only used
|
|
3191
|
+
if the movie file writer is configured to generate PTS values.
|
|
3192
|
+
|
|
3193
|
+
"""
|
|
3194
|
+
if self._movieWriter is None:
|
|
3195
|
+
raise RuntimeError(
|
|
3196
|
+
"Attempting to call `_submitFrameToFile()` before "
|
|
3197
|
+
"`_openMovieFileWriter()`.")
|
|
3198
|
+
|
|
3199
|
+
tStart = time.time() # start time for the operation
|
|
3200
|
+
if self._cameraLib == 'ffpyplayer':
|
|
3201
|
+
toReturn = self._submitFrameToFileFFPyPlayer(frames)
|
|
3202
|
+
else:
|
|
3203
|
+
raise ValueError(
|
|
3204
|
+
"Invalid value for parameter `encoderLib`, expected "
|
|
3205
|
+
"`'ffpyplayer'.")
|
|
3206
|
+
|
|
3207
|
+
logging.debug(
|
|
3208
|
+
"Submitted {} frames to the movie file writer (took {:.6f} seconds)".format(
|
|
3209
|
+
len(frames), time.time() - tStart))
|
|
3210
|
+
|
|
3211
|
+
return toReturn
|
|
3212
|
+
|
|
3213
|
+
def _closeMovieFileWriter(self):
|
|
3214
|
+
"""Close the movie file writer.
|
|
3215
|
+
|
|
3216
|
+
This will close the movie file writer and free up any resources used by
|
|
3217
|
+
the writer. If the writer is not open, this will do nothing.
|
|
3218
|
+
"""
|
|
3219
|
+
if self._movieWriter is None:
|
|
3220
|
+
logging.warning(
|
|
3221
|
+
"Attempting to call `_closeMovieFileWriter()` without an open "
|
|
3222
|
+
"movie file writer.")
|
|
3223
|
+
return
|
|
3224
|
+
|
|
3225
|
+
if self._cameraLib == 'ffpyplayer':
|
|
3226
|
+
self._closeMovieFileWriterFFPyPlayer()
|
|
3227
|
+
else:
|
|
3228
|
+
raise ValueError(
|
|
3229
|
+
"Invalid value for parameter `encoderLib`, expected one of "
|
|
3230
|
+
"`'ffpyplayer'` or `'opencv'`.")
|
|
3231
|
+
|
|
3232
|
+
self._movieWriter = None
|
|
3233
|
+
|
|
3234
|
+
# --------------------------------------------------------------------------
|
|
3235
|
+
# Destructor
|
|
3236
|
+
#
|
|
3237
|
+
|
|
3238
|
+
def __del__(self):
|
|
3239
|
+
"""Try to cleanly close the camera and output file.
|
|
3240
|
+
"""
|
|
3241
|
+
if hasattr(self, '_capture'):
|
|
3242
|
+
if self._capture is not None:
|
|
3243
|
+
try:
|
|
3244
|
+
self.close()
|
|
3245
|
+
except AttributeError:
|
|
3246
|
+
pass
|
|
3247
|
+
|
|
3248
|
+
if hasattr(self, '_movieWriter'):
|
|
3249
|
+
if self._movieWriter is not None:
|
|
3250
|
+
try:
|
|
3251
|
+
self._movieWriter.close()
|
|
3252
|
+
except AttributeError:
|
|
3253
|
+
pass
|
|
3254
|
+
|
|
3255
|
+
|
|
3256
|
+
DeviceManager.registerClassAlias("camera", "psychopy.hardware.camera.Camera")
|
|
3257
|
+
|
|
3258
|
+
|
|
3259
|
+
# ------------------------------------------------------------------------------
|
|
3260
|
+
# Functions
|
|
3261
|
+
#
|
|
3262
|
+
|
|
3263
|
+
def _getCameraInfoMacOS():
|
|
3264
|
+
"""Get a list of capabilities associated with a camera attached to the
|
|
3265
|
+
system.
|
|
3266
|
+
|
|
3267
|
+
This is used by `getCameraInfo()` for querying camera details on MacOS.
|
|
3268
|
+
Don't call this function directly unless testing.
|
|
3269
|
+
|
|
3270
|
+
Returns
|
|
3271
|
+
-------
|
|
3272
|
+
list of CameraInfo
|
|
3273
|
+
List of camera descriptors.
|
|
3274
|
+
|
|
3275
|
+
"""
|
|
3276
|
+
if platform.system() != 'Darwin':
|
|
3277
|
+
raise OSError(
|
|
3278
|
+
"Cannot query cameras with this function, platform not 'Darwin'.")
|
|
3279
|
+
|
|
3280
|
+
# import objc # may be needed in the future for more advanced stuff
|
|
3281
|
+
import AVFoundation as avf # only works on MacOS
|
|
3282
|
+
import CoreMedia as cm
|
|
3283
|
+
|
|
3284
|
+
# get a list of capture devices
|
|
3285
|
+
allDevices = avf.AVCaptureDevice.devices()
|
|
3286
|
+
|
|
3287
|
+
# get video devices
|
|
3288
|
+
videoDevices = {}
|
|
3289
|
+
devIdx = 0
|
|
3290
|
+
for device in allDevices:
|
|
3291
|
+
devFormats = device.formats()
|
|
3292
|
+
if devFormats[0].mediaType() != 'vide': # not a video device
|
|
3293
|
+
continue
|
|
3294
|
+
|
|
3295
|
+
# camera details
|
|
3296
|
+
cameraName = device.localizedName()
|
|
3297
|
+
|
|
3298
|
+
# found video formats
|
|
3299
|
+
supportedFormats = []
|
|
3300
|
+
for _format in devFormats:
|
|
3301
|
+
# get the format description object
|
|
3302
|
+
formatDesc = _format.formatDescription()
|
|
3303
|
+
|
|
3304
|
+
# get dimensions in pixels of the video format
|
|
3305
|
+
dimensions = cm.CMVideoFormatDescriptionGetDimensions(formatDesc)
|
|
3306
|
+
frameHeight = dimensions.height
|
|
3307
|
+
frameWidth = dimensions.width
|
|
3308
|
+
|
|
3309
|
+
# Extract the codec in use, pretty useless since FFMPEG uses its
|
|
3310
|
+
# own conventions, we'll need to map these ourselves to those
|
|
3311
|
+
# values
|
|
3312
|
+
codecType = cm.CMFormatDescriptionGetMediaSubType(formatDesc)
|
|
3313
|
+
|
|
3314
|
+
# Convert codec code to a FourCC code using the following byte
|
|
3315
|
+
# operations.
|
|
3316
|
+
#
|
|
3317
|
+
# fourCC = ((codecCode >> 24) & 0xff,
|
|
3318
|
+
# (codecCode >> 16) & 0xff,
|
|
3319
|
+
# (codecCode >> 8) & 0xff,
|
|
3320
|
+
# codecCode & 0xff)
|
|
3321
|
+
#
|
|
3322
|
+
pixelFormat4CC = ''.join(
|
|
3323
|
+
[chr((codecType >> bits) & 0xff) for bits in (24, 16, 8, 0)])
|
|
3324
|
+
|
|
3325
|
+
# Get the range of supported framerate, use the largest since the
|
|
3326
|
+
# ranges are rarely variable within a format.
|
|
3327
|
+
frameRateRange = _format.videoSupportedFrameRateRanges()[0]
|
|
3328
|
+
frameRateMax = frameRateRange.maxFrameRate()
|
|
3329
|
+
# frameRateMin = frameRateRange.minFrameRate() # don't use for now
|
|
3330
|
+
|
|
3331
|
+
# Create a new camera descriptor
|
|
3332
|
+
thisCamInfo = CameraInfo(
|
|
3333
|
+
index=devIdx,
|
|
3334
|
+
name=cameraName,
|
|
3335
|
+
pixelFormat=pixelFormat4CC, # macs only use pixel format
|
|
3336
|
+
codecFormat=CAMERA_NULL_VALUE,
|
|
3337
|
+
frameSize=(int(frameWidth), int(frameHeight)),
|
|
3338
|
+
frameRate=frameRateMax,
|
|
3339
|
+
cameraAPI=CAMERA_API_AVFOUNDATION,
|
|
3340
|
+
cameraLib="ffpyplayer",
|
|
3341
|
+
)
|
|
3342
|
+
|
|
3343
|
+
supportedFormats.append(thisCamInfo)
|
|
3344
|
+
|
|
3345
|
+
devIdx += 1
|
|
3346
|
+
|
|
3347
|
+
# add to output dictionary
|
|
3348
|
+
videoDevices[cameraName] = supportedFormats
|
|
3349
|
+
|
|
3350
|
+
return videoDevices
|
|
3351
|
+
|
|
3352
|
+
|
|
3353
|
+
def _getCameraInfoWindows():
|
|
3354
|
+
"""Get a list of capabilities for the specified associated with a camera
|
|
3355
|
+
attached to the system.
|
|
3356
|
+
|
|
3357
|
+
This is used by `getCameraInfo()` for querying camera details on Windows.
|
|
3358
|
+
Don't call this function directly unless testing.
|
|
3359
|
+
|
|
3360
|
+
Returns
|
|
3361
|
+
-------
|
|
3362
|
+
list of CameraInfo
|
|
3363
|
+
List of camera descriptors.
|
|
3364
|
+
|
|
3365
|
+
"""
|
|
3366
|
+
if platform.system() != 'Windows':
|
|
3367
|
+
raise OSError(
|
|
3368
|
+
"Cannot query cameras with this function, platform not 'Windows'.")
|
|
3369
|
+
|
|
3370
|
+
# FFPyPlayer can query the OS via DirectShow for Windows cameras
|
|
3371
|
+
from ffpyplayer.tools import list_dshow_devices
|
|
3372
|
+
videoDevs, _, names = list_dshow_devices()
|
|
3373
|
+
|
|
3374
|
+
# get all the supported modes for the camera
|
|
3375
|
+
videoDevices = {}
|
|
3376
|
+
|
|
3377
|
+
# iterate over names
|
|
3378
|
+
devIndex = 0
|
|
3379
|
+
for devURI in videoDevs.keys():
|
|
3380
|
+
supportedFormats = []
|
|
3381
|
+
cameraName = names[devURI]
|
|
3382
|
+
for _format in videoDevs[devURI]:
|
|
3383
|
+
pixelFormat, codecFormat, frameSize, frameRateRng = _format
|
|
3384
|
+
_, frameRateMax = frameRateRng
|
|
3385
|
+
temp = CameraInfo(
|
|
3386
|
+
index=devIndex,
|
|
3387
|
+
name=cameraName,
|
|
3388
|
+
pixelFormat=pixelFormat,
|
|
3389
|
+
codecFormat=codecFormat,
|
|
3390
|
+
frameSize=frameSize,
|
|
3391
|
+
frameRate=frameRateMax,
|
|
3392
|
+
cameraAPI=CAMERA_API_DIRECTSHOW,
|
|
3393
|
+
cameraLib="ffpyplayer",
|
|
3394
|
+
)
|
|
3395
|
+
supportedFormats.append(temp)
|
|
3396
|
+
devIndex += 1
|
|
3397
|
+
|
|
3398
|
+
videoDevices[names[devURI]] = supportedFormats
|
|
3399
|
+
|
|
3400
|
+
return videoDevices
|
|
3401
|
+
|
|
3402
|
+
|
|
3403
|
+
# Mapping for platform specific camera getter functions used by `getCameras`.
|
|
3404
|
+
_cameraGetterFuncTbl = {
|
|
3405
|
+
'Darwin': _getCameraInfoMacOS,
|
|
3406
|
+
'Windows': _getCameraInfoWindows
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
|
|
3410
|
+
def getCameras():
|
|
3411
|
+
"""Get information about installed cameras and their formats on this system.
|
|
3412
|
+
|
|
3413
|
+
Use `getCameraDescriptions` to get a mapping or list of human-readable
|
|
3414
|
+
camera formats.
|
|
3415
|
+
|
|
3416
|
+
Returns
|
|
3417
|
+
-------
|
|
3418
|
+
dict
|
|
3419
|
+
Mapping where camera names (`str`) are keys and values are and array of
|
|
3420
|
+
`CameraInfo` objects.
|
|
3421
|
+
|
|
3422
|
+
"""
|
|
3423
|
+
systemName = platform.system() # get the system name
|
|
3424
|
+
|
|
3425
|
+
# lookup the function for the given platform
|
|
3426
|
+
getCamerasFunc = _cameraGetterFuncTbl.get(systemName, None)
|
|
3427
|
+
if getCamerasFunc is None: # if unsupported
|
|
3428
|
+
raise OSError(
|
|
3429
|
+
"Cannot get cameras, unsupported platform '{}'.".format(
|
|
3430
|
+
systemName))
|
|
3431
|
+
|
|
3432
|
+
return getCamerasFunc()
|
|
3433
|
+
|
|
3434
|
+
|
|
3435
|
+
def getCameraDescriptions(collapse=False):
|
|
3436
|
+
"""Get a mapping or list of camera descriptions.
|
|
3437
|
+
|
|
3438
|
+
Camera descriptions are a compact way of representing camera settings and
|
|
3439
|
+
formats. Description strings can be used to specify which camera device and
|
|
3440
|
+
format to use with it to the `Camera` class.
|
|
3441
|
+
|
|
3442
|
+
Descriptions have the following format (example)::
|
|
3443
|
+
|
|
3444
|
+
'[Live! Cam Sync 1080p] 160x120@30fps, mjpeg'
|
|
3445
|
+
|
|
3446
|
+
This shows a specific camera format for the 'Live! Cam Sync 1080p' webcam
|
|
3447
|
+
which supports 160x120 frame size at 30 frames per second. The last value
|
|
3448
|
+
is the codec or pixel format used to decode the stream. Different pixel
|
|
3449
|
+
formats and codecs vary in performance.
|
|
3450
|
+
|
|
3451
|
+
Parameters
|
|
3452
|
+
----------
|
|
3453
|
+
collapse : bool
|
|
3454
|
+
Return camera information as string descriptions instead of `CameraInfo`
|
|
3455
|
+
objects. This provides a more compact way of representing camera formats
|
|
3456
|
+
in a (reasonably) human-readable format.
|
|
3457
|
+
|
|
3458
|
+
Returns
|
|
3459
|
+
-------
|
|
3460
|
+
dict or list
|
|
3461
|
+
Mapping (`dict`) of camera descriptions, where keys are camera names
|
|
3462
|
+
(`str`) and values are a `list` of format description strings associated
|
|
3463
|
+
with the camera. If `collapse=True`, all descriptions will be returned
|
|
3464
|
+
in a single flat list. This might be more useful for specifying camera
|
|
3465
|
+
formats from a single GUI list control.
|
|
3466
|
+
|
|
3467
|
+
"""
|
|
3468
|
+
connectedCameras = getCameras()
|
|
3469
|
+
|
|
3470
|
+
cameraDescriptions = {}
|
|
3471
|
+
for devName, formats in connectedCameras.items():
|
|
3472
|
+
cameraDescriptions[devName] = [
|
|
3473
|
+
_format.description() for _format in formats]
|
|
3474
|
+
|
|
3475
|
+
if not collapse:
|
|
3476
|
+
return cameraDescriptions
|
|
3477
|
+
|
|
3478
|
+
# collapse to a list if requested
|
|
3479
|
+
collapsedList = []
|
|
3480
|
+
for _, formatDescs in cameraDescriptions.items():
|
|
3481
|
+
collapsedList.extend(formatDescs)
|
|
3482
|
+
|
|
3483
|
+
return collapsedList
|
|
3484
|
+
|
|
3485
|
+
|
|
3486
|
+
def getFormatsForDevice(device):
|
|
3487
|
+
"""Get a list of formats available for the given device.
|
|
3488
|
+
|
|
3489
|
+
Parameters
|
|
3490
|
+
----------
|
|
3491
|
+
device : str or int
|
|
3492
|
+
Name or index of the device
|
|
3493
|
+
|
|
3494
|
+
Returns
|
|
3495
|
+
-------
|
|
3496
|
+
list
|
|
3497
|
+
List of formats, specified as strings in the format
|
|
3498
|
+
`{width}x{height}@{frame rate}fps`
|
|
3499
|
+
"""
|
|
3500
|
+
# get all devices
|
|
3501
|
+
connectedCameras = getCameras()
|
|
3502
|
+
# get formats for this device
|
|
3503
|
+
formats = connectedCameras.get(device, [])
|
|
3504
|
+
# sanitize
|
|
3505
|
+
formats = [f"{_format.frameSize[0]}x{_format.frameSize[1]}@{_format.frameRate}fps" for _format in formats]
|
|
3506
|
+
|
|
3507
|
+
return formats
|
|
3508
|
+
|
|
3509
|
+
|
|
3510
|
+
def getAllCameraInterfaces():
|
|
3511
|
+
"""Get a list of all camera interfaces supported by the system.
|
|
3512
|
+
|
|
3513
|
+
Returns
|
|
3514
|
+
-------
|
|
3515
|
+
dict
|
|
3516
|
+
Mapping of camera interface class names and references to the class.
|
|
3517
|
+
|
|
3518
|
+
"""
|
|
3519
|
+
# get all classes in this module
|
|
3520
|
+
classes = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
|
3521
|
+
|
|
3522
|
+
# filter for classes that are camera interfaces
|
|
3523
|
+
cameraInterfaces = {}
|
|
3524
|
+
for name, cls in classes:
|
|
3525
|
+
if issubclass(cls, CameraDevice):
|
|
3526
|
+
cameraInterfaces[name] = cls
|
|
3527
|
+
|
|
3528
|
+
return cameraInterfaces
|
|
3529
|
+
|
|
3530
|
+
|
|
3531
|
+
def getOpenCameras():
|
|
3532
|
+
"""Get a list of all open cameras.
|
|
3533
|
+
|
|
3534
|
+
Returns
|
|
3535
|
+
-------
|
|
3536
|
+
list
|
|
3537
|
+
List of references to open camera objects.
|
|
3538
|
+
|
|
3539
|
+
"""
|
|
3540
|
+
global _openCameras
|
|
3541
|
+
|
|
3542
|
+
return _openCameras.copy()
|
|
3543
|
+
|
|
3544
|
+
|
|
3545
|
+
def closeAllOpenCameras():
|
|
3546
|
+
"""Close all open cameras.
|
|
3547
|
+
|
|
3548
|
+
This closes all open cameras and releases any resources associated with
|
|
3549
|
+
them. This should only be called before exiting the application or after you
|
|
3550
|
+
are done using the cameras.
|
|
3551
|
+
|
|
3552
|
+
This is automatically called when the application exits to cleanly free up
|
|
3553
|
+
resources, as it is registered with `atexit` when the module is imported.
|
|
3554
|
+
|
|
3555
|
+
Returns
|
|
3556
|
+
-------
|
|
3557
|
+
int
|
|
3558
|
+
Number of cameras closed. Useful for debugging to ensure all cameras
|
|
3559
|
+
were closed.
|
|
3560
|
+
|
|
3561
|
+
"""
|
|
3562
|
+
global _openCameras
|
|
3563
|
+
|
|
3564
|
+
numCameras = len(_openCameras)
|
|
3565
|
+
for cam in _openCameras:
|
|
3566
|
+
cam.close()
|
|
3567
|
+
|
|
3568
|
+
_openCameras.clear()
|
|
3569
|
+
|
|
3570
|
+
return numCameras
|
|
3571
|
+
|
|
3572
|
+
|
|
3573
|
+
def renderVideo(outputFile, videoFile, audioFile=None, removeFiles=False):
|
|
3574
|
+
"""Render a video.
|
|
3575
|
+
|
|
3576
|
+
Combine visual and audio streams into a single movie file. This is used
|
|
3577
|
+
mainly for compositing video and audio data for the camera. Video and audio
|
|
3578
|
+
should have roughly the same duration.
|
|
3579
|
+
|
|
3580
|
+
This is a legacy function used originally for compositing video and audio
|
|
3581
|
+
data from the camera. It is not used anymore internally, but is kept here
|
|
3582
|
+
for reference and may be removed in the future. If you need to composite
|
|
3583
|
+
video and audio data, use `movietools.addAudioToMovie` instead.
|
|
3584
|
+
|
|
3585
|
+
Parameters
|
|
3586
|
+
----------
|
|
3587
|
+
outputFile : str
|
|
3588
|
+
Filename to write the movie to. Should have the extension of the file
|
|
3589
|
+
too.
|
|
3590
|
+
videoFile : str
|
|
3591
|
+
Video file path.
|
|
3592
|
+
audioFile : str or None
|
|
3593
|
+
Audio file path. If not provided the movie file will simply be copied
|
|
3594
|
+
to `outFile`.
|
|
3595
|
+
removeFiles : bool
|
|
3596
|
+
If `True`, the video (`videoFile`) and audio (`audioFile`) files will be
|
|
3597
|
+
deleted after the movie is rendered.
|
|
3598
|
+
|
|
3599
|
+
Returns
|
|
3600
|
+
-------
|
|
3601
|
+
int
|
|
3602
|
+
Size of the resulting file in bytes.
|
|
3603
|
+
|
|
3604
|
+
"""
|
|
3605
|
+
# if no audio file, just copy the video file
|
|
3606
|
+
if audioFile is None:
|
|
3607
|
+
import shutil
|
|
3608
|
+
shutil.copyfile(videoFile, outputFile)
|
|
3609
|
+
if removeFiles:
|
|
3610
|
+
os.remove(videoFile) # delete the old movie file
|
|
3611
|
+
return os.path.getsize(outputFile)
|
|
3612
|
+
|
|
3613
|
+
# merge video and audio, now using the new `movietools` module
|
|
3614
|
+
movietools.addAudioToMovie(
|
|
3615
|
+
videoFile,
|
|
3616
|
+
audioFile,
|
|
3617
|
+
outputFile,
|
|
3618
|
+
useThreads=False, # didn't use this before
|
|
3619
|
+
removeFiles=removeFiles)
|
|
3620
|
+
|
|
3621
|
+
return os.path.getsize(outputFile)
|
|
3622
|
+
|
|
3623
|
+
|
|
3624
|
+
# ------------------------------------------------------------------------------
|
|
3625
|
+
# Cleanup functions
|
|
3626
|
+
#
|
|
3627
|
+
# These functions are used to clean up resources when the application exits,
|
|
3628
|
+
# usually unexpectedly. This helps to ensure hardware interfaces are closed
|
|
3629
|
+
# and resources are freed up as best we can.
|
|
3630
|
+
#
|
|
3631
|
+
|
|
3632
|
+
import atexit
|
|
3633
|
+
|
|
3634
|
+
|
|
3635
|
+
def _closeAllCaptureInterfaces():
|
|
3636
|
+
"""Close all open capture interfaces.
|
|
3637
|
+
|
|
3638
|
+
This is registered with `atexit` to ensure that all open cameras are closed
|
|
3639
|
+
when the application exits. This is important to free up resources and
|
|
3640
|
+
ensure that cameras are not left open unintentionally.
|
|
3641
|
+
|
|
3642
|
+
"""
|
|
3643
|
+
global _openCaptureInterfaces
|
|
3644
|
+
|
|
3645
|
+
for cap in _openCaptureInterfaces.copy():
|
|
3646
|
+
try:
|
|
3647
|
+
cap.close()
|
|
3648
|
+
except Exception as e:
|
|
3649
|
+
logging.error(f"Error closing camera interface {cap}: {e}")
|
|
3650
|
+
|
|
3651
|
+
|
|
3652
|
+
# Register the function to close all cameras on exit
|
|
3653
|
+
atexit.register(_closeAllCaptureInterfaces)
|
|
3654
|
+
|
|
3655
|
+
# ------------------------------------------------------------------------------
|
|
3656
|
+
if __name__ == "__main__":
|
|
3657
|
+
pass
|