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,4910 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Various math functions for working with vectors, matrices, and quaternions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Part of the PsychoPy library
|
|
8
|
+
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2025 Open Science Tools Ltd.
|
|
9
|
+
# Distributed under the terms of the GNU General Public License (GPL).
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'VEC_AXES', # constants
|
|
13
|
+
'VEC_AXIS_FORWARD',
|
|
14
|
+
'VEC_AXIS_BACKWARD',
|
|
15
|
+
'VEC_AXIS_UP',
|
|
16
|
+
'VEC_AXIS_DOWN',
|
|
17
|
+
'VEC_AXIS_RIGHT',
|
|
18
|
+
'VEC_AXIS_LEFT',
|
|
19
|
+
'VEC_AXIS_POS_X',
|
|
20
|
+
'VEC_AXIS_NEG_X',
|
|
21
|
+
'VEC_AXIS_POS_Y',
|
|
22
|
+
'VEC_AXIS_NEG_Y',
|
|
23
|
+
'VEC_AXIS_POS_Z',
|
|
24
|
+
'VEC_AXIS_NEG_Z',
|
|
25
|
+
'RigidBodyPose', # rigid body pose class
|
|
26
|
+
'BoundingBox',
|
|
27
|
+
'length', # vector functions
|
|
28
|
+
'normalize',
|
|
29
|
+
'orthogonalize',
|
|
30
|
+
'reflect',
|
|
31
|
+
'dot',
|
|
32
|
+
'cross',
|
|
33
|
+
'project',
|
|
34
|
+
'lerp',
|
|
35
|
+
'distance',
|
|
36
|
+
'perp',
|
|
37
|
+
'bisector',
|
|
38
|
+
'angleTo',
|
|
39
|
+
'sortClockwise',
|
|
40
|
+
'surfaceNormal',
|
|
41
|
+
'surfaceBitangent',
|
|
42
|
+
'surfaceTangent',
|
|
43
|
+
'vertexNormal',
|
|
44
|
+
'fixTangentHandedness',
|
|
45
|
+
'fitBBox',
|
|
46
|
+
'computeBBoxCorners',
|
|
47
|
+
'intersectRayPlane',
|
|
48
|
+
'intersectRaySphere',
|
|
49
|
+
'intersectRayAABB',
|
|
50
|
+
'intersectRayOBB',
|
|
51
|
+
'intersectRayTriangle',
|
|
52
|
+
'ortho3Dto2D',
|
|
53
|
+
'articulate',
|
|
54
|
+
'slerp', # quaternion functions
|
|
55
|
+
'quatToAxisAngle',
|
|
56
|
+
'quatFromAxisAngle',
|
|
57
|
+
'quatYawPitchRoll',
|
|
58
|
+
'quatMagnitude',
|
|
59
|
+
'multQuat',
|
|
60
|
+
'invertQuat',
|
|
61
|
+
'applyQuat',
|
|
62
|
+
'accumQuat',
|
|
63
|
+
'alignTo',
|
|
64
|
+
'matrixToQuat',
|
|
65
|
+
'identityMatrix', # matrix functions
|
|
66
|
+
'quatToMatrix',
|
|
67
|
+
'scaleMatrix',
|
|
68
|
+
'rotationMatrix',
|
|
69
|
+
'translationMatrix',
|
|
70
|
+
'invertMatrix',
|
|
71
|
+
'multMatrix',
|
|
72
|
+
'concatenate',
|
|
73
|
+
'matrixFromEulerAngles',
|
|
74
|
+
'isOrthogonal',
|
|
75
|
+
'isAffine',
|
|
76
|
+
'applyMatrix',
|
|
77
|
+
'posOriToMatrix',
|
|
78
|
+
'transform',
|
|
79
|
+
'scale',
|
|
80
|
+
'normalMatrix',
|
|
81
|
+
'forwardProject',
|
|
82
|
+
'reverseProject',
|
|
83
|
+
'lookAt',
|
|
84
|
+
'zeroFix', # misc functions
|
|
85
|
+
'lensCorrection',
|
|
86
|
+
'lensCorrectionSpherical',
|
|
87
|
+
'infrange',
|
|
88
|
+
'setDefaultPrecision'
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
import numpy as np
|
|
93
|
+
import functools
|
|
94
|
+
import itertools
|
|
95
|
+
|
|
96
|
+
VEC_AXES = { # mapping of axis names to vectors
|
|
97
|
+
'+x': (1, 0, 0), '-x': (-1, 0, 0),
|
|
98
|
+
'+y': (0, 1, 0), '-y': (0, -1, 0),
|
|
99
|
+
'+z': (0, 0, 1), '-z': (0, 0, -1)}
|
|
100
|
+
|
|
101
|
+
# vectors for common axes
|
|
102
|
+
VEC_AXIS_BACKWARD = VEC_AXIS_POS_Z = VEC_AXES['+z']
|
|
103
|
+
VEC_AXIS_FORWARD = VEC_AXIS_NEG_Z = VEC_AXES['-z']
|
|
104
|
+
VEC_AXIS_UP = VEC_AXIS_POS_Y = VEC_AXES['+y']
|
|
105
|
+
VEC_AXIS_DOWN = VEC_AXIS_NEG_Y = VEC_AXES['-y']
|
|
106
|
+
VEC_AXIS_RIGHT = VEC_AXIS_POS_X = VEC_AXES['+x']
|
|
107
|
+
VEC_AXIS_LEFT = VEC_AXIS_NEG_X = VEC_AXES['-x']
|
|
108
|
+
|
|
109
|
+
DEFAULT_DTYPE = float
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def setDefaultPrecision(dtype='float64'):
|
|
113
|
+
"""Set the default precision for math functions.
|
|
114
|
+
|
|
115
|
+
Once set, all math functions in this module will use the specified data type
|
|
116
|
+
for computations in successive calls. This is useful when you want to ensure
|
|
117
|
+
a specific precision for all math operations.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
dtype : dtype or str
|
|
122
|
+
Data type for computations can either be 'float32' or 'float64'.
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
global DEFAULT_DTYPE
|
|
126
|
+
DEFAULT_DTYPE = np.dtype(dtype).type
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ------------------------------------------------------------------------------
|
|
130
|
+
# Classes for working with rigid body poses
|
|
131
|
+
#
|
|
132
|
+
|
|
133
|
+
class RigidBodyPose:
|
|
134
|
+
"""Class for representing rigid body poses.
|
|
135
|
+
|
|
136
|
+
This class is an abstract representation of a rigid body pose, where the
|
|
137
|
+
position of the body in a scene is represented by a vector/coordinate and
|
|
138
|
+
the orientation with a quaternion. Pose can be manipulated and interacted
|
|
139
|
+
with using class methods and attributes. Rigid body poses assume a
|
|
140
|
+
right-handed coordinate system (-Z is forward and +Y is up).
|
|
141
|
+
|
|
142
|
+
Poses can be converted to 4x4 transformation matrices with `getModelMatrix`.
|
|
143
|
+
One can use these matrices when rendering to transform the vertices of a
|
|
144
|
+
model associated with the pose by passing them to OpenGL. Matrices are
|
|
145
|
+
cached internally to avoid recomputing them if `pos` and `ori` attributes
|
|
146
|
+
have not been updated.
|
|
147
|
+
|
|
148
|
+
Operators `*` and `~` can be used on `RigidBodyPose` objects to combine and
|
|
149
|
+
invert poses. For instance, you can multiply (`*`) poses to get a new pose
|
|
150
|
+
which is the combination of both orientations and translations by::
|
|
151
|
+
|
|
152
|
+
newPose = rb1 * rb2
|
|
153
|
+
|
|
154
|
+
Likewise, a pose can be inverted by using the `~` operator::
|
|
155
|
+
|
|
156
|
+
invPose = ~rb
|
|
157
|
+
|
|
158
|
+
Multiplying a pose by its inverse will result in an identity pose with no
|
|
159
|
+
translation and default orientation where `pos=[0, 0, 0]` and
|
|
160
|
+
`ori=[0, 0, 0, 1]`::
|
|
161
|
+
|
|
162
|
+
identityPose = ~rb * rb
|
|
163
|
+
|
|
164
|
+
Warnings
|
|
165
|
+
--------
|
|
166
|
+
This class is experimental and may result in undefined behavior.
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
def __init__(self, pos=(0., 0., 0.), ori=(0., 0., 0., 1.), dtype=None):
|
|
170
|
+
"""
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
pos : array_like
|
|
174
|
+
Position vector `[x, y, z]` for the origin of the rigid body.
|
|
175
|
+
ori : array_like
|
|
176
|
+
Orientation quaternion `[x, y, z, w]` where `x`, `y`, `z` are
|
|
177
|
+
imaginary and `w` is real.
|
|
178
|
+
dtype : dtype or str, optional
|
|
179
|
+
Data type for computations can either be 'float32' or 'float64'.
|
|
180
|
+
Default is `None` which uses the default data configured by
|
|
181
|
+
`setDefaultPrecision`.
|
|
182
|
+
|
|
183
|
+
"""
|
|
184
|
+
# set the data type for computations
|
|
185
|
+
self._dtype = \
|
|
186
|
+
np.dtype(dtype).type if dtype is not None else DEFAULT_DTYPE
|
|
187
|
+
|
|
188
|
+
# position and orientation
|
|
189
|
+
self._pos = np.ascontiguousarray(pos, dtype=self.dtype)
|
|
190
|
+
self._ori = np.ascontiguousarray(ori, dtype=self.dtype)
|
|
191
|
+
|
|
192
|
+
self._modelMatrix = posOriToMatrix(
|
|
193
|
+
self._pos, self._ori, dtype=self.dtype)
|
|
194
|
+
|
|
195
|
+
# computed only if needed
|
|
196
|
+
self._normalMatrix = np.zeros((4, 4), dtype=self.dtype, order='C')
|
|
197
|
+
self._invModelMatrix = np.zeros((4, 4), dtype=self.dtype, order='C')
|
|
198
|
+
self._viewMatrix = np.zeros((4, 4), dtype=self.dtype, order='C')
|
|
199
|
+
self._invViewMatrix = np.zeros((4, 4), dtype=self.dtype, order='C')
|
|
200
|
+
|
|
201
|
+
# additional useful vectors
|
|
202
|
+
self._at = np.zeros((3,), dtype=self.dtype, order='C')
|
|
203
|
+
self._up = np.zeros((3,), dtype=self.dtype, order='C')
|
|
204
|
+
self._viewAxes = np.array( # cache for view matrix calculations
|
|
205
|
+
[VEC_AXIS_FORWARD, VEC_AXIS_UP], dtype=self.dtype, order='C')
|
|
206
|
+
|
|
207
|
+
# compute matrices only if `pos` and `ori` attributes have been updated,
|
|
208
|
+
# we track the state of the matrices with these flags
|
|
209
|
+
self._cacheFlags = {
|
|
210
|
+
'model': True, # already computed
|
|
211
|
+
'imodel': False,
|
|
212
|
+
'normal': False,
|
|
213
|
+
'view': False,
|
|
214
|
+
'iview': False
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
self.pos = pos
|
|
218
|
+
self.ori = ori
|
|
219
|
+
|
|
220
|
+
self._bounds = None
|
|
221
|
+
|
|
222
|
+
def __repr__(self):
|
|
223
|
+
return 'RigidBodyPose(pos={}, ori={})'.format(self.pos, self.ori)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def dtype(self):
|
|
227
|
+
"""Data type used for computations and arrays (`numpy.dtype`).
|
|
228
|
+
|
|
229
|
+
Cannot be changed after object creation.
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
return self._dtype
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def bounds(self):
|
|
236
|
+
"""Bounding box associated with this pose.
|
|
237
|
+
"""
|
|
238
|
+
return self._bounds
|
|
239
|
+
|
|
240
|
+
@bounds.setter
|
|
241
|
+
def bounds(self, value):
|
|
242
|
+
self._bounds = value
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def pos(self):
|
|
246
|
+
"""Position vector (X, Y, Z).
|
|
247
|
+
"""
|
|
248
|
+
return self._pos
|
|
249
|
+
|
|
250
|
+
@pos.setter
|
|
251
|
+
def pos(self, value):
|
|
252
|
+
self._pos = np.ascontiguousarray(value, dtype=self.dtype)
|
|
253
|
+
self._cacheFlags = dict.fromkeys(self._cacheFlags.keys(), True)
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def ori(self):
|
|
257
|
+
"""Orientation quaternion (X, Y, Z, W).
|
|
258
|
+
"""
|
|
259
|
+
return self._ori
|
|
260
|
+
|
|
261
|
+
@ori.setter
|
|
262
|
+
def ori(self, value):
|
|
263
|
+
self._ori = np.ascontiguousarray(value, dtype=self.dtype)
|
|
264
|
+
self._cacheFlags = dict.fromkeys(self._cacheFlags.keys(), True)
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def posOri(self):
|
|
268
|
+
"""The position (x, y, z) and orientation (x, y, z, w).
|
|
269
|
+
"""
|
|
270
|
+
return self._pos, self._ori
|
|
271
|
+
|
|
272
|
+
@posOri.setter
|
|
273
|
+
def posOri(self, value):
|
|
274
|
+
self._pos = np.ascontiguousarray(value[0], dtype=self.dtype)
|
|
275
|
+
self._ori = np.ascontiguousarray(value[1], dtype=self.dtype)
|
|
276
|
+
self._cacheFlags = dict.fromkeys(self._cacheFlags.keys(), True)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def at(self):
|
|
280
|
+
"""Vector defining the forward direction (-Z) of this pose.
|
|
281
|
+
"""
|
|
282
|
+
# matrix needs update, this need to be too
|
|
283
|
+
if self._cacheFlags['model']:
|
|
284
|
+
self._at = applyQuat(
|
|
285
|
+
self.ori, self._viewAxes[0, :], out=self._at, dtype=self.dtype)
|
|
286
|
+
|
|
287
|
+
return self._at
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def up(self):
|
|
291
|
+
"""Vector defining the up direction (+Y) of this pose.
|
|
292
|
+
"""
|
|
293
|
+
if self._cacheFlags['model']:
|
|
294
|
+
self._up = applyQuat(
|
|
295
|
+
self.ori, self._viewAxes[1, :], out=self._up, dtype=self.dtype)
|
|
296
|
+
|
|
297
|
+
return self._up
|
|
298
|
+
|
|
299
|
+
def __mul__(self, other):
|
|
300
|
+
"""Multiply two poses, combining them to get a new pose.
|
|
301
|
+
"""
|
|
302
|
+
newOri = multQuat(self._ori, other.ori)
|
|
303
|
+
return RigidBodyPose(transform(other.pos, newOri, self._pos), newOri)
|
|
304
|
+
|
|
305
|
+
def __imul__(self, other):
|
|
306
|
+
"""Inplace multiplication. Transforms this pose by another.
|
|
307
|
+
"""
|
|
308
|
+
self._ori = multQuat(self._ori, other.ori)
|
|
309
|
+
self._pos = transform(other.pos, self._ori, self._pos)
|
|
310
|
+
|
|
311
|
+
def copy(self):
|
|
312
|
+
"""Get a new `RigidBodyPose` object which copies the position and
|
|
313
|
+
orientation of this one. Copies are independent and do not reference
|
|
314
|
+
each others data.
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
RigidBodyPose
|
|
319
|
+
Copy of this pose.
|
|
320
|
+
|
|
321
|
+
"""
|
|
322
|
+
return RigidBodyPose(self._pos, self._ori)
|
|
323
|
+
|
|
324
|
+
def isEqual(self, other):
|
|
325
|
+
"""Check if poses have similar orientation and position.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
other : `RigidBodyPose`
|
|
330
|
+
Other pose to compare.
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
bool
|
|
335
|
+
Returns `True` is poses are effectively equal.
|
|
336
|
+
|
|
337
|
+
"""
|
|
338
|
+
return np.isclose(self._pos, other.pos) and \
|
|
339
|
+
np.isclose(self._ori, other.ori)
|
|
340
|
+
|
|
341
|
+
def clear(self):
|
|
342
|
+
"""Clear the pose, setting position and orientation to zero.
|
|
343
|
+
"""
|
|
344
|
+
self._pos.fill(0.0)
|
|
345
|
+
self._ori[:3] = 0.0
|
|
346
|
+
self._ori[3] = 1.0
|
|
347
|
+
self._cacheFlags = dict.fromkeys(a.keys(), True)
|
|
348
|
+
|
|
349
|
+
def setIdentity(self):
|
|
350
|
+
"""Clear rigid body transformations (alias for `clear`).
|
|
351
|
+
"""
|
|
352
|
+
self.clear()
|
|
353
|
+
|
|
354
|
+
def getOriAxisAngle(self, degrees=True):
|
|
355
|
+
"""Get the axis and angle of rotation for the rigid body. Converts the
|
|
356
|
+
orientation defined by the `ori` quaternion to and axis-angle
|
|
357
|
+
representation.
|
|
358
|
+
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
degrees : bool, optional
|
|
362
|
+
Specify ``True`` if `angle` is in degrees, or else it will be
|
|
363
|
+
treated as radians. Default is ``True``.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
tuple
|
|
368
|
+
Axis [rx, ry, rz] and angle.
|
|
369
|
+
|
|
370
|
+
"""
|
|
371
|
+
return quatToAxisAngle(self._ori, degrees, dtype=self.dtype)
|
|
372
|
+
|
|
373
|
+
def setOriAxisAngle(self, axis, angle, degrees=True):
|
|
374
|
+
"""Set the orientation of the rigid body using an `axis` and
|
|
375
|
+
`angle`. This sets the quaternion at `ori`.
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
axis : array_like
|
|
380
|
+
Axis of rotation [rx, ry, rz].
|
|
381
|
+
angle : float
|
|
382
|
+
Angle of rotation.
|
|
383
|
+
degrees : bool, optional
|
|
384
|
+
Specify ``True`` if `angle` is in degrees, or else it will be
|
|
385
|
+
treated as radians. Default is ``True``.
|
|
386
|
+
|
|
387
|
+
"""
|
|
388
|
+
self.ori = quatFromAxisAngle(axis, angle, degrees, dtype=self.dtype)
|
|
389
|
+
|
|
390
|
+
def getYawPitchRoll(self, degrees=True):
|
|
391
|
+
"""Get the yaw, pitch and roll angles for this pose relative to the -Z
|
|
392
|
+
world axis.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
degrees : bool, optional
|
|
397
|
+
Specify ``True`` if `angle` is in degrees, or else it will be
|
|
398
|
+
treated as radians. Default is ``True``.
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
return quatYawPitchRoll(self._ori, degrees, dtype=self.dtype)
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def modelMatrix(self):
|
|
405
|
+
"""Pose as a 4x4 model matrix (read-only)."""
|
|
406
|
+
if not self._cacheFlags['model']:
|
|
407
|
+
return self._modelMatrix
|
|
408
|
+
else:
|
|
409
|
+
return self.getModelMatrix()
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def inverseModelMatrix(self):
|
|
413
|
+
"""Inverse of the pose as a 4x4 model matrix (read-only)."""
|
|
414
|
+
if not self._cacheFlags['imodel']:
|
|
415
|
+
return self._invModelMatrix
|
|
416
|
+
else:
|
|
417
|
+
return self.getModelMatrix(inverse=True)
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def normalMatrix(self):
|
|
421
|
+
"""The 4x4 normal transformation matrix (read-only)."""
|
|
422
|
+
if not self._cacheFlags['normal']:
|
|
423
|
+
return self._normalMatrix
|
|
424
|
+
else:
|
|
425
|
+
return self.getNormalMatrix()
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def viewMatrix(self):
|
|
429
|
+
"""The 4x4 view matrix for this pose (read-only)."""
|
|
430
|
+
if not self._cacheFlags['view']:
|
|
431
|
+
return self._viewMatrix
|
|
432
|
+
else:
|
|
433
|
+
return self.getViewMatrix()
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def inverseViewMatrix(self):
|
|
437
|
+
"""The inverse of the 4x4 view matrix for this pose (read-only)."""
|
|
438
|
+
if not self._cacheFlags['iview']:
|
|
439
|
+
return self._invViewMatrix
|
|
440
|
+
else:
|
|
441
|
+
return self.getViewMatrix(inverse=True)
|
|
442
|
+
|
|
443
|
+
def getNormalMatrix(self, out=None):
|
|
444
|
+
"""Get the present normal matrix.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
out : ndarray or None
|
|
449
|
+
Optional 4x4 array to write values to. Values written are computed
|
|
450
|
+
using 32-bit float precision regardless of the data type of `out`.
|
|
451
|
+
|
|
452
|
+
Returns
|
|
453
|
+
-------
|
|
454
|
+
ndarray
|
|
455
|
+
4x4 normal transformation matrix.
|
|
456
|
+
|
|
457
|
+
"""
|
|
458
|
+
if not self._cacheFlags['normal']:
|
|
459
|
+
return self._normalMatrix
|
|
460
|
+
|
|
461
|
+
self._normalMatrix[:, :] = np.linalg.inv(self.modelMatrix).T
|
|
462
|
+
self._cacheFlags['normal'] = False
|
|
463
|
+
|
|
464
|
+
if out is not None:
|
|
465
|
+
out[:, :] = self._normalMatrix[:, :]
|
|
466
|
+
return out
|
|
467
|
+
|
|
468
|
+
return self._normalMatrix
|
|
469
|
+
|
|
470
|
+
def getModelMatrix(self, inverse=False, out=None):
|
|
471
|
+
"""Get the present rigid body transformation as a 4x4 matrix.
|
|
472
|
+
|
|
473
|
+
Matrices are computed only if the `pos` and `ori` attributes have been
|
|
474
|
+
updated since the last call to `getModelMatrix`. The returned matrix is
|
|
475
|
+
an `ndarray` and row-major.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
inverse : bool, optional
|
|
480
|
+
Return the inverse of the model matrix.
|
|
481
|
+
out : ndarray or None
|
|
482
|
+
Optional 4x4 array to write values to. Values written are computed
|
|
483
|
+
using 32-bit float precision regardless of the data type of `out`.
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
ndarray
|
|
488
|
+
4x4 transformation matrix.
|
|
489
|
+
|
|
490
|
+
Examples
|
|
491
|
+
--------
|
|
492
|
+
Using a rigid body pose to transform something in OpenGL::
|
|
493
|
+
|
|
494
|
+
rb = RigidBodyPose((0, 0, -2)) # 2 meters away from origin
|
|
495
|
+
|
|
496
|
+
# Use `array2pointer` from `psychopy.tools.arraytools` to convert
|
|
497
|
+
# array to something OpenGL accepts.
|
|
498
|
+
mv = array2pointer(rb.modelMatrix)
|
|
499
|
+
|
|
500
|
+
# use the matrix to transform the scene
|
|
501
|
+
glMatrixMode(GL_MODELVIEW)
|
|
502
|
+
glPushMatrix()
|
|
503
|
+
glLoadIdentity()
|
|
504
|
+
glMultTransposeMatrixf(mv)
|
|
505
|
+
|
|
506
|
+
# draw the thing here ...
|
|
507
|
+
|
|
508
|
+
glPopMatrix()
|
|
509
|
+
|
|
510
|
+
"""
|
|
511
|
+
if self._cacheFlags['model']:
|
|
512
|
+
self._modelMatrix = posOriToMatrix(
|
|
513
|
+
self._pos, self._ori,
|
|
514
|
+
out=self._modelMatrix,
|
|
515
|
+
dtype=self.dtype)
|
|
516
|
+
|
|
517
|
+
# all other matrices need update when next accessed
|
|
518
|
+
self._cacheFlags['model'] = False
|
|
519
|
+
self._cacheFlags['imodel'] = True
|
|
520
|
+
self._cacheFlags['normal'] = True
|
|
521
|
+
self._cacheFlags['view'] = True
|
|
522
|
+
self._cacheFlags['iview'] = True
|
|
523
|
+
|
|
524
|
+
if not inverse:
|
|
525
|
+
toReturn = self._modelMatrix
|
|
526
|
+
else:
|
|
527
|
+
if self._cacheFlags['imodel']:
|
|
528
|
+
self._invModelMatrix = invertMatrix(
|
|
529
|
+
self._modelMatrix, out=self._invModelMatrix,
|
|
530
|
+
dtype=self.dtype)
|
|
531
|
+
self._cacheFlags['imodel'] = False
|
|
532
|
+
|
|
533
|
+
toReturn = self._invModelMatrix
|
|
534
|
+
|
|
535
|
+
if out is not None:
|
|
536
|
+
out[:, :] = toReturn[:, :]
|
|
537
|
+
return out
|
|
538
|
+
|
|
539
|
+
return toReturn
|
|
540
|
+
|
|
541
|
+
def getViewMatrix(self, inverse=False, out=None):
|
|
542
|
+
"""Convert this pose into a view matrix.
|
|
543
|
+
|
|
544
|
+
Creates a view matrix which transforms points into eye space using the
|
|
545
|
+
current pose as the eye position in the scene. Furthermore, you can use
|
|
546
|
+
view matrices for rendering shadows if light positions are defined
|
|
547
|
+
as `RigidBodyPose` objects.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
inverse : bool
|
|
552
|
+
Return the inverse of the view matrix. Default is `False`.
|
|
553
|
+
out : ndarray or None
|
|
554
|
+
Optional 4x4 array to write values to. Values written are computed
|
|
555
|
+
using 32-bit float precision regardless of the data type of `out`.
|
|
556
|
+
|
|
557
|
+
Returns
|
|
558
|
+
-------
|
|
559
|
+
ndarray
|
|
560
|
+
4x4 transformation matrix.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
if self._cacheFlags['view']: # needs update?
|
|
564
|
+
# compute the view matrix
|
|
565
|
+
rotMatrix = quatToMatrix(self._ori, dtype=self.dtype)
|
|
566
|
+
transformedAxes = applyMatrix(
|
|
567
|
+
rotMatrix, self._viewAxes,
|
|
568
|
+
dtype=self.dtype)
|
|
569
|
+
|
|
570
|
+
fwdVec = transformedAxes[0, :] + self._pos
|
|
571
|
+
upVec = transformedAxes[1, :]
|
|
572
|
+
|
|
573
|
+
self._viewMatrix = lookAt(
|
|
574
|
+
self._pos, fwdVec, upVec,
|
|
575
|
+
out=self._viewMatrix,
|
|
576
|
+
dtype=self.dtype)
|
|
577
|
+
|
|
578
|
+
self._cacheFlags['view'] = False
|
|
579
|
+
self._cacheFlags['iview'] = True # inverse needs update
|
|
580
|
+
|
|
581
|
+
if not inverse:
|
|
582
|
+
toReturn = self._viewMatrix
|
|
583
|
+
else:
|
|
584
|
+
if self._cacheFlags['iview']:
|
|
585
|
+
self._invViewMatrix = invertMatrix(
|
|
586
|
+
self._viewMatrix,
|
|
587
|
+
out=self._invViewMatrix,
|
|
588
|
+
dtype=self.dtype)
|
|
589
|
+
self._cacheFlags['iview'] = False
|
|
590
|
+
|
|
591
|
+
toReturn = self._invViewMatrix
|
|
592
|
+
|
|
593
|
+
if out is not None:
|
|
594
|
+
out[:, :] = toReturn[:, :]
|
|
595
|
+
return out
|
|
596
|
+
|
|
597
|
+
return toReturn
|
|
598
|
+
|
|
599
|
+
def transform(self, v, out=None):
|
|
600
|
+
"""Transform a vector using this pose.
|
|
601
|
+
|
|
602
|
+
Parameters
|
|
603
|
+
----------
|
|
604
|
+
v : array_like
|
|
605
|
+
Vector to transform [x, y, z].
|
|
606
|
+
out : ndarray or None, optional
|
|
607
|
+
Optional array to write values to. Must have the same shape as
|
|
608
|
+
`v`.
|
|
609
|
+
|
|
610
|
+
Returns
|
|
611
|
+
-------
|
|
612
|
+
ndarray
|
|
613
|
+
Transformed points.
|
|
614
|
+
|
|
615
|
+
"""
|
|
616
|
+
return transform(
|
|
617
|
+
self._pos, self._ori, points=v, out=out, dtype=self.dtype)
|
|
618
|
+
|
|
619
|
+
def transformNormal(self, n):
|
|
620
|
+
"""Rotate a normal vector with respect to this pose.
|
|
621
|
+
|
|
622
|
+
Rotates a normal vector `n` using the orientation quaternion at `ori`.
|
|
623
|
+
|
|
624
|
+
Parameters
|
|
625
|
+
----------
|
|
626
|
+
n : array_like
|
|
627
|
+
Normal to rotate (1-D with length 3).
|
|
628
|
+
|
|
629
|
+
Returns
|
|
630
|
+
-------
|
|
631
|
+
ndarray
|
|
632
|
+
Rotated normal `n`.
|
|
633
|
+
|
|
634
|
+
"""
|
|
635
|
+
pout = np.zeros((3,), dtype=self.dtype)
|
|
636
|
+
pout[:] = n
|
|
637
|
+
t = np.cross(self._ori[:3], n[:3]) * 2.0
|
|
638
|
+
u = np.cross(self._ori[:3], t)
|
|
639
|
+
t *= self._ori[3]
|
|
640
|
+
pout[:3] += t
|
|
641
|
+
pout[:3] += u
|
|
642
|
+
|
|
643
|
+
return pout
|
|
644
|
+
|
|
645
|
+
def __invert__(self):
|
|
646
|
+
"""Operator `~` to invert the pose. Returns a `RigidBodyPose` object.
|
|
647
|
+
"""
|
|
648
|
+
return RigidBodyPose(
|
|
649
|
+
-self._pos, invertQuat(self._ori, dtype=self.dtype))
|
|
650
|
+
|
|
651
|
+
def invert(self):
|
|
652
|
+
"""Invert this pose.
|
|
653
|
+
"""
|
|
654
|
+
self._ori = invertQuat(self._ori, dtype=self.dtype)
|
|
655
|
+
self._pos *= -1.0
|
|
656
|
+
|
|
657
|
+
def inverted(self):
|
|
658
|
+
"""Get a pose which is the inverse of this one.
|
|
659
|
+
|
|
660
|
+
Returns
|
|
661
|
+
-------
|
|
662
|
+
RigidBodyPose
|
|
663
|
+
This pose inverted.
|
|
664
|
+
|
|
665
|
+
"""
|
|
666
|
+
return RigidBodyPose(
|
|
667
|
+
-self._pos, invertQuat(self._ori, dtype=self.dtype))
|
|
668
|
+
|
|
669
|
+
def distanceTo(self, v):
|
|
670
|
+
"""Get the distance to a pose or point in scene units.
|
|
671
|
+
|
|
672
|
+
Parameters
|
|
673
|
+
----------
|
|
674
|
+
v : RigidBodyPose or array_like
|
|
675
|
+
Pose or point [x, y, z] to compute distance to.
|
|
676
|
+
|
|
677
|
+
Returns
|
|
678
|
+
-------
|
|
679
|
+
float
|
|
680
|
+
Distance to `v` from this pose's origin.
|
|
681
|
+
|
|
682
|
+
"""
|
|
683
|
+
if hasattr(v, 'pos'): # v is pose-like object
|
|
684
|
+
targetPos = v.pos
|
|
685
|
+
else:
|
|
686
|
+
targetPos = np.asarray(v[:3])
|
|
687
|
+
|
|
688
|
+
return np.sqrt(np.sum(np.square(targetPos - self.pos)))
|
|
689
|
+
|
|
690
|
+
def interp(self, end, s):
|
|
691
|
+
"""Interpolate between poses.
|
|
692
|
+
|
|
693
|
+
Linear interpolation is used on position (Lerp) while the orientation
|
|
694
|
+
has spherical linear interpolation (Slerp) applied taking the shortest
|
|
695
|
+
arc on the hypersphere.
|
|
696
|
+
|
|
697
|
+
Parameters
|
|
698
|
+
----------
|
|
699
|
+
end : RigidBodyPose
|
|
700
|
+
End pose.
|
|
701
|
+
s : float
|
|
702
|
+
Interpolation factor between interval 0.0 and 1.0.
|
|
703
|
+
|
|
704
|
+
Returns
|
|
705
|
+
-------
|
|
706
|
+
RigidBodyPose
|
|
707
|
+
Rigid body pose whose position and orientation is at `s` between
|
|
708
|
+
this pose and `end`.
|
|
709
|
+
|
|
710
|
+
"""
|
|
711
|
+
if not (hasattr(end, 'pos') and hasattr(end, 'ori')):
|
|
712
|
+
raise TypeError("Object for `end` does not have attributes "
|
|
713
|
+
"`pos` and `ori`.")
|
|
714
|
+
|
|
715
|
+
interpPos = lerp(self._pos, end.pos, s, dtype=self.dtype)
|
|
716
|
+
interpOri = slerp(self._ori, end.ori, s, dtype=self.dtype)
|
|
717
|
+
|
|
718
|
+
return RigidBodyPose(interpPos, interpOri)
|
|
719
|
+
|
|
720
|
+
def alignTo(self, alignTo):
|
|
721
|
+
"""Align this pose to another point or pose.
|
|
722
|
+
|
|
723
|
+
This sets the orientation of this pose to one which orients the forward
|
|
724
|
+
axis towards `alignTo`.
|
|
725
|
+
|
|
726
|
+
Parameters
|
|
727
|
+
----------
|
|
728
|
+
alignTo : array_like or RigidBodyPose
|
|
729
|
+
Position vector [x, y, z] or pose to align to.
|
|
730
|
+
|
|
731
|
+
"""
|
|
732
|
+
if hasattr(alignTo, 'pos'): # v is pose-like object
|
|
733
|
+
targetPos = alignTo.pos
|
|
734
|
+
else:
|
|
735
|
+
targetPos = np.asarray(alignTo[:3]) # handle array_like
|
|
736
|
+
|
|
737
|
+
fwd = np.asarray([0, 0, -1], dtype=self.dtype)
|
|
738
|
+
toTarget = targetPos - self._pos
|
|
739
|
+
invOri = invertQuat(self._ori, dtype=self.dtype)
|
|
740
|
+
invPos = applyQuat(invOri, toTarget, dtype=self.dtype)
|
|
741
|
+
invPos = normalize(invPos, dtype=self.dtype)
|
|
742
|
+
|
|
743
|
+
self.ori = multQuat(
|
|
744
|
+
self._ori, alignTo(fwd, invPos, dtype=self.dtype))
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
class BoundingBox:
|
|
749
|
+
"""Class for representing object bounding boxes.
|
|
750
|
+
|
|
751
|
+
A bounding box is a construct which represents a 3D rectangular volume about
|
|
752
|
+
some pose, defined by its minimum and maximum extents in the reference frame
|
|
753
|
+
of the pose. The axes of the bounding box are aligned to the axes of the
|
|
754
|
+
world or the associated pose.
|
|
755
|
+
|
|
756
|
+
Bounding boxes are primarily used for visibility testing; to determine if
|
|
757
|
+
the extents of an object associated with a pose (eg. the vertices of a
|
|
758
|
+
model) falls completely outside of the viewing frustum. If so, the model can
|
|
759
|
+
be culled during rendering to avoid wasting CPU/GPU resources on objects not
|
|
760
|
+
visible to the viewer.
|
|
761
|
+
|
|
762
|
+
"""
|
|
763
|
+
def __init__(self, extents=None, dtype=None):
|
|
764
|
+
self._dtype = np.dtype(dtype).type if dtype is not None else DEFAULT_DTYPE
|
|
765
|
+
|
|
766
|
+
self._extents = np.zeros((2, 3), self.dtype)
|
|
767
|
+
self._posCorners = np.zeros((8, 4), self.dtype)
|
|
768
|
+
|
|
769
|
+
if extents is not None:
|
|
770
|
+
self._extents[0, :] = extents[0]
|
|
771
|
+
self._extents[1, :] = extents[1]
|
|
772
|
+
else:
|
|
773
|
+
self.clear()
|
|
774
|
+
|
|
775
|
+
self._computeCorners()
|
|
776
|
+
|
|
777
|
+
def _computeCorners(self):
|
|
778
|
+
"""Compute the corners of the bounding box.
|
|
779
|
+
|
|
780
|
+
These values are cached to speed up computations if extents hasn't been
|
|
781
|
+
updated.
|
|
782
|
+
|
|
783
|
+
"""
|
|
784
|
+
for i in range(8):
|
|
785
|
+
self._posCorners[i, 0] = \
|
|
786
|
+
self._extents[1, 0] if (i & 1) else self._extents[0, 0]
|
|
787
|
+
self._posCorners[i, 1] = \
|
|
788
|
+
self._extents[1, 1] if (i & 2) else self._extents[0, 1]
|
|
789
|
+
self._posCorners[i, 2] = \
|
|
790
|
+
self._extents[1, 2] if (i & 4) else self._extents[0, 2]
|
|
791
|
+
self._posCorners[i, 3] = 1.0
|
|
792
|
+
|
|
793
|
+
@property
|
|
794
|
+
def dtype(self):
|
|
795
|
+
"""Data type used for computations and arrays (`numpy.dtype`).
|
|
796
|
+
"""
|
|
797
|
+
# we use 32-bit float precision for all computations, this will be
|
|
798
|
+
# settable in the future
|
|
799
|
+
return self._dtype
|
|
800
|
+
|
|
801
|
+
@property
|
|
802
|
+
def isValid(self):
|
|
803
|
+
"""`True` if the bounding box is valid."""
|
|
804
|
+
return np.all(self._extents[0, :] <= self._extents[1, :])
|
|
805
|
+
|
|
806
|
+
@property
|
|
807
|
+
def extents(self):
|
|
808
|
+
return self._extents
|
|
809
|
+
|
|
810
|
+
@extents.setter
|
|
811
|
+
def extents(self, value):
|
|
812
|
+
self._extents[0, :] = value[0]
|
|
813
|
+
self._extents[1, :] = value[1]
|
|
814
|
+
self._computeCorners()
|
|
815
|
+
|
|
816
|
+
def fit(self, verts):
|
|
817
|
+
"""Fit the bounding box to vertices."""
|
|
818
|
+
np.amin(verts, axis=0, out=self._extents[0])
|
|
819
|
+
np.amax(verts, axis=0, out=self._extents[1])
|
|
820
|
+
self._computeCorners()
|
|
821
|
+
|
|
822
|
+
def clear(self):
|
|
823
|
+
"""Clear a bounding box, invalidating it."""
|
|
824
|
+
self._extents[0, :] = np.finfo(self.dtype).max
|
|
825
|
+
self._extents[1, :] = np.finfo(self.dtype).min
|
|
826
|
+
self._computeCorners()
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
# ------------------------------------------------------------------------------
|
|
830
|
+
# Vector Operations
|
|
831
|
+
#
|
|
832
|
+
|
|
833
|
+
def length(v, squared=False, out=None, dtype=None):
|
|
834
|
+
"""Get the length of a vector.
|
|
835
|
+
|
|
836
|
+
Parameters
|
|
837
|
+
----------
|
|
838
|
+
v : array_like
|
|
839
|
+
Vector to normalize, can be Nx2, Nx3, or Nx4. If a 2D array is
|
|
840
|
+
specified, rows are treated as separate vectors.
|
|
841
|
+
squared : bool, optional
|
|
842
|
+
If ``True`` the squared length is returned. The default is ``False``.
|
|
843
|
+
out : ndarray, optional
|
|
844
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
845
|
+
output if `out` was not specified.
|
|
846
|
+
dtype : dtype or str, optional
|
|
847
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
848
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
849
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
850
|
+
|
|
851
|
+
Returns
|
|
852
|
+
-------
|
|
853
|
+
float or ndarray
|
|
854
|
+
Length of vector `v`.
|
|
855
|
+
|
|
856
|
+
"""
|
|
857
|
+
if out is None:
|
|
858
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
859
|
+
toReturn = np.array(v, dtype=dtype)
|
|
860
|
+
else:
|
|
861
|
+
toReturn = out
|
|
862
|
+
|
|
863
|
+
v = np.asarray(v, dtype=dtype)
|
|
864
|
+
|
|
865
|
+
if v.ndim == 2:
|
|
866
|
+
assert v.shape[1] <= 4
|
|
867
|
+
toReturn = np.zeros((v.shape[0],), dtype=dtype) if out is None else out
|
|
868
|
+
v2d, vr = np.atleast_2d(v, toReturn) # 2d view of array
|
|
869
|
+
if squared:
|
|
870
|
+
vr[:, :] = np.sum(np.square(v2d), axis=1)
|
|
871
|
+
else:
|
|
872
|
+
vr[:, :] = np.sqrt(np.sum(np.square(v2d), axis=1))
|
|
873
|
+
elif v.ndim == 1:
|
|
874
|
+
assert v.shape[0] <= 4
|
|
875
|
+
if squared:
|
|
876
|
+
toReturn = np.sum(np.square(v))
|
|
877
|
+
else:
|
|
878
|
+
toReturn = np.sqrt(np.sum(np.square(v)))
|
|
879
|
+
else:
|
|
880
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
881
|
+
|
|
882
|
+
return toReturn
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def normalize(v, out=None, dtype=None):
|
|
886
|
+
"""Normalize a vector or quaternion.
|
|
887
|
+
|
|
888
|
+
v : array_like
|
|
889
|
+
Vector to normalize, can be Nx2, Nx3, or Nx4. If a 2D array is
|
|
890
|
+
specified, rows are treated as separate vectors. All vectors should have
|
|
891
|
+
nonzero length.
|
|
892
|
+
out : ndarray, optional
|
|
893
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
894
|
+
output if `out` was not specified.
|
|
895
|
+
dtype : dtype or str, optional
|
|
896
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
897
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
898
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
899
|
+
|
|
900
|
+
Returns
|
|
901
|
+
-------
|
|
902
|
+
ndarray
|
|
903
|
+
Normalized vector `v`.
|
|
904
|
+
|
|
905
|
+
Notes
|
|
906
|
+
-----
|
|
907
|
+
* If the vector has length is zero, a vector of all zeros is returned after
|
|
908
|
+
normalization.
|
|
909
|
+
|
|
910
|
+
Examples
|
|
911
|
+
--------
|
|
912
|
+
Normalize a vector::
|
|
913
|
+
|
|
914
|
+
v = [1., 2., 3., 4.]
|
|
915
|
+
vn = normalize(v)
|
|
916
|
+
|
|
917
|
+
The `normalize` function is vectorized. It's considerably faster to
|
|
918
|
+
normalize large arrays of vectors than to call `normalize` separately for
|
|
919
|
+
each one::
|
|
920
|
+
|
|
921
|
+
v = np.random.uniform(-1.0, 1.0, (1000, 4,)) # 1000 length 4 vectors
|
|
922
|
+
vn = np.zeros((1000, 4)) # place to write values
|
|
923
|
+
normalize(v, out=vn) # very fast!
|
|
924
|
+
|
|
925
|
+
# don't do this!
|
|
926
|
+
for i in range(1000):
|
|
927
|
+
vn[i, :] = normalize(v[i, :])
|
|
928
|
+
|
|
929
|
+
"""
|
|
930
|
+
if out is None:
|
|
931
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
932
|
+
toReturn = np.array(v, dtype=dtype)
|
|
933
|
+
else:
|
|
934
|
+
toReturn = out
|
|
935
|
+
|
|
936
|
+
v2d = np.atleast_2d(toReturn) # 2d view of array
|
|
937
|
+
norm = np.linalg.norm(v2d, axis=1)
|
|
938
|
+
norm[norm == 0.0] = np.nan # make sure if length==0 division succeeds
|
|
939
|
+
v2d /= norm[:, np.newaxis]
|
|
940
|
+
np.nan_to_num(v2d, copy=False) # fix NaNs
|
|
941
|
+
|
|
942
|
+
return toReturn
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def orthogonalize(v, n, out=None, dtype=None):
|
|
946
|
+
"""Orthogonalize a vector relative to a normal vector.
|
|
947
|
+
|
|
948
|
+
This function ensures that `v` is perpendicular (or orthogonal) to `n`.
|
|
949
|
+
|
|
950
|
+
Parameters
|
|
951
|
+
----------
|
|
952
|
+
v : array_like
|
|
953
|
+
Vector to orthogonalize, can be Nx2, Nx3, or Nx4. If a 2D array is
|
|
954
|
+
specified, rows are treated as separate vectors.
|
|
955
|
+
n : array_like
|
|
956
|
+
Normal vector, must have same shape as `v`.
|
|
957
|
+
out : ndarray, optional
|
|
958
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
959
|
+
output if `out` was not specified.
|
|
960
|
+
dtype : dtype or str, optional
|
|
961
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
962
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
963
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
964
|
+
|
|
965
|
+
Returns
|
|
966
|
+
-------
|
|
967
|
+
ndarray
|
|
968
|
+
Orthogonalized vector `v` relative to normal vector `n`.
|
|
969
|
+
|
|
970
|
+
Warnings
|
|
971
|
+
--------
|
|
972
|
+
If `v` and `n` are the same, the direction of the perpendicular vector is
|
|
973
|
+
indeterminate. The resulting vector is degenerate (all zeros).
|
|
974
|
+
|
|
975
|
+
"""
|
|
976
|
+
if out is None:
|
|
977
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
978
|
+
else:
|
|
979
|
+
dtype = np.dtype(out.dtype).type
|
|
980
|
+
|
|
981
|
+
v = np.asarray(v, dtype=dtype)
|
|
982
|
+
n = np.asarray(n, dtype=dtype)
|
|
983
|
+
|
|
984
|
+
if out is None:
|
|
985
|
+
toReturn = np.zeros_like(v, dtype=dtype)
|
|
986
|
+
else:
|
|
987
|
+
toReturn = out
|
|
988
|
+
toReturn.fill(0.0)
|
|
989
|
+
|
|
990
|
+
v, n, vr = np.atleast_2d(v, n, toReturn)
|
|
991
|
+
vr[:, :] = v
|
|
992
|
+
vr[:, :] -= n * np.sum(n * v, axis=1)[:, np.newaxis] # dot product
|
|
993
|
+
normalize(vr, out=vr)
|
|
994
|
+
|
|
995
|
+
return toReturn
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def reflect(v, n, out=None, dtype=None):
|
|
999
|
+
"""Reflection of a vector.
|
|
1000
|
+
|
|
1001
|
+
Get the reflection of `v` relative to normal `n`.
|
|
1002
|
+
|
|
1003
|
+
Parameters
|
|
1004
|
+
----------
|
|
1005
|
+
v : array_like
|
|
1006
|
+
Vector to reflect, can be Nx2, Nx3, or Nx4. If a 2D array is specified,
|
|
1007
|
+
rows are treated as separate vectors.
|
|
1008
|
+
n : array_like
|
|
1009
|
+
Normal vector, must have same shape as `v`.
|
|
1010
|
+
out : ndarray, optional
|
|
1011
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1012
|
+
output if `out` was not specified.
|
|
1013
|
+
dtype : dtype or str, optional
|
|
1014
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1015
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1016
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1017
|
+
|
|
1018
|
+
Returns
|
|
1019
|
+
-------
|
|
1020
|
+
ndarray
|
|
1021
|
+
Reflected vector `v` off normal `n`.
|
|
1022
|
+
|
|
1023
|
+
"""
|
|
1024
|
+
# based off https://github.com/glfw/glfw/blob/master/deps/linmath.h
|
|
1025
|
+
if out is None:
|
|
1026
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1027
|
+
else:
|
|
1028
|
+
dtype = np.dtype(out.dtype).type
|
|
1029
|
+
|
|
1030
|
+
v = np.asarray(v, dtype=dtype)
|
|
1031
|
+
n = np.asarray(n, dtype=dtype)
|
|
1032
|
+
|
|
1033
|
+
if out is None:
|
|
1034
|
+
toReturn = np.zeros_like(v, dtype=dtype)
|
|
1035
|
+
else:
|
|
1036
|
+
toReturn = out
|
|
1037
|
+
toReturn.fill(0.0)
|
|
1038
|
+
|
|
1039
|
+
v, n, vr = np.atleast_2d(v, n, toReturn)
|
|
1040
|
+
|
|
1041
|
+
vr[:, :] = v
|
|
1042
|
+
vr[:, :] -= (dtype(2.0) * np.sum(n * v, axis=1))[:, np.newaxis] * n
|
|
1043
|
+
|
|
1044
|
+
return toReturn
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def dot(v0, v1, out=None, dtype=None):
|
|
1048
|
+
"""Dot product of two vectors.
|
|
1049
|
+
|
|
1050
|
+
The behaviour of this function depends on the format of the input arguments:
|
|
1051
|
+
|
|
1052
|
+
* If `v0` and `v1` are 1D, the dot product is returned as a scalar and `out`
|
|
1053
|
+
is ignored.
|
|
1054
|
+
* If `v0` and `v1` are 2D, a 1D array of dot products between corresponding
|
|
1055
|
+
row vectors are returned.
|
|
1056
|
+
* If either `v0` and `v1` are 1D and 2D, an array of dot products
|
|
1057
|
+
between each row of the 2D vector and the 1D vector are returned.
|
|
1058
|
+
|
|
1059
|
+
Parameters
|
|
1060
|
+
----------
|
|
1061
|
+
v0, v1 : array_like
|
|
1062
|
+
Vector(s) to compute dot products of (e.g. [x, y, z]). `v0` must have
|
|
1063
|
+
equal or fewer dimensions than `v1`.
|
|
1064
|
+
out : ndarray, optional
|
|
1065
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1066
|
+
output if `out` was not specified.
|
|
1067
|
+
dtype : dtype or str, optional
|
|
1068
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1069
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1070
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1071
|
+
|
|
1072
|
+
Returns
|
|
1073
|
+
-------
|
|
1074
|
+
ndarray
|
|
1075
|
+
Dot product(s) of `v0` and `v1`.
|
|
1076
|
+
|
|
1077
|
+
"""
|
|
1078
|
+
if out is None:
|
|
1079
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1080
|
+
else:
|
|
1081
|
+
dtype = np.dtype(out.dtype).type
|
|
1082
|
+
|
|
1083
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1084
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1085
|
+
|
|
1086
|
+
if v0.ndim == v1.ndim == 2 or v0.ndim == 2 and v1.ndim == 1:
|
|
1087
|
+
toReturn = np.zeros((v0.shape[0],), dtype=dtype) if out is None else out
|
|
1088
|
+
vr = np.atleast_2d(toReturn) # make sure we have a 2d view
|
|
1089
|
+
vr[:] = np.sum(v1 * v0, axis=1)
|
|
1090
|
+
elif v0.ndim == v1.ndim == 1:
|
|
1091
|
+
toReturn = np.sum(v1 * v0)
|
|
1092
|
+
elif v0.ndim == 1 and v1.ndim == 2:
|
|
1093
|
+
toReturn = np.zeros((v1.shape[0],), dtype=dtype) if out is None else out
|
|
1094
|
+
vr = np.atleast_2d(toReturn) # make sure we have a 2d view
|
|
1095
|
+
vr[:] = np.sum(v1 * v0, axis=1)
|
|
1096
|
+
else:
|
|
1097
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
1098
|
+
|
|
1099
|
+
return toReturn
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def cross(v0, v1, out=None, dtype=None):
|
|
1103
|
+
"""Cross product of 3D vectors.
|
|
1104
|
+
|
|
1105
|
+
The behavior of this function depends on the dimensions of the inputs:
|
|
1106
|
+
|
|
1107
|
+
* If `v0` and `v1` are 1D, the cross product is returned as 1D vector.
|
|
1108
|
+
* If `v0` and `v1` are 2D, a 2D array of cross products between
|
|
1109
|
+
corresponding row vectors are returned.
|
|
1110
|
+
* If either `v0` and `v1` are 1D and 2D, an array of cross products
|
|
1111
|
+
between each row of the 2D vector and the 1D vector are returned.
|
|
1112
|
+
|
|
1113
|
+
Parameters
|
|
1114
|
+
----------
|
|
1115
|
+
v0, v1 : array_like
|
|
1116
|
+
Vector(s) in form [x, y, z] or [x, y, z, 1].
|
|
1117
|
+
out : ndarray, optional
|
|
1118
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1119
|
+
output if `out` was not specified.
|
|
1120
|
+
dtype : dtype or str, optional
|
|
1121
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1122
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1123
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1124
|
+
|
|
1125
|
+
Returns
|
|
1126
|
+
-------
|
|
1127
|
+
ndarray
|
|
1128
|
+
Cross product of `v0` and `v1`.
|
|
1129
|
+
|
|
1130
|
+
Notes
|
|
1131
|
+
-----
|
|
1132
|
+
* If input vectors are 4D, the last value of cross product vectors is always
|
|
1133
|
+
set to one.
|
|
1134
|
+
* If input vectors `v0` and `v1` are Nx3 and `out` is Nx4, the cross product
|
|
1135
|
+
is computed and the last column of `out` is filled with ones.
|
|
1136
|
+
|
|
1137
|
+
Examples
|
|
1138
|
+
--------
|
|
1139
|
+
|
|
1140
|
+
Find the cross product of two vectors::
|
|
1141
|
+
|
|
1142
|
+
a = normalize([1, 2, 3])
|
|
1143
|
+
b = normalize([3, 2, 1])
|
|
1144
|
+
c = cross(a, b)
|
|
1145
|
+
|
|
1146
|
+
If input arguments are 2D, the function returns the cross products of
|
|
1147
|
+
corresponding rows::
|
|
1148
|
+
|
|
1149
|
+
# create two 6x3 arrays with random numbers
|
|
1150
|
+
shape = (6, 3,)
|
|
1151
|
+
a = normalize(np.random.uniform(-1.0, 1.0, shape))
|
|
1152
|
+
b = normalize(np.random.uniform(-1.0, 1.0, shape))
|
|
1153
|
+
cprod = np.zeros(shape) # output has the same shape as inputs
|
|
1154
|
+
cross(a, b, out=cprod)
|
|
1155
|
+
|
|
1156
|
+
If a 1D and 2D vector are specified, the cross product of each row of the
|
|
1157
|
+
2D array and the 1D array is returned as a 2D array::
|
|
1158
|
+
|
|
1159
|
+
a = normalize([1, 2, 3])
|
|
1160
|
+
b = normalize(np.random.uniform(-1.0, 1.0, (6, 3,)))
|
|
1161
|
+
cprod = np.zeros(a.shape)
|
|
1162
|
+
cross(a, b, out=cprod)
|
|
1163
|
+
|
|
1164
|
+
"""
|
|
1165
|
+
if out is None:
|
|
1166
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1167
|
+
else:
|
|
1168
|
+
dtype = np.dtype(out.dtype).type
|
|
1169
|
+
|
|
1170
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1171
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1172
|
+
|
|
1173
|
+
if v0.ndim == v1.ndim == 2: # 2D x 2D
|
|
1174
|
+
assert v0.shape == v1.shape
|
|
1175
|
+
toReturn = np.zeros(v0.shape, dtype=dtype) if out is None else out
|
|
1176
|
+
vr = np.atleast_2d(toReturn)
|
|
1177
|
+
vr[:, 0] = v0[:, 1] * v1[:, 2] - v0[:, 2] * v1[:, 1]
|
|
1178
|
+
vr[:, 1] = v0[:, 2] * v1[:, 0] - v0[:, 0] * v1[:, 2]
|
|
1179
|
+
vr[:, 2] = v0[:, 0] * v1[:, 1] - v0[:, 1] * v1[:, 0]
|
|
1180
|
+
|
|
1181
|
+
if vr.shape[1] == 4:
|
|
1182
|
+
vr[:, 3] = dtype(1.0)
|
|
1183
|
+
|
|
1184
|
+
elif v0.ndim == v1.ndim == 1: # 1D x 1D
|
|
1185
|
+
assert v0.shape == v1.shape
|
|
1186
|
+
toReturn = np.zeros(v0.shape, dtype=dtype) if out is None else out
|
|
1187
|
+
toReturn[0] = v0[1] * v1[2] - v0[2] * v1[1]
|
|
1188
|
+
toReturn[1] = v0[2] * v1[0] - v0[0] * v1[2]
|
|
1189
|
+
toReturn[2] = v0[0] * v1[1] - v0[1] * v1[0]
|
|
1190
|
+
|
|
1191
|
+
if toReturn.shape[0] == 4:
|
|
1192
|
+
toReturn[3] = dtype(1.0)
|
|
1193
|
+
|
|
1194
|
+
elif v0.ndim == 2 and v1.ndim == 1: # 2D x 1D
|
|
1195
|
+
toReturn = np.zeros(v0.shape, dtype=dtype) if out is None else out
|
|
1196
|
+
vr = np.atleast_2d(toReturn)
|
|
1197
|
+
vr[:, 0] = v0[:, 1] * v1[2] - v0[:, 2] * v1[1]
|
|
1198
|
+
vr[:, 1] = v0[:, 2] * v1[0] - v0[:, 0] * v1[2]
|
|
1199
|
+
vr[:, 2] = v0[:, 0] * v1[1] - v0[:, 1] * v1[0]
|
|
1200
|
+
|
|
1201
|
+
if vr.shape[1] == 4:
|
|
1202
|
+
vr[:, 3] = dtype(1.0)
|
|
1203
|
+
|
|
1204
|
+
elif v0.ndim == 1 and v1.ndim == 2: # 1D x 2D
|
|
1205
|
+
toReturn = np.zeros(v1.shape, dtype=dtype) if out is None else out
|
|
1206
|
+
vr = np.atleast_2d(toReturn)
|
|
1207
|
+
vr[:, 0] = v1[:, 2] * v0[1] - v1[:, 1] * v0[2]
|
|
1208
|
+
vr[:, 1] = v1[:, 0] * v0[2] - v1[:, 2] * v0[0]
|
|
1209
|
+
vr[:, 2] = v1[:, 1] * v0[0] - v1[:, 0] * v0[1]
|
|
1210
|
+
|
|
1211
|
+
if vr.shape[1] == 4:
|
|
1212
|
+
vr[:, 3] = dtype(1.0)
|
|
1213
|
+
|
|
1214
|
+
else:
|
|
1215
|
+
raise ValueError("Input arguments have incorrect dimensions.")
|
|
1216
|
+
|
|
1217
|
+
return toReturn
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
def project(v0, v1, out=None, dtype=None):
|
|
1221
|
+
"""Project a vector onto another.
|
|
1222
|
+
|
|
1223
|
+
Parameters
|
|
1224
|
+
----------
|
|
1225
|
+
v0 : array_like
|
|
1226
|
+
Vector can be Nx2, Nx3, or Nx4. If a 2D array is specified, rows are
|
|
1227
|
+
treated as separate vectors.
|
|
1228
|
+
v1 : array_like
|
|
1229
|
+
Vector to project onto `v0`.
|
|
1230
|
+
out : ndarray, optional
|
|
1231
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1232
|
+
output if `out` was not specified.
|
|
1233
|
+
dtype : dtype or str, optional
|
|
1234
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1235
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1236
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1237
|
+
|
|
1238
|
+
Returns
|
|
1239
|
+
-------
|
|
1240
|
+
ndarray or float
|
|
1241
|
+
Projection of vector `v0` on `v1`.
|
|
1242
|
+
|
|
1243
|
+
"""
|
|
1244
|
+
if out is None:
|
|
1245
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1246
|
+
else:
|
|
1247
|
+
dtype = np.dtype(out.dtype).type
|
|
1248
|
+
|
|
1249
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1250
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1251
|
+
|
|
1252
|
+
if v0.ndim == v1.ndim == 2 or v0.ndim == 1 and v1.ndim == 2:
|
|
1253
|
+
toReturn = np.zeros_like(v1, dtype=dtype) if out is None else out
|
|
1254
|
+
toReturn[:, :] = v1[:, :]
|
|
1255
|
+
toReturn *= (dot(v0, v1, dtype=dtype) / length(v1))[:, np.newaxis]
|
|
1256
|
+
elif v0.ndim == v1.ndim == 1:
|
|
1257
|
+
toReturn = v1 * (dot(v0, v1, dtype=dtype) / np.sum(np.square(v1)))
|
|
1258
|
+
elif v0.ndim == 2 and v1.ndim == 1:
|
|
1259
|
+
toReturn = np.zeros_like(v0, dtype=dtype) if out is None else out
|
|
1260
|
+
toReturn[:, :] = v1[:]
|
|
1261
|
+
toReturn *= (dot(v0, v1, dtype=dtype) / length(v1))[:, np.newaxis]
|
|
1262
|
+
else:
|
|
1263
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
1264
|
+
|
|
1265
|
+
toReturn += 0.0 # remove negative zeros
|
|
1266
|
+
return toReturn
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def lerp(v0, v1, t, out=None, dtype=None):
|
|
1270
|
+
"""Linear interpolation (LERP) between two vectors/coordinates.
|
|
1271
|
+
|
|
1272
|
+
Parameters
|
|
1273
|
+
----------
|
|
1274
|
+
v0 : array_like
|
|
1275
|
+
Initial vector/coordinate. Can be 2D where each row is a point.
|
|
1276
|
+
v1 : array_like
|
|
1277
|
+
Final vector/coordinate. Must be the same shape as `v0`.
|
|
1278
|
+
t : float
|
|
1279
|
+
Interpolation weight factor [0, 1].
|
|
1280
|
+
out : ndarray, optional
|
|
1281
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1282
|
+
output if `out` was not specified.
|
|
1283
|
+
dtype : dtype or str, optional
|
|
1284
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1285
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1286
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1287
|
+
|
|
1288
|
+
Returns
|
|
1289
|
+
-------
|
|
1290
|
+
ndarray
|
|
1291
|
+
Vector at `t` with same shape as `v0` and `v1`.
|
|
1292
|
+
|
|
1293
|
+
Examples
|
|
1294
|
+
--------
|
|
1295
|
+
Find the coordinate of the midpoint between two vectors::
|
|
1296
|
+
|
|
1297
|
+
u = [0., 0., 0.]
|
|
1298
|
+
v = [0., 0., 1.]
|
|
1299
|
+
midpoint = lerp(u, v, 0.5) # 0.5 to interpolate half-way between points
|
|
1300
|
+
|
|
1301
|
+
"""
|
|
1302
|
+
if out is None:
|
|
1303
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1304
|
+
else:
|
|
1305
|
+
dtype = np.dtype(out.dtype).type
|
|
1306
|
+
|
|
1307
|
+
t = dtype(t)
|
|
1308
|
+
t0 = dtype(1.0) - t
|
|
1309
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1310
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1311
|
+
|
|
1312
|
+
toReturn = np.zeros_like(v0, dtype=dtype) if out is None else out
|
|
1313
|
+
|
|
1314
|
+
v0, v1, vr = np.atleast_2d(v0, v1, toReturn)
|
|
1315
|
+
vr[:, :] = v0 * t0
|
|
1316
|
+
vr[:, :] += v1 * t
|
|
1317
|
+
|
|
1318
|
+
return toReturn
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def distance(v0, v1, out=None, dtype=None):
|
|
1322
|
+
"""Get the distance between vectors/coordinates.
|
|
1323
|
+
|
|
1324
|
+
The behaviour of this function depends on the format of the input arguments:
|
|
1325
|
+
|
|
1326
|
+
* If `v0` and `v1` are 1D, the distance is returned as a scalar and `out` is
|
|
1327
|
+
ignored.
|
|
1328
|
+
* If `v0` and `v1` are 2D, an array of distances between corresponding row
|
|
1329
|
+
vectors are returned.
|
|
1330
|
+
* If either `v0` and `v1` are 1D and 2D, an array of distances
|
|
1331
|
+
between each row of the 2D vector and the 1D vector are returned.
|
|
1332
|
+
|
|
1333
|
+
Parameters
|
|
1334
|
+
----------
|
|
1335
|
+
v0, v1 : array_like
|
|
1336
|
+
Vectors to compute the distance between.
|
|
1337
|
+
out : ndarray, optional
|
|
1338
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1339
|
+
output if `out` was not specified.
|
|
1340
|
+
dtype : dtype or str, optional
|
|
1341
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1342
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1343
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1344
|
+
|
|
1345
|
+
Returns
|
|
1346
|
+
-------
|
|
1347
|
+
ndarray
|
|
1348
|
+
Distance between vectors `v0` and `v1`.
|
|
1349
|
+
|
|
1350
|
+
"""
|
|
1351
|
+
if out is None:
|
|
1352
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1353
|
+
else:
|
|
1354
|
+
dtype = np.dtype(out.dtype).type
|
|
1355
|
+
|
|
1356
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1357
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1358
|
+
|
|
1359
|
+
if v0.ndim == v1.ndim == 2 or (v0.ndim == 2 and v1.ndim == 1):
|
|
1360
|
+
dist = np.zeros((v1.shape[0],), dtype=dtype) if out is None else out
|
|
1361
|
+
dist[:] = np.sqrt(np.sum(np.square(v1 - v0), axis=1))
|
|
1362
|
+
elif v0.ndim == v1.ndim == 1:
|
|
1363
|
+
dist = np.sqrt(np.sum(np.square(v1 - v0)))
|
|
1364
|
+
elif v0.ndim == 1 and v1.ndim == 2:
|
|
1365
|
+
dist = np.zeros((v1.shape[0],), dtype=dtype) if out is None else out
|
|
1366
|
+
dist[:] = np.sqrt(np.sum(np.square(v0 - v1), axis=1))
|
|
1367
|
+
else:
|
|
1368
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
1369
|
+
|
|
1370
|
+
return dist
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
def perp(v, n, norm=True, out=None, dtype=None):
|
|
1374
|
+
"""Project `v` to be a perpendicular axis of `n`.
|
|
1375
|
+
|
|
1376
|
+
Parameters
|
|
1377
|
+
----------
|
|
1378
|
+
v : array_like
|
|
1379
|
+
Vector to project [x, y, z], may be Nx3.
|
|
1380
|
+
n : array_like
|
|
1381
|
+
Normal vector [x, y, z], may be Nx3.
|
|
1382
|
+
norm : bool
|
|
1383
|
+
Normalize the resulting axis. Default is `True`.
|
|
1384
|
+
out : ndarray, optional
|
|
1385
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1386
|
+
output if `out` was not specified.
|
|
1387
|
+
dtype : dtype or str, optional
|
|
1388
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1389
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1390
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1391
|
+
|
|
1392
|
+
Returns
|
|
1393
|
+
-------
|
|
1394
|
+
ndarray
|
|
1395
|
+
Perpendicular axis of `n` from `v`.
|
|
1396
|
+
|
|
1397
|
+
Examples
|
|
1398
|
+
--------
|
|
1399
|
+
Determine the local `up` (y-axis) of a surface or plane given `normal`::
|
|
1400
|
+
|
|
1401
|
+
normal = [0., 0.70710678, 0.70710678]
|
|
1402
|
+
up = [1., 0., 0.]
|
|
1403
|
+
|
|
1404
|
+
yaxis = perp(up, normal)
|
|
1405
|
+
|
|
1406
|
+
Do a cross product to get the x-axis perpendicular to both::
|
|
1407
|
+
|
|
1408
|
+
xaxis = cross(yaxis, normal)
|
|
1409
|
+
|
|
1410
|
+
"""
|
|
1411
|
+
if out is None:
|
|
1412
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1413
|
+
else:
|
|
1414
|
+
dtype = np.dtype(out.dtype).type
|
|
1415
|
+
|
|
1416
|
+
v = np.asarray(v, dtype=dtype)
|
|
1417
|
+
n = np.asarray(n, dtype=dtype)
|
|
1418
|
+
|
|
1419
|
+
toReturn = np.zeros_like(v, dtype=dtype) if out is None else out
|
|
1420
|
+
v2d, n2d, r2d = np.atleast_2d(v, n, toReturn)
|
|
1421
|
+
|
|
1422
|
+
# from GLM `glm/gtx/perpendicular.inl`
|
|
1423
|
+
r2d[:, :] = v2d - project(v2d, n2d, dtype=dtype)
|
|
1424
|
+
|
|
1425
|
+
if norm:
|
|
1426
|
+
normalize(toReturn, out=toReturn)
|
|
1427
|
+
|
|
1428
|
+
toReturn += 0.0 # clear negative zeros
|
|
1429
|
+
|
|
1430
|
+
return toReturn
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
def bisector(v0, v1, norm=False, out=None, dtype=None):
|
|
1434
|
+
"""Get the angle bisector.
|
|
1435
|
+
|
|
1436
|
+
Computes a vector which bisects the angle between `v0` and `v1`. Input
|
|
1437
|
+
vectors `v0` and `v1` must be non-zero.
|
|
1438
|
+
|
|
1439
|
+
Parameters
|
|
1440
|
+
----------
|
|
1441
|
+
v0, v1 : array_like
|
|
1442
|
+
Vectors to bisect [x, y, z]. Must be non-zero in length and have the
|
|
1443
|
+
same shape. Inputs can be Nx3 where the bisector for corresponding
|
|
1444
|
+
rows will be returned.
|
|
1445
|
+
norm : bool, optional
|
|
1446
|
+
Normalize the resulting bisector. Default is `False`.
|
|
1447
|
+
out : ndarray, optional
|
|
1448
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1449
|
+
output if `out` was not specified.
|
|
1450
|
+
dtype : dtype or str, optional
|
|
1451
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1452
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1453
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1454
|
+
|
|
1455
|
+
Returns
|
|
1456
|
+
-------
|
|
1457
|
+
ndarray
|
|
1458
|
+
Bisecting vector [x, y, z].
|
|
1459
|
+
|
|
1460
|
+
"""
|
|
1461
|
+
if out is None:
|
|
1462
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1463
|
+
else:
|
|
1464
|
+
dtype = np.dtype(out.dtype).type
|
|
1465
|
+
|
|
1466
|
+
v0 = np.asarray(v0, dtype=dtype)
|
|
1467
|
+
v1 = np.asarray(v1, dtype=dtype)
|
|
1468
|
+
|
|
1469
|
+
assert v0.shape == v1.shape
|
|
1470
|
+
|
|
1471
|
+
toReturn = np.zeros_like(v0, dtype=dtype) if out is None else out
|
|
1472
|
+
|
|
1473
|
+
v02d, v12d, r2d = np.atleast_2d(v0, v1, toReturn)
|
|
1474
|
+
|
|
1475
|
+
r2d[:, :] = v02d * length(v12d, dtype=dtype)[:, np.newaxis] + \
|
|
1476
|
+
v12d * length(v02d, dtype=dtype)[:, np.newaxis]
|
|
1477
|
+
|
|
1478
|
+
if norm:
|
|
1479
|
+
normalize(r2d, out=r2d)
|
|
1480
|
+
|
|
1481
|
+
return toReturn
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
def angleTo(v, point, degrees=True, out=None, dtype=None):
|
|
1485
|
+
"""Get the relative angle to a point from a vector.
|
|
1486
|
+
|
|
1487
|
+
The behaviour of this function depends on the format of the input arguments:
|
|
1488
|
+
|
|
1489
|
+
* If `v0` and `v1` are 1D, the angle is returned as a scalar and `out` is
|
|
1490
|
+
ignored.
|
|
1491
|
+
* If `v0` and `v1` are 2D, an array of angles between corresponding row
|
|
1492
|
+
vectors are returned.
|
|
1493
|
+
* If either `v0` and `v1` are 1D and 2D, an array of angles
|
|
1494
|
+
between each row of the 2D vector and the 1D vector are returned.
|
|
1495
|
+
|
|
1496
|
+
Parameters
|
|
1497
|
+
----------
|
|
1498
|
+
v : array_like
|
|
1499
|
+
Direction vector [x, y, z].
|
|
1500
|
+
point : array_like
|
|
1501
|
+
Point(s) to compute angle to from vector `v`.
|
|
1502
|
+
degrees : bool, optional
|
|
1503
|
+
Return the resulting angles in degrees. If `False`, angles will be
|
|
1504
|
+
returned in radians. Default is `True`.
|
|
1505
|
+
out : ndarray, optional
|
|
1506
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
1507
|
+
output if `out` was not specified.
|
|
1508
|
+
dtype : dtype or str, optional
|
|
1509
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1510
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1511
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1512
|
+
|
|
1513
|
+
Returns
|
|
1514
|
+
-------
|
|
1515
|
+
ndarray
|
|
1516
|
+
Distance between vectors `v0` and `v1`.
|
|
1517
|
+
|
|
1518
|
+
"""
|
|
1519
|
+
if out is None:
|
|
1520
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1521
|
+
else:
|
|
1522
|
+
dtype = np.dtype(out.dtype).type
|
|
1523
|
+
|
|
1524
|
+
v = np.asarray(v, dtype=dtype)
|
|
1525
|
+
point = np.asarray(point, dtype=dtype)
|
|
1526
|
+
|
|
1527
|
+
if v.ndim == point.ndim == 2 or (v.ndim == 2 and point.ndim == 1):
|
|
1528
|
+
angle = np.zeros((v.shape[0],), dtype=dtype) if out is None else out
|
|
1529
|
+
u = np.sqrt(length(v, squared=True, dtype=dtype) *
|
|
1530
|
+
length(point, squared=True, dtype=dtype))
|
|
1531
|
+
angle[:] = np.arccos(dot(v, point, dtype=dtype) / u)
|
|
1532
|
+
elif v.ndim == 1 and point.ndim == 2:
|
|
1533
|
+
angle = np.zeros((point.shape[0],), dtype=dtype) if out is None else out
|
|
1534
|
+
u = np.sqrt(length(v, squared=True, dtype=dtype) *
|
|
1535
|
+
length(point, squared=True, dtype=dtype))
|
|
1536
|
+
angle[:] = np.arccos(dot(v, point, dtype=dtype) / u)
|
|
1537
|
+
elif v.ndim == point.ndim == 1:
|
|
1538
|
+
u = np.sqrt(length(v, squared=True, dtype=dtype) *
|
|
1539
|
+
length(point, squared=True, dtype=dtype))
|
|
1540
|
+
angle = np.arccos(dot(v, point, dtype=dtype) / u)
|
|
1541
|
+
else:
|
|
1542
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
1543
|
+
|
|
1544
|
+
return np.degrees(angle) if degrees else angle
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
def sortClockwise(verts):
|
|
1548
|
+
"""Sort vertices clockwise from 12 O'Clock (aka vertex (0, 1)).
|
|
1549
|
+
|
|
1550
|
+
Parameters
|
|
1551
|
+
----------
|
|
1552
|
+
verts : array
|
|
1553
|
+
Array of vertices to sort.
|
|
1554
|
+
|
|
1555
|
+
Returns
|
|
1556
|
+
-------
|
|
1557
|
+
array
|
|
1558
|
+
Vertices sorted clockwise from 12 O'Clock.
|
|
1559
|
+
|
|
1560
|
+
"""
|
|
1561
|
+
# Blank array of angles
|
|
1562
|
+
angles = []
|
|
1563
|
+
# Calculate angle of each vertex
|
|
1564
|
+
for vert in verts:
|
|
1565
|
+
# Get angle
|
|
1566
|
+
ang = angleTo(v=[0, 1], point=vert)
|
|
1567
|
+
# Flip angle if we're past 6 O'clock
|
|
1568
|
+
if vert[0] < 0:
|
|
1569
|
+
ang = 360 - ang
|
|
1570
|
+
# Append to angles array
|
|
1571
|
+
angles.append(ang)
|
|
1572
|
+
# Sort vertices by angles array values
|
|
1573
|
+
verts = [x for _, x in sorted(zip(angles, verts), key=lambda pair: pair[0])]
|
|
1574
|
+
|
|
1575
|
+
return verts
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
def surfaceNormal(tri, norm=True, out=None, dtype=None):
|
|
1579
|
+
"""Compute the surface normal of a given triangle.
|
|
1580
|
+
|
|
1581
|
+
Parameters
|
|
1582
|
+
----------
|
|
1583
|
+
tri : array_like
|
|
1584
|
+
Triangle vertices as 2D (3x3) array [p0, p1, p2] where each vertex is a
|
|
1585
|
+
length 3 array [vx, xy, vz]. The input array can be 3D (Nx3x3) to
|
|
1586
|
+
specify multiple triangles.
|
|
1587
|
+
norm : bool, optional
|
|
1588
|
+
Normalize computed surface normals if ``True``, default is ``True``.
|
|
1589
|
+
out : ndarray, optional
|
|
1590
|
+
Optional output array. Must have one fewer dimensions than `tri`. The
|
|
1591
|
+
shape of the last dimension must be 3.
|
|
1592
|
+
dtype : dtype or str, optional
|
|
1593
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1594
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1595
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1596
|
+
|
|
1597
|
+
Returns
|
|
1598
|
+
-------
|
|
1599
|
+
ndarray
|
|
1600
|
+
Surface normal of triangle `tri`.
|
|
1601
|
+
|
|
1602
|
+
Examples
|
|
1603
|
+
--------
|
|
1604
|
+
Compute the surface normal of a triangle::
|
|
1605
|
+
|
|
1606
|
+
vertices = [[-1., 0., 0.], [0., 1., 0.], [1, 0, 0]]
|
|
1607
|
+
norm = surfaceNormal(vertices)
|
|
1608
|
+
|
|
1609
|
+
Find the normals for multiple triangles, and put results in a pre-allocated
|
|
1610
|
+
array::
|
|
1611
|
+
|
|
1612
|
+
vertices = [[[-1., 0., 0.], [0., 1., 0.], [1, 0, 0]], # 2x3x3
|
|
1613
|
+
[[1., 0., 0.], [0., 1., 0.], [-1, 0, 0]]]
|
|
1614
|
+
normals = np.zeros((2, 3)) # normals for two triangles
|
|
1615
|
+
surfaceNormal(vertices, out=normals)
|
|
1616
|
+
|
|
1617
|
+
"""
|
|
1618
|
+
if out is None:
|
|
1619
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1620
|
+
else:
|
|
1621
|
+
dtype = np.dtype(out.dtype).type
|
|
1622
|
+
|
|
1623
|
+
tris = np.asarray(tri, dtype=dtype)
|
|
1624
|
+
if tris.ndim == 2:
|
|
1625
|
+
tris = np.expand_dims(tri, axis=0)
|
|
1626
|
+
|
|
1627
|
+
if tris.shape[0] == 1:
|
|
1628
|
+
toReturn = np.zeros((3,), dtype=dtype) if out is None else out
|
|
1629
|
+
else:
|
|
1630
|
+
if out is None:
|
|
1631
|
+
toReturn = np.zeros((tris.shape[0], 3), dtype=dtype)
|
|
1632
|
+
else:
|
|
1633
|
+
toReturn = out
|
|
1634
|
+
|
|
1635
|
+
# from https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
|
1636
|
+
nr = np.atleast_2d(toReturn)
|
|
1637
|
+
u = tris[:, 1, :] - tris[:, 0, :]
|
|
1638
|
+
v = tris[:, 2, :] - tris[:, 1, :]
|
|
1639
|
+
nr[:, 0] = u[:, 1] * v[:, 2] - u[:, 2] * v[:, 1]
|
|
1640
|
+
nr[:, 1] = u[:, 2] * v[:, 0] - u[:, 0] * v[:, 2]
|
|
1641
|
+
nr[:, 2] = u[:, 0] * v[:, 1] - u[:, 1] * v[:, 0]
|
|
1642
|
+
|
|
1643
|
+
if norm:
|
|
1644
|
+
normalize(nr, out=nr)
|
|
1645
|
+
|
|
1646
|
+
return toReturn
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
def surfaceBitangent(tri, uv, norm=True, out=None, dtype=None):
|
|
1650
|
+
"""Compute the bitangent vector of a given triangle.
|
|
1651
|
+
|
|
1652
|
+
This function can be used to generate bitangent vertex attributes for normal
|
|
1653
|
+
mapping. After computing bitangents, one may orthogonalize them with vertex
|
|
1654
|
+
normals using the :func:`orthogonalize` function, or within the fragment
|
|
1655
|
+
shader. Uses texture coordinates at each triangle vertex to determine the
|
|
1656
|
+
direction of the vector.
|
|
1657
|
+
|
|
1658
|
+
Parameters
|
|
1659
|
+
----------
|
|
1660
|
+
tri : array_like
|
|
1661
|
+
Triangle vertices as 2D (3x3) array [p0, p1, p2] where each vertex is a
|
|
1662
|
+
length 3 array [vx, xy, vz]. The input array can be 3D (Nx3x3) to
|
|
1663
|
+
specify multiple triangles.
|
|
1664
|
+
uv : array_like
|
|
1665
|
+
Texture coordinates associated with each face vertex as a 2D array (3x2)
|
|
1666
|
+
where each texture coordinate is length 2 array [u, v]. The input array
|
|
1667
|
+
can be 3D (Nx3x2) to specify multiple texture coordinates if multiple
|
|
1668
|
+
triangles are specified.
|
|
1669
|
+
norm : bool, optional
|
|
1670
|
+
Normalize computed bitangents if ``True``, default is ``True``.
|
|
1671
|
+
out : ndarray, optional
|
|
1672
|
+
Optional output array. Must have one fewer dimensions than `tri`. The
|
|
1673
|
+
shape of the last dimension must be 3.
|
|
1674
|
+
dtype : dtype or str, optional
|
|
1675
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1676
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1677
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1678
|
+
|
|
1679
|
+
Returns
|
|
1680
|
+
-------
|
|
1681
|
+
ndarray
|
|
1682
|
+
Surface bitangent of triangle `tri`.
|
|
1683
|
+
|
|
1684
|
+
Examples
|
|
1685
|
+
--------
|
|
1686
|
+
Computing the bitangents for two triangles from vertex and texture
|
|
1687
|
+
coordinates (UVs)::
|
|
1688
|
+
|
|
1689
|
+
# array of triangle vertices (2x3x3)
|
|
1690
|
+
tri = np.asarray([
|
|
1691
|
+
[(-1.0, 1.0, 0.0), (-1.0, -1.0, 0.0), (1.0, -1.0, 0.0)], # 1
|
|
1692
|
+
[(-1.0, 1.0, 0.0), (-1.0, -1.0, 0.0), (1.0, -1.0, 0.0)]]) # 2
|
|
1693
|
+
|
|
1694
|
+
# array of triangle texture coordinates (2x3x2)
|
|
1695
|
+
uv = np.asarray([
|
|
1696
|
+
[(0.0, 1.0), (0.0, 0.0), (1.0, 0.0)], # 1
|
|
1697
|
+
[(0.0, 1.0), (0.0, 0.0), (1.0, 0.0)]]) # 2
|
|
1698
|
+
|
|
1699
|
+
bitangents = surfaceBitangent(tri, uv, norm=True) # bitangets (2x3)
|
|
1700
|
+
|
|
1701
|
+
"""
|
|
1702
|
+
if out is None:
|
|
1703
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1704
|
+
else:
|
|
1705
|
+
dtype = np.dtype(out.dtype).type
|
|
1706
|
+
|
|
1707
|
+
tris = np.asarray(tri, dtype=dtype)
|
|
1708
|
+
if tris.ndim == 2:
|
|
1709
|
+
tris = np.expand_dims(tri, axis=0)
|
|
1710
|
+
|
|
1711
|
+
if tris.shape[0] == 1:
|
|
1712
|
+
toReturn = np.zeros((3,), dtype=dtype) if out is None else out
|
|
1713
|
+
else:
|
|
1714
|
+
if out is None:
|
|
1715
|
+
toReturn = np.zeros((tris.shape[0], 3), dtype=dtype)
|
|
1716
|
+
else:
|
|
1717
|
+
toReturn = out
|
|
1718
|
+
|
|
1719
|
+
uvs = np.asarray(uv, dtype=dtype)
|
|
1720
|
+
if uvs.ndim == 2:
|
|
1721
|
+
uvs = np.expand_dims(uvs, axis=0)
|
|
1722
|
+
|
|
1723
|
+
# based off the implementation from
|
|
1724
|
+
# https://learnopengl.com/Advanced-Lighting/Normal-Mapping
|
|
1725
|
+
e1 = tris[:, 1, :] - tris[:, 0, :]
|
|
1726
|
+
e2 = tris[:, 2, :] - tris[:, 0, :]
|
|
1727
|
+
d1 = uvs[:, 1, :] - uvs[:, 0, :]
|
|
1728
|
+
d2 = uvs[:, 2, :] - uvs[:, 0, :]
|
|
1729
|
+
|
|
1730
|
+
# compute the bitangent
|
|
1731
|
+
nr = np.atleast_2d(toReturn)
|
|
1732
|
+
nr[:, 0] = -d2[:, 0] * e1[:, 0] + d1[:, 0] * e2[:, 0]
|
|
1733
|
+
nr[:, 1] = -d2[:, 0] * e1[:, 1] + d1[:, 0] * e2[:, 1]
|
|
1734
|
+
nr[:, 2] = -d2[:, 0] * e1[:, 2] + d1[:, 0] * e2[:, 2]
|
|
1735
|
+
|
|
1736
|
+
f = dtype(1.0) / (d1[:, 0] * d2[:, 1] - d2[:, 0] * d1[:, 1])
|
|
1737
|
+
nr *= f[:, np.newaxis]
|
|
1738
|
+
|
|
1739
|
+
if norm:
|
|
1740
|
+
normalize(toReturn, out=toReturn, dtype=dtype)
|
|
1741
|
+
|
|
1742
|
+
return toReturn
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
def surfaceTangent(tri, uv, norm=True, out=None, dtype=None):
|
|
1746
|
+
"""Compute the tangent vector of a given triangle.
|
|
1747
|
+
|
|
1748
|
+
This function can be used to generate tangent vertex attributes for normal
|
|
1749
|
+
mapping. After computing tangents, one may orthogonalize them with vertex
|
|
1750
|
+
normals using the :func:`orthogonalize` function, or within the fragment
|
|
1751
|
+
shader. Uses texture coordinates at each triangle vertex to determine the
|
|
1752
|
+
direction of the vector.
|
|
1753
|
+
|
|
1754
|
+
Parameters
|
|
1755
|
+
----------
|
|
1756
|
+
tri : array_like
|
|
1757
|
+
Triangle vertices as 2D (3x3) array [p0, p1, p2] where each vertex is a
|
|
1758
|
+
length 3 array [vx, xy, vz]. The input array can be 3D (Nx3x3) to
|
|
1759
|
+
specify multiple triangles.
|
|
1760
|
+
uv : array_like
|
|
1761
|
+
Texture coordinates associated with each face vertex as a 2D array (3x2)
|
|
1762
|
+
where each texture coordinate is length 2 array [u, v]. The input array
|
|
1763
|
+
can be 3D (Nx3x2) to specify multiple texture coordinates if multiple
|
|
1764
|
+
triangles are specified. If so `N` must be the same size as the first
|
|
1765
|
+
dimension of `tri`.
|
|
1766
|
+
norm : bool, optional
|
|
1767
|
+
Normalize computed tangents if ``True``, default is ``True``.
|
|
1768
|
+
out : ndarray, optional
|
|
1769
|
+
Optional output array. Must have one fewer dimensions than `tri`. The
|
|
1770
|
+
shape of the last dimension must be 3.
|
|
1771
|
+
dtype : dtype or str, optional
|
|
1772
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1773
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1774
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1775
|
+
|
|
1776
|
+
Returns
|
|
1777
|
+
-------
|
|
1778
|
+
ndarray
|
|
1779
|
+
Surface normal of triangle `tri`.
|
|
1780
|
+
|
|
1781
|
+
Examples
|
|
1782
|
+
--------
|
|
1783
|
+
Compute surface normals, tangents, and bitangents for a list of triangles::
|
|
1784
|
+
|
|
1785
|
+
# triangle vertices (2x3x3)
|
|
1786
|
+
vertices = [[[-1., 0., 0.], [0., 1., 0.], [1, 0, 0]],
|
|
1787
|
+
[[1., 0., 0.], [0., 1., 0.], [-1, 0, 0]]]
|
|
1788
|
+
|
|
1789
|
+
# array of triangle texture coordinates (2x3x2)
|
|
1790
|
+
uv = np.asarray([
|
|
1791
|
+
[(0.0, 1.0), (0.0, 0.0), (1.0, 0.0)], # 1
|
|
1792
|
+
[(0.0, 1.0), (0.0, 0.0), (1.0, 0.0)]]) # 2
|
|
1793
|
+
|
|
1794
|
+
normals = surfaceNormal(vertices)
|
|
1795
|
+
tangents = surfaceTangent(vertices, uv)
|
|
1796
|
+
bitangents = cross(normals, tangents) # or use `surfaceBitangent`
|
|
1797
|
+
|
|
1798
|
+
Orthogonalize a surface tangent with a vertex normal vector to get the
|
|
1799
|
+
vertex tangent and bitangent vectors::
|
|
1800
|
+
|
|
1801
|
+
vertexTangent = orthogonalize(faceTangent, vertexNormal)
|
|
1802
|
+
vertexBitangent = cross(vertexTangent, vertexNormal)
|
|
1803
|
+
|
|
1804
|
+
Ensure computed vectors have the same handedness, if not, flip the tangent
|
|
1805
|
+
vector (important for applications like normal mapping)::
|
|
1806
|
+
|
|
1807
|
+
# tangent, bitangent, and normal are 2D
|
|
1808
|
+
tangent[dot(cross(normal, tangent), bitangent) < 0.0, :] *= -1.0
|
|
1809
|
+
|
|
1810
|
+
"""
|
|
1811
|
+
if out is None:
|
|
1812
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1813
|
+
else:
|
|
1814
|
+
dtype = np.dtype(out.dtype).type
|
|
1815
|
+
|
|
1816
|
+
tris = np.asarray(tri, dtype=dtype)
|
|
1817
|
+
if tris.ndim == 2:
|
|
1818
|
+
tris = np.expand_dims(tri, axis=0)
|
|
1819
|
+
|
|
1820
|
+
if tris.shape[0] == 1:
|
|
1821
|
+
toReturn = np.zeros((3,), dtype=dtype) if out is None else out
|
|
1822
|
+
else:
|
|
1823
|
+
if out is None:
|
|
1824
|
+
toReturn = np.zeros((tris.shape[0], 3), dtype=dtype)
|
|
1825
|
+
else:
|
|
1826
|
+
toReturn = out
|
|
1827
|
+
|
|
1828
|
+
uvs = np.asarray(uv, dtype=dtype)
|
|
1829
|
+
if uvs.ndim == 2:
|
|
1830
|
+
uvs = np.expand_dims(uvs, axis=0)
|
|
1831
|
+
|
|
1832
|
+
# based off the implementation from
|
|
1833
|
+
# https://learnopengl.com/Advanced-Lighting/Normal-Mapping
|
|
1834
|
+
e1 = tris[:, 1, :] - tris[:, 0, :]
|
|
1835
|
+
e2 = tris[:, 2, :] - tris[:, 0, :]
|
|
1836
|
+
d1 = uvs[:, 1, :] - uvs[:, 0, :]
|
|
1837
|
+
d2 = uvs[:, 2, :] - uvs[:, 0, :]
|
|
1838
|
+
|
|
1839
|
+
# compute the bitangent
|
|
1840
|
+
nr = np.atleast_2d(toReturn)
|
|
1841
|
+
nr[:, 0] = d2[:, 1] * e1[:, 0] - d1[:, 1] * e2[:, 0]
|
|
1842
|
+
nr[:, 1] = d2[:, 1] * e1[:, 1] - d1[:, 1] * e2[:, 1]
|
|
1843
|
+
nr[:, 2] = d2[:, 1] * e1[:, 2] - d1[:, 1] * e2[:, 2]
|
|
1844
|
+
|
|
1845
|
+
f = dtype(1.0) / (d1[:, 0] * d2[:, 1] - d2[:, 0] * d1[:, 1])
|
|
1846
|
+
nr *= f[:, np.newaxis]
|
|
1847
|
+
|
|
1848
|
+
if norm:
|
|
1849
|
+
normalize(toReturn, out=toReturn, dtype=dtype)
|
|
1850
|
+
|
|
1851
|
+
return toReturn
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def vertexNormal(faceNorms, norm=True, out=None, dtype=None):
|
|
1855
|
+
"""Compute a vertex normal from shared triangles.
|
|
1856
|
+
|
|
1857
|
+
This function computes a vertex normal by averaging the surface normals of
|
|
1858
|
+
the triangles it belongs to. If model has no vertex normals, first use
|
|
1859
|
+
:func:`surfaceNormal` to compute them, then run :func:`vertexNormal` to
|
|
1860
|
+
compute vertex normal attributes.
|
|
1861
|
+
|
|
1862
|
+
While this function is mainly used to compute vertex normals, it can also
|
|
1863
|
+
be supplied triangle tangents and bitangents.
|
|
1864
|
+
|
|
1865
|
+
Parameters
|
|
1866
|
+
----------
|
|
1867
|
+
faceNorms : array_like
|
|
1868
|
+
An array (Nx3) of surface normals.
|
|
1869
|
+
norm : bool, optional
|
|
1870
|
+
Normalize computed normals if ``True``, default is ``True``.
|
|
1871
|
+
out : ndarray, optional
|
|
1872
|
+
Optional output array.
|
|
1873
|
+
dtype : dtype or str, optional
|
|
1874
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1875
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1876
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1877
|
+
|
|
1878
|
+
Returns
|
|
1879
|
+
-------
|
|
1880
|
+
ndarray
|
|
1881
|
+
Vertex normal.
|
|
1882
|
+
|
|
1883
|
+
Examples
|
|
1884
|
+
--------
|
|
1885
|
+
Compute a vertex normal from the face normals of the triangles it belongs
|
|
1886
|
+
to::
|
|
1887
|
+
|
|
1888
|
+
normals = [[1., 0., 0.], [0., 1., 0.]] # adjacent face normals
|
|
1889
|
+
vertexNorm = vertexNormal(normals)
|
|
1890
|
+
|
|
1891
|
+
"""
|
|
1892
|
+
if out is None:
|
|
1893
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1894
|
+
else:
|
|
1895
|
+
dtype = np.dtype(out.dtype).type
|
|
1896
|
+
|
|
1897
|
+
triNorms2d = np.atleast_2d(np.asarray(faceNorms, dtype=dtype))
|
|
1898
|
+
nFaces = triNorms2d.shape[0]
|
|
1899
|
+
|
|
1900
|
+
if out is None:
|
|
1901
|
+
toReturn = np.zeros((3,), dtype=dtype)
|
|
1902
|
+
else:
|
|
1903
|
+
toReturn = out
|
|
1904
|
+
|
|
1905
|
+
toReturn[0] = np.sum(triNorms2d[:, 0])
|
|
1906
|
+
toReturn[1] = np.sum(triNorms2d[:, 1])
|
|
1907
|
+
toReturn[2] = np.sum(triNorms2d[:, 2])
|
|
1908
|
+
toReturn /= nFaces
|
|
1909
|
+
|
|
1910
|
+
if norm:
|
|
1911
|
+
normalize(toReturn, out=toReturn, dtype=dtype)
|
|
1912
|
+
|
|
1913
|
+
return toReturn
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
def fixTangentHandedness(tangents, normals, bitangents, out=None, dtype=None):
|
|
1917
|
+
"""Ensure the handedness of tangent vectors are all the same.
|
|
1918
|
+
|
|
1919
|
+
Often 3D computed tangents may not have the same handedness due to how
|
|
1920
|
+
texture coordinates are specified. This function takes input surface vectors
|
|
1921
|
+
are ensures that tangents have the same handedness. Use this function if you
|
|
1922
|
+
notice that normal mapping shading appears reversed with respect to the
|
|
1923
|
+
incident light direction. The output array of corrected tangents can be used
|
|
1924
|
+
inplace of the original.
|
|
1925
|
+
|
|
1926
|
+
Parameters
|
|
1927
|
+
----------
|
|
1928
|
+
tangents, normals, bitangents : array_like
|
|
1929
|
+
Input Nx3 arrays of triangle tangents, normals and bitangents. All
|
|
1930
|
+
arrays must have the same size.
|
|
1931
|
+
out : ndarray, optional
|
|
1932
|
+
Optional output array for tangents. If not specified, a new array of
|
|
1933
|
+
tangents will be allocated.
|
|
1934
|
+
dtype : dtype or str, optional
|
|
1935
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1936
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1937
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1938
|
+
|
|
1939
|
+
Returns
|
|
1940
|
+
-------
|
|
1941
|
+
ndarray
|
|
1942
|
+
Array of tangents with handedness corrected.
|
|
1943
|
+
|
|
1944
|
+
"""
|
|
1945
|
+
if out is None:
|
|
1946
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1947
|
+
else:
|
|
1948
|
+
dtype = np.dtype(out.dtype).type
|
|
1949
|
+
|
|
1950
|
+
tangents = np.asarray(tangents, dtype=dtype)
|
|
1951
|
+
normals = np.asarray(normals, dtype=dtype)
|
|
1952
|
+
bitangents = np.asarray(bitangents, dtype=dtype)
|
|
1953
|
+
|
|
1954
|
+
toReturn = np.zeros_like(tangents, dtype=dtype) if out is None else out
|
|
1955
|
+
toReturn[:, :] = tangents
|
|
1956
|
+
toReturn[dot(cross(normals, tangents, dtype=dtype),
|
|
1957
|
+
bitangents, dtype=dtype) < 0.0, :] *= -1.0
|
|
1958
|
+
|
|
1959
|
+
return toReturn
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
# ------------------------------------------------------------------------------
|
|
1963
|
+
# Collision Detection, Interaction and Kinematics
|
|
1964
|
+
#
|
|
1965
|
+
def fitBBox(points, dtype=None):
|
|
1966
|
+
"""Fit an axis-aligned bounding box around points.
|
|
1967
|
+
|
|
1968
|
+
This computes the minimum and maximum extents for a bounding box to
|
|
1969
|
+
completely enclose `points`. Keep in mind the output in bounds are
|
|
1970
|
+
axis-aligned and may not optimally fits the points (i.e. fits the points
|
|
1971
|
+
with the minimum required volume). However, this should work well enough for
|
|
1972
|
+
applications such as visibility testing (see
|
|
1973
|
+
`~psychopy.tools.viewtools.volumeVisible` for more information..
|
|
1974
|
+
|
|
1975
|
+
Parameters
|
|
1976
|
+
----------
|
|
1977
|
+
points : array_like
|
|
1978
|
+
Nx3 or Nx4 array of points to fit the bounding box to.
|
|
1979
|
+
dtype : dtype or str, optional
|
|
1980
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
1981
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
1982
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
1983
|
+
|
|
1984
|
+
Returns
|
|
1985
|
+
-------
|
|
1986
|
+
ndarray
|
|
1987
|
+
Extents (mins, maxs) as a 2x3 array.
|
|
1988
|
+
|
|
1989
|
+
See Also
|
|
1990
|
+
--------
|
|
1991
|
+
computeBBoxCorners : Convert bounding box extents to corners.
|
|
1992
|
+
|
|
1993
|
+
"""
|
|
1994
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
1995
|
+
|
|
1996
|
+
points = np.asarray(points, dtype=dtype)
|
|
1997
|
+
extents = np.zeros((2, 3), dtype=dtype)
|
|
1998
|
+
|
|
1999
|
+
extents[0, :] = (np.min(points[:, 0]),
|
|
2000
|
+
np.min(points[:, 1]),
|
|
2001
|
+
np.min(points[:, 2]))
|
|
2002
|
+
extents[1, :] = (np.max(points[:, 0]),
|
|
2003
|
+
np.max(points[:, 1]),
|
|
2004
|
+
np.max(points[:, 2]))
|
|
2005
|
+
|
|
2006
|
+
return extents
|
|
2007
|
+
|
|
2008
|
+
|
|
2009
|
+
def computeBBoxCorners(extents, dtype=None):
|
|
2010
|
+
"""Get the corners of an axis-aligned bounding box.
|
|
2011
|
+
|
|
2012
|
+
Parameters
|
|
2013
|
+
----------
|
|
2014
|
+
extents : array_like
|
|
2015
|
+
2x3 array indicating the minimum and maximum extents of the bounding
|
|
2016
|
+
box.
|
|
2017
|
+
dtype : dtype or str, optional
|
|
2018
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2019
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2020
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2021
|
+
|
|
2022
|
+
Returns
|
|
2023
|
+
-------
|
|
2024
|
+
ndarray
|
|
2025
|
+
8x4 array of points defining the corners of the bounding box.
|
|
2026
|
+
|
|
2027
|
+
Examples
|
|
2028
|
+
--------
|
|
2029
|
+
Compute the corner points of a bounding box::
|
|
2030
|
+
|
|
2031
|
+
minExtent = [-1, -1, -1]
|
|
2032
|
+
maxExtent = [1, 1, 1]
|
|
2033
|
+
corners = computeBBoxCorners([minExtent, maxExtent])
|
|
2034
|
+
|
|
2035
|
+
# [[ 1. 1. 1. 1.]
|
|
2036
|
+
# [-1. 1. 1. 1.]
|
|
2037
|
+
# [ 1. -1. 1. 1.]
|
|
2038
|
+
# [-1. -1. 1. 1.]
|
|
2039
|
+
# [ 1. 1. -1. 1.]
|
|
2040
|
+
# [-1. 1. -1. 1.]
|
|
2041
|
+
# [ 1. -1. -1. 1.]
|
|
2042
|
+
# [-1. -1. -1. 1.]]
|
|
2043
|
+
|
|
2044
|
+
"""
|
|
2045
|
+
extents = np.asarray(extents, dtype=dtype)
|
|
2046
|
+
|
|
2047
|
+
assert extents.shape == (2, 3,)
|
|
2048
|
+
|
|
2049
|
+
corners = np.zeros((8, 4), dtype=dtype)
|
|
2050
|
+
idx = np.arange(0, 8)
|
|
2051
|
+
corners[:, 0] = np.where(idx[:] & 1, extents[0, 0], extents[1, 0])
|
|
2052
|
+
corners[:, 1] = np.where(idx[:] & 2, extents[0, 1], extents[1, 1])
|
|
2053
|
+
corners[:, 2] = np.where(idx[:] & 4, extents[0, 2], extents[1, 2])
|
|
2054
|
+
corners[:, 3] = 1.0
|
|
2055
|
+
|
|
2056
|
+
return corners
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
def intersectRayPlane(rayOrig, rayDir, planeOrig, planeNormal, dtype=None):
|
|
2060
|
+
"""Get the point which a ray intersects a plane.
|
|
2061
|
+
|
|
2062
|
+
Parameters
|
|
2063
|
+
----------
|
|
2064
|
+
rayOrig : array_like
|
|
2065
|
+
Origin of the line in space [x, y, z].
|
|
2066
|
+
rayDir : array_like
|
|
2067
|
+
Direction vector of the line [x, y, z].
|
|
2068
|
+
planeOrig : array_like
|
|
2069
|
+
Origin of the plane to test [x, y, z].
|
|
2070
|
+
planeNormal : array_like
|
|
2071
|
+
Normal vector of the plane [x, y, z].
|
|
2072
|
+
dtype : dtype or str, optional
|
|
2073
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2074
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2075
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2076
|
+
|
|
2077
|
+
Returns
|
|
2078
|
+
-------
|
|
2079
|
+
tuple or None
|
|
2080
|
+
Position (`ndarray`) in space which the line intersects the plane and
|
|
2081
|
+
the distance the intersect occurs from the origin (`float`). `None` is
|
|
2082
|
+
returned if the line does not intersect the plane at a single point or
|
|
2083
|
+
at all.
|
|
2084
|
+
|
|
2085
|
+
Examples
|
|
2086
|
+
--------
|
|
2087
|
+
Find the point in the scene a ray intersects the plane::
|
|
2088
|
+
|
|
2089
|
+
# plane information
|
|
2090
|
+
planeOrigin = [0, 0, 0]
|
|
2091
|
+
planeNormal = [0, 0, 1]
|
|
2092
|
+
planeUpAxis = perp([0, 1, 0], planeNormal)
|
|
2093
|
+
|
|
2094
|
+
# ray
|
|
2095
|
+
rayDir = [0, 0, -1]
|
|
2096
|
+
rayOrigin = [0, 0, 5]
|
|
2097
|
+
|
|
2098
|
+
# get the intersect and distance in 3D world space
|
|
2099
|
+
pnt, dist = intersectRayPlane(rayOrigin, rayDir, planeOrigin, planeNormal)
|
|
2100
|
+
|
|
2101
|
+
"""
|
|
2102
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2103
|
+
|
|
2104
|
+
# based off the method from GLM
|
|
2105
|
+
rayOrig = np.asarray(rayOrig, dtype=dtype)
|
|
2106
|
+
rayDir = np.asarray(rayDir, dtype=dtype)
|
|
2107
|
+
planeOrig = np.asarray(planeOrig, dtype=dtype)
|
|
2108
|
+
planeNormal = np.asarray(planeNormal, dtype=dtype)
|
|
2109
|
+
|
|
2110
|
+
denom = dot(rayDir, planeNormal, dtype=dtype)
|
|
2111
|
+
if denom == 0.0:
|
|
2112
|
+
return None
|
|
2113
|
+
|
|
2114
|
+
# distance to collision
|
|
2115
|
+
dist = dot((planeOrig - rayOrig), planeNormal, dtype=dtype) / denom
|
|
2116
|
+
intersect = dist * rayDir + rayOrig
|
|
2117
|
+
|
|
2118
|
+
return intersect, dist
|
|
2119
|
+
|
|
2120
|
+
|
|
2121
|
+
def intersectRaySphere(rayOrig, rayDir, sphereOrig=(0., 0., 0.), sphereRadius=1.0,
|
|
2122
|
+
dtype=None):
|
|
2123
|
+
"""Calculate the points which a ray/line intersects a sphere (if any).
|
|
2124
|
+
|
|
2125
|
+
Get the 3D coordinate of the point which the ray intersects the sphere and
|
|
2126
|
+
the distance to the point from `orig`. The nearest point is returned if
|
|
2127
|
+
the line intersects the sphere at multiple locations. All coordinates should
|
|
2128
|
+
be in world/scene units.
|
|
2129
|
+
|
|
2130
|
+
Parameters
|
|
2131
|
+
----------
|
|
2132
|
+
rayOrig : array_like
|
|
2133
|
+
Origin of the ray in space [x, y, z].
|
|
2134
|
+
rayDir : array_like
|
|
2135
|
+
Direction vector of the ray [x, y, z], should be normalized.
|
|
2136
|
+
sphereOrig : array_like
|
|
2137
|
+
Origin of the sphere to test [x, y, z].
|
|
2138
|
+
sphereRadius : float
|
|
2139
|
+
Sphere radius to test in scene units.
|
|
2140
|
+
dtype : dtype or str, optional
|
|
2141
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2142
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2143
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2144
|
+
|
|
2145
|
+
Returns
|
|
2146
|
+
-------
|
|
2147
|
+
tuple
|
|
2148
|
+
Coordinate in world space of the intersection and distance in scene
|
|
2149
|
+
units from `orig`. Returns `None` if there is no intersection.
|
|
2150
|
+
|
|
2151
|
+
"""
|
|
2152
|
+
# based off example from https://antongerdelan.net/opengl/raycasting.html
|
|
2153
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2154
|
+
|
|
2155
|
+
rayOrig = np.asarray(rayOrig, dtype=dtype)
|
|
2156
|
+
rayDir = np.asarray(rayDir, dtype=dtype)
|
|
2157
|
+
sphereOrig = np.asarray(sphereOrig, dtype=dtype)
|
|
2158
|
+
sphereRadius = np.asarray(sphereRadius, dtype=dtype)
|
|
2159
|
+
|
|
2160
|
+
d = rayOrig - sphereOrig
|
|
2161
|
+
b = np.dot(rayDir, d)
|
|
2162
|
+
c = np.dot(d, d) - np.square(sphereRadius)
|
|
2163
|
+
b2mc = np.square(b) - c # determinant
|
|
2164
|
+
|
|
2165
|
+
if b2mc < 0.0: # no roots, ray does not intersect sphere
|
|
2166
|
+
return None
|
|
2167
|
+
|
|
2168
|
+
u = np.sqrt(b2mc)
|
|
2169
|
+
nearestDist = np.minimum(-b + u, -b - u)
|
|
2170
|
+
pos = (rayDir * nearestDist) + rayOrig
|
|
2171
|
+
|
|
2172
|
+
return pos, nearestDist
|
|
2173
|
+
|
|
2174
|
+
|
|
2175
|
+
def intersectRayAABB(rayOrig, rayDir, boundsOffset, boundsExtents, dtype=None):
|
|
2176
|
+
"""Find the point a ray intersects an axis-aligned bounding box (AABB).
|
|
2177
|
+
|
|
2178
|
+
Parameters
|
|
2179
|
+
----------
|
|
2180
|
+
rayOrig : array_like
|
|
2181
|
+
Origin of the ray in space [x, y, z].
|
|
2182
|
+
rayDir : array_like
|
|
2183
|
+
Direction vector of the ray [x, y, z], should be normalized.
|
|
2184
|
+
boundsOffset : array_like
|
|
2185
|
+
Offset of the bounding box in the scene [x, y, z].
|
|
2186
|
+
boundsExtents : array_like
|
|
2187
|
+
Minimum and maximum extents of the bounding box.
|
|
2188
|
+
dtype : dtype or str, optional
|
|
2189
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2190
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2191
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2192
|
+
|
|
2193
|
+
Returns
|
|
2194
|
+
-------
|
|
2195
|
+
tuple
|
|
2196
|
+
Coordinate in world space of the intersection and distance in scene
|
|
2197
|
+
units from `rayOrig`. Returns `None` if there is no intersection.
|
|
2198
|
+
|
|
2199
|
+
Examples
|
|
2200
|
+
--------
|
|
2201
|
+
Get the point on an axis-aligned bounding box that the cursor is over and
|
|
2202
|
+
place a 3D stimulus there. The eye location is defined by `RigidBodyPose`
|
|
2203
|
+
object `camera`::
|
|
2204
|
+
|
|
2205
|
+
# get the mouse position on-screen
|
|
2206
|
+
mx, my = mouse.getPos()
|
|
2207
|
+
|
|
2208
|
+
# find the point which the ray intersects on the box
|
|
2209
|
+
result = intersectRayAABB(
|
|
2210
|
+
camera.pos,
|
|
2211
|
+
camera.transformNormal(win.coordToRay((mx, my))),
|
|
2212
|
+
myStim.pos,
|
|
2213
|
+
myStim.thePose.bounds.extents)
|
|
2214
|
+
|
|
2215
|
+
# if the ray intersects, set the position of the cursor object to it
|
|
2216
|
+
if result is not None:
|
|
2217
|
+
cursorModel.thePose.pos = result[0]
|
|
2218
|
+
cursorModel.draw() # don't draw anything if there is no intersect
|
|
2219
|
+
|
|
2220
|
+
Note that if the model is rotated, the bounding box may not be aligned
|
|
2221
|
+
anymore with the axes. Use `intersectRayOBB` if your model rotates.
|
|
2222
|
+
|
|
2223
|
+
"""
|
|
2224
|
+
# based of the example provided here:
|
|
2225
|
+
# https://www.scratchapixel.com/lessons/3d-basic-rendering/
|
|
2226
|
+
# minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection
|
|
2227
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2228
|
+
|
|
2229
|
+
rayOrig = np.asarray(rayOrig, dtype=dtype)
|
|
2230
|
+
rayDir = np.asarray(rayDir, dtype=dtype)
|
|
2231
|
+
boundsOffset = np.asarray(boundsOffset, dtype=dtype)
|
|
2232
|
+
extents = np.asarray(boundsExtents, dtype=dtype) + boundsOffset
|
|
2233
|
+
|
|
2234
|
+
invDir = 1.0 / rayDir
|
|
2235
|
+
sign = np.zeros((3,), dtype=int)
|
|
2236
|
+
sign[invDir < 0.0] = 1
|
|
2237
|
+
|
|
2238
|
+
tmin = (extents[sign[0], 0] - rayOrig[0]) * invDir[0]
|
|
2239
|
+
tmax = (extents[1 - sign[0], 0] - rayOrig[0]) * invDir[0]
|
|
2240
|
+
tymin = (extents[sign[1], 1] - rayOrig[1]) * invDir[1]
|
|
2241
|
+
tymax = (extents[1 - sign[1], 1] - rayOrig[1]) * invDir[1]
|
|
2242
|
+
|
|
2243
|
+
if tmin > tymax or tymin > tmax:
|
|
2244
|
+
return None
|
|
2245
|
+
|
|
2246
|
+
if tymin > tmin:
|
|
2247
|
+
tmin = tymin
|
|
2248
|
+
|
|
2249
|
+
if tymax < tmax:
|
|
2250
|
+
tmax = tymax
|
|
2251
|
+
|
|
2252
|
+
tzmin = (extents[sign[2], 2] - rayOrig[2]) * invDir[2]
|
|
2253
|
+
tzmax = (extents[1 - sign[2], 2] - rayOrig[2]) * invDir[2]
|
|
2254
|
+
|
|
2255
|
+
if tmin > tzmax or tzmin > tmax:
|
|
2256
|
+
return None
|
|
2257
|
+
|
|
2258
|
+
if tzmin > tmin:
|
|
2259
|
+
tmin = tzmin
|
|
2260
|
+
|
|
2261
|
+
if tzmax < tmax:
|
|
2262
|
+
tmax = tzmax
|
|
2263
|
+
|
|
2264
|
+
if tmin < 0:
|
|
2265
|
+
if tmax < 0:
|
|
2266
|
+
return None
|
|
2267
|
+
|
|
2268
|
+
return (rayDir * tmin) + rayOrig, tmin
|
|
2269
|
+
|
|
2270
|
+
|
|
2271
|
+
def intersectRayOBB(rayOrig, rayDir, modelMatrix, boundsExtents, dtype=None):
|
|
2272
|
+
"""Find the point a ray intersects an oriented bounding box (OBB).
|
|
2273
|
+
|
|
2274
|
+
Parameters
|
|
2275
|
+
----------
|
|
2276
|
+
rayOrig : array_like
|
|
2277
|
+
Origin of the ray in space [x, y, z].
|
|
2278
|
+
rayDir : array_like
|
|
2279
|
+
Direction vector of the ray [x, y, z], should be normalized.
|
|
2280
|
+
modelMatrix : array_like
|
|
2281
|
+
4x4 model matrix of the object and bounding box.
|
|
2282
|
+
boundsExtents : array_like
|
|
2283
|
+
Minimum and maximum extents of the bounding box.
|
|
2284
|
+
dtype : dtype or str, optional
|
|
2285
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2286
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2287
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2288
|
+
|
|
2289
|
+
Returns
|
|
2290
|
+
-------
|
|
2291
|
+
tuple
|
|
2292
|
+
Coordinate in world space of the intersection and distance in scene
|
|
2293
|
+
units from `rayOrig`. Returns `None` if there is no intersection.
|
|
2294
|
+
|
|
2295
|
+
Examples
|
|
2296
|
+
--------
|
|
2297
|
+
Get the point on an oriented bounding box that the cursor is over and place
|
|
2298
|
+
a 3D stimulus there. The eye location is defined by `RigidBodyPose` object
|
|
2299
|
+
`camera`::
|
|
2300
|
+
|
|
2301
|
+
# get the mouse position on-screen
|
|
2302
|
+
mx, my = mouse.getPos()
|
|
2303
|
+
|
|
2304
|
+
# find the point which the ray intersects on the box
|
|
2305
|
+
result = intersectRayOBB(
|
|
2306
|
+
camera.pos,
|
|
2307
|
+
camera.transformNormal(win.coordToRay((mx, my))),
|
|
2308
|
+
myStim.thePose.getModelMatrix(),
|
|
2309
|
+
myStim.thePose.bounds.extents)
|
|
2310
|
+
|
|
2311
|
+
# if the ray intersects, set the position of the cursor object to it
|
|
2312
|
+
if result is not None:
|
|
2313
|
+
cursorModel.thePose.pos = result[0]
|
|
2314
|
+
cursorModel.draw() # don't draw anything if there is no intersect
|
|
2315
|
+
|
|
2316
|
+
"""
|
|
2317
|
+
# based off algorithm:
|
|
2318
|
+
# https://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/
|
|
2319
|
+
# picking-with-custom-ray-obb-function/
|
|
2320
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2321
|
+
|
|
2322
|
+
rayOrig = np.asarray(rayOrig, dtype=dtype)
|
|
2323
|
+
rayDir = np.asarray(rayDir, dtype=dtype)
|
|
2324
|
+
modelMatrix = np.asarray(modelMatrix, dtype=dtype)
|
|
2325
|
+
boundsOffset = np.asarray(modelMatrix[:3, 3], dtype=dtype)
|
|
2326
|
+
extents = np.asarray(boundsExtents, dtype=dtype)
|
|
2327
|
+
|
|
2328
|
+
tmin = 0.0
|
|
2329
|
+
tmax = np.finfo(dtype).max
|
|
2330
|
+
d = boundsOffset - rayOrig
|
|
2331
|
+
|
|
2332
|
+
# solve intersects for each pair of planes along each axis
|
|
2333
|
+
for i in range(3):
|
|
2334
|
+
axis = modelMatrix[:3, i]
|
|
2335
|
+
e = np.dot(axis, d)
|
|
2336
|
+
f = np.dot(rayDir, axis)
|
|
2337
|
+
|
|
2338
|
+
if np.fabs(f) > 1e-5:
|
|
2339
|
+
t1 = (e + extents[0, i]) / f
|
|
2340
|
+
t2 = (e + extents[1, i]) / f
|
|
2341
|
+
|
|
2342
|
+
if t1 > t2:
|
|
2343
|
+
temp = t1
|
|
2344
|
+
t1 = t2
|
|
2345
|
+
t2 = temp
|
|
2346
|
+
|
|
2347
|
+
if t2 < tmax:
|
|
2348
|
+
tmax = t2
|
|
2349
|
+
|
|
2350
|
+
if t1 > tmin:
|
|
2351
|
+
tmin = t1
|
|
2352
|
+
|
|
2353
|
+
if tmin > tmax:
|
|
2354
|
+
return None
|
|
2355
|
+
|
|
2356
|
+
else:
|
|
2357
|
+
# very close to parallel with the face
|
|
2358
|
+
if -e + extents[0, i] > 0.0 or -e + extents[1, i] < 0.0:
|
|
2359
|
+
return None
|
|
2360
|
+
|
|
2361
|
+
return (rayDir * tmin) + rayOrig, tmin
|
|
2362
|
+
|
|
2363
|
+
|
|
2364
|
+
def intersectRayTriangle(rayOrig, rayDir, tri, dtype=None):
|
|
2365
|
+
"""Get the intersection of a ray and triangle(s).
|
|
2366
|
+
|
|
2367
|
+
This function can be used to achieve 'pixel-perfect' ray picking/casting on
|
|
2368
|
+
meshes defined with triangles. However, high-poly meshes may lead to
|
|
2369
|
+
performance issues.
|
|
2370
|
+
|
|
2371
|
+
Parameters
|
|
2372
|
+
----------
|
|
2373
|
+
rayOrig : array_like
|
|
2374
|
+
Origin of the ray in space [x, y, z].
|
|
2375
|
+
rayDir : array_like
|
|
2376
|
+
Direction vector of the ray [x, y, z], should be normalized.
|
|
2377
|
+
tri : array_like
|
|
2378
|
+
Triangle vertices as 2D (3x3) array [p0, p1, p2] where each vertex is a
|
|
2379
|
+
length 3 array [vx, xy, vz]. The input array can be 3D (Nx3x3) to
|
|
2380
|
+
specify multiple triangles.
|
|
2381
|
+
dtype : dtype or str, optional
|
|
2382
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2383
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2384
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2385
|
+
|
|
2386
|
+
Returns
|
|
2387
|
+
-------
|
|
2388
|
+
tuple
|
|
2389
|
+
Coordinate in world space of the intersection, distance in scene
|
|
2390
|
+
units from `rayOrig`, and the barycentric coordinates on the triangle
|
|
2391
|
+
[x, y]. Returns `None` if there is no intersection.
|
|
2392
|
+
|
|
2393
|
+
"""
|
|
2394
|
+
# based off `intersectRayTriangle` from GLM (https://glm.g-truc.net)
|
|
2395
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2396
|
+
|
|
2397
|
+
rayOrig = np.asarray(rayOrig, dtype=dtype)
|
|
2398
|
+
rayDir = np.asarray(rayDir, dtype=dtype)
|
|
2399
|
+
triVerts = np.asarray(tri, dtype=dtype)
|
|
2400
|
+
|
|
2401
|
+
edge1 = triVerts[1, :] - triVerts[0, :]
|
|
2402
|
+
edge2 = triVerts[2, :] - triVerts[0, :]
|
|
2403
|
+
|
|
2404
|
+
baryPos = np.zeros((2,), dtype=dtype)
|
|
2405
|
+
|
|
2406
|
+
p = np.cross(rayDir, edge2)
|
|
2407
|
+
det = np.dot(edge1, p)
|
|
2408
|
+
|
|
2409
|
+
if det > np.finfo(dtype).eps:
|
|
2410
|
+
dist = rayOrig - triVerts[0, :]
|
|
2411
|
+
|
|
2412
|
+
baryPos[0] = np.dot(dist, p)
|
|
2413
|
+
if baryPos[0] < 0.0 or baryPos[0] > det:
|
|
2414
|
+
return None
|
|
2415
|
+
|
|
2416
|
+
ortho = np.cross(dist, edge1)
|
|
2417
|
+
|
|
2418
|
+
baryPos[1] = np.dot(rayDir, ortho)
|
|
2419
|
+
if baryPos[1] < 0.0 or baryPos[0] + baryPos[1] > det:
|
|
2420
|
+
return None
|
|
2421
|
+
|
|
2422
|
+
elif det < -np.finfo(dtype).eps:
|
|
2423
|
+
dist = rayOrig - triVerts[0, :]
|
|
2424
|
+
|
|
2425
|
+
baryPos[0] = np.dot(dist, p)
|
|
2426
|
+
if baryPos[0] > 0.0 or baryPos[0] < det:
|
|
2427
|
+
return None
|
|
2428
|
+
|
|
2429
|
+
ortho = np.cross(dist, edge1)
|
|
2430
|
+
|
|
2431
|
+
baryPos[1] = np.dot(rayDir, ortho)
|
|
2432
|
+
if baryPos[1] > 0.0 or baryPos[0] + baryPos[1] < det:
|
|
2433
|
+
return None
|
|
2434
|
+
else:
|
|
2435
|
+
return None
|
|
2436
|
+
|
|
2437
|
+
invDet = 1.0 / det
|
|
2438
|
+
dist = np.dot(edge2, ortho) * invDet
|
|
2439
|
+
baryPos *= invDet
|
|
2440
|
+
|
|
2441
|
+
return (rayDir * dist) + rayOrig, dist, baryPos
|
|
2442
|
+
|
|
2443
|
+
|
|
2444
|
+
def ortho3Dto2D(p, orig, normal, up, right=None, dtype=None):
|
|
2445
|
+
"""Get the planar coordinates of an orthogonal projection of a 3D point onto
|
|
2446
|
+
a 2D plane.
|
|
2447
|
+
|
|
2448
|
+
This function gets the nearest point on the plane which a 3D point falls on
|
|
2449
|
+
the plane.
|
|
2450
|
+
|
|
2451
|
+
Parameters
|
|
2452
|
+
----------
|
|
2453
|
+
p : array_like
|
|
2454
|
+
Point to be projected on the plane.
|
|
2455
|
+
orig : array_like
|
|
2456
|
+
Origin of the plane to test [x, y, z].
|
|
2457
|
+
normal : array_like
|
|
2458
|
+
Normal vector of the plane [x, y, z], must be normalized.
|
|
2459
|
+
up : array_like
|
|
2460
|
+
Normalized up (+Y) direction of the plane's coordinate system. Must be
|
|
2461
|
+
perpendicular to `normal`.
|
|
2462
|
+
right : array_like, optional
|
|
2463
|
+
Perpendicular right (+X) axis. If not provided, the axis will be
|
|
2464
|
+
computed via the cross product between `normal` and `up`.
|
|
2465
|
+
dtype : dtype or str, optional
|
|
2466
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2467
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2468
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2469
|
+
|
|
2470
|
+
Returns
|
|
2471
|
+
-------
|
|
2472
|
+
ndarray
|
|
2473
|
+
Coordinates on the plane [X, Y] where the 3D point projects towards
|
|
2474
|
+
perpendicularly.
|
|
2475
|
+
|
|
2476
|
+
Examples
|
|
2477
|
+
--------
|
|
2478
|
+
This function can be used with :func:`intersectRayPlane` to find the
|
|
2479
|
+
location on the plane the ray intersects::
|
|
2480
|
+
|
|
2481
|
+
# plane information
|
|
2482
|
+
planeOrigin = [0, 0, 0]
|
|
2483
|
+
planeNormal = [0, 0, 1] # must be normalized
|
|
2484
|
+
planeUpAxis = perp([0, 1, 0], planeNormal) # must also be normalized
|
|
2485
|
+
|
|
2486
|
+
# ray
|
|
2487
|
+
rayDir = [0, 0, -1]
|
|
2488
|
+
rayOrigin = [0, 0, 5]
|
|
2489
|
+
|
|
2490
|
+
# get the intersect in 3D world space
|
|
2491
|
+
pnt = intersectRayPlane(rayOrigin, rayDir, planeOrigin, planeNormal)
|
|
2492
|
+
|
|
2493
|
+
# get the 2D coordinates on the plane the intersect occurred
|
|
2494
|
+
planeX, planeY = ortho3Dto2D(pnt, planeOrigin, planeNormal, planeUpAxis)
|
|
2495
|
+
|
|
2496
|
+
"""
|
|
2497
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2498
|
+
|
|
2499
|
+
p = np.asarray(p, dtype=dtype)
|
|
2500
|
+
orig = np.asarray(orig, dtype=dtype)
|
|
2501
|
+
normal = np.asarray(normal, dtype=dtype)
|
|
2502
|
+
up = np.asarray(up, dtype=dtype)
|
|
2503
|
+
|
|
2504
|
+
toReturn = np.zeros((2,))
|
|
2505
|
+
|
|
2506
|
+
offset = p - orig
|
|
2507
|
+
if right is None:
|
|
2508
|
+
# derive X axis with cross product
|
|
2509
|
+
toReturn[0] = dot(offset, cross(normal, up, dtype=dtype), dtype=dtype)
|
|
2510
|
+
else:
|
|
2511
|
+
toReturn[0] = dot(offset, np.asarray(right, dtype=dtype), dtype=dtype)
|
|
2512
|
+
|
|
2513
|
+
toReturn[1] = dot(offset, up)
|
|
2514
|
+
|
|
2515
|
+
return toReturn
|
|
2516
|
+
|
|
2517
|
+
|
|
2518
|
+
def articulate(boneVecs, boneOris, dtype=None):
|
|
2519
|
+
"""Articulate an armature.
|
|
2520
|
+
|
|
2521
|
+
This function is used for forward kinematics and posing by specifying a list
|
|
2522
|
+
of 'bones'. A bone has a length and orientation, where sequential bones are
|
|
2523
|
+
linked end-to-end. Returns the transformed origins of the bones in scene
|
|
2524
|
+
coordinates and their orientations.
|
|
2525
|
+
|
|
2526
|
+
There are many applications for forward kinematics such as posing armatures
|
|
2527
|
+
and stimuli for display (eg. mocap data). Another application is for getting
|
|
2528
|
+
the location of the end effector of coordinate measuring hardware, where
|
|
2529
|
+
encoders measure the joint angles and the length of linking members are
|
|
2530
|
+
known. This can be used for computing pose from "Sword of Damocles"[1]_ like
|
|
2531
|
+
hardware or some other haptic input devices which the participant wears (eg.
|
|
2532
|
+
a glove that measures joint angles in the hand). The computed pose of the
|
|
2533
|
+
joints can be used to interact with virtual stimuli.
|
|
2534
|
+
|
|
2535
|
+
Parameters
|
|
2536
|
+
----------
|
|
2537
|
+
boneVecs : array_like
|
|
2538
|
+
Bone lengths [x, y, z] as an Nx3 array.
|
|
2539
|
+
boneOris : array_like
|
|
2540
|
+
Orientation of the bones as quaternions in form [x, y, z, w], relative
|
|
2541
|
+
to the previous bone.
|
|
2542
|
+
dtype : dtype or str, optional
|
|
2543
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2544
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2545
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2546
|
+
|
|
2547
|
+
Returns
|
|
2548
|
+
-------
|
|
2549
|
+
tuple
|
|
2550
|
+
Array of bone origins and orientations. The first origin is root
|
|
2551
|
+
position which is always at [0, 0, 0]. Use :func:`transform` to
|
|
2552
|
+
reposition the armature, or create a transformation matrix and use
|
|
2553
|
+
`applyMatrix` to translate and rotate the whole armature into position.
|
|
2554
|
+
|
|
2555
|
+
References
|
|
2556
|
+
----------
|
|
2557
|
+
.. [1] Sutherland, I. E. (1968). "A head-mounted three dimensional display".
|
|
2558
|
+
Proceedings of AFIPS 68, pp. 757-764
|
|
2559
|
+
|
|
2560
|
+
Examples
|
|
2561
|
+
--------
|
|
2562
|
+
Compute the orientations and origins of segments of an arm::
|
|
2563
|
+
|
|
2564
|
+
# bone lengths
|
|
2565
|
+
boneLengths = [[0., 1., 0.], [0., 1., 0.], [0., 1., 0.]]
|
|
2566
|
+
|
|
2567
|
+
# create quaternions for joints
|
|
2568
|
+
shoulder = mt.quatFromAxisAngle('-y', 45.0)
|
|
2569
|
+
elbow = mt.quatFromAxisAngle('+z', 45.0)
|
|
2570
|
+
wrist = mt.quatFromAxisAngle('+z', 45.0)
|
|
2571
|
+
|
|
2572
|
+
# articulate the parts of the arm
|
|
2573
|
+
boxPos, boxOri = mt.articulate(pos, [shoulder, elbow, wrist])
|
|
2574
|
+
|
|
2575
|
+
# assign positions and orientations to 3D objects
|
|
2576
|
+
shoulderModel.thePose.posOri = (boxPos[0, :], boxOri[0, :])
|
|
2577
|
+
elbowModel.thePose.posOri = (boxPos[1, :], boxOri[1, :])
|
|
2578
|
+
wristModel.thePose.posOri = (boxPos[2, :], boxOri[2, :])
|
|
2579
|
+
|
|
2580
|
+
"""
|
|
2581
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2582
|
+
|
|
2583
|
+
boneVecs = np.asarray(boneVecs, dtype=dtype)
|
|
2584
|
+
boneOris = np.asarray(boneOris, dtype=dtype)
|
|
2585
|
+
|
|
2586
|
+
jointOri = accumQuat(boneOris, dtype=dtype) # get joint orientations
|
|
2587
|
+
bonesRotated = applyQuat(jointOri, boneVecs, dtype=dtype) # rotate bones
|
|
2588
|
+
|
|
2589
|
+
# accumulate
|
|
2590
|
+
bonesTranslated = np.asarray(
|
|
2591
|
+
tuple(itertools.accumulate(bonesRotated[:], lambda a, b: a + b)),
|
|
2592
|
+
dtype=dtype)
|
|
2593
|
+
bonesTranslated -= bonesTranslated[0, :] # offset root length
|
|
2594
|
+
|
|
2595
|
+
return bonesTranslated, jointOri
|
|
2596
|
+
|
|
2597
|
+
|
|
2598
|
+
# ------------------------------------------------------------------------------
|
|
2599
|
+
# Quaternion Operations
|
|
2600
|
+
#
|
|
2601
|
+
|
|
2602
|
+
def slerp(q0, q1, t, shortest=True, out=None, dtype=None):
|
|
2603
|
+
"""Spherical linear interpolation (SLERP) between two quaternions.
|
|
2604
|
+
|
|
2605
|
+
The behaviour of this function depends on the types of arguments:
|
|
2606
|
+
|
|
2607
|
+
* If `q0` and `q1` are both 1-D and `t` is scalar, the interpolation at `t`
|
|
2608
|
+
is returned.
|
|
2609
|
+
* If `q0` and `q1` are both 2-D Nx4 arrays and `t` is scalar, an Nx4 array
|
|
2610
|
+
is returned with each row containing the interpolation at `t` for each
|
|
2611
|
+
quaternion pair at matching row indices in `q0` and `q1`.
|
|
2612
|
+
|
|
2613
|
+
Parameters
|
|
2614
|
+
----------
|
|
2615
|
+
q0 : array_like
|
|
2616
|
+
Initial quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
2617
|
+
are imaginary components.
|
|
2618
|
+
q1 : array_like
|
|
2619
|
+
Final quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
2620
|
+
are imaginary components.
|
|
2621
|
+
t : float
|
|
2622
|
+
Interpolation weight factor within interval 0.0 and 1.0.
|
|
2623
|
+
shortest : bool, optional
|
|
2624
|
+
Ensure interpolation occurs along the shortest arc along the 4-D
|
|
2625
|
+
hypersphere (default is `True`).
|
|
2626
|
+
out : ndarray, optional
|
|
2627
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
2628
|
+
output if `out` was not specified.
|
|
2629
|
+
dtype : dtype or str, optional
|
|
2630
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2631
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2632
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2633
|
+
|
|
2634
|
+
Returns
|
|
2635
|
+
-------
|
|
2636
|
+
ndarray
|
|
2637
|
+
Quaternion [x, y, z, w] at `t`.
|
|
2638
|
+
|
|
2639
|
+
Examples
|
|
2640
|
+
--------
|
|
2641
|
+
Interpolate between two orientations::
|
|
2642
|
+
|
|
2643
|
+
q0 = quatFromAxisAngle(90.0, degrees=True)
|
|
2644
|
+
q1 = quatFromAxisAngle(-90.0, degrees=True)
|
|
2645
|
+
# halfway between 90 and -90 is 0.0 or quaternion [0. 0. 0. 1.]
|
|
2646
|
+
qr = slerp(q0, q1, 0.5)
|
|
2647
|
+
|
|
2648
|
+
Example of smooth rotation of an object with fixed angular velocity::
|
|
2649
|
+
|
|
2650
|
+
degPerSec = 10.0 # rotate a stimulus at 10 degrees per second
|
|
2651
|
+
|
|
2652
|
+
# initial orientation, axis rotates in the Z direction
|
|
2653
|
+
qr = quatFromAxisAngle([0., 0., -1.], 0.0, degrees=True)
|
|
2654
|
+
# amount to rotate every second
|
|
2655
|
+
qv = quatFromAxisAngle([0., 0., -1.], degPerSec, degrees=True)
|
|
2656
|
+
|
|
2657
|
+
# ---- within main experiment loop ----
|
|
2658
|
+
# `frameTime` is the time elapsed in seconds from last `slerp`.
|
|
2659
|
+
qr = multQuat(qr, slerp((0., 0., 0., 1.), qv, degPerSec * frameTime))
|
|
2660
|
+
_, angle = quatToAxisAngle(qr) # discard axis, only need angle
|
|
2661
|
+
|
|
2662
|
+
# myStim is a GratingStim or anything with an 'ori' argument which
|
|
2663
|
+
# accepts angle in degrees
|
|
2664
|
+
myStim.ori = angle
|
|
2665
|
+
myStim.draw()
|
|
2666
|
+
|
|
2667
|
+
"""
|
|
2668
|
+
# Implementation based on code found here:
|
|
2669
|
+
# https://en.wikipedia.org/wiki/Slerp
|
|
2670
|
+
#
|
|
2671
|
+
if out is None:
|
|
2672
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2673
|
+
else:
|
|
2674
|
+
dtype = np.dtype(out.dtype).type
|
|
2675
|
+
|
|
2676
|
+
q0 = normalize(q0, dtype=dtype)
|
|
2677
|
+
q1 = normalize(q1, dtype=dtype)
|
|
2678
|
+
assert q0.shape == q1.shape
|
|
2679
|
+
|
|
2680
|
+
toReturn = np.zeros(q0.shape, dtype=dtype) if out is None else out
|
|
2681
|
+
toReturn.fill(0.0)
|
|
2682
|
+
t = dtype(t)
|
|
2683
|
+
q0, q1, qr = np.atleast_2d(q0, q1, toReturn)
|
|
2684
|
+
|
|
2685
|
+
d = np.clip(np.sum(q0 * q1, axis=1), -1.0, 1.0)
|
|
2686
|
+
if shortest:
|
|
2687
|
+
d[d < 0.0] *= -1.0
|
|
2688
|
+
q1[d < 0.0] *= -1.0
|
|
2689
|
+
|
|
2690
|
+
theta0 = np.arccos(d)
|
|
2691
|
+
theta = theta0 * t
|
|
2692
|
+
sinTheta = np.sin(theta)
|
|
2693
|
+
s1 = sinTheta / np.sin(theta0)
|
|
2694
|
+
s0 = np.cos(theta[:, np.newaxis]) - d[:, np.newaxis] * s1[:, np.newaxis]
|
|
2695
|
+
qr[:, :] = q0 * s0
|
|
2696
|
+
qr[:, :] += q1 * s1[:, np.newaxis]
|
|
2697
|
+
qr[:, :] += 0.0
|
|
2698
|
+
|
|
2699
|
+
return toReturn
|
|
2700
|
+
|
|
2701
|
+
|
|
2702
|
+
def quatToAxisAngle(q, degrees=True, dtype=None):
|
|
2703
|
+
"""Convert a quaternion to `axis` and `angle` representation.
|
|
2704
|
+
|
|
2705
|
+
This allows you to use quaternions to set the orientation of stimuli that
|
|
2706
|
+
have an `ori` property.
|
|
2707
|
+
|
|
2708
|
+
Parameters
|
|
2709
|
+
----------
|
|
2710
|
+
q : tuple, list or ndarray of float
|
|
2711
|
+
Quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
2712
|
+
are imaginary components.
|
|
2713
|
+
degrees : bool, optional
|
|
2714
|
+
Indicate `angle` is to be returned in degrees, otherwise `angle` will be
|
|
2715
|
+
returned in radians.
|
|
2716
|
+
dtype : dtype or str, optional
|
|
2717
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2718
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2719
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2720
|
+
|
|
2721
|
+
Returns
|
|
2722
|
+
-------
|
|
2723
|
+
tuple
|
|
2724
|
+
Axis and angle of quaternion in form ([ax, ay, az], angle). If `degrees`
|
|
2725
|
+
is `True`, the angle returned is in degrees, radians if `False`.
|
|
2726
|
+
|
|
2727
|
+
Examples
|
|
2728
|
+
--------
|
|
2729
|
+
Using a quaternion to rotate a stimulus a fixed angle each frame::
|
|
2730
|
+
|
|
2731
|
+
# initial orientation, axis rotates in the Z direction
|
|
2732
|
+
qr = quatFromAxisAngle([0., 0., -1.], 0.0, degrees=True)
|
|
2733
|
+
# rotation per-frame, here it's 0.1 degrees per frame
|
|
2734
|
+
qf = quatFromAxisAngle([0., 0., -1.], 0.1, degrees=True)
|
|
2735
|
+
|
|
2736
|
+
# ---- within main experiment loop ----
|
|
2737
|
+
# myStim is a GratingStim or anything with an 'ori' argument which
|
|
2738
|
+
# accepts angle in degrees
|
|
2739
|
+
qr = multQuat(qr, qf) # cumulative rotation
|
|
2740
|
+
_, angle = quatToAxisAngle(qr) # discard axis, only need angle
|
|
2741
|
+
myStim.ori = angle
|
|
2742
|
+
myStim.draw()
|
|
2743
|
+
|
|
2744
|
+
"""
|
|
2745
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2746
|
+
q = normalize(q, dtype=dtype) # returns ndarray
|
|
2747
|
+
v = np.sqrt(np.sum(np.square(q[:3])))
|
|
2748
|
+
|
|
2749
|
+
if np.count_nonzero(q[:3]):
|
|
2750
|
+
axis = q[:3] / v
|
|
2751
|
+
angle = dtype(2.0) * np.arctan2(v, q[3])
|
|
2752
|
+
else:
|
|
2753
|
+
axis = np.zeros((3,), dtype=dtype)
|
|
2754
|
+
axis[0] = 1.
|
|
2755
|
+
angle = 0.0
|
|
2756
|
+
|
|
2757
|
+
axis += 0.0
|
|
2758
|
+
|
|
2759
|
+
return axis, np.degrees(angle) if degrees else angle
|
|
2760
|
+
|
|
2761
|
+
|
|
2762
|
+
def quatFromAxisAngle(axis, angle, degrees=True, dtype=None):
|
|
2763
|
+
"""Create a quaternion to represent a rotation about `axis` vector by
|
|
2764
|
+
`angle`.
|
|
2765
|
+
|
|
2766
|
+
Parameters
|
|
2767
|
+
----------
|
|
2768
|
+
axis : tuple, list, ndarray or str
|
|
2769
|
+
Axis vector components or axis name. If a vector, input must be length
|
|
2770
|
+
3 [x, y, z]. A string can be specified for rotations about world axes
|
|
2771
|
+
(eg. `'+x'`, `'-z'`, `'+y'`, etc.)
|
|
2772
|
+
angle : float
|
|
2773
|
+
Rotation angle in radians (or degrees if `degrees` is `True`. Rotations
|
|
2774
|
+
are right-handed about the specified `axis`.
|
|
2775
|
+
degrees : bool, optional
|
|
2776
|
+
Indicate `angle` is in degrees, otherwise `angle` will be treated as
|
|
2777
|
+
radians.
|
|
2778
|
+
dtype : dtype or str, optional
|
|
2779
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2780
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2781
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2782
|
+
|
|
2783
|
+
Returns
|
|
2784
|
+
-------
|
|
2785
|
+
ndarray
|
|
2786
|
+
Quaternion [x, y, z, w].
|
|
2787
|
+
|
|
2788
|
+
Examples
|
|
2789
|
+
--------
|
|
2790
|
+
Create a quaternion from specified `axis` and `angle`::
|
|
2791
|
+
|
|
2792
|
+
axis = [0., 0., -1.] # rotate about -Z axis
|
|
2793
|
+
angle = 90.0 # angle in degrees
|
|
2794
|
+
ori = quatFromAxisAngle(axis, angle, degrees=True) # using degrees!
|
|
2795
|
+
|
|
2796
|
+
"""
|
|
2797
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2798
|
+
toReturn = np.zeros((4,), dtype=dtype)
|
|
2799
|
+
|
|
2800
|
+
if degrees:
|
|
2801
|
+
halfRad = np.radians(angle, dtype=dtype) / dtype(2.0)
|
|
2802
|
+
else:
|
|
2803
|
+
halfRad = np.dtype(dtype).type(angle) / dtype(2.0)
|
|
2804
|
+
|
|
2805
|
+
try:
|
|
2806
|
+
axis = VEC_AXES[axis] if isinstance(axis, str) else axis
|
|
2807
|
+
except KeyError:
|
|
2808
|
+
raise ValueError(
|
|
2809
|
+
"Value of `axis` must be either '+X', '-X', '+Y', '-Y', '+Z' or "
|
|
2810
|
+
"'-Z' or length 3 vector.")
|
|
2811
|
+
|
|
2812
|
+
axis = normalize(axis, dtype=dtype)
|
|
2813
|
+
if np.count_nonzero(axis) == 0:
|
|
2814
|
+
raise ValueError("Value for `axis` is zero-length.")
|
|
2815
|
+
|
|
2816
|
+
np.multiply(axis, np.sin(halfRad), out=toReturn[:3])
|
|
2817
|
+
toReturn[3] = np.cos(halfRad)
|
|
2818
|
+
toReturn += 0.0 # remove negative zeros
|
|
2819
|
+
|
|
2820
|
+
return toReturn
|
|
2821
|
+
|
|
2822
|
+
|
|
2823
|
+
def quatYawPitchRoll(q, degrees=True, out=None, dtype=None):
|
|
2824
|
+
"""Get the yaw, pitch, and roll of a quaternion's orientation relative to
|
|
2825
|
+
the world -Z axis.
|
|
2826
|
+
|
|
2827
|
+
You can multiply the quaternion by the inverse of some other one to make the
|
|
2828
|
+
returned values referenced to a local coordinate system.
|
|
2829
|
+
|
|
2830
|
+
Parameters
|
|
2831
|
+
----------
|
|
2832
|
+
q : tuple, list or ndarray of float
|
|
2833
|
+
Quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
2834
|
+
are imaginary components.
|
|
2835
|
+
degrees : bool, optional
|
|
2836
|
+
Indicate angles are to be returned in degrees, otherwise they will be
|
|
2837
|
+
returned in radians.
|
|
2838
|
+
out : ndarray
|
|
2839
|
+
Optional output array. Must have same `shape` and `dtype` as what is
|
|
2840
|
+
expected to be returned by this function of `out` was not specified.
|
|
2841
|
+
dtype : dtype or str, optional
|
|
2842
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2843
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2844
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2845
|
+
|
|
2846
|
+
Returns
|
|
2847
|
+
-------
|
|
2848
|
+
ndarray
|
|
2849
|
+
Yaw, pitch and roll [yaw, pitch, roll] of quaternion `q`.
|
|
2850
|
+
|
|
2851
|
+
"""
|
|
2852
|
+
# based off code found here:
|
|
2853
|
+
# https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
|
|
2854
|
+
# Yields the same results as PsychXR's LibOVRPose.getYawPitchRoll method.
|
|
2855
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2856
|
+
q = np.asarray(q, dtype=dtype)
|
|
2857
|
+
|
|
2858
|
+
toReturn = np.zeros((3,), dtype=dtype) if out is None else out
|
|
2859
|
+
|
|
2860
|
+
sinRcosP = 2.0 * (q[3] * q[0] + q[1] * q[2])
|
|
2861
|
+
cosRcosP = 1.0 - 2.0 * (q[0] * q[0] + q[1] * q[1])
|
|
2862
|
+
|
|
2863
|
+
toReturn[0] = np.arctan2(sinRcosP, cosRcosP)
|
|
2864
|
+
|
|
2865
|
+
sinp = 2.0 * (q[3] * q[1] - q[2] * q[0])
|
|
2866
|
+
|
|
2867
|
+
if np.fabs(sinp) >= 1.:
|
|
2868
|
+
toReturn[1] = np.copysign(np.pi / 2., sinp)
|
|
2869
|
+
else:
|
|
2870
|
+
toReturn[1] = np.arcsin(sinp)
|
|
2871
|
+
|
|
2872
|
+
sinYcosP = 2.0 * (q[3] * q[2] + q[0] * q[1])
|
|
2873
|
+
cosYcosP = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2])
|
|
2874
|
+
|
|
2875
|
+
toReturn[2] = np.arctan2(sinYcosP, cosYcosP)
|
|
2876
|
+
|
|
2877
|
+
if degrees:
|
|
2878
|
+
toReturn[:] = np.degrees(toReturn[:])
|
|
2879
|
+
|
|
2880
|
+
return toReturn
|
|
2881
|
+
|
|
2882
|
+
|
|
2883
|
+
def quatMagnitude(q, squared=False, out=None, dtype=None):
|
|
2884
|
+
"""Get the magnitude of a quaternion.
|
|
2885
|
+
|
|
2886
|
+
A quaternion is normalized if its magnitude is 1.
|
|
2887
|
+
|
|
2888
|
+
Parameters
|
|
2889
|
+
----------
|
|
2890
|
+
q : array_like
|
|
2891
|
+
Quaternion(s) in form [x, y, z, w] where w is real and x, y, z are
|
|
2892
|
+
imaginary components.
|
|
2893
|
+
squared : bool, optional
|
|
2894
|
+
If ``True`` return the squared magnitude. If you are just checking if a
|
|
2895
|
+
quaternion is normalized, the squared magnitude will suffice to avoid
|
|
2896
|
+
the square root operation.
|
|
2897
|
+
out : ndarray, optional
|
|
2898
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
2899
|
+
output if `out` was not specified.
|
|
2900
|
+
dtype : dtype or str, optional
|
|
2901
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2902
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2903
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2904
|
+
|
|
2905
|
+
Returns
|
|
2906
|
+
-------
|
|
2907
|
+
float or ndarray
|
|
2908
|
+
Magnitude of quaternion `q`.
|
|
2909
|
+
|
|
2910
|
+
"""
|
|
2911
|
+
if out is None:
|
|
2912
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2913
|
+
else:
|
|
2914
|
+
dtype = np.dtype(out.dtype).type
|
|
2915
|
+
|
|
2916
|
+
q = np.asarray(q, dtype=dtype)
|
|
2917
|
+
if q.ndim == 1:
|
|
2918
|
+
assert q.shape[0] == 4
|
|
2919
|
+
if squared:
|
|
2920
|
+
toReturn = np.sum(np.square(q))
|
|
2921
|
+
else:
|
|
2922
|
+
toReturn = np.sqrt(np.sum(np.square(q)))
|
|
2923
|
+
elif q.ndim == 2:
|
|
2924
|
+
assert q.shape[1] == 4
|
|
2925
|
+
toReturn = np.zeros((q.shape[0],), dtype=dtype) if out is None else out
|
|
2926
|
+
if squared:
|
|
2927
|
+
toReturn[:] = np.sum(np.square(q), axis=1)
|
|
2928
|
+
else:
|
|
2929
|
+
toReturn[:] = np.sqrt(np.sum(np.square(q), axis=1))
|
|
2930
|
+
else:
|
|
2931
|
+
raise ValueError("Input argument 'q' has incorrect dimensions.")
|
|
2932
|
+
|
|
2933
|
+
return toReturn
|
|
2934
|
+
|
|
2935
|
+
|
|
2936
|
+
def multQuat(q0, q1, out=None, dtype=None):
|
|
2937
|
+
"""Multiply quaternion `q0` and `q1`.
|
|
2938
|
+
|
|
2939
|
+
The orientation of the returned quaternion is the combination of the input
|
|
2940
|
+
quaternions.
|
|
2941
|
+
|
|
2942
|
+
Parameters
|
|
2943
|
+
----------
|
|
2944
|
+
q0, q1 : array_like
|
|
2945
|
+
Quaternions to multiply in form [x, y, z, w] where w is real and x, y, z
|
|
2946
|
+
are imaginary components. If 2D (Nx4) arrays are specified, quaternions
|
|
2947
|
+
are multiplied row-wise between each array.
|
|
2948
|
+
out : ndarray, optional
|
|
2949
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
2950
|
+
output if `out` was not specified.
|
|
2951
|
+
dtype : dtype or str, optional
|
|
2952
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
2953
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
2954
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
2955
|
+
|
|
2956
|
+
Returns
|
|
2957
|
+
-------
|
|
2958
|
+
ndarray
|
|
2959
|
+
Combined orientations of `q0` amd `q1`.
|
|
2960
|
+
|
|
2961
|
+
Notes
|
|
2962
|
+
-----
|
|
2963
|
+
* Quaternions are normalized prior to multiplication.
|
|
2964
|
+
|
|
2965
|
+
Examples
|
|
2966
|
+
--------
|
|
2967
|
+
Combine the orientations of two quaternions::
|
|
2968
|
+
|
|
2969
|
+
a = quatFromAxisAngle([0, 0, -1], 45.0, degrees=True)
|
|
2970
|
+
b = quatFromAxisAngle([0, 0, -1], 90.0, degrees=True)
|
|
2971
|
+
c = multQuat(a, b) # rotates 135 degrees about -Z axis
|
|
2972
|
+
|
|
2973
|
+
"""
|
|
2974
|
+
if out is None:
|
|
2975
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
2976
|
+
else:
|
|
2977
|
+
dtype = np.dtype(out.dtype).type
|
|
2978
|
+
|
|
2979
|
+
q0 = normalize(q0, dtype=dtype)
|
|
2980
|
+
q1 = normalize(q1, dtype=dtype)
|
|
2981
|
+
assert q0.shape == q1.shape
|
|
2982
|
+
toReturn = np.zeros(q0.shape, dtype=dtype) if out is None else out
|
|
2983
|
+
toReturn.fill(0.0) # clear array
|
|
2984
|
+
q0, q1, qr = np.atleast_2d(q0, q1, toReturn)
|
|
2985
|
+
|
|
2986
|
+
# multiply quaternions for each row of the operand arrays
|
|
2987
|
+
qr[:, :3] = np.cross(q0[:, :3], q1[:, :3], axis=1)
|
|
2988
|
+
qr[:, :3] += q0[:, :3] * np.expand_dims(q1[:, 3], axis=1)
|
|
2989
|
+
qr[:, :3] += q1[:, :3] * np.expand_dims(q0[:, 3], axis=1)
|
|
2990
|
+
qr[:, 3] = q0[:, 3]
|
|
2991
|
+
qr[:, 3] *= q1[:, 3]
|
|
2992
|
+
qr[:, 3] -= np.sum(np.multiply(q0[:, :3], q1[:, :3]), axis=1) # dot product
|
|
2993
|
+
qr += 0.0
|
|
2994
|
+
|
|
2995
|
+
return toReturn
|
|
2996
|
+
|
|
2997
|
+
|
|
2998
|
+
def invertQuat(q, out=None, dtype=None):
|
|
2999
|
+
"""Get the multiplicative inverse of a quaternion.
|
|
3000
|
+
|
|
3001
|
+
This gives a quaternion which rotates in the opposite direction with equal
|
|
3002
|
+
magnitude. Multiplying a quaternion by its inverse returns an identity
|
|
3003
|
+
quaternion as both orientations cancel out.
|
|
3004
|
+
|
|
3005
|
+
Parameters
|
|
3006
|
+
----------
|
|
3007
|
+
q : ndarray, list, or tuple of float
|
|
3008
|
+
Quaternion to invert in form [x, y, z, w] where w is real and x, y, z
|
|
3009
|
+
are imaginary components. If `q` is 2D (Nx4), each row is treated as a
|
|
3010
|
+
separate quaternion and inverted.
|
|
3011
|
+
out : ndarray, optional
|
|
3012
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3013
|
+
output if `out` was not specified.
|
|
3014
|
+
dtype : dtype or str, optional
|
|
3015
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3016
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3017
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3018
|
+
|
|
3019
|
+
Returns
|
|
3020
|
+
-------
|
|
3021
|
+
ndarray
|
|
3022
|
+
Inverse of quaternion `q`.
|
|
3023
|
+
|
|
3024
|
+
Examples
|
|
3025
|
+
--------
|
|
3026
|
+
Show that multiplying a quaternion by its inverse returns an identity
|
|
3027
|
+
quaternion where [x=0, y=0, z=0, w=1]::
|
|
3028
|
+
|
|
3029
|
+
angle = 90.0
|
|
3030
|
+
axis = [0., 0., -1.]
|
|
3031
|
+
q = quatFromAxisAngle(axis, angle, degrees=True)
|
|
3032
|
+
qinv = invertQuat(q)
|
|
3033
|
+
qr = multQuat(q, qinv)
|
|
3034
|
+
qi = np.array([0., 0., 0., 1.]) # identity quaternion
|
|
3035
|
+
print(np.allclose(qi, qr)) # True
|
|
3036
|
+
|
|
3037
|
+
Notes
|
|
3038
|
+
-----
|
|
3039
|
+
* Quaternions are normalized prior to inverting.
|
|
3040
|
+
|
|
3041
|
+
"""
|
|
3042
|
+
if out is None:
|
|
3043
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3044
|
+
else:
|
|
3045
|
+
dtype = np.dtype(out.dtype).type
|
|
3046
|
+
|
|
3047
|
+
q = normalize(q, dtype=dtype)
|
|
3048
|
+
toReturn = np.zeros(q.shape, dtype=dtype) if out is None else out
|
|
3049
|
+
qn, qinv = np.atleast_2d(q, toReturn) # 2d views
|
|
3050
|
+
|
|
3051
|
+
# conjugate the quaternion
|
|
3052
|
+
qinv[:, :3] = -qn[:, :3]
|
|
3053
|
+
qinv[:, 3] = qn[:, 3]
|
|
3054
|
+
qinv /= np.sum(np.square(qn), axis=1)[:, np.newaxis]
|
|
3055
|
+
qinv += 0.0 # remove negative zeros
|
|
3056
|
+
|
|
3057
|
+
return toReturn
|
|
3058
|
+
|
|
3059
|
+
|
|
3060
|
+
def applyQuat(q, points, out=None, dtype=None):
|
|
3061
|
+
"""Rotate points/coordinates using a quaternion.
|
|
3062
|
+
|
|
3063
|
+
This is similar to using `applyMatrix` with a rotation matrix. However, it
|
|
3064
|
+
is computationally less intensive to use `applyQuat` if one only wishes to
|
|
3065
|
+
rotate points.
|
|
3066
|
+
|
|
3067
|
+
Parameters
|
|
3068
|
+
----------
|
|
3069
|
+
q : array_like
|
|
3070
|
+
Quaternion to invert in form [x, y, z, w] where w is real and x, y, z
|
|
3071
|
+
are imaginary components.
|
|
3072
|
+
points : array_like
|
|
3073
|
+
2D array of vectors or points to transform, where each row is a single
|
|
3074
|
+
point. Only the x, y, and z components (the first three columns) are
|
|
3075
|
+
rotated. Additional columns are copied.
|
|
3076
|
+
out : ndarray, optional
|
|
3077
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3078
|
+
output if `out` was not specified.
|
|
3079
|
+
dtype : dtype or str, optional
|
|
3080
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3081
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3082
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3083
|
+
|
|
3084
|
+
Returns
|
|
3085
|
+
-------
|
|
3086
|
+
ndarray
|
|
3087
|
+
Transformed points.
|
|
3088
|
+
|
|
3089
|
+
Examples
|
|
3090
|
+
--------
|
|
3091
|
+
Rotate points using a quaternion::
|
|
3092
|
+
|
|
3093
|
+
points = [[1., 0., 0.], [0., -1., 0.]]
|
|
3094
|
+
quat = quatFromAxisAngle(-90.0, [0., 0., -1.], degrees=True)
|
|
3095
|
+
pointsRotated = applyQuat(quat, points)
|
|
3096
|
+
# [[0. 1. 0.]
|
|
3097
|
+
# [1. 0. 0.]]
|
|
3098
|
+
|
|
3099
|
+
Show that you get the same result as a rotation matrix::
|
|
3100
|
+
|
|
3101
|
+
axis = [0., 0., -1.]
|
|
3102
|
+
angle = -90.0
|
|
3103
|
+
rotMat = rotationMatrix(axis, angle)[:3, :3] # rotation sub-matrix only
|
|
3104
|
+
rotQuat = quatFromAxisAngle(angle, axis, degrees=True)
|
|
3105
|
+
points = [[1., 0., 0.], [0., -1., 0.]]
|
|
3106
|
+
isClose = np.allclose(applyMatrix(rotMat, points), # True
|
|
3107
|
+
applyQuat(rotQuat, points))
|
|
3108
|
+
|
|
3109
|
+
Specifying an array to `q` where each row is a quaternion transforms points
|
|
3110
|
+
in corresponding rows of `points`::
|
|
3111
|
+
|
|
3112
|
+
points = [[1., 0., 0.], [0., -1., 0.]]
|
|
3113
|
+
quats = [quatFromAxisAngle(-90.0, [0., 0., -1.], degrees=True),
|
|
3114
|
+
quatFromAxisAngle(45.0, [0., 0., -1.], degrees=True)]
|
|
3115
|
+
applyQuat(quats, points)
|
|
3116
|
+
|
|
3117
|
+
"""
|
|
3118
|
+
# based on 'quat_mul_vec3' implementation from linmath.h
|
|
3119
|
+
if out is None:
|
|
3120
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3121
|
+
else:
|
|
3122
|
+
dtype = np.dtype(out.dtype).type
|
|
3123
|
+
|
|
3124
|
+
qin = np.asarray(q, dtype=dtype)
|
|
3125
|
+
points = np.asarray(points, dtype=dtype)
|
|
3126
|
+
|
|
3127
|
+
if out is not None:
|
|
3128
|
+
assert points.shape == out.shape
|
|
3129
|
+
|
|
3130
|
+
toReturn = np.zeros(points.shape, dtype=dtype) if out is None else out
|
|
3131
|
+
pin, pout = np.atleast_2d(points, toReturn)
|
|
3132
|
+
pout[:, :] = pin[:, :] # copy values into output array
|
|
3133
|
+
|
|
3134
|
+
if qin.ndim == 1:
|
|
3135
|
+
assert qin.shape[0] == 4
|
|
3136
|
+
t = cross(qin[:3], pin[:, :3]) * dtype(2.0)
|
|
3137
|
+
u = cross(qin[:3], t)
|
|
3138
|
+
t *= qin[3]
|
|
3139
|
+
pout[:, :3] += t
|
|
3140
|
+
pout[:, :3] += u
|
|
3141
|
+
elif qin.ndim == 2:
|
|
3142
|
+
assert qin.shape[1] == 4 and qin.shape[0] == pin.shape[0]
|
|
3143
|
+
t = cross(qin[:, :3], pin[:, :3])
|
|
3144
|
+
t *= dtype(2.0)
|
|
3145
|
+
u = cross(qin[:, :3], t)
|
|
3146
|
+
t *= np.expand_dims(qin[:, 3], axis=1)
|
|
3147
|
+
pout[:, :3] += t
|
|
3148
|
+
pout[:, :3] += u
|
|
3149
|
+
else:
|
|
3150
|
+
raise ValueError("Input arguments have invalid dimensions.")
|
|
3151
|
+
|
|
3152
|
+
return toReturn
|
|
3153
|
+
|
|
3154
|
+
|
|
3155
|
+
def accumQuat(qlist, out=None, dtype=None):
|
|
3156
|
+
"""Accumulate quaternion rotations.
|
|
3157
|
+
|
|
3158
|
+
Chain multiplies an Nx4 array of quaternions, accumulating their rotations.
|
|
3159
|
+
This function can be used for computing the orientation of joints in an
|
|
3160
|
+
armature for forward kinematics. The first quaternion is treated as the
|
|
3161
|
+
'root' and the last is the orientation of the end effector.
|
|
3162
|
+
|
|
3163
|
+
Parameters
|
|
3164
|
+
----------
|
|
3165
|
+
q : array_like
|
|
3166
|
+
Nx4 array of quaternions to accumulate, where each row is a quaternion.
|
|
3167
|
+
out : ndarray, optional
|
|
3168
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3169
|
+
output if `out` was not specified. In this case, the same shape as
|
|
3170
|
+
`qlist`.
|
|
3171
|
+
dtype : dtype or str, optional
|
|
3172
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3173
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3174
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3175
|
+
|
|
3176
|
+
Returns
|
|
3177
|
+
-------
|
|
3178
|
+
ndarray
|
|
3179
|
+
Nx4 array of quaternions.
|
|
3180
|
+
|
|
3181
|
+
Examples
|
|
3182
|
+
--------
|
|
3183
|
+
Get the orientation of joints in an armature if we know their relative
|
|
3184
|
+
angles::
|
|
3185
|
+
|
|
3186
|
+
shoulder = quatFromAxisAngle('-x', 45.0) # rotate shoulder down 45 deg
|
|
3187
|
+
elbow = quatFromAxisAngle('+x', 45.0) # rotate elbow up 45 deg
|
|
3188
|
+
wrist = quatFromAxisAngle('-x', 45.0) # rotate wrist down 45 deg
|
|
3189
|
+
finger = quatFromAxisAngle('+x', 0.0) # keep finger in-line with wrist
|
|
3190
|
+
|
|
3191
|
+
armRotations = accumQuat([shoulder, elbow, wrist, finger])
|
|
3192
|
+
|
|
3193
|
+
"""
|
|
3194
|
+
if out is None:
|
|
3195
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3196
|
+
else:
|
|
3197
|
+
dtype = np.dtype(out.dtype).type
|
|
3198
|
+
|
|
3199
|
+
qlist = np.asarray(qlist, dtype=dtype)
|
|
3200
|
+
qlist = np.atleast_2d(qlist)
|
|
3201
|
+
|
|
3202
|
+
qr = np.zeros_like(qlist, dtype=dtype) if out is None else out
|
|
3203
|
+
qr[:, :] = tuple(itertools.accumulate(
|
|
3204
|
+
qlist[:], lambda a, b: multQuat(a, b, dtype=dtype)))
|
|
3205
|
+
|
|
3206
|
+
return qr
|
|
3207
|
+
|
|
3208
|
+
|
|
3209
|
+
def alignTo(v, t, out=None, dtype=None):
|
|
3210
|
+
"""Compute a quaternion which rotates one vector to align with another.
|
|
3211
|
+
|
|
3212
|
+
Parameters
|
|
3213
|
+
----------
|
|
3214
|
+
v : array_like
|
|
3215
|
+
Vector [x, y, z] to rotate. Can be Nx3, but must have the same shape as
|
|
3216
|
+
`t`.
|
|
3217
|
+
t : array_like
|
|
3218
|
+
Target [x, y, z] vector to align to. Can be Nx3, but must have the same
|
|
3219
|
+
shape as `v`.
|
|
3220
|
+
out : ndarray, optional
|
|
3221
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3222
|
+
output if `out` was not specified.
|
|
3223
|
+
dtype : dtype or str, optional
|
|
3224
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3225
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3226
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3227
|
+
|
|
3228
|
+
Returns
|
|
3229
|
+
-------
|
|
3230
|
+
ndarray
|
|
3231
|
+
Quaternion which rotates `v` to `t`.
|
|
3232
|
+
|
|
3233
|
+
Examples
|
|
3234
|
+
--------
|
|
3235
|
+
Rotate some vectors to align with other vectors, inputs should be
|
|
3236
|
+
normalized::
|
|
3237
|
+
|
|
3238
|
+
vec = [[1, 0, 0], [0, 1, 0], [1, 0, 0]]
|
|
3239
|
+
targets = [[0, 1, 0], [0, -1, 0], [-1, 0, 0]]
|
|
3240
|
+
|
|
3241
|
+
qr = alignTo(vec, targets)
|
|
3242
|
+
vecRotated = applyQuat(qr, vec)
|
|
3243
|
+
|
|
3244
|
+
numpy.allclose(vecRotated, targets) # True
|
|
3245
|
+
|
|
3246
|
+
Get matrix which orients vertices towards a point::
|
|
3247
|
+
|
|
3248
|
+
point = [5, 6, 7]
|
|
3249
|
+
vec = [0, 0, -1] # initial facing is -Z (forward in GL)
|
|
3250
|
+
|
|
3251
|
+
targetVec = normalize(point - vec)
|
|
3252
|
+
qr = alignTo(vec, targetVec) # get rotation to align
|
|
3253
|
+
|
|
3254
|
+
M = quatToMatrix(qr) # 4x4 transformation matrix
|
|
3255
|
+
|
|
3256
|
+
"""
|
|
3257
|
+
# based off Quaternion::align from Quaternion.hpp from OpenMP
|
|
3258
|
+
if out is None:
|
|
3259
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3260
|
+
else:
|
|
3261
|
+
dtype = np.dtype(out.dtype).type
|
|
3262
|
+
|
|
3263
|
+
v = normalize(v, dtype=dtype)
|
|
3264
|
+
t = normalize(t, dtype=dtype)
|
|
3265
|
+
|
|
3266
|
+
if out is None:
|
|
3267
|
+
if v.ndim == 1:
|
|
3268
|
+
toReturn = np.zeros((4,), dtype=dtype)
|
|
3269
|
+
else:
|
|
3270
|
+
toReturn = np.zeros((v.shape[0], 4), dtype=dtype)
|
|
3271
|
+
else:
|
|
3272
|
+
toReturn = out
|
|
3273
|
+
|
|
3274
|
+
qr, v2d, t2d = np.atleast_2d(toReturn, v, t)
|
|
3275
|
+
|
|
3276
|
+
b = bisector(v2d, t2d, norm=True, dtype=dtype)
|
|
3277
|
+
cosHalfAngle = dot(v2d, b, dtype=dtype)
|
|
3278
|
+
|
|
3279
|
+
nonparallel = cosHalfAngle > 0.0 # rotation is not 180 degrees
|
|
3280
|
+
qr[nonparallel, :3] = cross(v2d[nonparallel], b[nonparallel], dtype=dtype)
|
|
3281
|
+
qr[nonparallel, 3] = cosHalfAngle[nonparallel]
|
|
3282
|
+
|
|
3283
|
+
if np.all(nonparallel): # don't bother handling special cases
|
|
3284
|
+
return toReturn + 0.0
|
|
3285
|
+
|
|
3286
|
+
# deal with cases where the vectors are facing exact opposite directions
|
|
3287
|
+
ry = np.logical_and(np.abs(v2d[:, 0]) >= np.abs(v2d[:, 1]), ~nonparallel)
|
|
3288
|
+
rx = np.logical_and(~ry, ~nonparallel)
|
|
3289
|
+
|
|
3290
|
+
getLength = lambda x, y: np.sqrt(x * x + y * y)
|
|
3291
|
+
if not np.all(rx):
|
|
3292
|
+
invLength = getLength(v2d[ry, 0], v2d[ry, 2])
|
|
3293
|
+
invLength = np.where(invLength > 0.0, 1.0 / invLength, invLength) # avoid x / 0
|
|
3294
|
+
qr[ry, 0] = -v2d[ry, 2] * invLength
|
|
3295
|
+
qr[ry, 2] = v2d[ry, 0] * invLength
|
|
3296
|
+
|
|
3297
|
+
if not np.all(ry): # skip if all the same edge case
|
|
3298
|
+
invLength = getLength(v2d[rx, 1], v2d[rx, 2])
|
|
3299
|
+
invLength = np.where(invLength > 0.0, 1.0 / invLength, invLength)
|
|
3300
|
+
qr[rx, 1] = v2d[rx, 2] * invLength
|
|
3301
|
+
qr[rx, 2] = -v2d[rx, 1] * invLength
|
|
3302
|
+
|
|
3303
|
+
return toReturn + 0.0
|
|
3304
|
+
|
|
3305
|
+
|
|
3306
|
+
def matrixToQuat(m, out=None, dtype=None):
|
|
3307
|
+
"""Convert a rotation matrix to a quaternion.
|
|
3308
|
+
|
|
3309
|
+
Parameters
|
|
3310
|
+
----------
|
|
3311
|
+
m : array_like
|
|
3312
|
+
3x3 rotation matrix (row-major). A 4x4 affine transformation matrix may
|
|
3313
|
+
be provided, assuming the top-left 3x3 sub-matrix is orthonormal and
|
|
3314
|
+
is a rotation group.
|
|
3315
|
+
out : ndarray, optional
|
|
3316
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3317
|
+
output if `out` was not specified.
|
|
3318
|
+
dtype : dtype or str, optional
|
|
3319
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3320
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3321
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3322
|
+
|
|
3323
|
+
Returns
|
|
3324
|
+
-------
|
|
3325
|
+
ndarray
|
|
3326
|
+
Rotation quaternion.
|
|
3327
|
+
|
|
3328
|
+
Notes
|
|
3329
|
+
-----
|
|
3330
|
+
* Depending on the input, returned quaternions may not be exactly the same
|
|
3331
|
+
as the one used to construct the rotation matrix (i.e. by calling
|
|
3332
|
+
`quatToMatrix`), typically when a large rotation angle is used. However,
|
|
3333
|
+
the returned quaternion should result in the same rotation when applied to
|
|
3334
|
+
points.
|
|
3335
|
+
|
|
3336
|
+
Examples
|
|
3337
|
+
--------
|
|
3338
|
+
Converting a rotation matrix from the OpenGL matrix stack to a quaternion::
|
|
3339
|
+
|
|
3340
|
+
glRotatef(45., -1, 0, 0)
|
|
3341
|
+
|
|
3342
|
+
m = np.zeros((4, 4), dtype='float32') # store the matrix
|
|
3343
|
+
GL.glGetFloatv(
|
|
3344
|
+
GL.GL_MODELVIEW_MATRIX,
|
|
3345
|
+
m.ctypes.data_as(ctypes.POINTER(ctypes.c_float)))
|
|
3346
|
+
|
|
3347
|
+
qr = matrixToQuat(m.T) # must be transposed
|
|
3348
|
+
|
|
3349
|
+
Interpolation between two 4x4 transformation matrices::
|
|
3350
|
+
|
|
3351
|
+
interpWeight = 0.5
|
|
3352
|
+
|
|
3353
|
+
posStart = mStart[:3, 3]
|
|
3354
|
+
oriStart = matrixToQuat(mStart)
|
|
3355
|
+
|
|
3356
|
+
posEnd = mEnd[:3, 3]
|
|
3357
|
+
oriEnd = matrixToQuat(mEnd)
|
|
3358
|
+
|
|
3359
|
+
oriInterp = slerp(qStart, qEnd, interpWeight)
|
|
3360
|
+
posInterp = lerp(posStart, posEnd, interpWeight)
|
|
3361
|
+
|
|
3362
|
+
mInterp = posOriToMatrix(posInterp, oriInterp)
|
|
3363
|
+
|
|
3364
|
+
"""
|
|
3365
|
+
# based off example `Maths - Conversion Matrix to Quaternion` from
|
|
3366
|
+
# https://www.euclideanspace.com/
|
|
3367
|
+
if out is None:
|
|
3368
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3369
|
+
else:
|
|
3370
|
+
dtype = np.dtype(out.dtype).type
|
|
3371
|
+
|
|
3372
|
+
m = np.asarray(m, dtype=dtype)
|
|
3373
|
+
|
|
3374
|
+
if m.shape == (4, 4,) or m.shape == (3, 4,):
|
|
3375
|
+
m = m[:3, :3] # keep only rotation group sub-matrix
|
|
3376
|
+
elif m.shape == (3, 3,):
|
|
3377
|
+
pass # fine, nop
|
|
3378
|
+
else:
|
|
3379
|
+
raise ValueError("Input matrix `m` must be 3x3 or 4x4.")
|
|
3380
|
+
|
|
3381
|
+
toReturn = np.zeros((4,), dtype=dtype) if out is None else out
|
|
3382
|
+
|
|
3383
|
+
tr = m[0, 0] + m[1, 1] + m[2, 2]
|
|
3384
|
+
if tr > 0.0:
|
|
3385
|
+
s = np.sqrt(tr + 1.0) * 2.0
|
|
3386
|
+
toReturn[3] = dtype(0.25) * s
|
|
3387
|
+
toReturn[0] = (m[2, 1] - m[1, 2]) / s
|
|
3388
|
+
toReturn[1] = (m[0, 2] - m[2, 0]) / s
|
|
3389
|
+
toReturn[2] = (m[1, 0] - m[0, 1]) / s
|
|
3390
|
+
elif m[0, 0] > m[1, 1] and m[0, 0] > m[2, 2]:
|
|
3391
|
+
s = np.sqrt(dtype(1.0) + m[0, 0] - m[1, 1] - m[2, 2]) * dtype(2.0)
|
|
3392
|
+
toReturn[3] = (m[2, 1] - m[1, 2]) / s
|
|
3393
|
+
toReturn[0] = dtype(0.25) * s
|
|
3394
|
+
toReturn[1] = (m[0, 1] + m[1, 0]) / s
|
|
3395
|
+
toReturn[2] = (m[0, 2] + m[2, 0]) / s
|
|
3396
|
+
elif m[1, 1] > m[2, 2]:
|
|
3397
|
+
s = np.sqrt(dtype(1.0) + m[1, 1] - m[0, 0] - m[2, 2]) * dtype(2.0)
|
|
3398
|
+
toReturn[3] = (m[0, 2] - m[2, 0]) / s
|
|
3399
|
+
toReturn[0] = (m[0, 1] + m[1, 0]) / s
|
|
3400
|
+
toReturn[1] = dtype(0.25) * s
|
|
3401
|
+
toReturn[2] = (m[1, 2] + m[2, 1]) / s
|
|
3402
|
+
else:
|
|
3403
|
+
s = np.sqrt(dtype(1.0) + m[2, 2] - m[0, 0] - m[1, 1]) * dtype(2.0)
|
|
3404
|
+
toReturn[3] = (m[1, 0] - m[0, 1]) / s
|
|
3405
|
+
toReturn[0] = (m[0, 2] + m[2, 0]) / s
|
|
3406
|
+
toReturn[1] = (m[1, 2] + m[2, 1]) / s
|
|
3407
|
+
toReturn[2] = dtype(0.25) * s
|
|
3408
|
+
|
|
3409
|
+
return toReturn
|
|
3410
|
+
|
|
3411
|
+
|
|
3412
|
+
# ------------------------------------------------------------------------------
|
|
3413
|
+
# Matrix Operations
|
|
3414
|
+
#
|
|
3415
|
+
|
|
3416
|
+
def identityMatrix(size=4, out=None, dtype=None):
|
|
3417
|
+
"""Create an sqaure identity matrix.
|
|
3418
|
+
|
|
3419
|
+
Parameters
|
|
3420
|
+
----------
|
|
3421
|
+
size : int, optional
|
|
3422
|
+
Size of the matrix. Default is `4` for a 4x4 identity matrix.
|
|
3423
|
+
out : ndarray, optional
|
|
3424
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3425
|
+
output if `out` was not specified.
|
|
3426
|
+
dtype : dtype or str, optional
|
|
3427
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3428
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3429
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3430
|
+
|
|
3431
|
+
Returns
|
|
3432
|
+
-------
|
|
3433
|
+
ndarray
|
|
3434
|
+
Identity matrix.
|
|
3435
|
+
|
|
3436
|
+
"""
|
|
3437
|
+
# this might seem overkill for creating an identity matrix, but it is
|
|
3438
|
+
# necessary to ensure the correct data type is used
|
|
3439
|
+
if out is None:
|
|
3440
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3441
|
+
ident = np.zeros((size, size), dtype=dtype)
|
|
3442
|
+
else:
|
|
3443
|
+
dtype = np.dtype(out.dtype).type
|
|
3444
|
+
ident = out
|
|
3445
|
+
ident.fill(0.0)
|
|
3446
|
+
|
|
3447
|
+
np.fill_diagonal(ident, 1.0)
|
|
3448
|
+
|
|
3449
|
+
return ident
|
|
3450
|
+
|
|
3451
|
+
|
|
3452
|
+
def quatToMatrix(q, out=None, dtype=None):
|
|
3453
|
+
"""Create a 4x4 rotation matrix from a quaternion.
|
|
3454
|
+
|
|
3455
|
+
Parameters
|
|
3456
|
+
----------
|
|
3457
|
+
q : tuple, list or ndarray of float
|
|
3458
|
+
Quaternion to convert in form [x, y, z, w] where w is real and x, y, z
|
|
3459
|
+
are imaginary components.
|
|
3460
|
+
out : ndarray or None
|
|
3461
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3462
|
+
output if `out` was not specified.
|
|
3463
|
+
dtype : dtype or str, optional
|
|
3464
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3465
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3466
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3467
|
+
|
|
3468
|
+
Returns
|
|
3469
|
+
-------
|
|
3470
|
+
ndarray or None
|
|
3471
|
+
4x4 rotation matrix in row-major order.
|
|
3472
|
+
|
|
3473
|
+
Examples
|
|
3474
|
+
--------
|
|
3475
|
+
Convert a quaternion to a rotation matrix::
|
|
3476
|
+
|
|
3477
|
+
point = [0., 1., 0., 1.] # 4-vector form [x, y, z, 1.0]
|
|
3478
|
+
ori = [0., 0., 0., 1.]
|
|
3479
|
+
rotMat = quatToMatrix(ori)
|
|
3480
|
+
# rotate 'point' using matrix multiplication
|
|
3481
|
+
newPoint = np.matmul(rotMat.T, point) # returns [-1., 0., 0., 1.]
|
|
3482
|
+
|
|
3483
|
+
Rotate all points in an array (each row is a coordinate)::
|
|
3484
|
+
|
|
3485
|
+
points = np.asarray([[0., 0., 0., 1.],
|
|
3486
|
+
[0., 1., 0., 1.],
|
|
3487
|
+
[1., 1., 0., 1.]])
|
|
3488
|
+
newPoints = points.dot(rotMat)
|
|
3489
|
+
|
|
3490
|
+
Notes
|
|
3491
|
+
-----
|
|
3492
|
+
* Quaternions are normalized prior to conversion.
|
|
3493
|
+
|
|
3494
|
+
"""
|
|
3495
|
+
# based off implementations from
|
|
3496
|
+
# https://github.com/glfw/glfw/blob/master/deps/linmath.h
|
|
3497
|
+
if out is None:
|
|
3498
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3499
|
+
R = np.zeros((4, 4,), dtype=dtype)
|
|
3500
|
+
else:
|
|
3501
|
+
dtype = np.dtype(out.dtype).type
|
|
3502
|
+
R = out
|
|
3503
|
+
R.fill(0.0)
|
|
3504
|
+
|
|
3505
|
+
q = normalize(q, dtype=dtype)
|
|
3506
|
+
b, c, d, a = q[:]
|
|
3507
|
+
vsqr = np.square(q)
|
|
3508
|
+
|
|
3509
|
+
u = dtype(2.0)
|
|
3510
|
+
R[0, 0] = vsqr[3] + vsqr[0] - vsqr[1] - vsqr[2]
|
|
3511
|
+
R[1, 0] = u * (b * c + a * d)
|
|
3512
|
+
R[2, 0] = u * (b * d - a * c)
|
|
3513
|
+
|
|
3514
|
+
R[0, 1] = u * (b * c - a * d)
|
|
3515
|
+
R[1, 1] = vsqr[3] - vsqr[0] + vsqr[1] - vsqr[2]
|
|
3516
|
+
R[2, 1] = u * (c * d + a * b)
|
|
3517
|
+
|
|
3518
|
+
R[0, 2] = u * (b * d + a * c)
|
|
3519
|
+
R[1, 2] = u * (c * d - a * b)
|
|
3520
|
+
R[2, 2] = vsqr[3] - vsqr[0] - vsqr[1] + vsqr[2]
|
|
3521
|
+
|
|
3522
|
+
R[3, 3] = dtype(1.0)
|
|
3523
|
+
R[:, :] += 0.0 # remove negative zeros
|
|
3524
|
+
|
|
3525
|
+
return R
|
|
3526
|
+
|
|
3527
|
+
|
|
3528
|
+
def scaleMatrix(s, out=None, dtype=None):
|
|
3529
|
+
"""Create a scaling matrix.
|
|
3530
|
+
|
|
3531
|
+
The resulting matrix is the same as a generated by a `glScale` call.
|
|
3532
|
+
|
|
3533
|
+
Parameters
|
|
3534
|
+
----------
|
|
3535
|
+
s : array_like, float or int
|
|
3536
|
+
Scaling factor(s). If `s` is scalar (float), scaling will be uniform.
|
|
3537
|
+
Providing a vector of scaling values [sx, sy, sz] will result in an
|
|
3538
|
+
anisotropic scaling matrix if any of the values differ.
|
|
3539
|
+
out : ndarray, optional
|
|
3540
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3541
|
+
output if `out` was not specified.
|
|
3542
|
+
dtype : dtype or str, optional
|
|
3543
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3544
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3545
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3546
|
+
|
|
3547
|
+
Returns
|
|
3548
|
+
-------
|
|
3549
|
+
ndarray
|
|
3550
|
+
4x4 scaling matrix in row-major order.
|
|
3551
|
+
|
|
3552
|
+
"""
|
|
3553
|
+
# from glScale
|
|
3554
|
+
if out is None:
|
|
3555
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3556
|
+
S = np.zeros((4, 4,), dtype=dtype)
|
|
3557
|
+
else:
|
|
3558
|
+
dtype = np.dtype(out.dtype).type
|
|
3559
|
+
S = out
|
|
3560
|
+
S.fill(0.0)
|
|
3561
|
+
|
|
3562
|
+
if isinstance(s, (float, int,)):
|
|
3563
|
+
S[0, 0] = S[1, 1] = S[2, 2] = dtype(s)
|
|
3564
|
+
else:
|
|
3565
|
+
S[0, 0] = dtype(s[0])
|
|
3566
|
+
S[1, 1] = dtype(s[1])
|
|
3567
|
+
S[2, 2] = dtype(s[2])
|
|
3568
|
+
|
|
3569
|
+
S[3, 3] = 1.0
|
|
3570
|
+
|
|
3571
|
+
return S
|
|
3572
|
+
|
|
3573
|
+
|
|
3574
|
+
def rotationMatrix(angle, axis=(0., 0., -1.), out=None, dtype=None):
|
|
3575
|
+
"""Create a rotation matrix.
|
|
3576
|
+
|
|
3577
|
+
The resulting matrix will rotate points about `axis` by `angle`. The
|
|
3578
|
+
resulting matrix is similar to that produced by a `glRotate` call.
|
|
3579
|
+
|
|
3580
|
+
Parameters
|
|
3581
|
+
----------
|
|
3582
|
+
angle : float
|
|
3583
|
+
Rotation angle in degrees.
|
|
3584
|
+
axis : array_like or str
|
|
3585
|
+
Axis vector components or axis name. If a vector, input must be length
|
|
3586
|
+
3. A string can be specified for rotations about world axes (eg. `'+x'`,
|
|
3587
|
+
`'-z'`, `'+y'`, etc.)
|
|
3588
|
+
out : ndarray, optional
|
|
3589
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3590
|
+
output if `out` was not specified.
|
|
3591
|
+
dtype : dtype or str, optional
|
|
3592
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3593
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3594
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3595
|
+
|
|
3596
|
+
Returns
|
|
3597
|
+
-------
|
|
3598
|
+
ndarray
|
|
3599
|
+
4x4 scaling matrix in row-major order. Will be the same array as `out`
|
|
3600
|
+
if specified, if not, a new array will be allocated.
|
|
3601
|
+
|
|
3602
|
+
Notes
|
|
3603
|
+
-----
|
|
3604
|
+
* Vector `axis` is normalized before creating the matrix.
|
|
3605
|
+
|
|
3606
|
+
"""
|
|
3607
|
+
if out is None:
|
|
3608
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3609
|
+
R = np.zeros((4, 4,), dtype=dtype)
|
|
3610
|
+
else:
|
|
3611
|
+
dtype = np.dtype(out.dtype).type
|
|
3612
|
+
R = out
|
|
3613
|
+
R.fill(0.0)
|
|
3614
|
+
|
|
3615
|
+
try:
|
|
3616
|
+
axis = VEC_AXES[axis] if isinstance(axis, str) else axis
|
|
3617
|
+
except KeyError:
|
|
3618
|
+
raise ValueError(
|
|
3619
|
+
"Value of `axis` must be either '+x', '-x', '+y', '-x', '+z' or "
|
|
3620
|
+
"'-z' or length 3 vector.")
|
|
3621
|
+
|
|
3622
|
+
axis = normalize(axis, dtype=dtype)
|
|
3623
|
+
if np.count_nonzero(axis) == 0:
|
|
3624
|
+
raise ValueError("Value for `axis` is zero-length.")
|
|
3625
|
+
|
|
3626
|
+
angle = np.radians(angle, dtype=dtype)
|
|
3627
|
+
c = np.cos(angle, dtype=dtype)
|
|
3628
|
+
s = np.sin(angle, dtype=dtype)
|
|
3629
|
+
|
|
3630
|
+
xs, ys, zs = axis * s
|
|
3631
|
+
x2, y2, z2 = np.square(axis) # type inferred by input
|
|
3632
|
+
x, y, z = axis
|
|
3633
|
+
cd = dtype(1.0) - c
|
|
3634
|
+
|
|
3635
|
+
R[0, 0] = x2 * cd + c
|
|
3636
|
+
R[0, 1] = x * y * cd - zs
|
|
3637
|
+
R[0, 2] = x * z * cd + ys
|
|
3638
|
+
|
|
3639
|
+
R[1, 0] = y * x * cd + zs
|
|
3640
|
+
R[1, 1] = y2 * cd + c
|
|
3641
|
+
R[1, 2] = y * z * cd - xs
|
|
3642
|
+
|
|
3643
|
+
R[2, 0] = x * z * cd - ys
|
|
3644
|
+
R[2, 1] = y * z * cd + xs
|
|
3645
|
+
R[2, 2] = z2 * cd + c
|
|
3646
|
+
|
|
3647
|
+
R[3, 3] = dtype(1.0)
|
|
3648
|
+
R[:, :] += 0.0 # remove negative zeros
|
|
3649
|
+
|
|
3650
|
+
return R
|
|
3651
|
+
|
|
3652
|
+
|
|
3653
|
+
def translationMatrix(t, out=None, dtype=None):
|
|
3654
|
+
"""Create a translation matrix.
|
|
3655
|
+
|
|
3656
|
+
The resulting matrix is the same as generated by a `glTranslate` call.
|
|
3657
|
+
|
|
3658
|
+
Parameters
|
|
3659
|
+
----------
|
|
3660
|
+
t : ndarray, tuple, or list of float
|
|
3661
|
+
Translation vector [tx, ty, tz].
|
|
3662
|
+
out : ndarray, optional
|
|
3663
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3664
|
+
output if `out` was not specified.
|
|
3665
|
+
dtype : dtype or str, optional
|
|
3666
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3667
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3668
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3669
|
+
|
|
3670
|
+
Returns
|
|
3671
|
+
-------
|
|
3672
|
+
ndarray
|
|
3673
|
+
4x4 translation matrix in row-major order. Will be the same array as
|
|
3674
|
+
`out` if specified, if not, a new array will be allocated.
|
|
3675
|
+
|
|
3676
|
+
"""
|
|
3677
|
+
if out is None:
|
|
3678
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3679
|
+
T = np.identity(4, dtype=dtype)
|
|
3680
|
+
else:
|
|
3681
|
+
dtype = np.dtype(out.dtype).type
|
|
3682
|
+
T = out
|
|
3683
|
+
T.fill(0.0)
|
|
3684
|
+
np.fill_diagonal(T, 1.0)
|
|
3685
|
+
|
|
3686
|
+
T[:3, 3] = np.asarray(t, dtype=dtype)
|
|
3687
|
+
|
|
3688
|
+
return T
|
|
3689
|
+
|
|
3690
|
+
|
|
3691
|
+
def invertMatrix(m, out=None, dtype=None):
|
|
3692
|
+
"""Invert a square matrix.
|
|
3693
|
+
|
|
3694
|
+
Parameters
|
|
3695
|
+
----------
|
|
3696
|
+
m : array_like
|
|
3697
|
+
Square matrix to invert. Inputs can be 4x4, 3x3 or 2x2.
|
|
3698
|
+
out : ndarray, optional
|
|
3699
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3700
|
+
output if `out` was not specified.
|
|
3701
|
+
dtype : dtype or str, optional
|
|
3702
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3703
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3704
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3705
|
+
|
|
3706
|
+
Returns
|
|
3707
|
+
-------
|
|
3708
|
+
ndarray
|
|
3709
|
+
Matrix which is the inverse of `m`
|
|
3710
|
+
|
|
3711
|
+
"""
|
|
3712
|
+
if out is None:
|
|
3713
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3714
|
+
else:
|
|
3715
|
+
dtype = out.dtype
|
|
3716
|
+
|
|
3717
|
+
m = np.asarray(m, dtype=dtype) # input as array
|
|
3718
|
+
toReturn = np.empty_like(m, dtype=dtype) if out is None else out
|
|
3719
|
+
toReturn.fill(0.0)
|
|
3720
|
+
|
|
3721
|
+
if m.shape == (4, 4,):
|
|
3722
|
+
# Special handling of 4x4 matrices, if affine and orthogonal
|
|
3723
|
+
# (homogeneous), simply transpose the matrix rather than doing a full
|
|
3724
|
+
# invert.
|
|
3725
|
+
if isOrthogonal(m[:3, :3]) and isAffine(m):
|
|
3726
|
+
rg = m[:3, :3]
|
|
3727
|
+
toReturn[:3, :3] = rg.T
|
|
3728
|
+
toReturn[:3, 3] = -m[:3, 3].dot(rg)
|
|
3729
|
+
#toReturn[0, 3] = \
|
|
3730
|
+
# -(m[0, 0] * m[0, 3] + m[1, 0] * m[1, 3] + m[2, 0] * m[2, 3])
|
|
3731
|
+
#toReturn[1, 3] = \
|
|
3732
|
+
# -(m[0, 1] * m[0, 3] + m[1, 1] * m[1, 3] + m[2, 1] * m[2, 3])
|
|
3733
|
+
#toReturn[2, 3] = \
|
|
3734
|
+
# -(m[0, 2] * m[0, 3] + m[1, 2] * m[1, 3] + m[2, 2] * m[2, 3])
|
|
3735
|
+
toReturn[3, 3] = 1.0
|
|
3736
|
+
else:
|
|
3737
|
+
toReturn[:, :] = np.linalg.inv(m)
|
|
3738
|
+
elif m.shape[0] == m.shape[1]: # square, other than 4x4
|
|
3739
|
+
toReturn[:, :] = np.linalg.inv(m) if not isOrthogonal(m) else m.T
|
|
3740
|
+
else:
|
|
3741
|
+
toReturn[:, :] = np.linalg.inv(m)
|
|
3742
|
+
|
|
3743
|
+
return toReturn
|
|
3744
|
+
|
|
3745
|
+
|
|
3746
|
+
def multMatrix(matrices, reverse=False, out=None, dtype=None):
|
|
3747
|
+
"""Chain multiplication of two or more matrices.
|
|
3748
|
+
|
|
3749
|
+
Multiply a sequence of matrices together, reducing to a single product
|
|
3750
|
+
matrix. For instance, specifying `matrices` the sequence of matrices (A, B,
|
|
3751
|
+
C, D) will return the product (((AB)C)D). If `reverse=True`, the product
|
|
3752
|
+
will be (A(B(CD))).
|
|
3753
|
+
|
|
3754
|
+
Alternatively, a 3D array can be specified to `matrices` as a stack, where
|
|
3755
|
+
an index along axis 0 references a 2D slice storing matrix values. The
|
|
3756
|
+
product of the matrices along the axis will be returned. This is a bit more
|
|
3757
|
+
efficient than specifying separate matrices in a sequence, but the
|
|
3758
|
+
difference is negligible when only a few matrices are being multiplied.
|
|
3759
|
+
|
|
3760
|
+
Parameters
|
|
3761
|
+
----------
|
|
3762
|
+
matrices : list, tuple or ndarray
|
|
3763
|
+
Sequence or stack of matrices to multiply. All matrices must have the
|
|
3764
|
+
same dimensions.
|
|
3765
|
+
reverse : bool, optional
|
|
3766
|
+
Multiply matrices right-to-left. This is useful when dealing with
|
|
3767
|
+
transformation matrices, where the order of operations for transforms
|
|
3768
|
+
will appear the same as the order the matrices are specified. Default is
|
|
3769
|
+
'False'. When `True`, this function behaves similarly to
|
|
3770
|
+
:func:`concatenate`.
|
|
3771
|
+
out : ndarray, optional
|
|
3772
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3773
|
+
output if `out` was not specified.
|
|
3774
|
+
dtype : dtype or str, optional
|
|
3775
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3776
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3777
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3778
|
+
|
|
3779
|
+
Returns
|
|
3780
|
+
-------
|
|
3781
|
+
ndarray
|
|
3782
|
+
Matrix product.
|
|
3783
|
+
|
|
3784
|
+
Notes
|
|
3785
|
+
-----
|
|
3786
|
+
* You may use `numpy.matmul` when dealing with only two matrices instead of
|
|
3787
|
+
`multMatrix`.
|
|
3788
|
+
* If a single matrix is specified, the returned product will have the same
|
|
3789
|
+
values.
|
|
3790
|
+
|
|
3791
|
+
Examples
|
|
3792
|
+
--------
|
|
3793
|
+
Chain multiplication of SRT matrices::
|
|
3794
|
+
|
|
3795
|
+
translate = translationMatrix((0.035, 0, -0.5))
|
|
3796
|
+
rotate = rotationMatrix(90.0, (0, 1, 0))
|
|
3797
|
+
scale = scaleMatrix(2.0)
|
|
3798
|
+
|
|
3799
|
+
SRT = multMatrix((translate, rotate, scale))
|
|
3800
|
+
|
|
3801
|
+
Same as above, but matrices are in a 3x4x4 array::
|
|
3802
|
+
|
|
3803
|
+
matStack = np.array((translate, rotate, scale))
|
|
3804
|
+
|
|
3805
|
+
# or ...
|
|
3806
|
+
# matStack = np.zeros((3, 4, 4))
|
|
3807
|
+
# matStack[0, :, :] = translate
|
|
3808
|
+
# matStack[1, :, :] = rotate
|
|
3809
|
+
# matStack[2, :, :] = scale
|
|
3810
|
+
|
|
3811
|
+
SRT = multMatrix(matStack)
|
|
3812
|
+
|
|
3813
|
+
Using `reverse=True` allows you to specify transformation matrices in the
|
|
3814
|
+
order which they will be applied::
|
|
3815
|
+
|
|
3816
|
+
SRT = multMatrix(np.array((scale, rotate, translate)), reverse=True)
|
|
3817
|
+
|
|
3818
|
+
"""
|
|
3819
|
+
# convert matrix types
|
|
3820
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3821
|
+
matrices = np.asarray(matrices, dtype=dtype) # convert to array
|
|
3822
|
+
|
|
3823
|
+
matrices = np.atleast_3d(matrices)
|
|
3824
|
+
prod = functools.reduce(
|
|
3825
|
+
np.matmul, matrices[:] if not reverse else matrices[::-1])
|
|
3826
|
+
|
|
3827
|
+
if out is not None:
|
|
3828
|
+
toReturn = out
|
|
3829
|
+
toReturn[:, :] = prod
|
|
3830
|
+
else:
|
|
3831
|
+
toReturn = prod
|
|
3832
|
+
|
|
3833
|
+
return toReturn
|
|
3834
|
+
|
|
3835
|
+
|
|
3836
|
+
def concatenate(matrices, out=None, dtype=None):
|
|
3837
|
+
"""Concatenate matrix transformations.
|
|
3838
|
+
|
|
3839
|
+
Chain multiply matrices describing transform operations into a single matrix
|
|
3840
|
+
product, that when applied, transforms points and vectors with each
|
|
3841
|
+
operation in the order they're specified.
|
|
3842
|
+
|
|
3843
|
+
Parameters
|
|
3844
|
+
----------
|
|
3845
|
+
matrices : list or tuple
|
|
3846
|
+
List of matrices to concatenate. All matrices must all have the same
|
|
3847
|
+
size, usually 4x4 or 3x3.
|
|
3848
|
+
out : ndarray, optional
|
|
3849
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3850
|
+
output if `out` was not specified.
|
|
3851
|
+
dtype : dtype or str, optional
|
|
3852
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3853
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3854
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3855
|
+
|
|
3856
|
+
Returns
|
|
3857
|
+
-------
|
|
3858
|
+
ndarray
|
|
3859
|
+
Matrix product.
|
|
3860
|
+
|
|
3861
|
+
See Also
|
|
3862
|
+
--------
|
|
3863
|
+
* multMatrix : Chain multiplication of matrices.
|
|
3864
|
+
|
|
3865
|
+
Notes
|
|
3866
|
+
-----
|
|
3867
|
+
* This function should only be used for combining transformation matrices.
|
|
3868
|
+
Use `multMatrix` for general matrix chain multiplication.
|
|
3869
|
+
|
|
3870
|
+
Examples
|
|
3871
|
+
--------
|
|
3872
|
+
Create an SRT (scale, rotate, and translate) matrix to convert model-space
|
|
3873
|
+
coordinates to world-space::
|
|
3874
|
+
|
|
3875
|
+
S = scaleMatrix([2.0, 2.0, 2.0]) # scale model 2x
|
|
3876
|
+
R = rotationMatrix(-90., [0., 0., -1]) # rotate -90 about -Z axis
|
|
3877
|
+
T = translationMatrix([0., 0., -5.]) # translate point 5 units away
|
|
3878
|
+
|
|
3879
|
+
# product matrix when applied to points will scale, rotate and transform
|
|
3880
|
+
# in that order.
|
|
3881
|
+
SRT = concatenate([S, R, T])
|
|
3882
|
+
|
|
3883
|
+
# transform a point in model-space coordinates to world-space
|
|
3884
|
+
pointModel = np.array([0., 1., 0., 1.])
|
|
3885
|
+
pointWorld = np.matmul(SRT, pointModel.T) # point in WCS
|
|
3886
|
+
# ... or ...
|
|
3887
|
+
pointWorld = matrixApply(SRT, pointModel)
|
|
3888
|
+
|
|
3889
|
+
Create a model-view matrix from a world-space pose represented by an
|
|
3890
|
+
orientation (quaternion) and position (vector). The resulting matrix will
|
|
3891
|
+
transform model-space coordinates to eye-space::
|
|
3892
|
+
|
|
3893
|
+
# eye pose as quaternion and vector
|
|
3894
|
+
stimOri = quatFromAxisAngle([0., 0., -1.], -45.0)
|
|
3895
|
+
stimPos = [0., 1.5, -5.]
|
|
3896
|
+
|
|
3897
|
+
# create model matrix
|
|
3898
|
+
R = quatToMatrix(stimOri)
|
|
3899
|
+
T = translationMatrix(stimPos)
|
|
3900
|
+
M = concatenate(R, T) # model matrix
|
|
3901
|
+
|
|
3902
|
+
# create a view matrix, can also be represented as 'pos' and 'ori'
|
|
3903
|
+
eyePos = [0., 1.5, 0.]
|
|
3904
|
+
eyeFwd = [0., 0., -1.]
|
|
3905
|
+
eyeUp = [0., 1., 0.]
|
|
3906
|
+
V = lookAt(eyePos, eyeFwd, eyeUp) # from viewtools
|
|
3907
|
+
|
|
3908
|
+
# modelview matrix
|
|
3909
|
+
MV = concatenate([M, V])
|
|
3910
|
+
|
|
3911
|
+
You can put the created matrix in the OpenGL matrix stack as shown below.
|
|
3912
|
+
Note that the matrix must have a 32-bit floating-point data type and needs
|
|
3913
|
+
to be loaded transposed since OpenGL takes matrices in column-major order::
|
|
3914
|
+
|
|
3915
|
+
GL.glMatrixMode(GL.GL_MODELVIEW)
|
|
3916
|
+
|
|
3917
|
+
# pyglet
|
|
3918
|
+
MV = np.asarray(MV, dtype='float32') # must be 32-bit float!
|
|
3919
|
+
ptrMV = MV.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
|
3920
|
+
GL.glLoadTransposeMatrixf(ptrMV)
|
|
3921
|
+
|
|
3922
|
+
# PyOpenGL
|
|
3923
|
+
MV = np.asarray(MV, dtype='float32')
|
|
3924
|
+
GL.glLoadTransposeMatrixf(MV)
|
|
3925
|
+
|
|
3926
|
+
Furthermore, you can convert a point from model-space to homogeneous
|
|
3927
|
+
clip-space by concatenating the projection, view, and model matrices::
|
|
3928
|
+
|
|
3929
|
+
# compute projection matrix, functions here are from 'viewtools'
|
|
3930
|
+
screenWidth = 0.52
|
|
3931
|
+
screenAspect = w / h
|
|
3932
|
+
scrDistance = 0.55
|
|
3933
|
+
frustum = computeFrustum(screenWidth, screenAspect, scrDistance)
|
|
3934
|
+
P = perspectiveProjectionMatrix(*frustum)
|
|
3935
|
+
|
|
3936
|
+
# multiply model-space points by MVP to convert them to clip-space
|
|
3937
|
+
MVP = concatenate([M, V, P])
|
|
3938
|
+
pointModel = np.array([0., 1., 0., 1.])
|
|
3939
|
+
pointClipSpace = np.matmul(MVP, pointModel.T)
|
|
3940
|
+
|
|
3941
|
+
"""
|
|
3942
|
+
return multMatrix(matrices, reverse=True, out=out, dtype=dtype)
|
|
3943
|
+
|
|
3944
|
+
|
|
3945
|
+
def matrixFromEulerAngles(rx, ry, rz, degrees=True, out=None, dtype=None):
|
|
3946
|
+
"""Construct a 4x4 rotation matrix from Euler angles.
|
|
3947
|
+
|
|
3948
|
+
Rotations are combined by first rotating about the X axis, then Y, and
|
|
3949
|
+
finally Z.
|
|
3950
|
+
|
|
3951
|
+
Parameters
|
|
3952
|
+
----------
|
|
3953
|
+
rx, ry, rz : float
|
|
3954
|
+
Rotation angles (pitch, yaw, and roll).
|
|
3955
|
+
degrees : bool, optional
|
|
3956
|
+
Rotation angles are specified in degrees. If `False`, they will be
|
|
3957
|
+
assumed as radians. Default is `True`.
|
|
3958
|
+
out : ndarray, optional
|
|
3959
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
3960
|
+
output if `out` was not specified.
|
|
3961
|
+
dtype : dtype or str, optional
|
|
3962
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
3963
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
3964
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
3965
|
+
|
|
3966
|
+
Returns
|
|
3967
|
+
-------
|
|
3968
|
+
ndarray
|
|
3969
|
+
4x4 rotation matrix.
|
|
3970
|
+
|
|
3971
|
+
Examples
|
|
3972
|
+
--------
|
|
3973
|
+
Demonstration of how a combination of axis-angle rotations is equivalent
|
|
3974
|
+
to a single call of `matrixFromEulerAngles`::
|
|
3975
|
+
|
|
3976
|
+
m1 = matrixFromEulerAngles(90., 45., 135.))
|
|
3977
|
+
|
|
3978
|
+
# construct rotation matrix from 3 orthogonal rotations
|
|
3979
|
+
rx = rotationMatrix(90., (1, 0, 0)) # x-axis
|
|
3980
|
+
ry = rotationMatrix(45., (0, 1, 0)) # y-axis
|
|
3981
|
+
rz = rotationMatrix(135., (0, 0, 1)) # z-axis
|
|
3982
|
+
m2 = concatenate([rz, ry, rx]) # note the order
|
|
3983
|
+
|
|
3984
|
+
print(numpy.allclose(m1, m2)) # True
|
|
3985
|
+
|
|
3986
|
+
Not only does `matrixFromEulerAngles` require less code, it also is
|
|
3987
|
+
considerably more efficient than constructing and multiplying multiple
|
|
3988
|
+
matrices.
|
|
3989
|
+
|
|
3990
|
+
"""
|
|
3991
|
+
# from https://www.j3d.org/matrix_faq/matrfaq_latest.html
|
|
3992
|
+
if out is None:
|
|
3993
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
3994
|
+
toReturn = np.zeros((4, 4,), dtype=dtype)
|
|
3995
|
+
else:
|
|
3996
|
+
dtype = np.dtype(dtype).type
|
|
3997
|
+
toReturn = out
|
|
3998
|
+
toReturn.fill(0.0)
|
|
3999
|
+
|
|
4000
|
+
angles = np.asarray([rx, ry, rz], dtype=dtype)
|
|
4001
|
+
if degrees:
|
|
4002
|
+
angles = np.radians(angles)
|
|
4003
|
+
|
|
4004
|
+
a, c, e = np.cos(angles)
|
|
4005
|
+
b, d, f = np.sin(angles)
|
|
4006
|
+
ad = a * d
|
|
4007
|
+
bd = b * d
|
|
4008
|
+
|
|
4009
|
+
toReturn[0, 0] = c * e
|
|
4010
|
+
toReturn[0, 1] = -c * f
|
|
4011
|
+
toReturn[0, 2] = d
|
|
4012
|
+
toReturn[1, 0] = bd * e + a * f
|
|
4013
|
+
toReturn[1, 1] = -bd * f + a * e
|
|
4014
|
+
toReturn[1, 2] = -b * c
|
|
4015
|
+
toReturn[2, 0] = -ad * e + b * f
|
|
4016
|
+
toReturn[2, 1] = ad * f + b * e
|
|
4017
|
+
toReturn[2, 2] = a * c
|
|
4018
|
+
toReturn[3, 3] = 1.0
|
|
4019
|
+
|
|
4020
|
+
return toReturn
|
|
4021
|
+
|
|
4022
|
+
|
|
4023
|
+
def isOrthogonal(m):
|
|
4024
|
+
"""Check if a square matrix is orthogonal.
|
|
4025
|
+
|
|
4026
|
+
If a matrix is orthogonal, its columns form an orthonormal basis and is
|
|
4027
|
+
non-singular. An orthogonal matrix is invertible by simply taking the
|
|
4028
|
+
transpose of the matrix.
|
|
4029
|
+
|
|
4030
|
+
Parameters
|
|
4031
|
+
----------
|
|
4032
|
+
m : array_like
|
|
4033
|
+
Square matrix, either 2x2, 3x3 or 4x4.
|
|
4034
|
+
|
|
4035
|
+
Returns
|
|
4036
|
+
-------
|
|
4037
|
+
bool
|
|
4038
|
+
`True` if the matrix is orthogonal.
|
|
4039
|
+
|
|
4040
|
+
"""
|
|
4041
|
+
if not isinstance(m, (np.ndarray,)):
|
|
4042
|
+
m = np.asarray(m)
|
|
4043
|
+
|
|
4044
|
+
assert 2 <= m.shape[0] <= 4 # 2x2 to 4x4
|
|
4045
|
+
assert m.shape[0] == m.shape[1] # must be square
|
|
4046
|
+
|
|
4047
|
+
dtype = np.dtype(m.dtype).type
|
|
4048
|
+
return np.allclose(np.matmul(m.T, m, dtype=dtype),
|
|
4049
|
+
np.identity(m.shape[0], dtype))
|
|
4050
|
+
|
|
4051
|
+
|
|
4052
|
+
def isAffine(m):
|
|
4053
|
+
"""Check if a 4x4 square matrix describes an affine transformation.
|
|
4054
|
+
|
|
4055
|
+
Parameters
|
|
4056
|
+
----------
|
|
4057
|
+
m : array_like
|
|
4058
|
+
4x4 transformation matrix.
|
|
4059
|
+
|
|
4060
|
+
Returns
|
|
4061
|
+
-------
|
|
4062
|
+
bool
|
|
4063
|
+
`True` if the matrix is affine.
|
|
4064
|
+
|
|
4065
|
+
"""
|
|
4066
|
+
assert m.shape[0] == m.shape[1] == 4
|
|
4067
|
+
|
|
4068
|
+
if not isinstance(m, (np.ndarray,)):
|
|
4069
|
+
m = np.asarray(m)
|
|
4070
|
+
|
|
4071
|
+
dtype = np.dtype(m.dtype).type
|
|
4072
|
+
eps = np.finfo(dtype).eps
|
|
4073
|
+
|
|
4074
|
+
return np.all(m[3, :3] < eps) and (dtype(1.0) - m[3, 3]) < eps
|
|
4075
|
+
|
|
4076
|
+
|
|
4077
|
+
def applyMatrix(m, points, out=None, dtype=None):
|
|
4078
|
+
"""Apply a matrix over a 2D array of points.
|
|
4079
|
+
|
|
4080
|
+
This function behaves similarly to the following `Numpy` statement::
|
|
4081
|
+
|
|
4082
|
+
points[:, :] = points.dot(m.T)
|
|
4083
|
+
|
|
4084
|
+
Transformation matrices specified to `m` must have dimensions 4x4, 3x4, 3x3
|
|
4085
|
+
or 2x2. With the exception of 4x4 matrices, input `points` must have the
|
|
4086
|
+
same number of columns as the matrix has rows. 4x4 matrices can be used to
|
|
4087
|
+
transform both Nx4 and Nx3 arrays.
|
|
4088
|
+
|
|
4089
|
+
Parameters
|
|
4090
|
+
----------
|
|
4091
|
+
m : array_like
|
|
4092
|
+
Matrix with dimensions 2x2, 3x3, 3x4 or 4x4.
|
|
4093
|
+
points : array_like
|
|
4094
|
+
2D array of points/coordinates to transform. Each row should have length
|
|
4095
|
+
appropriate for the matrix being used. If not, a square submatrix will
|
|
4096
|
+
be taken from the input matrix with dimensions equal to the number of
|
|
4097
|
+
columns in `points`.
|
|
4098
|
+
out : ndarray, optional
|
|
4099
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4100
|
+
output if `out` was not specified.
|
|
4101
|
+
dtype : dtype or str, optional
|
|
4102
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4103
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4104
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4105
|
+
|
|
4106
|
+
Returns
|
|
4107
|
+
-------
|
|
4108
|
+
ndarray
|
|
4109
|
+
Transformed coordinates.
|
|
4110
|
+
|
|
4111
|
+
Notes
|
|
4112
|
+
-----
|
|
4113
|
+
* Input (`points`) and output (`out`) arrays cannot be the same instance for
|
|
4114
|
+
this function.
|
|
4115
|
+
* In the case of 4x4 input matrices, this function performs optimizations
|
|
4116
|
+
based on whether the input matrix is affine, greatly improving performance
|
|
4117
|
+
when working with Nx3 arrays.
|
|
4118
|
+
|
|
4119
|
+
Examples
|
|
4120
|
+
--------
|
|
4121
|
+
Construct a matrix and transform a point::
|
|
4122
|
+
|
|
4123
|
+
# identity 3x3 matrix for this example
|
|
4124
|
+
M = [[1.0, 0.0, 0.0],
|
|
4125
|
+
[0.0, 1.0, 0.0],
|
|
4126
|
+
[0.0, 0.0, 1.0]]
|
|
4127
|
+
|
|
4128
|
+
pnt = [1.0, 0.0, 0.0]
|
|
4129
|
+
|
|
4130
|
+
pntNew = applyMatrix(M, pnt)
|
|
4131
|
+
|
|
4132
|
+
Construct an SRT matrix (scale, rotate, transform) and transform an array of
|
|
4133
|
+
points::
|
|
4134
|
+
|
|
4135
|
+
S = scaleMatrix([5.0, 5.0, 5.0]) # scale 5x
|
|
4136
|
+
R = rotationMatrix(180., [0., 0., -1]) # rotate 180 degrees
|
|
4137
|
+
T = translationMatrix([0., 1.5, -3.]) # translate point up and away
|
|
4138
|
+
M = concatenate([S, R, T]) # create transform matrix
|
|
4139
|
+
|
|
4140
|
+
# points to transform
|
|
4141
|
+
points = np.array([[0., 1., 0., 1.], [-1., 0., 0., 1.]]) # [x, y, z, w]
|
|
4142
|
+
newPoints = applyMatrix(M, points) # apply the transformation
|
|
4143
|
+
|
|
4144
|
+
Convert CIE-XYZ colors to sRGB::
|
|
4145
|
+
|
|
4146
|
+
sRGBMatrix = [[3.2404542, -1.5371385, -0.4985314],
|
|
4147
|
+
[-0.969266, 1.8760108, 0.041556 ],
|
|
4148
|
+
[0.0556434, -0.2040259, 1.0572252]]
|
|
4149
|
+
|
|
4150
|
+
colorsRGB = applyMatrix(sRGBMatrix, colorsXYZ)
|
|
4151
|
+
|
|
4152
|
+
"""
|
|
4153
|
+
if out is None:
|
|
4154
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4155
|
+
else:
|
|
4156
|
+
dtype = np.dtype(out.dtype).type
|
|
4157
|
+
|
|
4158
|
+
m = np.asarray(m, dtype=dtype)
|
|
4159
|
+
points = np.asarray(points, dtype=dtype)
|
|
4160
|
+
|
|
4161
|
+
if out is None:
|
|
4162
|
+
toReturn = np.zeros_like(points, dtype=dtype)
|
|
4163
|
+
else:
|
|
4164
|
+
if id(out) == id(points):
|
|
4165
|
+
raise ValueError('Output array cannot be same as input.')
|
|
4166
|
+
toReturn = out
|
|
4167
|
+
|
|
4168
|
+
pout, p = np.atleast_2d(toReturn, points)
|
|
4169
|
+
|
|
4170
|
+
nCols = p.shape[1]
|
|
4171
|
+
if m.shape[0] > nCols:
|
|
4172
|
+
if m.shape[1] < nCols:
|
|
4173
|
+
raise ValueError(
|
|
4174
|
+
'Input matrix dimensions are not compatible with input array.')
|
|
4175
|
+
m = m[:nCols, :nCols] # take sub matrix
|
|
4176
|
+
|
|
4177
|
+
if m.shape == (4, 4): # 4x4 matrix
|
|
4178
|
+
if pout.shape[1] == 3: # Nx3
|
|
4179
|
+
pout[:, :] = p.dot(m[:3, :3].T)
|
|
4180
|
+
pout += m[:3, 3]
|
|
4181
|
+
# find `rcpW` as suggested in OpenXR's xr_linear.h header
|
|
4182
|
+
# reciprocal of `w` if the matrix is not orthonormal
|
|
4183
|
+
if not isAffine(m):
|
|
4184
|
+
rcpW = 1.0 / (m[3, 0] * p[:, 0] +
|
|
4185
|
+
m[3, 1] * p[:, 1] +
|
|
4186
|
+
m[3, 2] * p[:, 2] +
|
|
4187
|
+
m[3, 3])
|
|
4188
|
+
pout *= rcpW[:, np.newaxis]
|
|
4189
|
+
elif pout.shape[1] == 4: # Nx4
|
|
4190
|
+
pout[:, :] = p.dot(m.T)
|
|
4191
|
+
else:
|
|
4192
|
+
raise ValueError(
|
|
4193
|
+
'Input array dimensions invalid. Should be Nx3 or Nx4 when '
|
|
4194
|
+
'input matrix is 4x4.')
|
|
4195
|
+
elif m.shape == (3, 4): # 3x4 matrix
|
|
4196
|
+
if pout.shape[1] == 3: # Nx3
|
|
4197
|
+
pout[:, :] = p.dot(m[:3, :3].T)
|
|
4198
|
+
pout += m[:3, 3]
|
|
4199
|
+
else:
|
|
4200
|
+
raise ValueError(
|
|
4201
|
+
'Input array dimensions invalid. Should be Nx3 when input '
|
|
4202
|
+
'matrix is 3x4.')
|
|
4203
|
+
elif m.shape == (3, 3): # 3x3 matrix, e.g colors
|
|
4204
|
+
if pout.shape[1] == 3: # Nx3
|
|
4205
|
+
pout[:, :] = p.dot(m.T)
|
|
4206
|
+
else:
|
|
4207
|
+
raise ValueError(
|
|
4208
|
+
'Input array dimensions invalid. Should be Nx3 when '
|
|
4209
|
+
'input matrix is 3x3.')
|
|
4210
|
+
elif m.shape == (2, 2): # 2x2 matrix
|
|
4211
|
+
if pout.shape[1] == 2: # Nx2
|
|
4212
|
+
pout[:, :] = p.dot(m.T)
|
|
4213
|
+
else:
|
|
4214
|
+
raise ValueError(
|
|
4215
|
+
'Input array dimensions invalid. Should be Nx2 when '
|
|
4216
|
+
'input matrix is 2x2.')
|
|
4217
|
+
else:
|
|
4218
|
+
raise ValueError(
|
|
4219
|
+
'Only a square matrix with dimensions 2, 3 or 4 can be used.')
|
|
4220
|
+
|
|
4221
|
+
return toReturn
|
|
4222
|
+
|
|
4223
|
+
|
|
4224
|
+
def posOriToMatrix(pos, ori, out=None, dtype=None):
|
|
4225
|
+
"""Convert a rigid body pose to a 4x4 transformation matrix.
|
|
4226
|
+
|
|
4227
|
+
A pose is represented by a position coordinate `pos` and orientation
|
|
4228
|
+
quaternion `ori`.
|
|
4229
|
+
|
|
4230
|
+
Parameters
|
|
4231
|
+
----------
|
|
4232
|
+
pos : ndarray, tuple, or list of float
|
|
4233
|
+
Position vector [x, y, z].
|
|
4234
|
+
ori : tuple, list or ndarray of float
|
|
4235
|
+
Orientation quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
4236
|
+
are imaginary components.
|
|
4237
|
+
out : ndarray, optional
|
|
4238
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4239
|
+
output if `out` was not specified.
|
|
4240
|
+
dtype : dtype or str, optional
|
|
4241
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4242
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4243
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4244
|
+
|
|
4245
|
+
Returns
|
|
4246
|
+
-------
|
|
4247
|
+
ndarray
|
|
4248
|
+
4x4 transformation matrix.
|
|
4249
|
+
|
|
4250
|
+
"""
|
|
4251
|
+
if out is None:
|
|
4252
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4253
|
+
toReturn = np.zeros((4, 4,), dtype=dtype)
|
|
4254
|
+
else:
|
|
4255
|
+
dtype = np.dtype(dtype).type
|
|
4256
|
+
toReturn = out
|
|
4257
|
+
|
|
4258
|
+
transMat = translationMatrix(pos, dtype=dtype)
|
|
4259
|
+
rotMat = quatToMatrix(ori, dtype=dtype)
|
|
4260
|
+
|
|
4261
|
+
return np.matmul(transMat, rotMat, out=toReturn)
|
|
4262
|
+
|
|
4263
|
+
|
|
4264
|
+
def transform(pos, ori, points, out=None, dtype=None):
|
|
4265
|
+
"""Transform points using a position and orientation. Points are rotated
|
|
4266
|
+
then translated.
|
|
4267
|
+
|
|
4268
|
+
Parameters
|
|
4269
|
+
----------
|
|
4270
|
+
pos : array_like
|
|
4271
|
+
Position vector in form [x, y, z] or [x, y, z, 1].
|
|
4272
|
+
ori : array_like
|
|
4273
|
+
Orientation quaternion in form [x, y, z, w] where w is real and x, y, z
|
|
4274
|
+
are imaginary components.
|
|
4275
|
+
points : array_like
|
|
4276
|
+
Point(s) [x, y, z] to transform.
|
|
4277
|
+
out : ndarray, optional
|
|
4278
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4279
|
+
output if `out` was not specified.
|
|
4280
|
+
dtype : dtype or str, optional
|
|
4281
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4282
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4283
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4284
|
+
|
|
4285
|
+
Returns
|
|
4286
|
+
-------
|
|
4287
|
+
ndarray
|
|
4288
|
+
Transformed points.
|
|
4289
|
+
|
|
4290
|
+
Examples
|
|
4291
|
+
--------
|
|
4292
|
+
Transform points by a position coordinate and orientation quaternion::
|
|
4293
|
+
|
|
4294
|
+
# rigid body pose
|
|
4295
|
+
ori = quatFromAxisAngle([0., 0., -1.], 90.0, degrees=True)
|
|
4296
|
+
pos = [0., 1.5, -3.]
|
|
4297
|
+
# points to transform
|
|
4298
|
+
points = np.array([[0., 1., 0., 1.], [-1., 0., 0., 1.]]) # [x, y, z, 1]
|
|
4299
|
+
outPoints = np.zeros_like(points) # output array
|
|
4300
|
+
transform(pos, ori, points, out=outPoints) # do the transformation
|
|
4301
|
+
|
|
4302
|
+
You can get the same results as the previous example using a matrix by doing
|
|
4303
|
+
the following::
|
|
4304
|
+
|
|
4305
|
+
R = rotationMatrix(90., [0., 0., -1])
|
|
4306
|
+
T = translationMatrix([0., 1.5, -3.])
|
|
4307
|
+
M = concatenate([R, T])
|
|
4308
|
+
applyMatrix(M, points, out=outPoints)
|
|
4309
|
+
|
|
4310
|
+
If you are defining transformations with quaternions and coordinates, you
|
|
4311
|
+
can skip the costly matrix creation process by using `transform`.
|
|
4312
|
+
|
|
4313
|
+
Notes
|
|
4314
|
+
-----
|
|
4315
|
+
* In performance tests, `applyMatrix` is noticeably faster than `transform`
|
|
4316
|
+
for very large arrays, however this is only true if you are applying the
|
|
4317
|
+
same transformation to all points.
|
|
4318
|
+
* If the input arrays for `points` or `pos` is Nx4, the last column is
|
|
4319
|
+
ignored.
|
|
4320
|
+
|
|
4321
|
+
"""
|
|
4322
|
+
if out is None:
|
|
4323
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4324
|
+
else:
|
|
4325
|
+
dtype = np.dtype(dtype).type
|
|
4326
|
+
|
|
4327
|
+
pos = np.asarray(pos, dtype=dtype)
|
|
4328
|
+
ori = np.asarray(ori, dtype=dtype)
|
|
4329
|
+
points = np.asarray(points, dtype=dtype)
|
|
4330
|
+
|
|
4331
|
+
if out is None:
|
|
4332
|
+
toReturn = np.zeros_like(points, dtype=dtype)
|
|
4333
|
+
else:
|
|
4334
|
+
if out.shape != points.shape:
|
|
4335
|
+
raise ValueError(
|
|
4336
|
+
"Array 'out' and 'points' do not have matching shapes.")
|
|
4337
|
+
|
|
4338
|
+
toReturn = out
|
|
4339
|
+
|
|
4340
|
+
pout, points, pos2d = np.atleast_2d(toReturn, points, pos) # create 2d views
|
|
4341
|
+
|
|
4342
|
+
# apply rotation
|
|
4343
|
+
applyQuat(ori, points, out=pout)
|
|
4344
|
+
|
|
4345
|
+
# apply translation
|
|
4346
|
+
pout[:, 0] += pos2d[:, 0]
|
|
4347
|
+
pout[:, 1] += pos2d[:, 1]
|
|
4348
|
+
pout[:, 2] += pos2d[:, 2]
|
|
4349
|
+
|
|
4350
|
+
return toReturn
|
|
4351
|
+
|
|
4352
|
+
|
|
4353
|
+
def scale(sf, points, out=None, dtype=None):
|
|
4354
|
+
"""Scale points by a factor.
|
|
4355
|
+
|
|
4356
|
+
This is useful for converting points between units, and to stretch or
|
|
4357
|
+
compress points along a given axis. Scaling can be uniform which the same
|
|
4358
|
+
factor is applied along all axes, or anisotropic along specific axes.
|
|
4359
|
+
|
|
4360
|
+
Parameters
|
|
4361
|
+
----------
|
|
4362
|
+
sf : array_like or float
|
|
4363
|
+
Scaling factor. If scalar, all points will be scaled uniformly by that
|
|
4364
|
+
factor. If a vector, scaling will be anisotropic along an axis.
|
|
4365
|
+
points : array_like
|
|
4366
|
+
Point(s) [x, y, z] to scale.
|
|
4367
|
+
out : ndarray, optional
|
|
4368
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4369
|
+
output if `out` was not specified.
|
|
4370
|
+
dtype : dtype or str, optional
|
|
4371
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4372
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4373
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4374
|
+
|
|
4375
|
+
Returns
|
|
4376
|
+
-------
|
|
4377
|
+
ndarray
|
|
4378
|
+
Scaled points.
|
|
4379
|
+
|
|
4380
|
+
Examples
|
|
4381
|
+
--------
|
|
4382
|
+
Apply uniform scaling to points, here we scale to convert points in
|
|
4383
|
+
centimeters to meters::
|
|
4384
|
+
|
|
4385
|
+
CM_TO_METERS = 1.0 / 100.0
|
|
4386
|
+
pointsCM = [[1, 2, 3], [4, 5, 6], [-1, 1, 0]]
|
|
4387
|
+
pointsM = scale(CM_TO_METERS, pointsCM)
|
|
4388
|
+
|
|
4389
|
+
Anisotropic scaling along the X and Y axis::
|
|
4390
|
+
|
|
4391
|
+
pointsM = scale((SCALE_FACTOR_X, SCALE_FACTOR_Y), pointsCM)
|
|
4392
|
+
|
|
4393
|
+
Scale only on the X axis::
|
|
4394
|
+
|
|
4395
|
+
pointsM = scale((SCALE_FACTOR_X,), pointsCM)
|
|
4396
|
+
|
|
4397
|
+
Apply scaling on the Z axis only::
|
|
4398
|
+
|
|
4399
|
+
pointsM = scale((1.0, 1.0, SCALE_FACTOR_Z), pointsCM)
|
|
4400
|
+
|
|
4401
|
+
"""
|
|
4402
|
+
if out is None:
|
|
4403
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4404
|
+
else:
|
|
4405
|
+
dtype = np.dtype(dtype).type
|
|
4406
|
+
|
|
4407
|
+
points = np.asarray(points, dtype=dtype)
|
|
4408
|
+
toReturn = np.zeros_like(points, dtype=dtype) if out is None else out
|
|
4409
|
+
toReturn, points = np.atleast_2d(toReturn, points) # create 2d views
|
|
4410
|
+
|
|
4411
|
+
# uniform scaling
|
|
4412
|
+
if isinstance(sf, (float, int)):
|
|
4413
|
+
toReturn[:, :] = points * sf
|
|
4414
|
+
elif isinstance(sf, (list, tuple, np.ndarray)): # anisotropic
|
|
4415
|
+
sf = np.asarray(sf, dtype=dtype)
|
|
4416
|
+
sfLen = len(sf)
|
|
4417
|
+
if sfLen <= 3:
|
|
4418
|
+
toReturn[:, :] = points
|
|
4419
|
+
toReturn[:, :len(sf)] *= sf
|
|
4420
|
+
else:
|
|
4421
|
+
raise ValueError("Scale factor array must have length <= 3.")
|
|
4422
|
+
|
|
4423
|
+
return toReturn
|
|
4424
|
+
|
|
4425
|
+
|
|
4426
|
+
def normalMatrix(modelMatrix, out=None, dtype=None):
|
|
4427
|
+
"""Get the normal matrix from a model matrix.
|
|
4428
|
+
|
|
4429
|
+
Parameters
|
|
4430
|
+
----------
|
|
4431
|
+
modelMatrix : array_like
|
|
4432
|
+
4x4 homogeneous model matrix.
|
|
4433
|
+
out : ndarray, optional
|
|
4434
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4435
|
+
output if `out` was not specified.
|
|
4436
|
+
dtype : dtype or str, optional
|
|
4437
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4438
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4439
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4440
|
+
|
|
4441
|
+
Returns
|
|
4442
|
+
-------
|
|
4443
|
+
ndarray
|
|
4444
|
+
Normal matrix.
|
|
4445
|
+
|
|
4446
|
+
"""
|
|
4447
|
+
if out is None:
|
|
4448
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4449
|
+
else:
|
|
4450
|
+
dtype = np.dtype(dtype).type
|
|
4451
|
+
|
|
4452
|
+
modelMatrix = np.asarray(modelMatrix, dtype=dtype)
|
|
4453
|
+
|
|
4454
|
+
toReturn = np.zeros((4, 4), dtype=dtype) if out is None else out
|
|
4455
|
+
toReturn[:, :] = np.linalg.inv(modelMatrix).T
|
|
4456
|
+
|
|
4457
|
+
return toReturn
|
|
4458
|
+
|
|
4459
|
+
|
|
4460
|
+
def forwardProject(objPos, modelView, proj, viewport=None, out=None, dtype=None):
|
|
4461
|
+
"""Project a point in a scene to a window coordinate.
|
|
4462
|
+
|
|
4463
|
+
This function is similar to `gluProject` and can be used to find the window
|
|
4464
|
+
coordinate which a point projects to.
|
|
4465
|
+
|
|
4466
|
+
Parameters
|
|
4467
|
+
----------
|
|
4468
|
+
objPos : array_like
|
|
4469
|
+
Object coordinates (x, y, z). If an Nx3 array of coordinates is
|
|
4470
|
+
specified, where each row contains a window coordinate this function
|
|
4471
|
+
will return an array of projected coordinates with the same size.
|
|
4472
|
+
modelView : array_like
|
|
4473
|
+
4x4 combined model and view matrix for returned value to be object
|
|
4474
|
+
coordinates. Specify only the view matrix for a coordinate in the scene.
|
|
4475
|
+
proj : array_like
|
|
4476
|
+
4x4 projection matrix used for rendering.
|
|
4477
|
+
viewport : array_like
|
|
4478
|
+
Viewport rectangle for the window [x, y, w, h]. If not specified, the
|
|
4479
|
+
returned values will be in normalized device coordinates.
|
|
4480
|
+
out : ndarray, optional
|
|
4481
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4482
|
+
output if `out` was not specified.
|
|
4483
|
+
dtype : dtype or str, optional
|
|
4484
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4485
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4486
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4487
|
+
|
|
4488
|
+
Returns
|
|
4489
|
+
-------
|
|
4490
|
+
ndarray
|
|
4491
|
+
Normalized device or viewport coordinates [x, y, z] of the point. The
|
|
4492
|
+
`z` component is similar to the depth buffer value for the object point.
|
|
4493
|
+
|
|
4494
|
+
"""
|
|
4495
|
+
if out is None:
|
|
4496
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4497
|
+
else:
|
|
4498
|
+
dtype = np.dtype(dtype).type
|
|
4499
|
+
|
|
4500
|
+
toReturn = np.zeros_like(objPos, dtype=dtype) if out is None else out
|
|
4501
|
+
winCoord, objPos = np.atleast_2d(toReturn, objPos)
|
|
4502
|
+
|
|
4503
|
+
# transformation matrix
|
|
4504
|
+
mvp = np.matmul(proj, modelView)
|
|
4505
|
+
|
|
4506
|
+
# must have `w` for this one
|
|
4507
|
+
if objPos.shape[1] == 3:
|
|
4508
|
+
temp = np.zeros((objPos.shape[1], 4), dtype=dtype)
|
|
4509
|
+
temp[:, :3] = objPos
|
|
4510
|
+
objPos = temp
|
|
4511
|
+
|
|
4512
|
+
# transform the points
|
|
4513
|
+
objNorm = applyMatrix(mvp, objPos, dtype=dtype)
|
|
4514
|
+
|
|
4515
|
+
if viewport is not None:
|
|
4516
|
+
# if we have a viewport, transform it
|
|
4517
|
+
objNorm[:, :] += 1.0
|
|
4518
|
+
winCoord[:, 0] = viewport[0] + viewport[2] * objNorm[:, 0]
|
|
4519
|
+
winCoord[:, 1] = viewport[1] + viewport[3] * objNorm[:, 1]
|
|
4520
|
+
winCoord[:, 2] = objNorm[:, 2]
|
|
4521
|
+
winCoord[:, :] /= 2.0
|
|
4522
|
+
else:
|
|
4523
|
+
# already in NDC
|
|
4524
|
+
winCoord[:, :] = objNorm
|
|
4525
|
+
|
|
4526
|
+
return toReturn # ref to winCoord
|
|
4527
|
+
|
|
4528
|
+
|
|
4529
|
+
def reverseProject(winPos, modelView, proj, viewport=None, out=None, dtype=None):
|
|
4530
|
+
"""Unproject window coordinates into object or scene coordinates.
|
|
4531
|
+
|
|
4532
|
+
This function works like `gluUnProject` and can be used to find to an object
|
|
4533
|
+
or scene coordinate at the point on-screen (mouse coordinate or pixel). The
|
|
4534
|
+
coordinate can then be used to create a direction vector from the viewer's
|
|
4535
|
+
eye location. Another use of this function is to convert depth buffer
|
|
4536
|
+
samples to object or scene coordinates. This is the inverse operation of
|
|
4537
|
+
:func:`forwardProject`.
|
|
4538
|
+
|
|
4539
|
+
Parameters
|
|
4540
|
+
----------
|
|
4541
|
+
winPos : array_like
|
|
4542
|
+
Window coordinates (x, y, z). If `viewport` is not specified, these
|
|
4543
|
+
should be normalized device coordinates. If an Nx3 array of coordinates
|
|
4544
|
+
is specified, where each row contains a window coordinate this function
|
|
4545
|
+
will return an array of unprojected coordinates with the same size.
|
|
4546
|
+
Usually, you only need to specify the `x` and `y` coordinate, leaving
|
|
4547
|
+
`z` as zero. However, you can specify `z` if sampling from a depth map
|
|
4548
|
+
or buffer to convert a depth sample to an actual location.
|
|
4549
|
+
modelView : array_like
|
|
4550
|
+
4x4 combined model and view matrix for returned value to be object
|
|
4551
|
+
coordinates. Specify only the view matrix for a coordinate in the scene.
|
|
4552
|
+
proj : array_like
|
|
4553
|
+
4x4 projection matrix used for rendering.
|
|
4554
|
+
viewport : array_like
|
|
4555
|
+
Viewport rectangle for the window [x, y, w, h]. Do not specify one if
|
|
4556
|
+
`winPos` is in already in normalized device coordinates.
|
|
4557
|
+
out : ndarray, optional
|
|
4558
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4559
|
+
output if `out` was not specified.
|
|
4560
|
+
dtype : dtype or str, optional
|
|
4561
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4562
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4563
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4564
|
+
|
|
4565
|
+
Returns
|
|
4566
|
+
-------
|
|
4567
|
+
ndarray
|
|
4568
|
+
Object or scene coordinates.
|
|
4569
|
+
|
|
4570
|
+
"""
|
|
4571
|
+
if out is None:
|
|
4572
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4573
|
+
else:
|
|
4574
|
+
dtype = np.dtype(dtype).type
|
|
4575
|
+
|
|
4576
|
+
toReturn = np.zeros_like(winPos, dtype=dtype) if out is None else out
|
|
4577
|
+
objCoord, winPos = np.atleast_2d(toReturn, winPos)
|
|
4578
|
+
|
|
4579
|
+
# get inverse of model and projection matrix
|
|
4580
|
+
invMVP = np.linalg.inv(np.matmul(proj, modelView))
|
|
4581
|
+
|
|
4582
|
+
if viewport is not None:
|
|
4583
|
+
# if we have a viewport, we need to transform to NDC first
|
|
4584
|
+
objCoord[:, 0] = ((2 * winPos[:, 0] - viewport[0]) / viewport[2])
|
|
4585
|
+
objCoord[:, 1] = ((2 * winPos[:, 1] - viewport[1]) / viewport[3])
|
|
4586
|
+
objCoord[:, 2] = 2 * winPos[:, 2]
|
|
4587
|
+
objCoord -= 1
|
|
4588
|
+
objCoord[:, :] = applyMatrix(invMVP, objCoord, dtype=dtype)
|
|
4589
|
+
else:
|
|
4590
|
+
# already in NDC, just apply
|
|
4591
|
+
objCoord[:, :] = applyMatrix(invMVP, winPos, dtype=dtype)
|
|
4592
|
+
|
|
4593
|
+
return toReturn # ref to objCoord
|
|
4594
|
+
|
|
4595
|
+
|
|
4596
|
+
def lookAt(eyePos, centerPos, upVec=(0.0, 1.0, 0.0), out=None, dtype=None):
|
|
4597
|
+
"""Create a transformation matrix to orient a view towards some point.
|
|
4598
|
+
|
|
4599
|
+
Based on the same algorithm as 'gluLookAt'. This does not generate a
|
|
4600
|
+
projection matrix, but rather the matrix to transform the observer's view in
|
|
4601
|
+
the scene.
|
|
4602
|
+
|
|
4603
|
+
For more information see:
|
|
4604
|
+
https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
|
|
4605
|
+
|
|
4606
|
+
Parameters
|
|
4607
|
+
----------
|
|
4608
|
+
eyePos : list of float or ndarray
|
|
4609
|
+
Eye position in the scene.
|
|
4610
|
+
centerPos : list of float or ndarray
|
|
4611
|
+
Position of the object center in the scene.
|
|
4612
|
+
upVec : list of float or ndarray, optional
|
|
4613
|
+
Vector defining the up vector. Default is +Y is up.
|
|
4614
|
+
out : ndarray, optional
|
|
4615
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4616
|
+
output if `out` was not specified.
|
|
4617
|
+
dtype : dtype or str, optional
|
|
4618
|
+
Data type for arrays, can either be 'float32' or 'float64'. If `None` is
|
|
4619
|
+
specified, the data type is inferred by `out`. If `out` is not provided,
|
|
4620
|
+
the default is 'float64'.
|
|
4621
|
+
|
|
4622
|
+
Returns
|
|
4623
|
+
-------
|
|
4624
|
+
ndarray
|
|
4625
|
+
4x4 view matrix
|
|
4626
|
+
|
|
4627
|
+
Notes
|
|
4628
|
+
-----
|
|
4629
|
+
* This function was moved from `viewtools` in version 2025.1.0.
|
|
4630
|
+
* The returned matrix is row-major. Values are floats with 32-bits of
|
|
4631
|
+
precision stored as a contiguous (C-order) array.
|
|
4632
|
+
|
|
4633
|
+
"""
|
|
4634
|
+
if out is None:
|
|
4635
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4636
|
+
else:
|
|
4637
|
+
dtype = np.dtype(out.dtype).type
|
|
4638
|
+
|
|
4639
|
+
toReturn = np.zeros((4, 4,), dtype=dtype) if out is None else out
|
|
4640
|
+
if out is not None:
|
|
4641
|
+
toReturn.fill(0.0)
|
|
4642
|
+
|
|
4643
|
+
eyePos = np.asarray(eyePos, dtype=dtype)
|
|
4644
|
+
centerPos = np.asarray(centerPos, dtype=dtype)
|
|
4645
|
+
upVec = np.asarray(upVec, dtype=dtype)
|
|
4646
|
+
|
|
4647
|
+
f = centerPos - eyePos
|
|
4648
|
+
f /= np.linalg.norm(f)
|
|
4649
|
+
upVec /= np.linalg.norm(upVec)
|
|
4650
|
+
|
|
4651
|
+
s = np.cross(f, upVec)
|
|
4652
|
+
u = np.cross(s / np.linalg.norm(s), f)
|
|
4653
|
+
|
|
4654
|
+
rotMat = np.zeros((4, 4), dtype=dtype)
|
|
4655
|
+
rotMat[0, :3] = s
|
|
4656
|
+
rotMat[1, :3] = u
|
|
4657
|
+
rotMat[2, :3] = -f
|
|
4658
|
+
rotMat[3, 3] = 1.0
|
|
4659
|
+
|
|
4660
|
+
transMat = np.identity(4, dtype=dtype)
|
|
4661
|
+
transMat[:3, 3] = -eyePos
|
|
4662
|
+
|
|
4663
|
+
return np.matmul(rotMat, transMat, out=toReturn)
|
|
4664
|
+
|
|
4665
|
+
|
|
4666
|
+
# ------------------------------------------------------------------------------
|
|
4667
|
+
# Misc. Math Functions
|
|
4668
|
+
#
|
|
4669
|
+
|
|
4670
|
+
def zeroFix(a, inplace=False, threshold=None):
|
|
4671
|
+
"""Fix zeros in an array.
|
|
4672
|
+
|
|
4673
|
+
This function truncates very small numbers in an array to zero and removes
|
|
4674
|
+
any negative zeros.
|
|
4675
|
+
|
|
4676
|
+
Parameters
|
|
4677
|
+
----------
|
|
4678
|
+
a : ndarray
|
|
4679
|
+
Input array, must be a Numpy array.
|
|
4680
|
+
inplace : bool
|
|
4681
|
+
Fix an array inplace. If `True`, the input array will be modified,
|
|
4682
|
+
otherwise a new array will be returned with same `dtype` and shape with
|
|
4683
|
+
the fixed values.
|
|
4684
|
+
threshold : float or None
|
|
4685
|
+
Threshold for truncation. If `None`, the machine epsilon value for the
|
|
4686
|
+
input array `dtype` will be used. You can specify a custom threshold as
|
|
4687
|
+
a float.
|
|
4688
|
+
|
|
4689
|
+
Returns
|
|
4690
|
+
-------
|
|
4691
|
+
ndarray
|
|
4692
|
+
Output array with zeros fixed.
|
|
4693
|
+
|
|
4694
|
+
"""
|
|
4695
|
+
toReturn = np.copy(a) if not inplace else a
|
|
4696
|
+
toReturn += 0.0 # remove negative zeros
|
|
4697
|
+
threshold = np.finfo(a.dtype).eps if threshold is None else float(threshold)
|
|
4698
|
+
toReturn[np.abs(toReturn) < threshold] = 0.0 # make zero
|
|
4699
|
+
|
|
4700
|
+
return toReturn
|
|
4701
|
+
|
|
4702
|
+
|
|
4703
|
+
def lensCorrection(xys, coefK=(1.0,), distCenter=(0., 0.), out=None,
|
|
4704
|
+
dtype=None):
|
|
4705
|
+
"""Lens correction (or distortion) using the division model with even
|
|
4706
|
+
polynomial terms.
|
|
4707
|
+
|
|
4708
|
+
Calculate new vertex positions or texture coordinates to apply radial
|
|
4709
|
+
warping, such as 'pincushion' and 'barrel' distortion. This is to compensate
|
|
4710
|
+
for optical distortion introduced by lenses placed in the optical path of
|
|
4711
|
+
the viewer and the display (such as in an HMD).
|
|
4712
|
+
|
|
4713
|
+
See references[1]_ for implementation details.
|
|
4714
|
+
|
|
4715
|
+
Parameters
|
|
4716
|
+
----------
|
|
4717
|
+
xys : array_like
|
|
4718
|
+
Nx2 list of vertex positions or texture coordinates to distort. Works
|
|
4719
|
+
correctly only if input values range between -1.0 and 1.0.
|
|
4720
|
+
coefK : array_like or float
|
|
4721
|
+
Distortion coefficients K_n. Specifying multiple values will add more
|
|
4722
|
+
polynomial terms to the distortion formula. Positive values will produce
|
|
4723
|
+
'barrel' distortion, whereas negative will produce 'pincushion'
|
|
4724
|
+
distortion. In most cases, two or three coefficients are adequate,
|
|
4725
|
+
depending on the degree of distortion.
|
|
4726
|
+
distCenter : array_like, optional
|
|
4727
|
+
X and Y coordinate of the distortion center (eg. (0.2, -0.4)).
|
|
4728
|
+
out : ndarray, optional
|
|
4729
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4730
|
+
output if `out` was not specified.
|
|
4731
|
+
dtype : dtype or str, optional
|
|
4732
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4733
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4734
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4735
|
+
|
|
4736
|
+
Returns
|
|
4737
|
+
-------
|
|
4738
|
+
ndarray
|
|
4739
|
+
Array of distorted vertices.
|
|
4740
|
+
|
|
4741
|
+
Notes
|
|
4742
|
+
-----
|
|
4743
|
+
* At this time tangential distortion (i.e. due to a slant in the display)
|
|
4744
|
+
cannot be corrected for.
|
|
4745
|
+
|
|
4746
|
+
References
|
|
4747
|
+
----------
|
|
4748
|
+
.. [1] Fitzgibbon, W. (2001). Simultaneous linear estimation of multiple
|
|
4749
|
+
view geometry and lens distortion. Proceedings of the 2001 IEEE Computer
|
|
4750
|
+
Society Conference on Computer Vision and Pattern Recognition (CVPR).
|
|
4751
|
+
IEEE.
|
|
4752
|
+
|
|
4753
|
+
Examples
|
|
4754
|
+
--------
|
|
4755
|
+
Creating a lens correction mesh with barrel distortion (eg. for HMDs)::
|
|
4756
|
+
|
|
4757
|
+
vertices, textureCoords, normals, faces = gltools.createMeshGrid(
|
|
4758
|
+
subdiv=11, tessMode='center')
|
|
4759
|
+
|
|
4760
|
+
# recompute vertex positions
|
|
4761
|
+
vertices[:, :2] = mt.lensCorrection(vertices[:, :2], coefK=(5., 5.))
|
|
4762
|
+
|
|
4763
|
+
"""
|
|
4764
|
+
if out is None:
|
|
4765
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4766
|
+
else:
|
|
4767
|
+
dtype = np.dtype(dtype).type
|
|
4768
|
+
|
|
4769
|
+
if isinstance(coefK, (float, int,)):
|
|
4770
|
+
coefK = (coefK,)
|
|
4771
|
+
|
|
4772
|
+
xys = np.asarray(xys, dtype=dtype)
|
|
4773
|
+
coefK = np.asarray(coefK, dtype=dtype)
|
|
4774
|
+
|
|
4775
|
+
d_minus_c = xys - np.asarray(distCenter, dtype=dtype)
|
|
4776
|
+
r = np.power(length(d_minus_c, dtype=dtype)[:, np.newaxis],
|
|
4777
|
+
np.arange(len(coefK), dtype=dtype) * 2. + 2.)
|
|
4778
|
+
|
|
4779
|
+
toReturn = np.zeros_like(xys, dtype=dtype) if out is None else out
|
|
4780
|
+
|
|
4781
|
+
denom = dtype(1.0) + dot(coefK, r, dtype=dtype)
|
|
4782
|
+
toReturn[:, :] = xys + (d_minus_c / denom[:, np.newaxis])
|
|
4783
|
+
|
|
4784
|
+
return toReturn
|
|
4785
|
+
|
|
4786
|
+
|
|
4787
|
+
def lensCorrectionSpherical(xys, coefK=1.0, aspect=1.0, out=None, dtype=None):
|
|
4788
|
+
"""Simple lens correction.
|
|
4789
|
+
|
|
4790
|
+
Lens correction for a spherical lenses with distortion centered at the
|
|
4791
|
+
middle of the display. See references[1]_ for implementation details.
|
|
4792
|
+
|
|
4793
|
+
Parameters
|
|
4794
|
+
----------
|
|
4795
|
+
xys : array_like
|
|
4796
|
+
Nx2 list of vertex positions or texture coordinates to distort. Assumes
|
|
4797
|
+
the output will be rendered to normalized device coordinates where
|
|
4798
|
+
points range from -1.0 to 1.0.
|
|
4799
|
+
coefK : float
|
|
4800
|
+
Distortion coefficient. Use positive numbers for pincushion distortion
|
|
4801
|
+
and negative for barrel distortion.
|
|
4802
|
+
aspect : float
|
|
4803
|
+
Aspect ratio of the target window or buffer (width / height).
|
|
4804
|
+
out : ndarray, optional
|
|
4805
|
+
Optional output array. Must be same `shape` and `dtype` as the expected
|
|
4806
|
+
output if `out` was not specified.
|
|
4807
|
+
dtype : dtype or str, optional
|
|
4808
|
+
Data type for computations can either be 'float32' or 'float64'. If
|
|
4809
|
+
`out` is specified, the data type of `out` is used and this argument is
|
|
4810
|
+
ignored. If `out` is not provided, 'float64' is used by default.
|
|
4811
|
+
|
|
4812
|
+
Returns
|
|
4813
|
+
-------
|
|
4814
|
+
ndarray
|
|
4815
|
+
Array of distorted vertices.
|
|
4816
|
+
|
|
4817
|
+
References
|
|
4818
|
+
----------
|
|
4819
|
+
.. [1] Lens Distortion White Paper, Andersson Technologies LLC,
|
|
4820
|
+
www.ssontech.com/content/lensalg.html (obtained 07/28/2020)
|
|
4821
|
+
|
|
4822
|
+
Examples
|
|
4823
|
+
--------
|
|
4824
|
+
Creating a lens correction mesh with barrel distortion (eg. for HMDs)::
|
|
4825
|
+
|
|
4826
|
+
vertices, textureCoords, normals, faces = gltools.createMeshGrid(
|
|
4827
|
+
subdiv=11, tessMode='center')
|
|
4828
|
+
|
|
4829
|
+
# recompute vertex positions
|
|
4830
|
+
vertices[:, :2] = mt.lensCorrection2(vertices[:, :2], coefK=2.0)
|
|
4831
|
+
|
|
4832
|
+
"""
|
|
4833
|
+
if out is None:
|
|
4834
|
+
dtype = DEFAULT_DTYPE if dtype is None else np.dtype(dtype).type
|
|
4835
|
+
else:
|
|
4836
|
+
dtype = np.dtype(dtype).type
|
|
4837
|
+
|
|
4838
|
+
toReturn = np.empty_like(xys, dtype=dtype) if out is None else out
|
|
4839
|
+
|
|
4840
|
+
xys = np.asarray(xys, dtype=dtype)
|
|
4841
|
+
toReturn[:, 0] = u = xys[:, 0]
|
|
4842
|
+
toReturn[:, 1] = v = xys[:, 1]
|
|
4843
|
+
coefKCubed = np.power(coefK, 3, dtype=dtype)
|
|
4844
|
+
|
|
4845
|
+
r2 = aspect * aspect * u * u + v * v
|
|
4846
|
+
r2sqr = np.sqrt(r2, dtype=dtype)
|
|
4847
|
+
f = 1. + r2 * (coefK + coefKCubed * r2sqr)
|
|
4848
|
+
|
|
4849
|
+
toReturn[:, 0] *= f
|
|
4850
|
+
toReturn[:, 1] *= f
|
|
4851
|
+
|
|
4852
|
+
return toReturn
|
|
4853
|
+
|
|
4854
|
+
|
|
4855
|
+
class infrange():
|
|
4856
|
+
"""
|
|
4857
|
+
Similar to base Python `range`, but allowing the step to be a float or even
|
|
4858
|
+
0, useful for specifying ranges for logical comparisons.
|
|
4859
|
+
"""
|
|
4860
|
+
def __init__(self, min, max, step=0):
|
|
4861
|
+
self.min = min
|
|
4862
|
+
self.max = max
|
|
4863
|
+
self.step = step
|
|
4864
|
+
|
|
4865
|
+
@property
|
|
4866
|
+
def range(self):
|
|
4867
|
+
return abs(self.max-self.min)
|
|
4868
|
+
|
|
4869
|
+
def __lt__(self, other):
|
|
4870
|
+
return other > self.max
|
|
4871
|
+
|
|
4872
|
+
def __le__(self, other):
|
|
4873
|
+
return other > self.min
|
|
4874
|
+
|
|
4875
|
+
def __gt__(self, other):
|
|
4876
|
+
return self.min > other
|
|
4877
|
+
|
|
4878
|
+
def __ge__(self, other):
|
|
4879
|
+
return self.max > other
|
|
4880
|
+
|
|
4881
|
+
def __contains__(self, item):
|
|
4882
|
+
if self.step == 0:
|
|
4883
|
+
return self.min < item < self.max
|
|
4884
|
+
else:
|
|
4885
|
+
return item in np.linspace(self.min, self.max, int(self.range/self.step)+1)
|
|
4886
|
+
|
|
4887
|
+
def __eq__(self, item):
|
|
4888
|
+
if isinstance(item, self.__class__):
|
|
4889
|
+
return all((
|
|
4890
|
+
self.min == item.min,
|
|
4891
|
+
self.max == item.max,
|
|
4892
|
+
self.step == item.step
|
|
4893
|
+
))
|
|
4894
|
+
return item in self
|
|
4895
|
+
|
|
4896
|
+
def __add__(self, other):
|
|
4897
|
+
return self.__class__(self.min+other, self.max+other, self.step)
|
|
4898
|
+
|
|
4899
|
+
def __sub__(self, other):
|
|
4900
|
+
return self.__class__(self.min - other, self.max - other, self.step)
|
|
4901
|
+
|
|
4902
|
+
def __mul__(self, other):
|
|
4903
|
+
return self.__class__(self.min * other, self.max * other, self.step * other)
|
|
4904
|
+
|
|
4905
|
+
def __truedic__(self, other):
|
|
4906
|
+
return self.__class__(self.min / other, self.max / other, self.step / other)
|
|
4907
|
+
|
|
4908
|
+
|
|
4909
|
+
if __name__ == "__main__":
|
|
4910
|
+
pass
|