├── .all-contributorsrc ├── .codespellrc ├── .gitattributes ├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── check_md_links.yml │ ├── codespell.yml │ └── run_tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierrc ├── .readthedocs.yml ├── CITATION.cff ├── LICENSE ├── Makefile ├── README.md ├── cpp_ptb.m ├── demos ├── CPP_checkAbortDemo.m ├── CPP_getResponseDemo.m ├── CPP_pressSpaceForMeDemo.m ├── CPP_waitForTriggerDemo.m ├── miss_hit.cfg └── video │ ├── CPP_playVideoFramesDemo.m │ └── stimuli │ ├── coffee │ ├── coffee01.jpeg │ ├── coffee02.jpeg │ ├── coffee03.jpeg │ ├── coffee04.jpeg │ ├── coffee05.jpeg │ ├── coffee06.jpeg │ ├── coffee07.jpeg │ ├── coffee08.jpeg │ ├── coffee09.jpeg │ ├── coffee10.jpeg │ ├── coffee11.jpeg │ ├── coffee12.jpeg │ ├── coffee13.jpeg │ ├── coffee14.jpeg │ ├── coffee15.jpeg │ ├── coffee16.jpeg │ ├── coffee17.jpeg │ ├── coffee18.jpeg │ ├── coffee19.jpeg │ ├── coffee20.jpeg │ ├── coffee21.jpeg │ ├── coffee22.jpeg │ ├── coffee23.jpeg │ ├── coffee24.jpeg │ ├── coffee25.jpeg │ ├── coffee26.jpeg │ ├── coffee27.jpeg │ ├── coffee28.jpeg │ ├── coffee29.jpeg │ └── coffee30.jpeg │ └── leaves │ ├── leaves01.jpeg │ ├── leaves02.jpeg │ ├── leaves03.jpeg │ ├── leaves04.jpeg │ ├── leaves05.jpeg │ ├── leaves06.jpeg │ ├── leaves07.jpeg │ ├── leaves08.jpeg │ ├── leaves09.jpeg │ ├── leaves10.jpeg │ ├── leaves11.jpeg │ ├── leaves12.jpeg │ ├── leaves13.jpeg │ ├── leaves14.jpeg │ ├── leaves15.jpeg │ ├── leaves16.jpeg │ ├── leaves17.jpeg │ ├── leaves18.jpeg │ ├── leaves19.jpeg │ ├── leaves20.jpeg │ ├── leaves21.jpeg │ ├── leaves22.jpeg │ ├── leaves23.jpeg │ ├── leaves24.jpeg │ ├── leaves25.jpeg │ ├── leaves26.jpeg │ ├── leaves27.jpeg │ ├── leaves28.jpeg │ ├── leaves29.jpeg │ └── leaves30.jpeg ├── docs ├── Makefile ├── README.md ├── cpp_ptb-manual.pdf ├── create_manual.sh ├── make.bat └── source │ ├── _static │ └── cpp_lab_logo.png │ ├── conf.py │ ├── function_description.rst │ ├── index.rst │ ├── installation.rst │ └── set_up.rst ├── lib ├── miss_hit.cfg └── utils │ └── unfold.m ├── manualTests ├── miss_hit.cfg ├── test_dotMotionSimulation.m ├── test_getResponse.m └── test_radialMotion.m ├── miss_hit.cfg ├── requirements.txt ├── scripts └── ptbSoundDeviceTest.m ├── src ├── aperture │ ├── apertureTexture.m │ ├── eccenLogSpeed.m │ ├── getApertureName.m │ ├── saveAperture.m │ ├── saveApertures.m │ ├── smoothOval.m │ └── smoothRect.m ├── defaults │ ├── checkCppPtbCfg.m │ ├── cppPtbDefaults.m │ └── setDefaultFields.m ├── dot │ ├── computeCartCoord.m │ ├── computeRadialMotionDirection.m │ ├── decomposeMotion.m │ ├── dotMotionSimulation.m │ ├── dotTexture.m │ ├── generateNewDotPositions.m │ ├── initDots.m │ ├── postInitDots.m │ ├── reseedDots.m │ ├── seedDots.m │ ├── setDotDirection.m │ └── updateDots.m ├── drawFieldOfVIew.m ├── errors │ ├── errorAbort.m │ ├── errorAbortGetReponse.m │ ├── errorDistanceToScreen.m │ └── errorRestrictedKeysGetReponse.m ├── eyeTracker.m ├── fixation │ ├── drawFixation.m │ └── initFixation.m ├── getExperimentEnd.m ├── getExperimentStart.m ├── initPTB.m ├── isOctave.m ├── keyboard │ ├── checkAbort.m │ ├── checkAbortGetResponse.m │ ├── collectAndSaveResponses.m │ ├── getResponse.m │ ├── pressSpaceForMe.m │ └── testKeyboards.m ├── miss_hit.cfg ├── randomization │ ├── repeatShuffleConditions.m │ ├── setTargetPositionInSequence.m │ └── shuffle.m ├── screen │ ├── farewellScreen.m │ └── standByScreen.m ├── templates │ ├── miss_hit.cfg │ ├── templateFunction.m │ └── templateFunctionExample.m ├── utils │ ├── checkPtbVersion.m │ ├── cleanUp.m │ ├── computeFOV.m │ ├── degToPix.m │ ├── makeGif.m │ ├── pixToDeg.m │ ├── printCreditsCppPtb.m │ ├── printScreen.m │ └── setUpRand.m ├── video │ ├── createFramesTextureStructure.m │ └── playVideoFrames.m ├── waitFor.m └── waitForTrigger.m ├── tests ├── miss_hit.cfg ├── test_checkAbortGetResponse.m ├── test_checkCppPtbCfg.m ├── test_computeFOV.m ├── test_computeRadialMotionDirection.m ├── test_decomposeMotion.m ├── test_degToPix.m ├── test_generateNewDotPositions.m ├── test_initDots.m ├── test_initFixation.m ├── test_pixToDeg.m ├── test_repeatShuffleConditions.m ├── test_reseedDots.m ├── test_seedDots.m ├── test_setDefaultFields.m ├── test_setDotDirection.m ├── test_setTargetPositionInSequence.m └── test_utils.m └── version.txt /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Remi-Gau", 10 | "name": "Remi Gau", 11 | "avatar_url": "https://avatars3.githubusercontent.com/u/6961185?v=4", 12 | "profile": "https://remi-gau.github.io/", 13 | "contributions": [ 14 | "code", 15 | "design", 16 | "doc", 17 | "bug", 18 | "userTesting", 19 | "ideas", 20 | "infra", 21 | "maintenance", 22 | "test", 23 | "question" 24 | ] 25 | }, 26 | { 27 | "login": "marcobarilari", 28 | "name": "marcobarilari", 29 | "avatar_url": "https://avatars3.githubusercontent.com/u/38101692?v=4", 30 | "profile": "https://github.com/marcobarilari", 31 | "contributions": [ 32 | "code", 33 | "design", 34 | "doc", 35 | "bug", 36 | "userTesting", 37 | "ideas" 38 | ] 39 | }, 40 | { 41 | "login": "CerenB", 42 | "name": "CerenB", 43 | "avatar_url": "https://avatars1.githubusercontent.com/u/10451654?v=4", 44 | "profile": "https://github.com/CerenB", 45 | "contributions": [ 46 | "code", 47 | "design", 48 | "doc", 49 | "review", 50 | "test", 51 | "bug", 52 | "userTesting", 53 | "ideas" 54 | ] 55 | }, 56 | { 57 | "login": "fedefalag", 58 | "name": "Fede F.", 59 | "avatar_url": "https://avatars.githubusercontent.com/u/50373329?v=4", 60 | "profile": "https://github.com/fedefalag", 61 | "contributions": [ 62 | "ideas", 63 | "code", 64 | "content" 65 | ] 66 | }, 67 | { 68 | "login": "iqrashahzad14", 69 | "name": "iqrashahzad14", 70 | "avatar_url": "https://avatars.githubusercontent.com/u/75671348?v=4", 71 | "profile": "https://github.com/iqrashahzad14", 72 | "contributions": [ 73 | "bug", 74 | "test" 75 | ] 76 | } 77 | ], 78 | "contributorsPerLine": 7, 79 | "projectName": "CPP_PTB", 80 | "projectOwner": "cpp-lln-lab", 81 | "repoType": "github", 82 | "repoHost": "https://github.com", 83 | "skipCi": false 84 | } 85 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = .git,env,*build,lib 3 | ignore-words-list = nwe 4 | builtin = clear,rare 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | # Documentation 3 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 4 | version: 2 5 | updates: 6 | - package-ecosystem: 'github-actions' 7 | directory: '/' 8 | schedule: 9 | interval: 'monthly' 10 | 11 | - package-ecosystem: 'gitsubmodule' 12 | directory: '/' 13 | schedule: 14 | interval: 'monthly' 15 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## Release Notes 3 | 4 | ## CHANGES 5 | $CHANGES 6 | -------------------------------------------------------------------------------- /.github/workflows/check_md_links.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | 3 | # checking for any dead links in markdown files 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: ['*'] 11 | 12 | jobs: 13 | markdown-link-check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 18 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | name: codespell 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | # Check for common misspellings 15 | codespell: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: codespell-project/actions-codespell@master 20 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: tests and coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: ['*'] 9 | 10 | jobs: 11 | 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | fetch-depth: 0 20 | 21 | - name: MOxUnit Action 22 | uses: joergbrech/moxunit-action@v1.3.0 23 | with: 24 | tests: tests 25 | src: src 26 | with_coverage: true 27 | cover_xml_file: coverage.xml 28 | 29 | - name: Code coverage 30 | uses: codecov/codecov-action@v5 31 | with: 32 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 33 | file: coverage.xml # optional 34 | flags: unittests # optional 35 | name: codecov-umbrella # optional 36 | fail_ci_if_error: false # optional (default = false) 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *DS_Store 2 | 3 | .vscode/* 4 | 5 | *.tsv 6 | 7 | *.mat 8 | 9 | # exclude content of files for virtual env (mostly for the doc) 10 | cpp_ptb 11 | env 12 | 13 | # ignore file created by sphynx 14 | /docs/build 15 | 16 | # exclude temp files from tests and coverage 17 | test_report.log 18 | *coverage* 19 | 20 | ## MATLAB / OCTAVE gitignore template 21 | 22 | # From : https://github.com/github/gitignore/blob/master/Global/MATLAB.gitignore 23 | 24 | # Windows default autosave extension 25 | *.asv 26 | 27 | # OSX / *nix default autosave extension 28 | *.m~ 29 | 30 | # Compiled MEX binaries (all platforms) 31 | *.mex* 32 | 33 | # Packaged app and toolbox files 34 | *.mlappinstall 35 | *.mltbx 36 | 37 | # Generated helpsearch folders 38 | helpsearch*/ 39 | 40 | # Simulink code generation folders 41 | slprj/ 42 | sccprj/ 43 | 44 | # Matlab code generation folders 45 | codegen/ 46 | 47 | # Simulink autosave extension 48 | *.autosave 49 | 50 | # Simulink cache files 51 | *.slxc 52 | 53 | # Octave session info 54 | octave-workspace 55 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | 5 | hooks: 6 | 7 | - id: mh_version 8 | name: mh_version 9 | entry: mh_style 10 | args: [-v] 11 | verbose: true 12 | language: python 13 | additional_dependencies: [miss_hit_core] 14 | 15 | # - id: mh_style 16 | # name: mh_style 17 | # entry: mh_style 18 | # args: [--process-slx, --fix] 19 | # files: ^(.*\.(m|slx))$ 20 | # language: python 21 | # additional_dependencies: [miss_hit_core] 22 | 23 | # - id: mh_metric 24 | # name: mh_metric 25 | # entry: mh_metric 26 | # args: [--ci] 27 | # files: ^(.*\.(m|slx))$ 28 | # language: python 29 | # additional_dependencies: [miss_hit_core] 30 | 31 | # - id: mh_lint 32 | # name: mh_lint 33 | # entry: mh_lint 34 | # files: ^(.*\.(m|slx))$ 35 | # language: python 36 | # additional_dependencies: [miss_hit] 37 | 38 | - repo: https://github.com/pre-commit/pre-commit-hooks 39 | rev: v5.0.0 40 | hooks: 41 | - id: trailing-whitespace 42 | - id: end-of-file-fixer 43 | - id: check-yaml 44 | - id: check-added-large-files 45 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "markdown", 3 | "proseWrap": "always", 4 | "tabWidth": 2, 5 | "overrides": [ 6 | { 7 | "files": "*.md", 8 | "options": { 9 | "tabWidth": 4 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: '3.12' 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/source/conf.py 16 | builder: html 17 | fail_on_warning: true 18 | 19 | # Build documentation with MkDocs 20 | #mkdocs: 21 | # configuration: mkdocs.yml 22 | 23 | # Optionally build your docs in additional formats such as PDF 24 | formats: 25 | - pdf 26 | 27 | # Optionally set the version of Python and requirements required to build your docs 28 | python: 29 | install: 30 | - requirements: requirements.txt 31 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | 3 | title: "CPP PTB" 4 | 5 | version: 1.3.0 6 | 7 | abstract: a set of function to make it easier to create behavioral, EEG, fMRI experiment with psychtoolbox . 8 | 9 | message: "If you use this software, please cite it as below." 10 | 11 | repository-code: "https://github.com/cpp-lln-lab/CPP_PTB" 12 | 13 | identifiers: 14 | - description: This is the collection of archived snapshots of all releases of CPP PTB 15 | type: doi 16 | value: "10.5281/zenodo.4007672" 17 | 18 | contact: 19 | - affiliation: "Université catholique de Louvain" 20 | email: remi.gau@uclouvain.be 21 | family-names: Gau 22 | given-names: Rémi 23 | 24 | authors: 25 | - family-names: "Gau" 26 | given-names: "Rémi" 27 | orcid: "https://orcid.org/0000-0002-1535-9767" 28 | affiliation: "Université catholique de Louvain" 29 | 30 | - family-names: "Barilari" 31 | given-names: "Marco" 32 | orcid: "https://orcid.org/0000-0002-3313-3120" 33 | affiliation: "Université catholique de Louvain" 34 | 35 | - family-names: "Battal" 36 | given-names: "Ceren" 37 | orcid: "https://orcid.org/0000-0002-9844-7630" 38 | affiliation: "Université catholique de Louvain" 39 | 40 | - family-names: "Falagiarda" 41 | given-names: "Federica" 42 | orcid: "https://orcid.org/0000-0001-7844-1605" 43 | affiliation: "Université catholique de Louvain" 44 | 45 | - family-names: "Shahzad" 46 | given-names: "Iqra" 47 | orcid: "https://orcid.org/0000-0002-8724-7668" 48 | affiliation: "Université catholique de Louvain" 49 | 50 | license: MIT license 51 | 52 | keywords: 53 | - PsychToolBox 54 | - neuroscience 55 | - MATLAB 56 | - Octave 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Crossmodal Perception and Plasticity laboratory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # TODO make more general to use the local matlab version 2 | MATLAB = /usr/local/MATLAB/R2017a/bin/matlab 3 | ARG = -nodisplay -nosplash -nodesktop 4 | 5 | lint: 6 | mh_style --fix && mh_metric --ci && mh_lint 7 | 8 | test: 9 | $(MATLAB) $(ARG) -r "runTests; exit()" 10 | 11 | version.txt: CITATION.cff 12 | grep -w "^version" CITATION.cff | sed "s/version: /v/g" > version.txt 13 | 14 | validate_cff: CITATION.cff 15 | cffconvert --validate 16 | 17 | manual: 18 | cd docs && sh create_manual.sh 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Status: stable](https://readthedocs.org/projects/cpp-ptb/badge/?version=stable)](https://cpp-ptb.readthedocs.io/en/stable/?badge=stable) 2 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4007672.svg)](https://doi.org/10.5281/zenodo.4007672) 3 | [![tests and coverage](https://github.com/cpp-lln-lab/CPP_PTB/actions/workflows/run_tests.yml/badge.svg)](https://github.com/cpp-lln-lab/CPP_PTB/actions/workflows/run_tests.yml) 4 | [![codecov](https://codecov.io/gh/cpp-lln-lab/CPP_PTB/branch/master/graph/badge.svg)](https://codecov.io/gh/cpp-lln-lab/CPP_PTB) 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) 6 | 7 | # CPP_PTB 8 | 9 | - [CPP\_PTB](#cpp_ptb) 10 | - [Documentation](#documentation) 11 | - [Content](#content) 12 | - [Contributing](#contributing) 13 | - [Contributors ✨](#contributors-) 14 | 15 | This is the Crossmodal Perception and Plasticity lab (CPP) PsychToolBox (PTB) 16 | toolbox. 17 | 18 | Those functions are mostly wrappers around some PTB functions to facilitate 19 | their use and their reuse (#DontRepeatYourself) 20 | 21 | ## Documentation 22 | 23 | All the documentation and installation information is accessible 24 | [here](https://cpp-ptb.readthedocs.io/en/stable/index.html#). 25 | 26 | ## Content 27 | 28 | ```bash 29 | ├── demos # quick demo of how to use some functions 30 | ├── docs # documentation 31 | ├── manualTests # all the tests that cannot be automated (yet) 32 | ├── src # actual code of the CPP_PTB 33 | │ ├── aperture # function related to create apertur (circle, wedge, bar...) 34 | │ ├── dot # functions to simplify the creations of RDK 35 | │ ├── errors # all error functions 36 | │ ├── fixation # to create fixation cross, dots 37 | │ ├── keyboard # to collect responses, abort experiment... 38 | │ ├── randomization # functions to help with trial randomization 39 | │ └── utils # set of general functions 40 | └── tests # all the tests that that can be run by github actions 41 | ``` 42 | 43 | ## Contributing 44 | 45 | Feel free to open issues to report a bug and ask for improvements. 46 | 47 | If you want to contribute, have a look at our 48 | [contributing guidelines](https://github.com/cpp-lln-lab/.github/blob/main/CONTRIBUTING.md) 49 | that are meant to guide you and help you get started. If something is not clear 50 | or you get stuck: it is more likely we did not do good enough a job at 51 | explaining things. So do not hesitate to open an issue, just to ask for 52 | clarification. 53 | 54 | ## Contributors ✨ 55 | 56 | Thanks goes to these wonderful people 57 | ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |

Remi Gau

💻 🎨 📖 🐛 📓 🤔 🚇 🚧 ⚠️ 💬

marcobarilari

💻 🎨 📖 🐛 📓 🤔

CerenB

💻 🎨 📖 👀 ⚠️ 🐛 📓 🤔

Fede F.

🤔 💻 🖋

iqrashahzad14

🐛 ⚠️
71 | 72 | 73 | 74 | 75 | 76 | 77 | This project follows the 78 | [all-contributors](https://github.com/all-contributors/all-contributors) 79 | specification. Contributions of any kind welcome! 80 | -------------------------------------------------------------------------------- /cpp_ptb.m: -------------------------------------------------------------------------------- 1 | function cpp_ptb(varargin) 2 | % 3 | % General intro function for CPP SPM 4 | % 5 | % USAGE:: 6 | % 7 | % cpp_ptb 8 | % cpp_ptb('init') 9 | % cpp_ptb('uninit') 10 | % cpp_ptb('dev') 11 | % 12 | % :param action: 13 | % :type action: string 14 | % 15 | % :returns: - :action: (type) (dimension) 16 | % 17 | % Example:: 18 | % 19 | % (C) Copyright 2022 CPP_PTB developers 20 | 21 | p = inputParser; 22 | 23 | defaultAction = 'init'; 24 | 25 | addOptional(p, 'action', defaultAction, @ischar); 26 | 27 | parse(p, varargin{:}); 28 | 29 | action = p.Results.action; 30 | 31 | switch lower(action) 32 | 33 | case 'init' 34 | 35 | initCppPtb(); 36 | 37 | case 'uninit' 38 | 39 | uninitCppPtb(); 40 | 41 | case 'run_tests' 42 | 43 | runTests(); 44 | 45 | end 46 | 47 | end 48 | 49 | function initCppPtb() 50 | % 51 | % Adds the relevant folders to the path for a given session. 52 | % Has to be run to be able to use CPP_PTB. 53 | % 54 | % USAGE:: 55 | % 56 | % initCppPtb() 57 | % 58 | % (C) Copyright 2021 CPP_PTB developers 59 | 60 | thisDirectory = fileparts(mfilename('fullpath')); 61 | 62 | global CPP_PTB_INITIALIZED 63 | global CPP_PTB_PATHS 64 | 65 | if isempty(CPP_PTB_INITIALIZED) 66 | 67 | pathSep = ':'; 68 | if ~isunix 69 | pathSep = ';'; 70 | end 71 | 72 | CPP_PTB_PATHS = fullfile(thisDirectory); 73 | CPP_PTB_PATHS = cat(2, CPP_PTB_PATHS, ... 74 | pathSep, ... 75 | genpath(fullfile(thisDirectory, 'src'))); 76 | CPP_PTB_PATHS = cat(2, CPP_PTB_PATHS, pathSep, ... 77 | genpath(fullfile(thisDirectory, 'lib'))); 78 | 79 | addpath(CPP_PTB_PATHS, '-begin'); 80 | 81 | checkPtbVersion(); 82 | 83 | CPP_PTB_INITIALIZED = true(); 84 | 85 | detectCppPtb(); 86 | 87 | else 88 | fprintf('\n\nCPP_PTB already initialized\n\n'); 89 | 90 | end 91 | 92 | end 93 | 94 | function detectCppPtb() 95 | 96 | workflowsDir = cellstr(which('initPTB.m', '-ALL')); 97 | 98 | if isempty(workflowsDir) 99 | error('CPP_PTB is not in your MATLAB / Octave path.\n'); 100 | 101 | elseif numel(workflowsDir) > 1 102 | fprintf('CPP_PTB seems to appear in several different folders:\n'); 103 | for i = 1:numel(workflowsDir) 104 | fprintf(' * %s\n', fullfile(workflowsDir{i}, '..', '..')); 105 | end 106 | error('Remove all but one with ''pathtool'' .\n'); % or ''spm_rmpath 107 | 108 | end 109 | end 110 | 111 | function uninitCppPtb() 112 | % 113 | % Removes the added folders from the path for a given session. 114 | % 115 | % USAGE:: 116 | % 117 | % uninitCppPtb() 118 | % 119 | % (C) Copyright 2021 CPP_PTB developers 120 | 121 | thisDirectory = fileparts(mfilename('fullpath')); 122 | 123 | global CPP_PTB_INITIALIZED 124 | global CPP_PTB_PATHS 125 | 126 | if isempty(CPP_PTB_INITIALIZED) || ~CPP_PTB_INITIALIZED 127 | fprintf('\n\nCPP_PTB not initialized\n\n'); 128 | return 129 | 130 | else 131 | rmpath(CPP_PTB_PATHS); 132 | 133 | if IsOctave 134 | clear -g CPP_PTB_PATHS CPP_PTB_INITIALIZED; 135 | else 136 | clearvars -GLOBAL CPP_PTB_PATHS CPP_PTB_INITIALIZED; 137 | end 138 | 139 | end 140 | 141 | end 142 | 143 | function runTests() 144 | % 145 | % (C) Copyright 2019 CPP_PTB developers 146 | 147 | tic; 148 | 149 | cpp_ptb(); 150 | 151 | cd(fileparts(mfilename('fullpath'))); 152 | 153 | fprintf(sprintf('\nHome is %s\n', getenv('HOME'))); 154 | 155 | warning('OFF'); 156 | 157 | folderToCover = fullfile(pwd, 'src'); 158 | testFolder = fullfile(pwd, 'tests'); 159 | 160 | success = moxunit_runtests(testFolder, ... 161 | '-verbose', '-recursive', '-with_coverage', ... 162 | '-cover', folderToCover, ... 163 | '-cover_xml_file', 'coverage.xml', ... 164 | '-cover_html_dir', fullfile(pwd, 'coverage_html')); 165 | 166 | if success 167 | system('echo 0 > test_report.log'); 168 | else 169 | system('echo 1 > test_report.log'); 170 | end 171 | 172 | toc; 173 | 174 | end 175 | -------------------------------------------------------------------------------- /demos/CPP_checkAbortDemo.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2020 CPP_PTB developers 2 | 3 | % add parent directory to the path (to make sure we can access the CPP_PTB 4 | % functions) 5 | addpath(fullfile(pwd, '..')); 6 | 7 | % set up 8 | cfg.keyboard.escapeKey = 'ESCAPE'; 9 | 10 | % beginning of demo 11 | KbName('UnifyKeyNames'); 12 | 13 | try 14 | 15 | % stay in the loop until the escape key is pressed 16 | while GetSecs < Inf 17 | 18 | checkAbort(cfg); 19 | 20 | end 21 | 22 | catch ME 23 | 24 | switch ME.identifier 25 | case 'checkAbort:abortRequested' 26 | warning('You pressed the escape key: will try to fail gracefully.'); 27 | fprintf('\nWe did catch your abort signal.\n'); 28 | otherwise 29 | rethrow(ME); % display other errors 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /demos/CPP_getResponseDemo.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2020 CPP_PTB developers 2 | 3 | %% Demo showing how to use the getResponse function 4 | 5 | % This small script shows how to use the getReponse function 6 | 7 | % add parent directory to matlab path (so we can access the CPP_PTB functions) 8 | addpath(fullfile(pwd, '..')); 9 | 10 | %% start with a clean slate 11 | clear; 12 | clc; 13 | 14 | if IsOctave 15 | more off; % for a better display experience 16 | end 17 | 18 | % use the default set up (use main keyboard and use the ESCAPE key to abort) 19 | cfg = setDefaultsPTB; 20 | 21 | % show the default option 22 | disp(cfg.keyboard); 23 | 24 | %% set parameters 25 | 26 | % Change the values set by defaults (for more info about the keyboard see the doc) 27 | % cfg.keyboard.keyboard = ?? 28 | % cfg.keyboard.responseBox = ?? 29 | % cfg.keyboard.escapeKey = ?? 30 | 31 | % Decide which device you want to collect responses from 32 | deviceNumber = []; % default device (PTB will find it for you) 33 | % deviceNumber = cfg.keyboard.keyboard; % the one you may have chosen as the main keyboard 34 | % deviceNumber = cfg.keyboard.responseBox; % the one you may have chosen as the response box 35 | 36 | % if you want getResponse to ignore the key release 37 | getOnlyPress = 1; 38 | 39 | % We set which keys are "valid", any keys other than those will be ignored 40 | cfg.keyboard.responseKey = {'a', 'b'}; 41 | 42 | % This would make sure that you listen to presses of the escape key 43 | cfg.keyboard.responseKey{end + 1} = cfg.keyboard.escapeKey; 44 | 45 | %% Final checks 46 | 47 | % Make sure keyboard mapping is the same on all supported operating systems 48 | KbName('UnifyKeyNames'); 49 | 50 | % Test that the keyboards are correctly configured 51 | testKeyboards(cfg); 52 | 53 | % Give the time to the test key to be released and not listened to 54 | WaitSecs(1); 55 | 56 | fprintf('\nDuring the next 5 seconds we will collect responses on the following keys: \n\n'); 57 | if isempty(cfg.keyboard.responseKey) 58 | fprintf('\nALL KEYS\n\n'); 59 | else 60 | for iKey = 1:numel(cfg.keyboard.responseKey) 61 | fprintf('\n%s', cfg.keyboard.responseKey{iKey}); 62 | end 63 | fprintf('\n\n'); 64 | end 65 | 66 | %% Run demo 67 | 68 | try 69 | 70 | % Create the keyboard queue to collect responses. 71 | getResponse('init', deviceNumber, cfg); 72 | 73 | % Start collecting responses for 5 seconds 74 | % Each new key press is added to the queue of events recorded by KbQueue 75 | startSecs = GetSecs(); 76 | getResponse('start', deviceNumber); 77 | 78 | % Here we wait for 5 seconds but are still collecting responses. 79 | % So you could still be doing something else (presenting audio and visual stim) and 80 | % still collect responses. 81 | WaitSecs(5); 82 | 83 | % Check what keys were pressed (all of them) 84 | % If the escapeKey was pressed at any time, it will only abort when you 85 | % getResponse('check') 86 | responseEvents = getResponse('check', deviceNumber, cfg, getOnlyPress); 87 | 88 | % This can be used to flush the queue: empty all events that are still present in the queue 89 | getResponse('flush', deviceNumber); 90 | 91 | % If you wan to stop listening to key presses. You could start listening again 92 | % later by calling: getResponse('start', deviceNumber) 93 | getResponse('stop', deviceNumber); 94 | 95 | % If you wan to destroyt the queue: you would have to initialize it again 96 | getResponse('release', deviceNumber); 97 | 98 | %% Now we look what keys were pressed and when 99 | for iEvent = 1:size(responseEvents, 1) 100 | 101 | if responseEvents(iEvent).pressed 102 | eventType = 'pressed'; 103 | else 104 | eventType = 'released'; 105 | end 106 | 107 | fprintf('\n %s was %s at time %.3f seconds\n', ... 108 | responseEvents(iEvent).keyName, ... 109 | eventType, ... 110 | responseEvents(iEvent).onset - startSecs); 111 | 112 | end 113 | 114 | catch ME 115 | 116 | getResponse('release', deviceNumber); 117 | 118 | switch ME.identifier 119 | 120 | case 'getResponse:abortRequested' 121 | warning('You pressed the escape key: will try to fail gracefully.'); 122 | 123 | fprintf('\nWe did catch your abort signal.\n'); 124 | 125 | otherwise 126 | rethrow(ME); % display other errors 127 | 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /demos/CPP_pressSpaceForMeDemo.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2020 CPP_PTB developers 2 | 3 | % add parent directory to the path (to make sure we can access the CPP_PTB 4 | % functions) 5 | addpath(fullfile(pwd, '..')); 6 | 7 | % beginning of demo 8 | KbName('UnifyKeyNames'); 9 | 10 | % press the key "space" to "start" the experiment 11 | pressSpaceForMe; 12 | -------------------------------------------------------------------------------- /demos/CPP_waitForTriggerDemo.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2020 CPP_PTB developers 2 | 3 | % add parent/src directory to the path (to make sure we can access the CPP_PTB functions) 4 | addpath(genpath(fullfile(pwd, '..', 'src'))); 5 | 6 | cfg.testingDevice = 'mri'; 7 | 8 | cfg.mri.triggerNb = 5; 9 | cfg.mri.triggerKey = 't'; 10 | 11 | KbName('UnifyKeyNames'); 12 | 13 | quietMode = false; 14 | 15 | fprintf(1, 'Press the letter %s %i times, please.\n', cfg.mri.triggerKey, cfg.mri.triggerNb); 16 | 17 | lastTriggerTimeStamp = waitForTrigger(cfg, [], quietMode, cfg.mri.triggerNb); 18 | 19 | fprintf(1, 'Thank you. The time stamp of the last trigger was %f.\n', lastTriggerTimeStamp); 20 | -------------------------------------------------------------------------------- /demos/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | # style guide (https://florianschanda.github.io/miss_hit/style_checker.html) 2 | line_length: 100 3 | regex_function_name: "CPP_[a-z]+(([A-Z]){1}[A-Za-z]+)*" 4 | regex_script_name: "CPP_[a-z]+(([A-Z]){1}[A-Za-z]+)*" 5 | copyright_entity: "Sam Schwarzkopf" 6 | copyright_entity: "Agah Karakuzu" 7 | copyright_entity: "CPP_PTB developers" 8 | 9 | # metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html) 10 | metric "cnest": limit 4 11 | metric "file_length": limit 500 12 | metric "cyc": limit 15 13 | metric "parameters": limit 5 14 | -------------------------------------------------------------------------------- /demos/video/CPP_playVideoFramesDemo.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2021 CPP_PTB developers 2 | 3 | %% Demo showing how to use the createFramesTextureStructure and playVideoFrames functions 4 | 5 | % as it is, it presents two example videos in succession 6 | 7 | % add parent directory to matlab path (so we can access the CPP_PTB functions) 8 | addpath(fullfile(pwd, '..')); 9 | 10 | %% start with a clean slate 11 | clear; 12 | clc; 13 | 14 | %% initialize PTB % This can def be improved by using other CPP_PTB functions% 15 | 16 | % Skip the PTB sync test 17 | Screen('Preference', 'SkipSyncTests', 2); 18 | 19 | % Get the screen numbers and draw to the external screen if available 20 | cfg.screen.idx = max(Screen('Screens')); 21 | 22 | % Set the PTB window background manually 23 | cfg.color.background = [127 127 127]; 24 | 25 | % Get the screen numbers and draw to the external screen if available 26 | cfg.screen.idx = max(Screen('Screens')); 27 | 28 | % Open an on screen window 29 | [cfg.screen.win, cfg.screen.winRect] = Screen('OpenWindow', cfg.screen.idx, cfg.color.background); 30 | 31 | %% Structure for video related info 32 | 33 | % The name of your "video" and its images format 34 | video.names = {'coffee', 'leaves'}; 35 | 36 | % The number of frames of your video you want to present 37 | % If left empty, all available frames are taken 38 | video.frame.numbers = []; 39 | % video.frame.numbers = 30; 40 | % video.frame.numbers = [30,15]; 41 | 42 | % The format of the images 43 | video.frame.format = '.jpeg'; % can be any img format compatible with the "imread" function 44 | 45 | % the folder where the images are (from the current folder) 46 | video.stimuli.folder = 'stimuli/'; 47 | 48 | % The frame rate at which you want to present your video 49 | video.frame.rate = 29.97; 50 | 51 | % Time bw videos in s 52 | video.isi = 1; 53 | 54 | %% present the videos in a loop 55 | 56 | for trial = 1:length(video.names) 57 | 58 | % select name and folder where current video is 59 | video.name = video.names{trial}; 60 | video.path = fullfile(video.stimuli.folder, video.name); 61 | 62 | % how many frames are going to be presented 63 | if numel(video.frame.numbers) < 1 64 | video.frame.number = size(dir([video.path, '/*', video.frame.format]), 1); 65 | 66 | elseif numel(video.frame.numbers) > 1 67 | video.frame.number = video.frame.numbers(trial); 68 | 69 | else 70 | video.frame.number = video.frame.numbers; 71 | end 72 | 73 | % Read the images and create the structure with their textures 74 | [video, cfg] = createFramesTextureStructure(video, cfg); 75 | 76 | % Play the video 77 | playVideoFrames(video, cfg); 78 | 79 | % ISI (better would be to use Flip) 80 | WaitSecs(video.isi); 81 | 82 | end 83 | 84 | % The end 85 | sca; 86 | -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee01.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee02.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee03.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee03.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee04.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee04.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee05.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee05.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee06.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee06.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee07.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee07.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee08.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee08.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee09.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee09.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee10.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee11.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee12.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee13.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee14.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee15.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee15.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee16.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee17.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee17.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee18.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee18.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee19.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee19.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee20.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee21.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee21.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee22.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee22.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee23.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee23.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee24.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee24.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee25.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee25.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee26.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee26.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee27.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee27.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee28.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee28.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee29.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee29.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/coffee/coffee30.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/coffee/coffee30.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves01.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves02.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves03.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves03.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves04.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves04.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves05.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves05.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves06.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves06.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves07.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves07.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves08.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves08.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves09.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves09.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves10.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves11.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves12.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves13.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves14.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves15.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves15.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves16.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves17.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves17.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves18.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves18.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves19.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves19.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves20.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves21.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves21.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves22.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves22.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves23.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves23.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves24.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves24.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves25.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves25.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves26.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves26.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves27.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves27.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves28.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves28.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves29.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves29.jpeg -------------------------------------------------------------------------------- /demos/video/stimuli/leaves/leaves30.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/demos/video/stimuli/leaves/leaves30.jpeg -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Information on how to write and build the documentation can be found in our 2 | [CONTRIBUTING guidelines](https://github.com/cpp-lln-lab/.github/blob/main/CONTRIBUTING.md). 3 | -------------------------------------------------------------------------------- /docs/cpp_ptb-manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/docs/cpp_ptb-manual.pdf -------------------------------------------------------------------------------- /docs/create_manual.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sphinx-build -M latexpdf source build 4 | 5 | cp build/latex/cpp_ptb.pdf cpp_ptb-manual.pdf 6 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_static/cpp_lab_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpp-lln-lab/CPP_PTB/baea91b47cbfff3a719f9e16eea6de53f667f868/docs/source/_static/cpp_lab_logo.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../..')) 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = 'CPP_PTB' 20 | copyright = '2020, CPP_PTB developers' 21 | author = 'CPP_PTB developers' 22 | 23 | # The full version, including alpha/beta/rc tags 24 | release = 'v1.2.1dev' 25 | 26 | 27 | # -- General configuration --------------------------------------------------- 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinxcontrib.matlab', 34 | 'sphinx.ext.autodoc'] 35 | matlab_src_dir = os.path.dirname(os.path.abspath('../../src')) 36 | primary_domain = 'mat' 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | html_logo = '_static/cpp_lab_logo.png' 59 | 60 | # html_theme_options = { 61 | # 'github_user': 'cpp-lln-lab', 62 | # 'github_repo': 'CPP_BIDS_SPM_pipeline', 63 | # 'github_button': True, 64 | # 'github_banner': True 65 | # } 66 | # html_theme_options = { 67 | # 'collapse_navigation': False, 68 | # 'display_version': False, 69 | # 'navigation_depth': 4, 70 | # } 71 | 72 | html_sidebars = { 73 | '**': [ 74 | 'about.html', 75 | 'navigation.html', 76 | 'relations.html', # needs 'show_related': True theme option to display 77 | 'searchbox.html', 78 | 'donate.html', 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /docs/source/function_description.rst: -------------------------------------------------------------------------------- 1 | Function description 2 | ******************** 3 | 4 | List of functions in the ``src`` folder. 5 | 6 | .. module:: src 7 | 8 | .. autofunction:: initPTB 9 | .. autofunction:: drawFieldOfVIew 10 | .. autofunction:: eyeTracker 11 | .. autofunction:: getExperimentEnd 12 | .. autofunction:: getExperimentStart 13 | .. autofunction:: isOctave 14 | .. autofunction:: waitForTrigger 15 | .. autofunction:: waitFor 16 | 17 | ---- 18 | 19 | Defaults 20 | ======== 21 | 22 | List of functions in the ``src/defaults`` folder. 23 | 24 | .. module:: src.defaults 25 | 26 | .. autofunction:: checkCppPtbCfg 27 | .. autofunction:: cppPtbDefaults 28 | .. autofunction:: setDefaultFields 29 | 30 | 31 | Aperture 32 | ======== 33 | 34 | List of functions in the ``src/aperture`` folder. 35 | 36 | (to add saveAperture) 37 | 38 | .. module:: src.aperture 39 | 40 | .. autofunction:: apertureTexture 41 | .. autofunction:: eccenLogSpeed 42 | .. autofunction:: getApertureName 43 | .. autofunction:: saveApertures 44 | .. autofunction:: smoothOval 45 | .. autofunction:: smoothRect 46 | 47 | ---- 48 | 49 | Dot 50 | === 51 | 52 | List of functions in the ``src/dot`` folder. 53 | 54 | .. module:: src.dot 55 | 56 | .. autofunction:: computeCartCoord 57 | .. autofunction:: computeRadialMotionDirection 58 | .. autofunction:: decomposeMotion 59 | .. autofunction:: dotMotionSimulation 60 | .. autofunction:: dotTexture 61 | .. autofunction:: generateNewDotPositions 62 | .. autofunction:: initDots 63 | .. autofunction:: reseedDots 64 | .. autofunction:: seedDots 65 | .. autofunction:: setDotDirection 66 | .. autofunction:: updateDots 67 | 68 | ---- 69 | 70 | Errors 71 | ====== 72 | 73 | List of functions in the ``src/errors`` folder. 74 | 75 | .. module:: src.errors 76 | 77 | .. autofunction:: errorAbort 78 | .. autofunction:: errorAbortGetReponse 79 | .. autofunction:: errorDistanceToScreen 80 | .. autofunction:: errorRestrictedKeysGetReponse 81 | 82 | ---- 83 | 84 | Fixation 85 | ======== 86 | 87 | List of functions in the ``src/fixation`` folder. 88 | 89 | .. module:: src.fixation 90 | 91 | .. autofunction:: drawFixation 92 | .. autofunction:: initFixation 93 | 94 | ---- 95 | 96 | Keyboard 97 | ======== 98 | 99 | List of functions in the ``src/keyboard`` folder. 100 | 101 | .. module:: src.keyboard 102 | 103 | .. autofunction:: getResponse 104 | .. autofunction:: checkAbort 105 | .. autofunction:: checkAbortGetResponse 106 | .. autofunction:: collectAndSaveResponses 107 | .. autofunction:: pressSpaceForMe 108 | .. autofunction:: testKeyboards 109 | 110 | ---- 111 | 112 | Randomization 113 | ============= 114 | 115 | List of functions in the ``src/randomization`` folder. 116 | 117 | .. module:: src.randomization 118 | 119 | .. autofunction:: repeatShuffleConditions 120 | .. autofunction:: setTargetPositionInSequence 121 | .. autofunction:: shuffle 122 | 123 | ---- 124 | 125 | Screen 126 | ====== 127 | 128 | List of functions in the ``src/screen`` folder. 129 | 130 | .. module:: src.screen 131 | 132 | .. autofunction:: farewellScreen 133 | .. autofunction:: standByScreen 134 | 135 | ---- 136 | 137 | Utilities 138 | ========= 139 | 140 | List of functions in the ``src/utils`` folder. 141 | 142 | (to add makeGif) 143 | 144 | .. module:: src.utils 145 | 146 | .. autofunction:: checkPtbVersion 147 | .. autofunction:: cleanUp 148 | .. autofunction:: computeFOV 149 | .. autofunction:: degToPix 150 | .. autofunction:: pixToDeg 151 | .. autofunction:: printCreditsCppPtb 152 | .. autofunction:: printScreen 153 | .. autofunction:: setUpRand 154 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. CPP_PTB documentation master file, created by 2 | sphinx-quickstart on Sun Oct 25 14:42:27 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to CPP_PTB's documentation! 7 | =================================== 8 | 9 | This is the Crossmodal Perception and Plasticity lab (CPP) PsychToolBox (PTB) 10 | toolbox. 11 | 12 | Those functions are mostly wrappers around some PTB functions to facilitate 13 | their use and their reuse (#DontRepeatYourself) 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :caption: Contents: 18 | 19 | installation 20 | set_up 21 | function_description 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | ************ 2 | Installation 3 | ************ 4 | 5 | Requirements 6 | ============ 7 | 8 | Make sure that the following toolboxes are installed and added to the matlab / 9 | octave path. 10 | 11 | For instructions see the following links: 12 | 13 | +------------------------------------------------------------+--------------+ 14 | | Requirements | Used version | 15 | +============================================================+==============+ 16 | | `PsychToolBox `_ | >=3.0.14 | 17 | +------------------------------------------------------------+--------------+ 18 | | `Matlab `_ | >=2015b | 19 | +------------------------------------------------------------+--------------+ 20 | | or `Octave `_ | 4.? | 21 | +------------------------------------------------------------+--------------+ 22 | 23 | Tested: 24 | 25 | - matlab 2015b or octave 4.2.2 and PTB 3.0.14. 26 | 27 | How to install 28 | ============== 29 | 30 | The easiest way to use this repository is to create a new repository by using 31 | the 32 | `template PTB experiment repository `_: 33 | this creates a new repository on your github account with all the basic folders, 34 | files and submodules already set up. You only have to then clone the repository 35 | and you are good to go. 36 | 37 | Download with git 38 | ***************** 39 | 40 | .. code-block:: bash 41 | :linenos: 42 | 43 | cd fullpath_to_directory_where_to_install 44 | 45 | # use git to download the code 46 | git clone https://github.com/cpp-lln-lab/CPP_PTB.git 47 | 48 | # move into the folder you have just created 49 | cd CPP_PTB 50 | 51 | 52 | Then get the latest commit to stay up to date: 53 | 54 | .. code-block:: bash 55 | :linenos: 56 | 57 | # from the directory where you downloaded the code 58 | git pull origin master 59 | 60 | To work with a specific version, create a branch at a specific version tag 61 | number 62 | 63 | .. code-block:: bash 64 | :linenos: 65 | 66 | # creating and checking out a branch that will be called version1 at the version tag v1.0.0 67 | git checkout -b version1 v1.0.0 68 | 69 | 70 | Add as a submodule 71 | ****************** 72 | 73 | Add it as a submodule in the repo you are working on. 74 | 75 | .. code-block:: bash 76 | :linenos: 77 | 78 | cd fullpath_to_directory_where_to_install 79 | 80 | # use git to download the code 81 | git submodule add https://github.com/cpp-lln-lab/CPP_PTB.git 82 | 83 | To get the latest commit you then need to update the submodule with the 84 | information on its remote repository and then merge those locally. 85 | 86 | .. code-block:: bash 87 | :linenos: 88 | 89 | git submodule update --remote --merge 90 | 91 | Remember that updates to submodules need to be committed as well. 92 | 93 | Example for submodule usage 94 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 95 | 96 | So say you want to clone a repo that has some nested submodules, then you would 97 | type this to get the content of all the submodules at once (here with my 98 | experiment repo): 99 | 100 | .. code-block:: bash 101 | :linenos: 102 | 103 | git clone --recurse-submodules https://github.com/user_name/yourExperiment.git 104 | 105 | This would be the way to do it "by hand" 106 | 107 | .. code-block:: bash 108 | :linenos: 109 | 110 | # clone the repo 111 | git clone https://github.com/user_name/yourExperiment.git 112 | 113 | # go into the directory 114 | cd yourExperiment 115 | 116 | # initialize and get the content of the first level of submodules (CPP_PTB and CPP_BIDS) 117 | git submodule init 118 | git submodule update 119 | 120 | # get the nested submodules JSONio and BIDS-matlab for CPP_BIDS 121 | git submodule foreach --recursive 'git submodule init' 122 | git submodule foreach --recursive 'git submodule update' 123 | 124 | Direct download 125 | *************** 126 | 127 | Download the code. Unzip. And add to the matlab path. 128 | 129 | Pick a specific version from `here `_. 130 | 131 | Or take `the latest commit `_ - 132 | NOT RECOMMENDED. 133 | 134 | Add CPP_PTB globally to the matlab path 135 | *************************************** 136 | 137 | This is NOT RECOMMENDED as this might create conflicts if you use different 138 | versions of CPP_PTB as sub-modules. 139 | 140 | Also note that this might not work at all if you have not set a command line 141 | alias to start Matlab from a terminal window by just typing `matlab`. :wink: 142 | 143 | .. code-block:: bash 144 | :linenos: 145 | 146 | # from within the CPP_PTB folder 147 | matlab -nojvm -nosplash -r "addpath(genpath(fullfile(pwd, 'src'))); savepath(); path(); exit();" 148 | -------------------------------------------------------------------------------- /docs/source/set_up.rst: -------------------------------------------------------------------------------- 1 | Set up 2 | ****** 3 | 4 | The CFG structure 5 | ================= 6 | 7 | The ``cfg`` structure is where most of the information about your experiment will 8 | be defined. 9 | 10 | Below we try to outline what it contains. 11 | 12 | Some of those fields you can set yourself while some others will be created and 13 | filled after running ``setDefaultsPTB.m`` and ``initPTB.m``. 14 | 15 | - ``setDefaultsPTB.m`` sets some default values for things about your experiment 16 | that that do not "depend" on your system or that PTB cannot "know". For 17 | example the width of the screen in cm or the dimensions of the fixation 18 | cross you want to use... 19 | 20 | - ``initPTB.m`` will fill in the fields that ARE system dependent like the 21 | screen refresh rate, the reference of the window that PTB opened and where 22 | to flip stimulus to. When it runs, ``initPTB.m`` will call ``setDefaultsPTB.m`` 23 | to make sure that all the required fields are non-empty. 24 | 25 | If no value is provided below, it means that there is no set default (or that the 26 | `initPTB` takes care of it). 27 | 28 | Fields set ``setDefaultsPTB`` 29 | ----------------------------- 30 | 31 | .. code-block:: matlab 32 | 33 | cfg.testingDevice = 'pc'; 34 | 35 | Other options include: 36 | 37 | - ``'mri'`` 38 | - ``'eeg'`` 39 | - ``'meg'`` 40 | 41 | cfg.keyboard 42 | ++++++++++++ 43 | 44 | .. code-block:: matlab 45 | 46 | cfg.keyboard.keyboard = []; % device index for the main keyboard 47 | % (that of the experimenter) 48 | cfg.keyboard.responseBox = []; % device index used by the participants 49 | cfg.keyboard.responseKey = {}; % list the keys that PTB should "listen" to when 50 | % using KbQueue to collect responses ; 51 | % if empty PTB will listen to all key presses 52 | cfg.keyboard.escapeKey = 'ESCAPE'; % key to press to escape 53 | 54 | 55 | cfg.debug 56 | +++++++++ 57 | 58 | .. code-block:: matlab 59 | 60 | cfg.debug.do = true; % if true this will make less PTB tolerant with 61 | % bad synchronisation 62 | cfg.debug.transpWin = true; % makes the stimulus windows semi-transparent: 63 | % useful when designing your experiment 64 | cfg.debug.smallWin = true; % open a small window and not a full screen window ; 65 | % can be useful for debugging 66 | 67 | cfg.text 68 | ++++++++ 69 | 70 | .. code-block:: matlab 71 | 72 | cfg.text.font = 'Courier New'; 73 | cfg.text.size = 18; 74 | cfg.text.style = 1; % bold 75 | 76 | 77 | cfg.color 78 | +++++++++ 79 | 80 | .. code-block:: matlab 81 | 82 | cfg.color.background = [0 0 0]; % [r g b] each in 0-255 83 | 84 | cfg.screen 85 | ++++++++++ 86 | 87 | .. code-block:: matlab 88 | 89 | cfg.screen.monitorWidth = 42; % in cm 90 | cfg.screen.monitorDistance = 134; % in cm 91 | cfg.screen.resolution = {[], [], []}; 92 | 93 | 94 | cfg.fixation 95 | ++++++++++++ 96 | 97 | .. code-block:: matlab 98 | 99 | cfg.fixation.type = 'cross'; % can also be 'dot' or 'bestFixation' 100 | cfg.fixation.xDisplacement = 0; % horizontal offset from window center 101 | cfg.fixation.yDisplacement = 0; % vertical offset from window center 102 | cfg.fixation.color = [255 255 255]; 103 | cfg.fixation.width = 1; % in degrees of visual angle 104 | cfg.fixation.lineWidthPix = 5; % width of the lines in pixel 105 | 106 | cfg.aperture 107 | ++++++++++++ 108 | 109 | Mostly relevant for retinotopy scripts but can be reused for other types of 110 | experiments where an aperture is needed. 111 | 112 | .. code-block:: matlab 113 | 114 | cfg.aperture.type = 'none'; 115 | 116 | Other options include: 117 | 118 | - ``'bar'`` 119 | - ``'wedge'`` 120 | - ``'ring'`` 121 | - ``'circle'`` 122 | 123 | 124 | cfg.audio 125 | +++++++++ 126 | 127 | Check the ``scripts/ptbSoundDeviceTest.m`` to help you figure out what devices are connected 128 | to the computer and which one you can use. 129 | 130 | .. code-block:: matlab 131 | 132 | cfg.audio.do = false; % set to true if you are going to play some sounds 133 | cfg.audio.requestedLatency = 3; 134 | cfg.audio.fs 44100; % sampling frequency 135 | cfg.audio.channels = 2; % number of auditory channels 136 | cfg.audio.initVolume = 1; 137 | cfg.audio.repeat = 1; 138 | cfg.audio.startCue = 0; 139 | cfg.audio.waitForDevice = 1; 140 | 141 | Fields set by ``initPTB`` 142 | ------------------------- 143 | 144 | cfg.screen 145 | ++++++++++ 146 | 147 | .. code-block:: matlab 148 | 149 | cfg.screen.idx % screen index 150 | cfg.screen.win % window index 151 | cfg.screen.winRect % rectangle definition of the window 152 | cfg.screen.winWidth 153 | cfg.screen.winHeight 154 | cfg.screen.center % [x y] ; pixel coordinate of the window center 155 | cfg.screen.FOV % width of the field of view in degrees of visual angle 156 | cfg.screen.ppd % pixel per degree 157 | cfg.screen.ifi % inter frame interval 158 | cfg.screen.monRefresh % monitor refresh rate ; 1 / ifi 159 | 160 | 161 | cfg.audio 162 | +++++++++ 163 | 164 | .. code-block:: matlab 165 | 166 | cfg.audio.requestOffsetTime = 1; 167 | cfg.audio.reqsSampleOffset 168 | cfg.audio.pushSize 169 | cfg.audio.playbackMode = 1; 170 | cfg.audio.devIdx = []; 171 | cfg.audio.pahandle 172 | 173 | 174 | operating system information 175 | ++++++++++++++++++++++++++++ 176 | 177 | .. code-block:: matlab 178 | 179 | cfg.software.os 180 | cfg.software.name = 'Psychtoolbox'; 181 | cfg.software.RRID = 'SCR_002881'; 182 | cfg.software.version % psychtoolbox version 183 | cfg.software.runsOn % matlab or octave and version number 184 | 185 | 186 | Setting up keyboards 187 | ==================== 188 | 189 | To select a specific keyboard to be used by the experimenter or the participant, 190 | you need to know the value assigned by PTB to each keyboard device. 191 | 192 | To know this copy-paste this on the command window: 193 | 194 | .. code-block:: matlab 195 | 196 | [keyboardNumbers, keyboardNames] = GetKeyboardIndices; 197 | 198 | disp(keyboardNumbers); 199 | disp(keyboardNames); 200 | 201 | 202 | You can then assign a specific device number to the main keyboard or the 203 | response box in the ``cfg`` structure: 204 | 205 | - ``cfg.keyboard.responseBox`` would be the device number of the device used by 206 | the participant to give his/her response: like the button box in the scanner 207 | or a separate keyboard for a behavioral experiment 208 | 209 | - ``cfg.keyboard.keyboard`` would be the device number of the keyboard on which 210 | the experimenter will type or press the keys necessary to start or abort the 211 | experiment. 212 | 213 | ``cfg.keyboard.responseBox`` and ``cfg.keyboard.keyboard`` can be different or the 214 | same. 215 | 216 | Using empty vectors (like ``[]``) or a negative value for those means that you will 217 | let PTB find and use the default device. 218 | -------------------------------------------------------------------------------- /lib/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | enable: false 2 | -------------------------------------------------------------------------------- /lib/utils/unfold.m: -------------------------------------------------------------------------------- 1 | function unfold(input, varargin) 2 | % 3 | % Unfolds a structure. 4 | % 5 | % USAGE:: 6 | % 7 | % unfold(SC, varargin) 8 | % 9 | % UNFOLD(SC) displays the content of a variable. 10 | % 11 | % If SC is a structure it recursively shows the name of SC and the fieldnames of SC and their 12 | % contents. 13 | % 14 | % If SC is a cell array the contents of each cell are displayed. 15 | % 16 | % It uses the caller's workspace variable name as the name of SC. 17 | % UNFOLD(SC,NAME) uses NAME as the name of SC. 18 | % 19 | % UNFOLD(SC,SHOW) If SHOW is false only the fieldnames and their sizes 20 | % are shown, if SHOW is true the contents are shown also. 21 | % 22 | % (C) Copyright 2022 Remi Gau 23 | % (C) Copyright 2005-2006 R.F. Tap 24 | 25 | % R.F. Tap 26 | % 15-6-2005, 7-12-2005, 5-1-2006, 3-4-2006 27 | 28 | % taken and adapted from 29 | % https://nl.mathworks.com/matlabcentral/answers/94122-is-there-a-function-that-will-display-all-the-fields-of-a-nested-structure-in-matlab#answer_103473 30 | 31 | % check input 32 | switch nargin 33 | case 1 34 | Name = inputname(1); 35 | show = true; 36 | case 2 37 | if islogical(varargin{1}) 38 | Name = inputname(1); 39 | show = varargin{1}; 40 | elseif ischar(varargin{1}) 41 | Name = varargin{1}; 42 | show = true; 43 | else 44 | error('Second input argument must be a string or a logical'); 45 | end 46 | case 3 47 | if ischar(varargin{1}) 48 | if islogical(varargin{2}) 49 | Name = varargin{1}; 50 | show = varargin{2}; 51 | else 52 | error('Third input argument must be a logical'); 53 | end 54 | else 55 | error('Second input argument must be a string'); 56 | end 57 | otherwise 58 | error('Invalid number of input arguments'); 59 | end 60 | 61 | if isstruct(input) 62 | 63 | input = orderfields(input); 64 | % number of elements to be displayed 65 | NS = numel(input); 66 | if show 67 | hmax = NS; 68 | else 69 | hmax = min(1, NS); 70 | end 71 | 72 | % recursively display structure including fieldnames 73 | for h = 1:hmax 74 | F = fieldnames(input(h)); 75 | NF = length(F); 76 | for i = 1:NF 77 | 78 | if NS > 1 79 | siz = size(input); 80 | if show 81 | Namei = [Name '(' indToStr(siz, h) ').' F{i}]; 82 | else 83 | Namei = [Name '(' indToStr(siz, NS) ').' F{i}]; 84 | end 85 | else 86 | Namei = [Name '.' F{i}]; 87 | end 88 | 89 | if isstruct(input(h).(F{i})) 90 | unfold(input(h).(F{i}), Namei, show); 91 | else 92 | 93 | if iscell(input(h).(F{i})) 94 | if numel(input(h).(F{i})) == 0 95 | printKeyToScreen(Namei); 96 | fprintf(' =\t{};'); 97 | else 98 | siz = size(input(h).(F{i})); 99 | NC = numel(input(h).(F{i})); 100 | if show 101 | jmax = NC; 102 | else 103 | jmax = 1; 104 | end 105 | for j = 1:jmax 106 | if show 107 | Namej = [Namei '{' indToStr(siz, j) '}']; 108 | else 109 | Namej = [Namei '{' indToStr(siz, NC) '}']; 110 | end 111 | printKeyToScreen(Namej); 112 | if show 113 | printValueToScreen(input(h).(F{i}){j}); 114 | end 115 | end 116 | end 117 | 118 | else 119 | printKeyToScreen(Namei); 120 | if show 121 | printValueToScreen(input(h).(F{i})); 122 | end 123 | end 124 | 125 | end 126 | end 127 | end 128 | 129 | elseif iscell(input) 130 | 131 | if numel(input) == 0 132 | fprintf(' =\t{};'); 133 | else 134 | % recursively display cell 135 | siz = size(input); 136 | for i = 1:numel(input) 137 | Namei = [Name '{' indToStr(siz, i) '}']; 138 | unfold(input{i}, Namei, show); 139 | end 140 | end 141 | 142 | else 143 | 144 | printKeyToScreen(Name); 145 | if show 146 | printValueToScreen(input); 147 | end 148 | 149 | end 150 | 151 | callStack = dbstack(); 152 | if numel(callStack) > 1 && ~strcmp(callStack(2).name, mfilename()) 153 | fprintf('\n'); 154 | elseif numel(callStack) == 1 && strcmp(callStack(1).name, mfilename()) 155 | fprintf('\n'); 156 | end 157 | 158 | end 159 | 160 | function printKeyToScreen(input) 161 | fprintf(sprintf('\n%s', input)); 162 | end 163 | 164 | function printValueToScreen(input) 165 | base_string = ' =\t'; 166 | msg = ''; 167 | if ischar(input) 168 | msg = sprintf('%s''%s'' ', base_string, input); 169 | elseif isinteger(input) || islogical(input) 170 | if isempty(input) 171 | msg = ' =\t[] '; 172 | else 173 | pattern = repmat('%i, ', 1, numel(input)); 174 | msg = sprintf(['%s' pattern], base_string, input); 175 | end 176 | elseif isnumeric(input) 177 | if isempty(input) 178 | msg = ' =\t[] '; 179 | else 180 | pattern = repmat('%f, ', 1, numel(input)); 181 | msg = sprintf(['%s' pattern], base_string, input); 182 | end 183 | end 184 | fprintf(msg); 185 | fprintf('\b\b;'); 186 | end 187 | 188 | % local functions 189 | % -------------------------------------------------------------------------- 190 | function str = indToStr(siz, ndx) 191 | 192 | n = length(siz); 193 | % treat vectors and scalars correctly 194 | if n == 2 195 | if siz(1) == 1 196 | siz = siz(2); 197 | n = 1; 198 | elseif siz(2) == 1 199 | siz = siz(1); 200 | n = 1; 201 | end 202 | end 203 | k = [1 cumprod(siz(1:end - 1))]; 204 | ndx = ndx - 1; 205 | str = ''; 206 | for i = n:-1:1 207 | v = floor(ndx / k(i)) + 1; 208 | if i == n 209 | str = num2str(v); 210 | else 211 | str = [num2str(v) ',' str]; 212 | end 213 | ndx = rem(ndx, k(i)); 214 | end 215 | 216 | end 217 | -------------------------------------------------------------------------------- /manualTests/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | # style guide (https://florianschanda.github.io/miss_hit/style_checker.html) 2 | regex_function_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*" 3 | regex_script_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*" 4 | regex_parameter_name: "test_suite|[A-Z]{2,}|[a-z]+([A-Z]+[a-z]*)*" 5 | -------------------------------------------------------------------------------- /manualTests/test_dotMotionSimulation.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_dotMotionSimulation %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_dotMotionSimulationStatic() 12 | 13 | nbEvents = 1; 14 | doPlot = false; 15 | 16 | thisEvent.direction = -1; % degrees 17 | thisEvent.speedPix = 1; % pix per frame 18 | 19 | cfg.design.motionType = 'translation'; 20 | cfg.dot.coherence = 1; % proportion 21 | cfg.dot.lifeTime = .5; % in seconds 22 | cfg.dot.matrixWidth = 250; % in pixels 23 | cfg.dot.proportionKilledPerFrame = 0; 24 | cfg.timing.eventDuration = 1.8; % in seconds 25 | 26 | relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); 27 | 28 | end 29 | 30 | function test_dotMotionSimulationTranslation() 31 | % ensure that dog homogeneity is not too low when we kill dots often enough 32 | 33 | nbEvents = 500; 34 | doPlot = false; 35 | 36 | thisEvent.direction = 0; % degrees 37 | thisEvent.speedPix = 1; % pix per frame 38 | 39 | cfg.design.motionType = 'translation'; 40 | cfg.dot.coherence = 1; % proportion 41 | cfg.dot.lifeTime = .1; % in seconds 42 | cfg.dot.matrixWidth = 250; % in pixels 43 | cfg.dot.proportionKilledPerFrame = 0; 44 | cfg.timing.eventDuration = 1.8; % in seconds 45 | 46 | relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); 47 | 48 | assertLessThan(relativeDensityContrast, 0.5); 49 | 50 | end 51 | -------------------------------------------------------------------------------- /manualTests/test_getResponse.m: -------------------------------------------------------------------------------- 1 | % 2 | % (C) Copyright 2020 CPP_PTB developers 3 | 4 | addpath(fullfile(pwd, '..')); 5 | 6 | clear; 7 | clc; 8 | 9 | if IsOctave 10 | more off; % for a better display experience 11 | end 12 | 13 | cfg = setDefaultsPTB; 14 | 15 | %% Set parameters 16 | 17 | % Decide which device you want to collect responses from 18 | deviceNumber = []; % default device (PTB will find it for you) 19 | 20 | cfg.keyboard.responseKey = {'a', 'b'}; 21 | cfg.keyboard.responseKey{end + 1} = cfg.keyboard.escapeKey; 22 | 23 | %% Final checks 24 | 25 | getOnlyPress = 1; 26 | 27 | % Make sure keyboard mapping is the same on all supported operating systems 28 | KbName('UnifyKeyNames'); 29 | 30 | fprintf('\nDuring the next 5 seconds we will collect responses on the following keys: \n\n'); 31 | if isempty(cfg.keyboard.responseKey) 32 | fprintf('\nALL KEYS\n\n'); 33 | else 34 | for iKey = 1:numel(cfg.keyboard.responseKey) 35 | fprintf('\n%s', cfg.keyboard.responseKey{iKey}); 36 | end 37 | fprintf('\n\n'); 38 | end 39 | 40 | %% Run manual test 41 | try 42 | 43 | getResponse('init', deviceNumber, cfg); 44 | 45 | getResponse('start', deviceNumber); 46 | 47 | WaitSecs(5); 48 | 49 | responseEvents = getResponse('check', deviceNumber, cfg, getOnlyPress); 50 | 51 | getResponse('release', deviceNumber); 52 | 53 | % if some keys were pressed and that we are supposed to listen to only some 54 | % keys, we make sure that only those keys were listened to 55 | 56 | if ~isempty(cfg.keyboard.responseKey) && isfield(responseEvents, 'keyName') 57 | 58 | for iEvent = 1:size(responseEvents, 1) 59 | fprintf(' %s was pressed\n ', ... 60 | responseEvents(iEvent).keyName); 61 | 62 | if ~any(strcmp({responseEvents(iEvent).keyName}, cfg.keyboard.responseKey)) 63 | errorRestrictedKeysGetReponse(); 64 | end 65 | end 66 | 67 | end 68 | 69 | catch ME 70 | 71 | switch ME.identifier 72 | case 'getResponse:restrictedKey' 73 | rethrow(ME); 74 | 75 | otherwise 76 | getResponse('release', deviceNumber); 77 | 78 | rethrow(ME); 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /manualTests/test_radialMotion.m: -------------------------------------------------------------------------------- 1 | % 2 | % (C) Copyright 2020 CPP_PTB developers 3 | 4 | % ensure that dot density contrast is not too low when we kill dots often enough 5 | 6 | % There is an actual official unit test in the tests folder so this here is more 7 | % to have the visualization turned on. 8 | 9 | nbEvents = 100; 10 | doPlot = true; 11 | 12 | thisEvent.direction = 0; % degrees 13 | thisEvent.speed = 1; % pix per frame 14 | 15 | cfg.design.motionType = 'radial'; 16 | 17 | cfg.dot.coherence = 1; % proportion 18 | cfg.dot.lifeTime = .1; % in seconds 19 | cfg.dot.matrixWidth = 250; % in pixels 20 | cfg.dot.proportionKilledPerFrame = 0; 21 | 22 | cfg.timing.eventDuration = 1.8; % in seconds 23 | 24 | relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); 25 | -------------------------------------------------------------------------------- /miss_hit.cfg: -------------------------------------------------------------------------------- 1 | # style guide (https://florianschanda.github.io/miss_hit/style_checker.html) 2 | project_root 3 | 4 | line_length: 100 5 | regex_function_name: "[a-z]+(_*[a-zA-Z0-9]+[a-z]*)*" 6 | regex_script_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" 7 | regex_parameter_name: "[A-Z]{2,}|[a-z]+([A-Z]+[a-z]*)*" 8 | copyright_entity: "Sam Schwarzkopf" 9 | copyright_entity: "Agah Karakuzu" 10 | copyright_entity: "CPP_PTB developers" 11 | 12 | # metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html) 13 | metric "cnest": limit 4 14 | metric "file_length": limit 500 15 | metric "cyc": limit 15 16 | metric "parameters": limit 5 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | sphinxcontrib-matlabdomain 3 | sphinxcontrib-napoleon 4 | sphinx_rtd_theme 5 | miss_hit==0.9.35 6 | pre-commit 7 | -------------------------------------------------------------------------------- /scripts/ptbSoundDeviceTest.m: -------------------------------------------------------------------------------- 1 | % 2 | % Small script to loop through all the devices 3 | % and try to play some white noise on it to see which one actually works. 4 | % 5 | % (C) Copyright 2022 CPP_PTB developers 6 | 7 | sca; 8 | clear; 9 | clc; 10 | 11 | % init CPP PTB 12 | rootDir = cd(fullfile(fileparts(mfilename('fullpath')), '..')); 13 | cpp_ptb; 14 | 15 | % PsychPortAudio('Close'); 16 | 17 | cfg.audio.playbackMode = 1; 18 | cfg.audio.channels = 2; 19 | cfg.audio.requestedLatency = 3; 20 | 21 | InitializePsychSound(1); 22 | 23 | % Get a list of all the audio devices connected 24 | audioDev = PsychPortAudio('GetDevices'); 25 | 26 | % Here we can decide to test just a few (value must be between 1 and numel(audioDev)) 27 | tmp = 10; 28 | 29 | % Here we loop through all the devices and try to play some white noise on it 30 | % to see which one actually works. 31 | for idx = 10 32 | 33 | fprintf(1, '\n%i: %s\n', idx, audioDev(idx).DeviceName); 34 | 35 | cfg.audio.devIdx = audioDev(idx).DeviceIndex; 36 | 37 | % get device's sampling rate 38 | cfg.audio.fs = audioDev(idx).DefaultSampleRate; 39 | 40 | try 41 | cfg.audio.pahandle = PsychPortAudio('Open', ... 42 | cfg.audio.devIdx, ... 43 | cfg.audio.playbackMode, ... 44 | cfg.audio.requestedLatency, ... 45 | cfg.audio.fs, ... 46 | cfg.audio.channels); 47 | 48 | clear sound; 49 | sound = rand(cfg.audio.channels, cfg.audio.fs); 50 | 51 | PsychPortAudio('FillBuffer', cfg.audio.pahandle, sound); 52 | PsychPortAudio('Start', cfg.audio.pahandle); 53 | 54 | WaitSecs(1.5); 55 | 56 | PsychPortAudio('Close'); 57 | 58 | WaitSecs(1); 59 | catch 60 | end 61 | 62 | pressSpaceForMe; 63 | 64 | end 65 | -------------------------------------------------------------------------------- /src/aperture/apertureTexture.m: -------------------------------------------------------------------------------- 1 | function [cfg, thisEvent] = apertureTexture(action, cfg, thisEvent) 2 | % 3 | % USAGE:: 4 | % 5 | % [cfg, thisEvent] = apertureTexture(action, cfg, thisEvent) 6 | % 7 | % 8 | 9 | % (C) Copyright 2010-2020 Sam Schwarzkopf 10 | % (C) Copyright 2020 CPP_PTB developers 11 | 12 | switch action 13 | 14 | case 'init' 15 | 16 | cfg = apertureInit(cfg); 17 | 18 | cfg.aperture.texture = Screen('MakeTexture', cfg.screen.win, ... 19 | cfg.color.background(1) * ... 20 | ones(cfg.screen.winRect([3 3]))); 21 | 22 | case 'make' 23 | 24 | cfg = apertureTextureMake(cfg, thisEvent); 25 | 26 | case 'draw' 27 | 28 | scalingFactor = 1; 29 | if isfield(cfg, 'scalingFactor') && ~isempty(cfg.scalingFactor) 30 | scalingFactor = cfg.scalingFactor; 31 | end 32 | 33 | rotationAngle = []; 34 | if strcmp(cfg.aperture.type, 'bar') 35 | rotationAngle = thisEvent.condition - 90; 36 | end 37 | 38 | Screen('DrawTexture', cfg.screen.win, cfg.aperture.texture, ... 39 | cfg.screen.winRect, ... 40 | CenterRect(cfg.screen.winRect * scalingFactor, cfg.screen.winRect), ... 41 | rotationAngle); 42 | 43 | end 44 | 45 | end 46 | 47 | function cfg = apertureInit(cfg) 48 | 49 | switch cfg.aperture.type 50 | 51 | case 'circle' 52 | % we take the screen height as maximum aperture width if not 53 | % specified. 54 | if ~isfield(cfg.aperture, 'width') || isempty(cfg.aperture.width) 55 | cfg.aperture.width = cfg.screen.winRect(4) / cfg.screen.ppd; 56 | end 57 | cfg.aperture = degToPix('width', cfg.aperture, cfg); 58 | 59 | case 'ring' 60 | 61 | % Set parameters for rings 62 | if strcmp(cfg.aperture.type, 'ring') 63 | % scale of outer ring (exceeding screen until 64 | % inner ring reaches window boarder) 65 | cfg.ring.maxEcc = ... 66 | cfg.screen.FOV / 2 + ... 67 | cfg.aperture.width + ... 68 | log(cfg.screen.FOV / 2 + 1); 69 | % ring.CsFuncFact is used to expand with log increasing speed so 70 | % that ring is at ring.maxEcc at end of cycle 71 | cfg.ring.csFuncFact = ... 72 | 1 / ... 73 | ((cfg.ring.maxEcc + exp(1)) * ... 74 | log(cfg.ring.maxEcc + exp(1)) - ... 75 | (cfg.ring.maxEcc + exp(1))); 76 | end 77 | 78 | case 'bar' 79 | 80 | % Set parameters drifting bars 81 | cfg.aperture.barWidthPix = cfg.stimRect(3) / cfg.volsPerCycle; 82 | 83 | barPosPix = ... 84 | [0:cfg.aperture.barWidthPix:cfg.stimRect(3) - cfg.aperture.barWidthPix] + ... 85 | (cfg.screen.winRect(3) / 2 - cfg.stimRect(3) / 2) + ... 86 | cfg.aperture.barWidthPix / 2; %#ok 87 | 88 | cfg.aperture.barPosPix = barPosPix; 89 | 90 | % Width of bar in degrees of VA (needed for saving) 91 | cfg.aperture.width = cfg.aperture.barWidthPix / cfg.screen.ppd; 92 | cfg.aperture.barPos = ... 93 | (cfg.aperture.barPosPix - cfg.screen.center(1)) / ... 94 | cfg.screen.ppd; 95 | 96 | end 97 | 98 | end 99 | 100 | function cfg = apertureTextureMake(cfg, thisEvent) 101 | 102 | TRANSPARENT = [0, 0, 0, 0]; 103 | 104 | xCenter = cfg.screen.center(1); 105 | yCenter = cfg.screen.center(2); 106 | 107 | switch cfg.aperture.type 108 | 109 | case 'none' 110 | 111 | Screen('Fillrect', cfg.aperture.texture, TRANSPARENT); 112 | 113 | case 'circle' 114 | 115 | Screen('Fillrect', cfg.aperture.texture, cfg.color.background); 116 | 117 | diameter = cfg.aperture.widthPix; 118 | 119 | if isfield(cfg.aperture, 'xPosPix') 120 | xCenter = xCenter + cfg.aperture.xPosPix; 121 | end 122 | if isfield(cfg.aperture, 'yPosPix') 123 | yCenter = yCenter + cfg.aperture.yPosPix; 124 | end 125 | 126 | Screen('FillOval', cfg.aperture.texture, TRANSPARENT, ... 127 | CenterRectOnPoint([0, 0, repmat(diameter, 1, 2)], ... 128 | xCenter, yCenter)); 129 | 130 | case 'ring' 131 | 132 | % expansion speed is log over eccentricity 133 | [cfg] = eccenLogSpeed(cfg, thisEvent.time); 134 | 135 | Screen('Fillrect', cfg.aperture.texture, cfg.color.background); 136 | 137 | Screen('FillOval', cfg.aperture.texture, TRANSPARENT, ... 138 | CenterRectOnPoint( ... 139 | [0, 0, repmat(cfg.ring.outerRimPix, 1, 2)], ... 140 | xCenter, yCenter)); 141 | 142 | Screen('FillOval', cfg.aperture.texture, [cfg.color.background 255], ... 143 | CenterRectOnPoint( ... 144 | [0, 0, repmat(cfg.ring.innerRimPix, 1, 2)], ... 145 | xCenter, yCenter)); 146 | 147 | case 'wedge' 148 | 149 | cycleDuration = cfg.mri.repetitionTime * cfg.volsPerCycle; 150 | 151 | % Update angle for rotation of background and for aperture for wedge 152 | switch cfg.direction 153 | 154 | case '+' 155 | thisEvent.angle = 90 - ... 156 | cfg.aperture.width / 2 + ... 157 | (thisEvent.time / cycleDuration) * 360; 158 | case '-' 159 | thisEvent.angle = 90 - ... 160 | cfg.aperture.width / 2 - ... 161 | (thisEvent.time / cycleDuration) * 360; 162 | 163 | end 164 | 165 | Screen('Fillrect', cfg.aperture.texture, cfg.color.background); 166 | 167 | Screen('FillArc', cfg.aperture.texture, TRANSPARENT, ... 168 | CenterRect( ... 169 | cfg.destinationRect, ... 170 | cfg.screen.winRect), ... 171 | thisEvent.angle, ... % start angle 172 | cfg.aperture.width); % arc angle 173 | 174 | case 'bar' 175 | 176 | % aperture is the color of the background 177 | Screen('FillRect', cfg.aperture.texture, cfg.color.background); 178 | 179 | % We let the stimulus through 180 | Screen('FillOval', cfg.aperture.texture, TRANSPARENT, ... 181 | CenterRect( ... 182 | [0, 0, repmat(cfg.screen.winRect(4), 1, 2)], ... 183 | cfg.screen.winRect)); 184 | 185 | % Then we add the position of the bar aperture 186 | 187 | % which one is the right and which one is the left?? 188 | 189 | Screen('FillRect', cfg.aperture.texture, cfg.color.background, ... 190 | [0, ... 191 | 0, ... 192 | thisEvent.barPosPix - cfg.aperture.barWidthPix / 2, ... 193 | cfg.screen.winRect(4)]); 194 | 195 | Screen('FillRect', cfg.aperture.texture, cfg.color.background, ... 196 | [thisEvent.barPosPix + cfg.aperture.barWidthPix / 2, ... 197 | 0, ... 198 | cfg.screen.winRect(3), ... 199 | cfg.screen.winRect(4)]); 200 | 201 | otherwise 202 | 203 | error('unknown aperture type: %s.', cfg.aperture.type); 204 | 205 | end 206 | 207 | end 208 | -------------------------------------------------------------------------------- /src/aperture/eccenLogSpeed.m: -------------------------------------------------------------------------------- 1 | function [cfg] = eccenLogSpeed(cfg, time) 2 | % 3 | % Vary CurrScale so that expansion speed is log over eccentricity 4 | % cf. Tootell 1997; Swisher 2007; Warnking 2002 etc 5 | % 6 | % 7 | 8 | % (C) Copyright 2020 CPP_PTB developers 9 | 10 | TR = cfg.mri.repetitionTime; 11 | cycleDuration = TR * cfg.volsPerCycle; 12 | 13 | % CurrScale only influences ring 14 | if strcmp(cfg.aperture.type, 'ring') 15 | 16 | csFuncFact = cfg.ring.csFuncFact; 17 | ringWidthVA = cfg.ring.ringWidthVA; 18 | maxEcc = cfg.ring.maxEcc; 19 | 20 | switch cfg.direction 21 | case '+' 22 | % current visual angle linear in time 23 | outerRimVA = 0 + mod(time, cycleDuration) / cycleDuration * maxEcc; 24 | % ensure some foveal stimulation at beginning (which is hidden by 25 | % fixation cross otherwise) 26 | if outerRimVA < cfg.fixation.size 27 | outerRimVA = cfg.fixation.size + .1; 28 | end 29 | case '-' 30 | outerRimVA = maxEcc - mod(time, cycleDuration) / cycleDuration * maxEcc; 31 | if outerRimVA > maxEcc 32 | outerRimVA = maxEcc; 33 | end 34 | end 35 | 36 | % near-exp visual angle 37 | newOuterRimVA = ((outerRimVA + exp(1)) * log(outerRimVA + exp(1)) - ... 38 | (outerRimVA + exp(1))) * maxEcc * csFuncFact; 39 | outerRimPix = newOuterRimVA * cfg.screen.ppd; % in pixel 40 | 41 | % width of aperture changes logarithmically with eccentricity of inner ring 42 | oldScaleInnerVA = outerRimVA - ringWidthVA; 43 | if oldScaleInnerVA < 0 44 | oldScaleInnerVA = 0; 45 | end 46 | 47 | % growing with inner ring ecc 48 | ringWidthVA = cfg.aperture.width + log(oldScaleInnerVA + 1); 49 | innerRimVA = newOuterRimVA - ringWidthVA; 50 | 51 | if innerRimVA < 0 52 | innerRimVA = 0; 53 | end 54 | 55 | % in pixel 56 | innerRimPix = innerRimVA * cfg.screen.ppd; 57 | 58 | % update cfg that we are about to return 59 | cfg.ring.outerRimPix = outerRimPix; 60 | cfg.ring.innerRimPix = innerRimPix; 61 | cfg.ring.ring_outer_rim = newOuterRimVA; 62 | cfg.ring.ring_inner_rim = innerRimVA; 63 | 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /src/aperture/getApertureName.m: -------------------------------------------------------------------------------- 1 | function apertureName = getApertureName(cfg, apertures, iApert) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | switch cfg.aperture 6 | case 'Bar' 7 | apertureName = sprintf('bar_angle-%i_position-%02.2f.tif', ... 8 | apertures.barAngle(iApert), ... 9 | apertures.barPostion(iApert)); 10 | case 'Wedge' 11 | apertureName = sprintf('wedge_nb-%i.tif', iApert); 12 | case 'Ring' 13 | apertureName = sprintf('ring_nb-%i.tif', iApert); 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /src/aperture/saveAperture.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2010-2020 Sam Schwarzkopf 2 | % (C) Copyright 2020 CPP_PTB developers 3 | 4 | if SaveAps 5 | if SaveAps == 1 6 | ApFrm = zeros(100, 100, Parameters.Volumes_per_Trial * length(Parameters.Conditions)); 7 | elseif SaveAps == 2 8 | ApFrm = zeros(640, 480, 3); 9 | sf = 0; 10 | end 11 | SavWin = Screen('MakeTexture', Win, 127 * ones(StimRect([3 3]))); 12 | end 13 | 14 | % If saving movie 15 | if SaveAps == 1 && PrevVolume ~= CurrVolume 16 | PrevVolume = CurrVolume; 17 | CurApImg = Screen('GetImage', Win); 18 | CurApImg = rgb2gray(CurApImg); 19 | CurApImg = imresize(CurApImg, [Rect(4) Rect(3)]); 20 | Fxy = round(CenterRect(FrameRect, Rect) + ... 21 | [Parameters.Image_Position Parameters.Image_Position]); 22 | CurApImg = CurApImg(Fxy(2):Fxy(4), Fxy(1):Fxy(3)); 23 | CurApImg = double(abs(double(CurApImg) - 127) > 1); 24 | CurApImg = imresize(CurApImg, [100 100]); 25 | ApFrm(:, :, Parameters.Volumes_per_Trial * (Trial - 1) + CurrVolume) = CurApImg; 26 | elseif SaveAps == 2 27 | CurApImg = Screen('GetImage', Win); 28 | CurApImg = imresize(CurApImg, [640 480]); 29 | sf = sf + 1; 30 | ApFrm(:, :, :, sf) = CurApImg; 31 | end 32 | 33 | if SaveAps == 2 34 | ApFrm = uint8(ApFrm); 35 | save('Stimulus_movie', 'ApFrm'); 36 | end 37 | -------------------------------------------------------------------------------- /src/aperture/saveApertures.m: -------------------------------------------------------------------------------- 1 | function saveApertures(saveAps, cfg, apertures) 2 | % 3 | 4 | % (C) Copyright 2010-2020 Sam Schwarzkopf 5 | % (C) Copyright 2020 CPP_PTB developers 6 | if saveAps 7 | 8 | matFile = fullfile( ... 9 | cfg.outputDir, ... 10 | strrep(cfg.fileName.events, '.tsv', '_AperturesPRF.mat')); 11 | if IsOctave 12 | save(matFile, '-mat7-binary'); 13 | else 14 | save(matFile, '-v7.3'); 15 | end 16 | 17 | for iApert = 1:size(apertures.Frames, 3) 18 | 19 | tmp = apertures.Frames(:, :, iApert); 20 | 21 | % We skip the all nan frames and print the others 22 | if ~all(isnan(tmp(:))) 23 | 24 | close all; 25 | 26 | imagesc(apertures.Frames(:, :, iApert)); 27 | 28 | colormap gray; 29 | 30 | box off; 31 | axis off; 32 | axis square; 33 | 34 | apertureName = getApertureName(cfg, apertures, iApert); 35 | 36 | print(gcf, ... 37 | fullfile(cfg.aperture.outputDir, [ApertureName '.tif']), ... 38 | '-dtiff'); 39 | end 40 | 41 | end 42 | 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /src/aperture/smoothOval.m: -------------------------------------------------------------------------------- 1 | function smoothOval(win, color, rect, fringe) 2 | % Draws a filled oval (using the PTB parameters) with a transparent fringe. 3 | % 4 | % USAGE:: 5 | % 6 | % SmoothOval(WindowPtr, Color, Rect, Fringe) 7 | % 8 | % 9 | 10 | % (C) Copyright 2010-2020 Sam Schwarzkopf 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | alphas = linspace(0, 255, fringe); 14 | 15 | for f = 0:fringe - 1 16 | Screen('FillOval', win, ... 17 | [color alphas(f + 1)], ... 18 | [rect(1) + f rect(2) + f rect(3) - f rect(4) - f]); 19 | end 20 | -------------------------------------------------------------------------------- /src/aperture/smoothRect.m: -------------------------------------------------------------------------------- 1 | function smoothRect(win, color, rect, fringe) 2 | % 3 | % Draws a filled rect (using the PTB parameters) with a transparent fringe. 4 | % 5 | % USAGE:: 6 | % 7 | % SmoothRect(WindowPtr, Color, Rect, Fringe) 8 | % 9 | 10 | % (C) Copyright 2010-2020 Sam Schwarzkopf 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | alphas = linspace(0, 255, fringe); 14 | 15 | for f = 0:fringe - 1 16 | Screen('FillRect', win, ... 17 | [color alphas(f + 1)], ... 18 | [rect(1) + f rect(2) + f rect(3) - f rect(4) - f]); 19 | end 20 | -------------------------------------------------------------------------------- /src/defaults/checkCppPtbCfg.m: -------------------------------------------------------------------------------- 1 | function cfg = checkCppPtbCfg(cfg) 2 | % Set some defaults values if none have been set before. 3 | % 4 | % USAGE:: 5 | % 6 | % cfg = checkDefaultsPTB(cfg) 7 | % 8 | % :param cfg: 9 | % :type cfg: structure 10 | % 11 | % :returns: - :cfg: (structure) 12 | % 13 | 14 | % (C) Copyright 2020 CPP_PTB developers 15 | 16 | if nargin < 1 17 | cfg = struct; 18 | end 19 | 20 | fieldsToSet = cppPtbDefaults('all'); 21 | 22 | if isfield(cfg, 'audio') && cfg.audio.do 23 | fieldsToSet.audio = cppPtbDefaults('audio'); 24 | end 25 | 26 | if isfield(cfg, 'eyeTracker') && cfg.eyeTracker.do 27 | fieldsToSet.eyeTracker = cppPtbDefaults('eyeTracker'); 28 | end 29 | 30 | if isfield(cfg, 'testingDevice') && strcmpi(cfg.testingDevice, 'mri') 31 | fieldsToSet.bids.mri.RepetitionTime = []; 32 | fieldsToSet.pacedByTriggers.do = false; 33 | end 34 | 35 | cfg = setDefaultFields(cfg, fieldsToSet); 36 | 37 | %% checks 38 | if cfg.debug.do 39 | cfg.eyeTracker.do = false; 40 | cfg.skipSyncTests = 1; 41 | cfg.hideCursor = false; 42 | end 43 | 44 | if cfg.skipSyncTests == false 45 | cfg.skipSyncTests = 0; 46 | elseif cfg.skipSyncTests == true 47 | cfg.skipSyncTests = 1; 48 | end 49 | 50 | if ~ismember(cfg.fixation.type, {'cross', 'dot', 'bestFixation'}) 51 | error('cfg.fixation.type must be one of ''cross'', ''dot'', ''bestFixation'''); 52 | end 53 | 54 | % sort fields alphabetically 55 | cfg = orderfields(cfg); 56 | 57 | end 58 | -------------------------------------------------------------------------------- /src/defaults/cppPtbDefaults.m: -------------------------------------------------------------------------------- 1 | function value = cppPtbDefaults(type) 2 | % 3 | % USAGE:: 4 | % 5 | % value = cppPtbDefaults(type) 6 | % 7 | 8 | % (C) Copyright 2022 CPP_PTB developers 9 | 10 | value = []; 11 | 12 | switch lower(type) 13 | 14 | case 'all' 15 | value = struct('testingDevice', 'pc', ... 16 | 'color', struct('background', [0 0 0]), ... 17 | 'skipSyncTests', 0, ... 18 | 'verbose', 1, ... 19 | 'hideCursor', true); 20 | 21 | value.aperture.type = 'none'; 22 | 23 | value.debug = cppPtbDefaults('debug'); 24 | value.text = cppPtbDefaults('text'); 25 | value.screen = cppPtbDefaults('screen'); 26 | value.fixation = cppPtbDefaults('fixation'); 27 | value.keyboard = cppPtbDefaults('keyboard'); 28 | 29 | case 'debug' 30 | value.do = true; 31 | value.transpWin = true; 32 | value.smallWin = true; 33 | 34 | case 'keyboard' 35 | value.keyboard = []; 36 | value.responseBox = []; 37 | value.responseKey = {}; 38 | value.escapeKey = 'ESCAPE'; 39 | 40 | case 'text' 41 | value.font = 'Courier New'; 42 | value.size = 18; 43 | value.style = 1; 44 | 45 | case 'fixation' 46 | value.type = 'cross'; 47 | value.xDisplacement = 0; 48 | value.yDisplacement = 0; 49 | value.color = [255 255 255]; 50 | value.width = 1; 51 | value.lineWidthPix = 5; 52 | 53 | case 'screen' 54 | value.monitorWidth = 42; 55 | value.monitorDistance = 134; 56 | value.resolution = {[], [], []}; 57 | 58 | case 'audio' 59 | value.devIdx = []; 60 | value.playbackMode = 1; 61 | 62 | value.fs = 44100; 63 | value.channels = 2; 64 | value.initVolume = 1; 65 | value.requestedLatency = 3; 66 | 67 | value.repeat = 1; 68 | 69 | % Start immediately (0 = immediately) 70 | value.startCue = 0; 71 | 72 | % Should we wait for the device to really start? 73 | value.waitForDevice = 1; 74 | 75 | value.pushSize = value.fs * 0.010; % ! push N ms only 76 | 77 | value.requestOffsetTime = 1; % offset 1 sec 78 | value.reqsSampleOffset = value.requestOffsetTime * value.fs; 79 | 80 | case 'eyetracker' 81 | value.defaultCalibration = true; 82 | value.backgroundColor = [192 192 192]; 83 | value.fontColor = [0 0 0]; 84 | value.calibrationTargetColor = [0 0 0]; 85 | value.calibrationTargetSize = 1; 86 | value.calibrationTargetWidth = 0.5; 87 | value.displayCalResults = 1; 88 | 89 | case 'color' 90 | 91 | value.white = [255 255 255]; 92 | value.black = [0 0 0]; 93 | value.grey = mean([value.black; value.white]); 94 | value.red = [255 0 0]; 95 | value.blue = [0 255 0]; 96 | value.green = [0 0 255]; 97 | 98 | case 'dot' 99 | % Speed in visual angles / second 100 | value.speed = 15; 101 | % Coherence Level (0-1) 102 | value.coherence = 1; 103 | % Number of dots per visual angle square. 104 | value.density = 1; 105 | % Dot life time in seconds 106 | value.lifeTime = 0.4; 107 | % proportion of dots killed per frame 108 | value.proportionKilledPerFrame = 0; 109 | % Dot Size (dot width) in visual angles. 110 | value.size = 0.2; 111 | 112 | otherwise 113 | error('unknown action'); 114 | 115 | end 116 | 117 | end 118 | -------------------------------------------------------------------------------- /src/defaults/setDefaultFields.m: -------------------------------------------------------------------------------- 1 | function structure = setDefaultFields(structure, fieldsToSet) 2 | % 3 | % Recursively loop through the fields of a structure and sets a value if they don't exist. 4 | % 5 | % USAGE:: 6 | % 7 | % structure = setDefaultFields(structure, fieldsToSet) 8 | % 9 | % :param structure: 10 | % :type structure: structure 11 | % 12 | % :param fieldsToSet: 13 | % :type fieldsToSet: structure 14 | % 15 | % :returns: - :structure: (structure) 16 | % 17 | 18 | % (C) Copyright 2020 CPP_PTB developers 19 | 20 | fieldsToSet = orderfields(fieldsToSet); 21 | 22 | names = fieldnames(fieldsToSet); 23 | 24 | for i = 1:numel(names) 25 | 26 | thisField = fieldsToSet.(names{i}); 27 | 28 | if isfield(structure, names{i}) && isstruct(structure.(names{i})) 29 | 30 | structure.(names{i}) = ... 31 | setDefaultFields( ... 32 | structure.(names{i}), ... 33 | fieldsToSet.(names{i}) ... 34 | ); 35 | 36 | else 37 | 38 | structure = setFieldToIfNotPresent( ... 39 | structure, ... 40 | names{i}, ... 41 | thisField); 42 | end 43 | 44 | end 45 | 46 | structure = orderfields(structure); 47 | 48 | end 49 | 50 | function structure = setFieldToIfNotPresent(structure, fieldName, value) 51 | if ~isfield(structure, fieldName) 52 | structure.(fieldName) = value; 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/dot/computeCartCoord.m: -------------------------------------------------------------------------------- 1 | function cartesianCoordinates = computeCartCoord(positions, dotMatrixWidth) 2 | % 3 | % USAGE:: 4 | % 5 | % cartesianCoordinates = computeCartCoord(positions, dotMatrixWidth) 6 | % 7 | 8 | % (C) Copyright 2020 CPP_PTB developers 9 | 10 | cartesianCoordinates = ... 11 | [positions(:, 1) - dotMatrixWidth / 2, ... % x coordinate 12 | positions(:, 2) - dotMatrixWidth / 2]; % y coordinate 13 | 14 | end 15 | -------------------------------------------------------------------------------- /src/dot/computeRadialMotionDirection.m: -------------------------------------------------------------------------------- 1 | function angleMotion = computeRadialMotionDirection(positions, dotMatrixWidth, dots) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | positions = computeCartCoord(positions, dotMatrixWidth); 7 | 8 | [angleMotion, ~] = cart2pol(positions(:, 1), positions(:, 2)); 9 | angleMotion = angleMotion / pi * 180; 10 | 11 | if dots.direction == -666 12 | angleMotion = angleMotion - 180; 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /src/dot/decomposeMotion.m: -------------------------------------------------------------------------------- 1 | function [horVector, vertVector] = decomposeMotion(angleMotion) 2 | % 3 | % Decompose angle of start motion into horizontal and vertical vector. 4 | % 5 | % USAGE:: 6 | % 7 | % [horVector, vertVector] = decomposeMotion(angleMotion) 8 | % 9 | % :param angleMotion: in degrees 10 | % :type angleMotion: scalar 11 | % 12 | % :returns: - :horVector: horizontal component of motion 13 | % - :vertVector: vertical component of motion 14 | % 15 | % 16 | 17 | % (C) Copyright 2020 CPP_PTB developers 18 | 19 | horVector = cos(pi * angleMotion / 180); 20 | vertVector = -sin(pi * angleMotion / 180); 21 | end 22 | -------------------------------------------------------------------------------- /src/dot/dotMotionSimulation.m: -------------------------------------------------------------------------------- 1 | function relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot) 2 | % 3 | % To simulate where the dots are more dense on the screen 4 | % relativeDensityContrast : hard to get it below 0.10. 5 | % 6 | % USAGE:: 7 | % 8 | % relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot) 9 | % 10 | 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | close all; 14 | 15 | if nargin < 4 16 | doPlot = 1; 17 | end 18 | 19 | if nargin < 3 20 | nbEvents = 100; 21 | end 22 | 23 | if nargin < 2 24 | thisEvent.direction = 0; % degrees 25 | thisEvent.speed = 1; % pix per frame 26 | end 27 | 28 | if nargin < 1 29 | 30 | cfg.design.motionType = 'translation'; 31 | 32 | cfg.dot.coherence = 1; % proportion 33 | 34 | cfg.dot.lifeTime = Inf; % in seconds 35 | 36 | cfg.dot.matrixWidth = 250; % in pixels 37 | 38 | cfg.dot.proportionKilledPerFrame = 0; 39 | 40 | cfg.timing.eventDuration = 1.8; % in seconds 41 | 42 | end 43 | 44 | % interframe interval 45 | cfg.screen.ifi = 0.016; % in seconds 46 | 47 | % size of the fixation is 1% of screen width 48 | cfg.fixation.widthPix = ceil(cfg.dot.matrixWidth * 1 / 100); 49 | 50 | % dot size 51 | cfg.dot.sizePix = 1; 52 | 53 | if ~isfield(cfg.dot, 'number') 54 | % We fill 25% of the screen with dots 55 | cfg.dot.number = round(cfg.dot.matrixWidth^2 * 25 / 100); 56 | end 57 | 58 | fprintf(1, '\n\nDot motion simulation:'); 59 | 60 | nbFrames = ceil(cfg.timing.eventDuration / cfg.screen.ifi); 61 | 62 | % to keep track of where the dots have been 63 | dotDensity = zeros(cfg.dot.matrixWidth); 64 | 65 | for iEvent = 1:nbEvents 66 | 67 | [dots] = initDots(cfg, thisEvent); 68 | dotDensity = updateDotDensity(dotDensity, dots); 69 | 70 | for iFrame = 1:nbFrames 71 | 72 | [dots] = updateDots(dots, cfg); 73 | 74 | dotDensity = updateDotDensity(dotDensity, dots); 75 | 76 | end 77 | 78 | end 79 | 80 | %% Post sim 81 | % trim the edges (to avoid super high/low values 82 | dotDensity = dotDensity(2:end - 1, 2:end - 1); 83 | 84 | % computes the maximum difference in dot density over the all screen 85 | % to be used for unit test 86 | relativeDensityContrast = (max(dotDensity(:)) - min(dotDensity(:))) / max(dotDensity(:)); 87 | 88 | if doPlot 89 | imagesc(dotDensity); 90 | axis square; 91 | title('dot density'); 92 | end 93 | 94 | fprintf(1, '\n'); 95 | 96 | end 97 | 98 | function dotDensity = updateDotDensity(dotDensity, dots) 99 | 100 | x = round(dots.positions(:, 1)); 101 | x = avoidEdgeValues(x, size(dotDensity, 2)); 102 | 103 | y = round(dots.positions(:, 2)); 104 | y = avoidEdgeValues(y, size(dotDensity, 1)); 105 | 106 | ind = sub2ind(size(dotDensity), y, x); 107 | 108 | dotDensity(ind) = dotDensity(ind) + 1; 109 | 110 | end 111 | 112 | function x = avoidEdgeValues(x, dim) 113 | x(x < 1) = 1; 114 | x(x > dim) = dim; 115 | end 116 | -------------------------------------------------------------------------------- /src/dot/dotTexture.m: -------------------------------------------------------------------------------- 1 | function cfg = dotTexture(action, cfg, thisEvent) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | switch action 6 | 7 | case 'init' 8 | cfg.dot.texture = Screen('MakeTexture', cfg.screen.win, ... 9 | cfg.color.background(1) * ... 10 | ones(cfg.screen.winRect([4 3]))); 11 | 12 | case 'make' 13 | 14 | dotType = 2; 15 | 16 | xCenter = cfg.screen.center(1) + thisEvent.dotCenterXPosPix; 17 | yCenter = cfg.screen.center(2); 18 | 19 | if isfield(thisEvent, 'dotCenterYPosPix') 20 | yCenter = cfg.screen.center(2) + thisEvent.dotCenterYPosPix; 21 | end 22 | 23 | Screen('FillRect', cfg.dot.texture, cfg.color.background); 24 | Screen('DrawDots', cfg.dot.texture, ... 25 | thisEvent.dot.positions, ... 26 | cfg.dot.sizePix, ... 27 | cfg.dot.color, ... 28 | [xCenter yCenter], ... 29 | dotType); 30 | 31 | case 'draw' 32 | 33 | Screen('DrawTexture', cfg.screen.win, cfg.dot.texture); 34 | 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /src/dot/generateNewDotPositions.m: -------------------------------------------------------------------------------- 1 | function newPositions = generateNewDotPositions(dotMatrixWidth, nbDots) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | newPositions = rand(nbDots, 2) * dotMatrixWidth; 7 | 8 | end 9 | -------------------------------------------------------------------------------- /src/dot/initDots.m: -------------------------------------------------------------------------------- 1 | function dots = initDots(cfg, thisEvent) 2 | % 3 | % Initialize dots for RDK 4 | % 5 | % USAGE:: 6 | % 7 | % dots = initDots(cfg, thisEvent) 8 | % 9 | % :param cfg: 10 | % :type cfg: structure 11 | % :param thisEvent: 12 | % :type thisEvent: structure 13 | % 14 | % :returns: - :dots: (structure) 15 | % 16 | % 17 | % - ``cfg.dot.lifeTime``: dot life time in seconds 18 | % - ``cfg.dot.number``: number of dots 19 | % - ``cfg.dot.coherence``: proportion of coherent dots. 20 | % 21 | % - ``thisEvent.direction``: direction (an angle in degrees) 22 | % - ``thisEvent.speed``: speed expressed in pixels per frame 23 | % 24 | % - ``dots.direction`` 25 | % - ``dots.isSignal``: signal dots (1) and those are noise dots (0) 26 | % - ``dots.directionAllDots`` 27 | % - ``dots.lifeTime``: in frames 28 | % - ``dots.speeds``: ``[ndots, 2]`` ; horizontal and vertical speed ; in pixels per frame 29 | % - ``dots.speedPixPerFrame`` 30 | % 31 | 32 | % (C) Copyright 2020 CPP_PTB developers 33 | 34 | dots.direction = thisEvent.direction(1); 35 | 36 | dots.isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence; 37 | 38 | dots.speedPixPerFrame = thisEvent.speedPix(1); 39 | lifeTime = cfg.dot.lifeTime; 40 | 41 | % for static dots 42 | if dots.direction == -1 43 | dots.isSignal = true(cfg.dot.number, 1); 44 | dots.speedPixPerFrame = 0; 45 | lifeTime = Inf; 46 | end 47 | 48 | % set position and directions of the dots 49 | [dots.positions, dots.speeds, dots.time] = ... 50 | seedDots(dots, cfg, dots.isSignal); 51 | 52 | %% Convert from seconds to frames 53 | lifeTime = ceil(lifeTime / cfg.screen.ifi); 54 | dots.lifeTime = lifeTime; 55 | 56 | end 57 | -------------------------------------------------------------------------------- /src/dot/postInitDots.m: -------------------------------------------------------------------------------- 1 | function cfg = postInitDots(cfg) 2 | % 3 | % cfg = postInitDots(cfg) 4 | % 5 | % generic function to finalize dots set up after psychtoolbox initialization 6 | % 7 | % 8 | 9 | % (C) Copyright 2022 CPP_PTB developers 10 | 11 | cfg.dot.matrixWidth = cfg.screen.winWidth; 12 | 13 | % Convert some values from degrees to pixels 14 | cfg.dot = degToPix('size', cfg.dot, cfg); 15 | cfg.dot = degToPix('speed', cfg.dot, cfg); 16 | 17 | % Get dot speeds in pixels per frame 18 | cfg.dot.speedPixPerFrame = cfg.dot.speedPix / cfg.screen.monitorRefresh; 19 | 20 | cfg.aperture = degToPix('xPos', cfg.aperture, cfg); 21 | 22 | % dots are displayed on a square with a length in visual angle equal to the 23 | % field of view 24 | cfg.dot.number = round(cfg.dot.density * ... 25 | (cfg.dot.matrixWidth / cfg.screen.ppd)^2); 26 | 27 | end 28 | -------------------------------------------------------------------------------- /src/dot/reseedDots.m: -------------------------------------------------------------------------------- 1 | function dots = reseedDots(dots, cfg) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | fixationWidthPix = 0; 7 | if isfield(cfg.fixation, 'widthPix') 8 | fixationWidthPix = cfg.fixation.widthPix; 9 | end 10 | 11 | cartesianCoordinates = computeCartCoord(dots.positions, cfg.dot.matrixWidth); 12 | [~, radius] = cart2pol(cartesianCoordinates(:, 1), cartesianCoordinates(:, 2)); 13 | 14 | % Create a logical vector to detect any dot that has: 15 | % - an xy position inferior to 0 16 | % - an xy position superior to winWidth 17 | % - has exceeded its lifetime 18 | % - is on the fixation cross 19 | % - has been been picked to be killed 20 | 21 | N = any([ ... 22 | dots.positions > cfg.dot.matrixWidth, ... 23 | dots.positions < 0, ... 24 | dots.time > dots.lifeTime, ... 25 | radius - cfg.dot.sizePix < fixationWidthPix / 2, ... 26 | rand(cfg.dot.number, 1) < cfg.dot.proportionKilledPerFrame ... 27 | ], 2); 28 | 29 | % If there is any such dot we relocate it to a new random position 30 | % and change its lifetime to 1 31 | if any(N) 32 | 33 | isSignal = dots.isSignal(N); 34 | 35 | [positions, speeds] = seedDots(dots, cfg, isSignal); 36 | 37 | dots.positions(N, :) = positions; 38 | dots.speeds(N, :) = speeds; 39 | dots.time(N, 1) = 0; 40 | 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /src/dot/seedDots.m: -------------------------------------------------------------------------------- 1 | function [positions, speeds, time] = seedDots(varargin) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | [dots, cfg, isSignal] = deal(varargin{:}); 7 | 8 | nbDots = numel(isSignal); 9 | 10 | %% Set an array of dot positions [xposition, yposition] 11 | % These can never be bigger than 1 or lower than 0 12 | % [0,0] is the top / left of the square 13 | % [1,1] is the bottom / right of the square 14 | positions = generateNewDotPositions(cfg.dot.matrixWidth, nbDots); 15 | 16 | %% Set vertical and horizontal speed for all dots 17 | directionAllDots = setDotDirection(positions, cfg, dots, isSignal); 18 | [horVector, vertVector] = decomposeMotion(directionAllDots); 19 | 20 | if strcmp(cfg.design.motionType, 'radial') 21 | vertVector = vertVector * -1; 22 | end 23 | % we were working with unit vectors. we now switch to pixels 24 | speeds = [horVector, vertVector] * dots.speedPixPerFrame; 25 | 26 | %% Create a vector to update to dotlife time of each dot 27 | % Not all set to 1 so the dots will die at different times 28 | % The maximum value is the duration of the event in frames 29 | time = floor(rand(nbDots, 1) * cfg.timing.eventDuration / cfg.screen.ifi); 30 | 31 | end 32 | -------------------------------------------------------------------------------- /src/dot/setDotDirection.m: -------------------------------------------------------------------------------- 1 | function directionAllDots = setDotDirection(positions, cfg, dots, isSignal) 2 | % 3 | % Creates some new direction for the dots. 4 | % 5 | % USAGE:: 6 | % 7 | % directionAllDots = setDotDirection(positions, cfg, dots, isSignal) 8 | % 9 | % :param positions: 10 | % :type positions: 11 | % :param cfg: 12 | % :type cfg: 13 | % :param dots: 14 | % :type dots: 15 | % :param isSignal: 16 | % :type isSignal: 17 | % 18 | % :returns: - :directionAllDots: 19 | % 20 | % Coherent dots have a true value in the vector ``isSignal`` 21 | % and get assigned a value equals to the one in ``dots.direction``. 22 | % 23 | % All the other dots get a random value between 0 and 360. 24 | % 25 | % All directions are in end expressed between 0 and 360. 26 | % 27 | 28 | % (C) Copyright 2020 CPP_PTB developers 29 | 30 | directionAllDots = dots.direction; 31 | 32 | % when we initialize the direction for all the dots 33 | % after that dots.direction will be a vector 34 | if numel(directionAllDots) == 1 35 | 36 | directionAllDots = ones(sum(isSignal), 1) * dots.direction; 37 | 38 | end 39 | 40 | %% Coherent dots 41 | if strcmp(cfg.design.motionType, 'radial') 42 | 43 | angleMotion = computeRadialMotionDirection(positions, cfg.dot.matrixWidth, dots); 44 | 45 | directionAllDots(isSignal == 1) = angleMotion; 46 | 47 | end 48 | 49 | %% Random direction for the non coherent dots 50 | directionAllDots(isSignal ~= 1) = rand(sum(isSignal ~= 1), 1) * 360; 51 | 52 | %% Express the direction in the 0 to 360 range 53 | directionAllDots = mod(directionAllDots, 360); 54 | 55 | % ensure we return a column vector 56 | if size(directionAllDots, 1) == 1 57 | directionAllDots = directionAllDots'; 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /src/dot/updateDots.m: -------------------------------------------------------------------------------- 1 | function [dots] = updateDots(dots, cfg) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | % Move the selected dots 7 | dots.positions = dots.positions + dots.speeds; 8 | 9 | if all(isnan(dots.positions(:))) 10 | errorStruct.message = 'All dots position have NaN values.'; 11 | errorStruct.identifier = 'updateDots:onlyNans'; 12 | 13 | error(errorStruct); 14 | end 15 | 16 | % Add one frame to the dot lifetime to each dot 17 | dots.time = dots.time + 1; 18 | 19 | dots = reseedDots(dots, cfg); 20 | 21 | end 22 | -------------------------------------------------------------------------------- /src/drawFieldOfVIew.m: -------------------------------------------------------------------------------- 1 | function fov = drawFieldOfVIew(cfg, centerOnScreen) 2 | % 3 | % Draws a red rectangle on the screen to materialize the field of view of 4 | % the participant. This can be used during debugging to help design the 5 | % stimuli if you know the FOV of the participant will be obstructed by 6 | % something 7 | % 8 | % USAGE:: 9 | % 10 | % fov = drawFieldOfVIew(cfg, centerOnScreen) 11 | % 12 | % :param cfg: 13 | % :type cfg: structure 14 | % :param centerOnScreen: 15 | % :type centerOnScreen: boolean 16 | % 17 | % :returns: - :fov: (array) PTB rectangle 18 | % 19 | 20 | % (C) Copyright 2020 CPP_PTB developers 21 | 22 | if nargin < 2 23 | centerOnScreen = true; 24 | end 25 | 26 | fov = []; 27 | 28 | if isfield(cfg.screen, 'effectiveFieldOfView') && ... 29 | numel(cfg.screen.effectiveFieldOfView) == 4 30 | 31 | RED = [255 0 0]; 32 | penWidth = 2; 33 | 34 | fov = cfg.screen.effectiveFieldOfView; 35 | 36 | if centerOnScreen 37 | fov = CenterRect( ... 38 | fov, ... 39 | cfg.screen.winRect); 40 | end 41 | 42 | Screen('FrameRect', ... 43 | cfg.screen.win, ... 44 | RED, ... 45 | fov, ... 46 | penWidth); 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /src/errors/errorAbort.m: -------------------------------------------------------------------------------- 1 | function errorAbort 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | errorStruct.message = 'Escape key press detected: aborting experiment.'; 7 | errorStruct.identifier = 'checkAbort:abortRequested'; 8 | 9 | error(errorStruct); 10 | end 11 | -------------------------------------------------------------------------------- /src/errors/errorAbortGetReponse.m: -------------------------------------------------------------------------------- 1 | function errorAbortGetReponse 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | errorStruct.message = 'Escape key press detected by getResponse: aborting experiment.'; 7 | errorStruct.identifier = 'getResponse:abortRequested'; 8 | 9 | error(errorStruct); 10 | end 11 | -------------------------------------------------------------------------------- /src/errors/errorDistanceToScreen.m: -------------------------------------------------------------------------------- 1 | function errorDistanceToScreen(cfg) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | errorStruct.message = sprintf([ 7 | 'Distance to monitor seems too small.\n' ... 8 | 'It should be in centimeters.\n' ... 9 | 'cfg.screen.monitorDistance = %f'], cfg.screen.monitorDistance); 10 | 11 | errorStruct.identifier = 'computeFOV:wrongDistanceToScreen'; 12 | 13 | error(errorStruct); 14 | end 15 | -------------------------------------------------------------------------------- /src/errors/errorRestrictedKeysGetReponse.m: -------------------------------------------------------------------------------- 1 | function errorRestrictedKeysGetReponse 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | errorStruct.message = 'getResponse reported a key press on a restricted key'; 7 | errorStruct.identifier = 'getResponse:restrictedKey'; 8 | 9 | error(errorStruct); 10 | end 11 | -------------------------------------------------------------------------------- /src/fixation/drawFixation.m: -------------------------------------------------------------------------------- 1 | function drawFixation(cfg) 2 | % 3 | % Define the parameters of the fixation cross in `cfg`. 4 | % 5 | % USAGE:: 6 | % 7 | % drawFixation(cfg) 8 | % 9 | % There are 3 types of fixations: 10 | % 11 | % - ``cross`` 12 | % - ``dot`` 13 | % - ``bestFixation`` 14 | % 15 | % See initFixation for more info. 16 | % 17 | 18 | % (C) Copyright 2020 CPP_PTB developers 19 | 20 | switch cfg.fixation.type 21 | case 'cross' 22 | 23 | smooth = 1; 24 | 25 | Screen('DrawLines', ... 26 | cfg.screen.win, ... 27 | cfg.fixation.allCoords, ... 28 | cfg.fixation.lineWidthPix, ... 29 | cfg.fixation.color, ... 30 | [cfg.screen.center(1) cfg.screen.center(2)], smooth); 31 | 32 | case 'dot' 33 | 34 | % Draw gap around fixation of 20% the size 35 | Screen('FillOval', ... 36 | cfg.screen.win, ... 37 | cfg.color.background, ... 38 | CenterRect( ... 39 | [0 0 repmat(1.2 * cfg.fixation.widthPix, 1, 2)], ... 40 | cfg.screen.winRect)); 41 | 42 | % Draw fixation 43 | Screen('FillOval', ... 44 | cfg.screen.win, ... 45 | cfg.color.foreground, ... 46 | CenterRect( ... 47 | [0 0 repmat(cfg.fixation.widthPix, 1, 2)], ... 48 | cfg.screen.winRect)); 49 | 50 | case 'bestFixation' 51 | 52 | % Draw gap around fixation of 20% the size 53 | Screen('FillOval', ... 54 | cfg.screen.win, ... 55 | cfg.color.background, ... 56 | CenterRect( ... 57 | [0 0 repmat(1.5 * cfg.fixation.widthPix, 1, 2)], ... 58 | cfg.screen.winRect)); 59 | 60 | Screen('FillOval', ... 61 | cfg.screen.win, ... 62 | cfg.color.black, ... 63 | cfg.fixation.outerOval, ... 64 | cfg.fixation.widthPix); 65 | 66 | Screen('DrawLines', ... 67 | cfg.screen.win, ... 68 | cfg.fixation.allCoords, ... 69 | cfg.fixation.widthPix / 3, ... 70 | cfg.color.white, ... 71 | [cfg.screen.center(1) cfg.screen.center(2)]); 72 | 73 | Screen('FillOval', ... 74 | cfg.screen.win, ... 75 | cfg.color.black, ... 76 | cfg.fixation.innerOval, ... 77 | cfg.fixation.widthPix / 3); 78 | 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /src/fixation/initFixation.m: -------------------------------------------------------------------------------- 1 | function cfg = initFixation(cfg) 2 | % 3 | % Prepare the details for fixation "cross". 4 | % 5 | % USAGE:: 6 | % 7 | % cfg = initFixation(cfg) 8 | % 9 | % the fixation has a width defined by 10 | % ``cfg.fixation.width`` : in degrees of visual 11 | % 12 | % The horizontal and vertical offset (in degrees of visual) with respect to the center of the 13 | % screen is defined by: 14 | % 15 | % - cfg.fixation.xDisplacement 16 | % - cfg.fixation.yDisplacement 17 | % 18 | % For cfg.fixation.type == 'bestFixation' 19 | % 20 | % Code adapted from: "What is the best fixation target?" 21 | % DOI 10.1016/j.visres.2012.10.012 22 | % 23 | % Contains a fixation cross and a dot 24 | % 25 | 26 | % (C) Copyright 2020 CPP_PTB developers 27 | 28 | % Convert some values from degrees to pixels 29 | cfg.fixation = degToPix('width', cfg.fixation, cfg); 30 | cfg.fixation = degToPix('xDisplacement', cfg.fixation, cfg); 31 | cfg.fixation = degToPix('yDisplacement', cfg.fixation, cfg); 32 | 33 | % Prepare fixation cross 34 | xLine = [-cfg.fixation.widthPix cfg.fixation.widthPix 0 0] / 2; 35 | yLine = [0 0 -cfg.fixation.widthPix cfg.fixation.widthPix] / 2; 36 | 37 | cfg.fixation.xCoords = xLine + cfg.fixation.xDisplacementPix; 38 | cfg.fixation.yCoords = yLine + cfg.fixation.yDisplacementPix; 39 | 40 | cfg.fixation.allCoords = [cfg.fixation.xCoords; cfg.fixation.yCoords]; 41 | 42 | switch cfg.fixation.type 43 | 44 | case 'bestFixation' 45 | 46 | cfg.fixation.outerOval = [ ... 47 | cfg.screen.center(1) - cfg.fixation.widthPix / 2, ... 48 | cfg.screen.center(2) - cfg.fixation.widthPix / 2, ... 49 | cfg.screen.center(1) + cfg.fixation.widthPix / 2, ... 50 | cfg.screen.center(2) + cfg.fixation.widthPix / 2]; 51 | 52 | cfg.fixation.innerOval = [ ... 53 | cfg.screen.center(1) - cfg.fixation.widthPix / 6, ... 54 | cfg.screen.center(2) - cfg.fixation.widthPix / 6, ... 55 | cfg.screen.center(1) + cfg.fixation.widthPix / 6, ... 56 | cfg.screen.center(2) + cfg.fixation.widthPix / 6]; 57 | 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /src/getExperimentEnd.m: -------------------------------------------------------------------------------- 1 | function cfg = getExperimentEnd(cfg) 2 | % 3 | % Wrapper function that will show a fixation cross and display in the console the 4 | % whole experiment's duration in minutes and seconds 5 | % 6 | 7 | % (C) Copyright 2020 CPP_PTB developers 8 | 9 | drawFixation(cfg); 10 | endExpmt = Screen('Flip', cfg.screen.win); 11 | 12 | disp(' '); 13 | ExpmtDur = endExpmt - cfg.experimentStart; 14 | ExpmtDurMin = floor(ExpmtDur / 60); 15 | ExpmtDurSec = mod(ExpmtDur, 60); 16 | disp(['Experiment lasted ', ... 17 | num2str(ExpmtDurMin), ' minutes ', ... 18 | num2str(ExpmtDurSec), ' seconds']); 19 | disp(' '); 20 | 21 | end 22 | -------------------------------------------------------------------------------- /src/getExperimentStart.m: -------------------------------------------------------------------------------- 1 | function cfg = getExperimentStart(cfg) 2 | % 3 | % Wrapper function that will show a fixation cross and collect a start timestamp 4 | % in ``cfg.experimentStart`` 5 | % 6 | % USAGE:: 7 | % 8 | % cfg = getExperimentStart(cfg) 9 | % 10 | 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | drawFixation(cfg); 14 | vbl = Screen('Flip', cfg.screen.win); 15 | cfg.experimentStart = vbl; 16 | 17 | end 18 | -------------------------------------------------------------------------------- /src/initPTB.m: -------------------------------------------------------------------------------- 1 | function cfg = initPTB(cfg) 2 | % 3 | % This will initialize PsychToolBox: 4 | % 5 | % - screen 6 | % 7 | % - the windon opened takes the whole screen unless 8 | % ``cfg.debug.smallWin`` is set to ``true`` 9 | % - can skip synch test if you ask for it (nicely) 10 | % - window transparency enabled by ``cfg.debug.transpWin`` set to ``true`` 11 | % - gets the flip interval 12 | % - computes the pixel per degree of visual angle: 13 | % the computation for ppd assumes the windows takes the whole screen width 14 | % 15 | % - set font details 16 | % - keyboard 17 | % - hides cursor 18 | % - sound 19 | % 20 | % USAGE:: 21 | % 22 | % cfg = initPTB(cfg) 23 | % 24 | % See the set up page of the documentation for more details on the content of cfg 25 | % 26 | % 27 | 28 | % (C) Copyright 2020 CPP_PTB developers 29 | 30 | checkPtbVersion(); 31 | 32 | cfg = getOsInfo(cfg); 33 | 34 | pth = fileparts(mfilename('fullpath')); 35 | addpath(genpath(fullfile(pth, 'src'))); 36 | 37 | % For octave: to avoid displaying messaging one screen at a time 38 | more off; 39 | 40 | % Resets the seed of the random number generator 41 | setUpRand(); 42 | 43 | % check for OpenGL compatibility, abort otherwise: 44 | AssertOpenGL; 45 | 46 | cfg = checkCppPtbCfg(cfg); 47 | 48 | Screen('Preference', 'SkipSyncTests', cfg.skipSyncTests); 49 | 50 | initKeyboard; 51 | initDebug(cfg); 52 | 53 | %% Audio 54 | cfg = initAudio(cfg); 55 | 56 | %% Visual 57 | 58 | % Make sure we have black splash screen 59 | Screen('Preference', 'VisualDebugLevel', 1); 60 | 61 | % Get the screen numbers and draw to the external screen if available 62 | cfg.screen.idx = max(Screen('Screens')); 63 | 64 | if isfield(cfg.screen, 'resolution') 65 | [newWidth, newHeight, newHz] = deal(cfg.screen.resolution{:}); 66 | cfg.screen.oldResolution = Screen('Resolution', cfg.screen.idx, ... 67 | newWidth, newHeight, newHz); 68 | end 69 | 70 | cfg = openWindow(cfg); 71 | 72 | % window size info 73 | [cfg.screen.winWidth, cfg.screen.winHeight] = WindowSize(cfg.screen.win); 74 | 75 | % Get the Center of the Screen 76 | cfg.screen.center = [cfg.screen.winRect(3), cfg.screen.winRect(4)] / 2; 77 | 78 | % Computes the number of pixels per degree given the distance to screen and 79 | % monitor width 80 | % This assumes that the window fills the whole screen 81 | cfg.screen.FOV = computeFOV(cfg); 82 | cfg.screen.ppd = cfg.screen.winWidth / cfg.screen.FOV; 83 | 84 | % Initialize visual parameters for fixation cross or dot 85 | cfg = initFixation(cfg); 86 | 87 | %% Select specific text font, style and size 88 | initText(cfg); 89 | 90 | %% Timing 91 | % Query frame duration 92 | cfg.screen.ifi = Screen('GetFlipInterval', cfg.screen.win); 93 | cfg.screen.monitorRefresh = 1 / cfg.screen.ifi; 94 | 95 | % Set priority for script execution to realtime priority: 96 | Priority(MaxPriority(cfg.screen.win)); 97 | 98 | %% Warm up some functions 99 | % Do dummy calls to GetSecs, WaitSecs, KbCheck 100 | % to make sure they are loaded and ready when we need them 101 | KbCheck; 102 | WaitSecs(0.1); 103 | GetSecs; 104 | 105 | % Mouse 106 | if cfg.hideCursor 107 | HideCursor(cfg.screen.win); 108 | end 109 | 110 | end 111 | 112 | function cfg = getOsInfo(cfg) 113 | 114 | cfg.StimulusPresentation.OperatingSystem = computer(); 115 | 116 | cfg.StimulusPresentation.SoftwareRRID = 'SCR_002881'; 117 | cfg.StimulusPresentation.Code = ''; 118 | 119 | [~, versionStruc] = PsychtoolboxVersion; 120 | 121 | cfg.StimulusPresentation.SoftwareVersion = sprintf('%i.%i.%i', ... 122 | versionStruc.major, ... 123 | versionStruc.minor, ... 124 | versionStruc.point); 125 | 126 | runsOn = 'Matlab - '; 127 | if IsOctave 128 | runsOn = 'Octave - '; 129 | end 130 | runsOn = [runsOn version()]; 131 | cfg.StimulusPresentation.SoftwareName = ['Psychtoolbox on ' runsOn]; 132 | 133 | end 134 | 135 | function initDebug(cfg) 136 | 137 | % init PTB with different options in concordance to the debug Parameters 138 | 139 | if cfg.debug.do 140 | 141 | Screen('Preference', 'SkipSyncTests', cfg.skipSyncTests); 142 | Screen('Preference', 'Verbosity', 0); 143 | % disable all visual alerts 144 | Screen('Preference', 'VisualDebugLevel', 0); 145 | % disable all output to the command window 146 | Screen('Preference', 'SuppressAllWarnings', 1); 147 | 148 | fprintf('\n\n\n\n'); 149 | fprintf('########################################\n'); 150 | fprintf('## DEBUG MODE. TIMING WILL BE OFF. ##\n'); 151 | fprintf('########################################'); 152 | fprintf('\n\n\n\n'); 153 | 154 | testKeyboards(cfg); 155 | 156 | end 157 | 158 | if cfg.debug.transpWin 159 | PsychDebugWindowConfiguration; 160 | end 161 | 162 | end 163 | 164 | function initKeyboard 165 | 166 | % Make sure keyboard mapping is the same on all supported operating systems 167 | % Apple MacOS/X, MS-Windows and GNU/Linux: 168 | KbName('UnifyKeyNames'); 169 | 170 | % Don't echo keypresses to Matlab window 171 | ListenChar(-1); 172 | 173 | end 174 | 175 | function cfg = initAudio(cfg) 176 | 177 | if cfg.audio.do 178 | 179 | InitializePsychSound(1); 180 | 181 | if isfield(cfg.audio, 'useDevice') 182 | 183 | % get audio device list 184 | audioDev = PsychPortAudio('GetDevices'); 185 | 186 | % find output device to use 187 | idx = find(audioDev.NrInputChannels == cfg.audio.inputChannels && ... 188 | audioDev.NrOutputChannels == cfg.audio.channels && ... 189 | ~cellfun(@isempty, regexp({audioDev.HostAudioAPIName}, ... 190 | cfg.audio.deviceName))); 191 | 192 | % save device ID 193 | cfg.audio.devIdx = audioDev(idx).DeviceIndex; 194 | 195 | % get device's sampling rate 196 | cfg.audio.fs = audioDev(idx).DefaultSampleRate; 197 | 198 | end 199 | 200 | cfg.audio.pahandle = PsychPortAudio('Open', ... 201 | cfg.audio.devIdx, ... 202 | cfg.audio.playbackMode, ... 203 | cfg.audio.requestedLatency, ... 204 | cfg.audio.fs, ... 205 | cfg.audio.channels); 206 | 207 | % set initial PTB volume for safety (participants can adjust this manually 208 | % at the beginning of the experiment) 209 | PsychPortAudio('Volume', cfg.audio.pahandle, cfg.audio.initVolume); 210 | 211 | end 212 | end 213 | 214 | function cfg = openWindow(cfg) 215 | 216 | if cfg.debug.smallWin 217 | [cfg.screen.win, cfg.screen.winRect] = ... 218 | Screen('OpenWindow', cfg.screen.idx, cfg.color.background, ... 219 | [0, 0, 480, 270]); 220 | else 221 | [cfg.screen.win, cfg.screen.winRect] = ... 222 | Screen('OpenWindow', cfg.screen.idx, cfg.color.background); 223 | end 224 | 225 | % Enable alpha-blending, set it to a blend equation usable for linear 226 | % superposition with alpha-weighted source. 227 | % Required for drwing smooth lines and screen('DrawDots') 228 | Screen('BlendFunction', cfg.screen.win, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 229 | 230 | end 231 | 232 | function initText(cfg) 233 | 234 | Screen('TextFont', cfg.screen.win, cfg.text.font); 235 | Screen('TextSize', cfg.screen.win, cfg.text.size); 236 | Screen('TextStyle', cfg.screen.win, cfg.text.style); 237 | 238 | end 239 | -------------------------------------------------------------------------------- /src/isOctave.m: -------------------------------------------------------------------------------- 1 | function retval = isOctave 2 | % Return: true if the environment is Octave. 3 | % mostly used to testing when PTB is not in the path 4 | % 5 | % Must stay in the 'src' folder for continuous integration with github 6 | % action to work. Not sure why. 7 | % 8 | 9 | % (C) Copyright 2010-2020 Agah Karakuzu 10 | % (C) Copyright 2020 CPP_PTB developers 11 | 12 | persistent cacheval % speeds up repeated calls 13 | 14 | if isempty (cacheval) 15 | cacheval = (exist ('OCTAVE_VERSION', 'builtin') > 0); 16 | end 17 | 18 | retval = cacheval; 19 | end 20 | -------------------------------------------------------------------------------- /src/keyboard/checkAbort.m: -------------------------------------------------------------------------------- 1 | function checkAbort(cfg, deviceNumber) 2 | % 3 | % Will quit your experiment if you press the key you have defined in 4 | % ``cfg.keyboard.escapeKey``. 5 | % When no deviceNumber is set then it will check the default device. 6 | % When an abort key is detected this will throw a 7 | % specific error that can then be caught. 8 | % 9 | % USAGE:: 10 | % 11 | % checkAbort(cfg, deviceNumber) 12 | % 13 | % EXAMPLE:: 14 | % 15 | % try 16 | % 17 | % % Your awesome experiment 18 | % 19 | % catch ME % when something goes wrong 20 | % 21 | % switch ME.identifier 22 | % 23 | % case 'checkAbort:abortRequested' 24 | % 25 | % % stuff to do when an abort is requested (save data...) 26 | % 27 | % otherwise 28 | % 29 | % % stuff to do otherwise 30 | % rethrow(ME) % display the error 31 | % 32 | % end 33 | % end 34 | % 35 | 36 | % (C) Copyright 2020 CPP_PTB developers 37 | 38 | if nargin < 1 || isempty(cfg) 39 | error('I need at least one input.'); 40 | end 41 | 42 | if nargin < 2 || isempty(deviceNumber) 43 | deviceNumber = -1; 44 | end 45 | 46 | [keyIsDown, ~, keyCode] = KbCheck(deviceNumber); 47 | 48 | if keyIsDown && keyCode(KbName(cfg.keyboard.escapeKey)) 49 | 50 | errorAbort(); 51 | 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /src/keyboard/checkAbortGetResponse.m: -------------------------------------------------------------------------------- 1 | function checkAbortGetResponse(responseEvents, cfg) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | if isfield(responseEvents, 'keyName') > 0 && ... 6 | any( ... 7 | strcmpi({responseEvents(:).keyName}, cfg.keyboard.escapeKey) ... 8 | ) 9 | errorAbortGetReponse; 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /src/keyboard/collectAndSaveResponses.m: -------------------------------------------------------------------------------- 1 | function responseEvents = collectAndSaveResponses(cfg, logFile, experimentStart) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg); 7 | 8 | if isfield(responseEvents(1), 'onset') && ~isempty(responseEvents(1).onset) 9 | 10 | for iResp = 1:size(responseEvents, 1) 11 | responseEvents(iResp).onset = ... 12 | responseEvents(iResp).onset - experimentStart; 13 | end 14 | 15 | responseEvents(1).fileID = logFile.fileID; 16 | responseEvents(1).extraColumns = logFile.extraColumns; 17 | saveEventsFile('save', cfg, responseEvents); 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /src/keyboard/getResponse.m: -------------------------------------------------------------------------------- 1 | function responseEvents = getResponse(action, deviceNumber, cfg, getOnlyPress) 2 | % 3 | % Wrapper function to use ``KbQueue`` which is definitely what you should use 4 | % to collect responses. You can easily collect responses while running some 5 | % other code at the same time. 6 | % 7 | % The queue will be listening to key presses on a keyboard device: 8 | % ``cfg.keyboard.responseBox`` or ``cfg.keyboard.keyboard`` are 2 main examples. 9 | % 10 | % When no ``deviceNumber`` is set then it will listen to the default device. 11 | % 12 | % You can use it in a way so that it only takes responses from certain keys and 13 | % ignore others (like the triggers from an MRI scanner). 14 | % 15 | % Check the ``CPP_getResponseDemo`` for a quick script on how to use it. 16 | % 17 | % USAGE:: 18 | % 19 | % responseEvents = getResponse(action, deviceNumber, cfg, getOnlyPress) 20 | % 21 | % :param action: Defines what we want the function to do 22 | % :param deviceNumber: device number of the keyboard or trigger box in MRI 23 | % :type deviceNumber: integer 24 | % :param cfg: 25 | % :param getOnlyPress: if set to true the function will only return the key presses and will not 26 | % return when the keys were released (default=true). See the section on 27 | % `Returns` below for more info 28 | % 29 | % :returns: 30 | % 31 | % - :responseEvents: returns all the keypresses and return them 32 | % as a structure with field names that make it easier 33 | % to save the output of in a BIDS format 34 | % 35 | % - ``responseEvents.onset`` this is an absolute value and you should subtract 36 | % the "experiment start time" to get a value relative to when the experiment was started. 37 | % 38 | % - ``responseEvents.trial_type = response`` 39 | % 40 | % - ``responseEvents.duration = 0`` 41 | % 42 | % - ``responseEvents.keyName`` the name of the key pressed 43 | % 44 | % - ``responseEvents(iEvent,1).pressed`` if 45 | % 46 | % - ``pressed == 1`` --> the key was pressed 47 | % - ``pressed == 0`` --> the key was released 48 | % 49 | % --- 50 | % 51 | % ``action`` options: 52 | % 53 | % - ``init`` to initialise the queue. 54 | % Initialize the buffer for key presses on a given device (you can also 55 | % specify the keys of interest that should be listened to). 56 | % 57 | % - ``start`` to start listening to the key presses (carefully insert into your 58 | % script - where do you want to start buffering the responses). 59 | % 60 | % - ``check`` checks all the key presses events since 'start', or since last 'check' 61 | % or 'flush' (whichever was the most recent) 62 | % 63 | % - can check for demand to abort if the ``escapeKey`` is listed in the Keys of interest 64 | % 65 | % - can only check for demands to abort when ``getResponse('check')`` is called 66 | % so there will be a delay between the key press and the experiment stopping 67 | % 68 | % - abort errors send specific signals that allow the catch to get them and allows us 69 | % to "close" nicely 70 | % 71 | % - ``flush`` empties the queue of events in case you want to restart from a clean queue 72 | % 73 | % - ``stop`` stops listening to key presses 74 | % 75 | 76 | % (C) Copyright 2020 CPP_PTB developers 77 | 78 | if nargin < 2 || isempty(deviceNumber) 79 | deviceNumber = -1; 80 | end 81 | 82 | if nargin < 3 83 | cfg = struct( ... 84 | 'keyboard', struct('responseKey', {}) ... 85 | ); 86 | end 87 | 88 | if nargin < 4 89 | getOnlyPress = true; 90 | end 91 | 92 | responseEvents = struct; 93 | 94 | switch action 95 | 96 | case 'init' 97 | 98 | % Prevent spilling of keystrokes into console. 99 | % If you use ListenChar(2), this will prevent you from using KbQueue. 100 | ListenChar(-1); 101 | 102 | % Clean and release any queue that might be opened 103 | KbQueueRelease(deviceNumber); 104 | 105 | keysOfInterest = setKeysOfInterest(cfg.keyboard); 106 | 107 | % Create the keyboard queue to collect responses. 108 | KbQueueCreate(deviceNumber, keysOfInterest); 109 | 110 | case 'start' 111 | KbQueueStart(deviceNumber); 112 | 113 | case 'check' 114 | responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress); 115 | 116 | checkAbortGetResponse(responseEvents, cfg); 117 | 118 | case 'flush' 119 | KbQueueFlush(deviceNumber); 120 | 121 | case 'stop' 122 | KbQueueStop(deviceNumber); 123 | 124 | case 'release' 125 | KbQueueRelease(deviceNumber); 126 | 127 | % Give me my keyboard back... Pretty please. 128 | ListenChar(0); 129 | 130 | end 131 | 132 | talkToMe(action, cfg); 133 | 134 | end 135 | 136 | function keysOfInterest = setKeysOfInterest(keyboard) 137 | % list all the response keys we want KbQueue to listen to 138 | % by default we listen to all keys 139 | % but if responseKey is set in the parameters we override this 140 | 141 | keysOfInterest = zeros(1, 256); 142 | 143 | fprintf('\n Will be listening for key presses on : '); 144 | 145 | if ~isempty(keyboard.responseKey) 146 | 147 | responseTargetKeys = nan(1, numel(keyboard.responseKey)); 148 | 149 | for iKey = 1:numel(keyboard.responseKey) 150 | fprintf('\n - %s ', keyboard.responseKey{iKey}); 151 | responseTargetKeys(iKey) = KbName(keyboard.responseKey(iKey)); 152 | end 153 | 154 | keysOfInterest(responseTargetKeys) = 1; 155 | 156 | else 157 | 158 | keysOfInterest = ones(1, 256); 159 | 160 | fprintf('ALL KEYS.'); 161 | 162 | end 163 | 164 | fprintf('\n\n'); 165 | end 166 | 167 | function responseEvents = getAllKeyEvents(responseEvents, deviceNumber, getOnlyPress) 168 | 169 | iEvent = 1; 170 | 171 | while KbEventAvail(deviceNumber) 172 | 173 | event = KbEventGet(deviceNumber); 174 | 175 | % we only return the pressed keys by default 176 | if getOnlyPress == true && event.Pressed == 0 177 | else 178 | 179 | responseEvents(iEvent, 1).onset = event.Time; 180 | responseEvents(iEvent, 1).trial_type = 'response'; 181 | responseEvents(iEvent, 1).duration = 0; 182 | responseEvents(iEvent, 1).keyName = KbName(event.Keycode); 183 | responseEvents(iEvent, 1).pressed = event.Pressed; 184 | 185 | iEvent = iEvent + 1; 186 | 187 | end 188 | 189 | end 190 | 191 | end 192 | 193 | function talkToMe(action, cfg) 194 | 195 | verbose = false; 196 | if isfield(cfg, 'verbose') 197 | verbose = cfg.verbose; 198 | end 199 | 200 | switch action 201 | 202 | case 'init' 203 | msg = 'Initialising KbQueue.'; 204 | 205 | case 'start' 206 | msg = 'Starting to listen to keypresses.'; 207 | 208 | case 'check' 209 | msg = 'Checking recent keypresses.'; 210 | 211 | case 'flush' 212 | msg = 'Reinitialising keyboard queue.'; 213 | 214 | case 'stop' 215 | msg = 'Stopping to listen to keypresses.'; 216 | 217 | case 'release' 218 | msg = 'Releasing KbQueue.'; 219 | 220 | otherwise 221 | msg = ''; 222 | 223 | end 224 | 225 | if verbose > 2 226 | fprintf('\n %s\n\n', msg); 227 | 228 | end 229 | 230 | end 231 | -------------------------------------------------------------------------------- /src/keyboard/pressSpaceForMe.m: -------------------------------------------------------------------------------- 1 | function pressSpaceForMe() 2 | % 3 | % Use that to stop your script and only restart when the space bar is pressed. 4 | % This can be useful if as an experimenter you want to have one final check on 5 | % some set up before giving the green light. 6 | % 7 | % USAGE:: 8 | % 9 | % pressSpaceForMe() 10 | % 11 | 12 | % (C) Copyright 2020 CPP_PTB developers 13 | 14 | fprintf('\nPress space to continue.\n'); 15 | 16 | while 1 17 | 18 | WaitSecs(0.1); 19 | 20 | [~, keyCode, ~] = KbWait(-1); 21 | 22 | if strcmp(KbName(find(keyCode)), 'space') 23 | fprintf('starting the experiment...\n'); 24 | break 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /src/keyboard/testKeyboards.m: -------------------------------------------------------------------------------- 1 | function testKeyboards(cfg) 2 | % 3 | % Checks that the keyboards asked for properly connected. 4 | % 5 | % If no key is pressed on the correct keyboard after the ``timeOut`` time, 6 | % this exits with an error. 7 | % 8 | % USAGE:: 9 | % 10 | % testKeyboards(cfg) 11 | % 12 | 13 | % (C) Copyright 2020 CPP_PTB developers 14 | 15 | timeOut = 5; 16 | 17 | % Main keyboard used by the experimenter to quit the experiment if it is necessary 18 | % cfg.keyboard 19 | fprintf('\n This is a test: press any key on the main keyboard\n'); 20 | t = GetSecs; 21 | [~, keyCode, ~] = KbPressWait(cfg.keyboard.keyboard, t + timeOut); 22 | throwError(keyCode, cfg.keyboard.keyboard, 1); 23 | 24 | if ~isequal(cfg.keyboard.keyboard, cfg.keyboard.responseBox) 25 | 26 | % For key presses for the subject 27 | % cfg.keyboard.responseBox 28 | fprintf('\n Thiscfg.keyboard is a test: press any key on the participant response box\n'); 29 | t = GetSecs; 30 | [~, keyCode, ~] = KbPressWait(cfg.keyboard.responseBox, t + timeOut); 31 | throwError(keyCode, cfg.keyboard.responseBox, 2); 32 | 33 | end 34 | 35 | end 36 | 37 | function throwError(keyCode, deviceNumber, keyboardType) 38 | 39 | switch keyboardType 40 | case 1 41 | keyboardType = 'main keyboard'; 42 | case 2 43 | keyboardType = 'response box'; 44 | end 45 | 46 | text1 = ['\nYou asked for this keyboard device number to be used as ' keyboardType '.\n\n']; 47 | 48 | text2 = '\nThese are the devices currently connected.\n\n'; 49 | 50 | errorText = 'No key was pressed. Did you configure the keyboards properly? See above for info.'; 51 | 52 | if all(keyCode == 0) 53 | 54 | % Give me my keyboard back... Pretty please. 55 | ListenChar(); 56 | 57 | fprintf(text1); 58 | 59 | if isempty(deviceNumber) 60 | disp('No keyboard selected, default is the main keyboard'); 61 | else 62 | disp(deviceNumber); 63 | end 64 | 65 | fprintf(text2); 66 | 67 | [keyboardNumbers, keyboardNames] = GetKeyboardIndices; %#ok<*NOPRT,*ASGLU> 68 | 69 | error(errorText); 70 | 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /src/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | # camelCase 2 | regex_function_name: "[a-z]+([A-Z0-9]+[a-z]*)*" 3 | -------------------------------------------------------------------------------- /src/randomization/repeatShuffleConditions.m: -------------------------------------------------------------------------------- 1 | function shuffledRepeats = repeatShuffleConditions(baseConditionVector, nbRepeats) 2 | % 3 | % Given ``baseConditionVector``, a vector of conditions (coded as numbers), 4 | % this will create a longer vector made of ``nbRepeats`` of this base vector 5 | % and make sure that a given condition is not repeated one after the other. 6 | % 7 | % USAGE:: 8 | % 9 | % shuffledRepeats = repeatShuffleConditions(baseConditionVector, nbRepeats) 10 | % 11 | % :param baseConditionVector: 12 | % :type baseConditionVector: vector 13 | % :param nbRepeats: 14 | % :type nbRepeats: integer 15 | % 16 | % :returns: - :shuffledRepeats: (vector) (dimension) 17 | % 18 | 19 | % (C) Copyright 2020 CPP_PTB developers 20 | 21 | % TODO 22 | % - needs some input checks to make sure that there is actually a solution 23 | % for this randomization (e.g having [1 1 1 1 1 2] as input will lead to an 24 | % infinite loop) 25 | 26 | if numel(unique(baseConditionVector)) == 1 27 | error('There should be more than one condition to shuffle'); 28 | end 29 | 30 | baseConditionVector = baseConditionVector(:)'; 31 | 32 | while 1 33 | tmp = []; 34 | for iRepeat = 1:nbRepeats 35 | 36 | tmp = [tmp, shuffle(baseConditionVector)]; 37 | 38 | end 39 | if ~any(diff(tmp, [], 2) == 0) 40 | break 41 | end 42 | end 43 | 44 | shuffledRepeats = tmp; 45 | 46 | end 47 | -------------------------------------------------------------------------------- /src/randomization/setTargetPositionInSequence.m: -------------------------------------------------------------------------------- 1 | function chosenPositions = setTargetPositionInSequence(seqLength, nbTarget, forbiddenPos) 2 | % 3 | % For a sequence of length ``seqLength`` where we want to insert ``nbTarget`` targets, this 4 | % will return ``nbTarget`` random position in that sequence and make sure that, 5 | % they are not consecutive positions. 6 | % 7 | % USAGE:: 8 | % 9 | % chosenPositions = setTargetPositionInSequence(seqLength, nbTarget, forbiddenPos) 10 | % 11 | % :param seqLength: 12 | % :type seqLength: integer 13 | % :param nbTarget: 14 | % :type nbTarget: integer 15 | % :param forbiddenPos: 16 | % :type forbiddenPos: vector of integers 17 | % 18 | % :returns: - :chosenPositions: 19 | % 20 | 21 | % (C) Copyright 2020 CPP_PTB developers 22 | 23 | REPLACE = false; 24 | 25 | allowedPositions = setxor(forbiddenPos, 1:seqLength); 26 | 27 | chosenPositions = []; 28 | 29 | if nbTarget < 1 30 | return 31 | 32 | elseif nbTarget == 1 33 | 34 | chosenPositions = randsample(allowedPositions, nbTarget, REPLACE); 35 | 36 | else 37 | 38 | targetDifference = 0; 39 | 40 | while any(abs(targetDifference) < 2) 41 | chosenPositions = randsample(allowedPositions, nbTarget, REPLACE); 42 | chosenPositions = sort(chosenPositions); 43 | targetDifference = diff(chosenPositions, [], 2); 44 | end 45 | 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /src/randomization/shuffle.m: -------------------------------------------------------------------------------- 1 | function shuffled = shuffle(unshuffled) 2 | % 3 | % Is just there to replace the ``Shuffle`` function from PTB in case it is not in the 4 | % path. Can be useful for testing or for continuous integration. 5 | % 6 | % 7 | % USAGE:: 8 | % 9 | % shuffled = shuffle(unshuffled) 10 | % 11 | 12 | % (C) Copyright 2020 CPP_PTB developers 13 | 14 | try 15 | shuffled = Shuffle(unshuffled); 16 | catch 17 | shuffled = unshuffled(randperm(length(unshuffled))); 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/screen/farewellScreen.m: -------------------------------------------------------------------------------- 1 | function farewellScreen(cfg) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | Screen('FillRect', cfg.screen.win, cfg.color.background, cfg.screen.winRect); 7 | DrawFormattedText(cfg.screen.win, 'Thank you!', 'center', 'center', cfg.text.color); 8 | Screen('Flip', cfg.screen.win); 9 | if isfield(cfg, 'mri') 10 | WaitSecs(cfg.mri.repetitionTime * 2); 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /src/screen/standByScreen.m: -------------------------------------------------------------------------------- 1 | function standByScreen(cfg) 2 | % 3 | % It shows a basic one-page instruction stored in `cfg.task.instruction` and wait 4 | % for `space` stroke. 5 | % 6 | % USAGE:: 7 | % 8 | % standByScreen(cfg) 9 | % 10 | 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | Screen('FillRect', cfg.screen.win, cfg.color.background, cfg.screen.winRect); 14 | 15 | DrawFormattedText(cfg.screen.win, ... 16 | cfg.task.instruction, ... 17 | 'center', 'center', cfg.text.color); 18 | 19 | Screen('Flip', cfg.screen.win); 20 | 21 | % Wait for space key to be pressed 22 | pressSpaceForMe(); 23 | 24 | end 25 | -------------------------------------------------------------------------------- /src/templates/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | regex_parameter_name: "test_suite|[a-z0-9]+([A-Z]+[a-z]*)*" 2 | -------------------------------------------------------------------------------- /src/templates/templateFunction.m: -------------------------------------------------------------------------------- 1 | function [argout1, argout2] = templateFunction(argin1, argin2, argin3) 2 | % 3 | % Short description of what the function does goes here. 4 | % 5 | % USAGE:: 6 | % 7 | % [argout1, argout2] = templateFunction(argin1, [argin2 == default,] [argin3]) 8 | % 9 | % :param argin1: 10 | % :type argin1: 11 | % :param argin2: 12 | % :type argin2: 13 | % :param argin3: 14 | % :type argin3: 15 | % 16 | % :returns: - :argout1: (type) (dimension) 17 | % - :argout2: (type) (dimension) 18 | % 19 | % .. todo: 20 | % 21 | % - item 1 22 | % - item 2 23 | % 24 | 25 | % (C) Copyright 2020 CPP_PTB developers 26 | 27 | % The code goes below 28 | 29 | end 30 | -------------------------------------------------------------------------------- /src/templates/templateFunctionExample.m: -------------------------------------------------------------------------------- 1 | function templateFunctionExample() 2 | % This function illustrates a documentation test defined for MOdox. 3 | % Other than that it does absolutely nothinghort description of what 4 | % the function does goes here. 5 | % 6 | % Examples: 7 | % a=2; 8 | % disp(a) 9 | % % Expected output is prefixed by '%||' as in the following line: 10 | % %|| 2 11 | % % 12 | % % The test continues because no interruption through whitespace, 13 | % % as the previous line used a '%' comment character; 14 | % % thus the 'a' variable is still in the namespace and can be 15 | % % accessed. 16 | % b=3+a; 17 | % disp(a+[3 4]) 18 | % %|| [5 6] 19 | % 20 | % % A new test starts here because the previous line was white-space 21 | % % only. Thus the 'a' and 'b' variables are not present here anymore. 22 | % % The following expression raises an error because the 'b' variable 23 | % % is not defined (and does not carry over from the previous test). 24 | % % Because the expected output indicates an error as well, 25 | % % the test passes 26 | % disp(b) 27 | % %|| error('Some error') 28 | % 29 | % % A set of expressions is ignored if there is no expected output 30 | % % (that is, no lines starting with '%||'). 31 | % % Thus, the following expression is not part of any test, 32 | % % and therefore does not raise an error. 33 | % error('this is never executed) 34 | % 35 | % 36 | % % tests end here because test indentation has ended 37 | % 38 | 39 | % (C) Copyright 2020 CPP_PTB developers 40 | 41 | % The code goes below 42 | 43 | end 44 | -------------------------------------------------------------------------------- /src/utils/checkPtbVersion.m: -------------------------------------------------------------------------------- 1 | function checkPtbVersion() 2 | % 3 | % Checks that the right dependencies are installed. 4 | % 5 | % USAGE:: 6 | % 7 | % checkPtbVersion() 8 | % 9 | 10 | % (C) Copyright 2020 CPP_PTB developers 11 | 12 | printCreditsCppPtb(); 13 | 14 | PTB.major = 3; 15 | PTB.minor = 0; 16 | PTB.point = 14; 17 | 18 | fprintf('Checking dependencies\n'); 19 | 20 | % check ptb version 21 | try 22 | 23 | [~, versionStruc] = PsychtoolboxVersion; 24 | 25 | fprintf(' Using PTB %i.%i.%i\n', ... 26 | versionStruc.major, ... 27 | versionStruc.minor, ... 28 | versionStruc.point); 29 | 30 | if any([ ... 31 | versionStruc.major < PTB.major, ... 32 | versionStruc.minor < PTB.minor, ... 33 | versionStruc.point < PTB.point ... 34 | ]) 35 | 36 | str = sprintf('%s %i.%i.%i %s.\n%s', ... 37 | 'The current version PTB version is not', ... 38 | PTB.major, ... 39 | PTB.minor, ... 40 | PTB.point, ... 41 | 'In case of problems (e.g json file related) consider updating.'); 42 | warning(str); %#ok<*SPWRN> 43 | end 44 | catch 45 | error('Failed to check the PTB version: Are you sure that PTB is in the matlab path?'); 46 | end 47 | 48 | fprintf(' We got all we need. Let us get to work.\n'); 49 | 50 | end 51 | -------------------------------------------------------------------------------- /src/utils/cleanUp.m: -------------------------------------------------------------------------------- 1 | function cleanUp() 2 | % 3 | % A wrapper function to close all windows, ports, show mouse cursor, close keyboard queues 4 | % and give access back to the keyboards. 5 | % 6 | % USAGE:: 7 | % 8 | % cleanUp() 9 | % 10 | 11 | % (C) Copyright 2020 CPP_PTB developers 12 | 13 | WaitSecs(0.5); 14 | 15 | Priority(0); 16 | 17 | ListenChar(0); 18 | KbQueueRelease(); 19 | 20 | ShowCursor; 21 | 22 | % Screen Close All 23 | sca; 24 | 25 | try 26 | % Close Psychportaudio if open 27 | if PsychPortAudio('GetOpenDeviceCount') ~= 0 28 | PsychPortAudio('Close'); 29 | end 30 | catch 31 | end 32 | 33 | if ~ismac 34 | % Remove PsychDebugWindowConfiguration 35 | clear Screen; 36 | end 37 | 38 | close all; 39 | 40 | end 41 | -------------------------------------------------------------------------------- /src/utils/computeFOV.m: -------------------------------------------------------------------------------- 1 | function FOV = computeFOV(cfg) 2 | % 3 | % Computes the number of degrees of visual angle in the whole field of view. 4 | % 5 | % USAGE:: 6 | % 7 | % FOV = computeFOV(cfg) 8 | % 9 | % :param cfg: 10 | % :type cfg: structure 11 | % 12 | % :returns: - :FOV: (scalar) 13 | % 14 | % ``delta = 2 arctan ( d / 2D )`` 15 | % 16 | % - delta is the angular diameter 17 | % - d is the actual diameter of the object 18 | % - D is the distance to the object 19 | % 20 | % The result obtained is in radians. 21 | % 22 | 23 | % (C) Copyright 2020 CPP_PTB developers 24 | 25 | if cfg.screen.monitorDistance < 2 26 | errorDistanceToScreen(cfg); 27 | end 28 | 29 | FOV = ... 30 | 180 / pi * ... 31 | 2 * atan(cfg.screen.monitorWidth / (2 * cfg.screen.monitorDistance)); 32 | 33 | end 34 | -------------------------------------------------------------------------------- /src/utils/degToPix.m: -------------------------------------------------------------------------------- 1 | function structure = degToPix(fieldName, structure, cfg) 2 | % For a given field value in degrees of visual angle in the structure, this computes its value 3 | % in pixel using the pixel per degree value of the cfg structure and returns a structure with 4 | % an additional field with Pix suffix holding that new value. 5 | % 6 | % USAGE:: 7 | % 8 | % structure = degToPix(fieldName, structure, cfg) 9 | % 10 | % :param fieldName: 11 | % :type fieldName: string 12 | % :param structure: 13 | % :type structure: structure 14 | % :param cfg: 15 | % :type cfg: structure 16 | % 17 | % :returns: - :structure: (structure) 18 | % 19 | % EXAMPLE:: 20 | % 21 | % fixation.width = 2; 22 | % cfg.screen.ppd = 10; 23 | % 24 | % fixation = degToPix('width', fixation, cfg); 25 | % 26 | % 27 | 28 | % (C) Copyright 2020 CPP_PTB developers 29 | 30 | deg = getfield(structure, fieldName); %#ok 31 | 32 | structure = setfield(structure, [fieldName 'Pix'], ... 33 | deg * cfg.screen.ppd); %#ok 34 | 35 | end 36 | -------------------------------------------------------------------------------- /src/utils/makeGif.m: -------------------------------------------------------------------------------- 1 | % (C) Copyright 2020 CPP_PTB developers 2 | 3 | close all; 4 | clear; 5 | clc; 6 | 7 | output_folder = fullfile(pwd, 'ouputs'); 8 | screen_capture_folder = fullfile(output_folder, 'screen_capture'); 9 | screen_capture_filename = fullfile(screen_capture_folder, ... 10 | 'EMCL_kaks_frame-'); 11 | 12 | gif_file = fullfile(screen_capture_folder, ... 13 | 'EMCL_kaks_frame.gif'); 14 | 15 | FigDim = [100, 100, 1000, 1500]; 16 | Visibility = 'on'; 17 | 18 | for tif_img = 6:26 % 128 19 | 20 | filename = fullfile([screen_capture_filename sprintf('%04.0f', tif_img) '.tif']); 21 | 22 | h = figure('name', 'test', ... 23 | 'Position', FigDim, 'Color', [1 1 1], ... 24 | 'Visible', Visibility); 25 | 26 | imshow(imread(filename)); 27 | 28 | frame = getframe(h); 29 | im = frame2im(frame); 30 | [imind, cm] = rgb2ind(im, 256); 31 | 32 | if tif_img == 1 33 | imwrite(imind, cm, gif_file, 'gif', 'Loopcount', inf); 34 | else 35 | imwrite(imind, cm, gif_file, 'gif', 'WriteMode', 'append'); 36 | end 37 | 38 | close all; 39 | end 40 | -------------------------------------------------------------------------------- /src/utils/pixToDeg.m: -------------------------------------------------------------------------------- 1 | function structure = pixToDeg(fieldName, structure, cfg) 2 | % 3 | % For a given field value in pixel in the structure, 4 | % this computes its value in degrees of viual angle using the pixel per 5 | % degree value of the cfg structure and returns a structure with an 6 | % additional field holding that new value and with a fieldname with any 7 | % 'Pix' suffix removed and replaced with the 'DegVA' suffix . 8 | % 9 | % USAGE:: 10 | % 11 | % structure = pixToDeg(fieldName, structure, cfg) 12 | % 13 | % :param fieldName: 14 | % :type fieldName: string 15 | % :param structure: 16 | % :type structure: structure 17 | % :param cfg: 18 | % :type cfg: structure 19 | % 20 | % :returns: - :structure: (structure) 21 | % 22 | % EXAMPLE:: 23 | % 24 | % fixation.widthPix = 20; 25 | % cfg.screen.ppd = 10; 26 | % 27 | % fixation = degToPix('widthPix', fixation, cfg); 28 | % 29 | 30 | % (C) Copyright 2020 CPP_PTB developers 31 | 32 | pix = getfield(structure, fieldName); %#ok 33 | 34 | structure = setfield(structure, [strrep(fieldName, 'Pix', '') 'DegVA'], ... 35 | pix / cfg.screen.ppd); %#ok 36 | 37 | end 38 | -------------------------------------------------------------------------------- /src/utils/printCreditsCppPtb.m: -------------------------------------------------------------------------------- 1 | function printCreditsCppPtb() 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | try 6 | version = fileread(fullfile(fileparts(mfilename('fullpath')), ... 7 | '..', '..', 'version.txt')); 8 | catch 9 | version = 'v1.0.0'; 10 | end 11 | 12 | contributors = { ... 13 | 'Remi Gau', ... 14 | 'Marco Barilari', ... 15 | 'Ceren Battal', ... 16 | 'Federica Falagiarda', ... 17 | 'Iqra Shahzad'}; 18 | 19 | % DOI_URL = 'https://doi.org/10.5281/zenodo.3554331.'; 20 | 21 | repoURL = 'https://github.com/cpp-lln-lab/CPP_PTB'; 22 | 23 | disp('___________________________________________________'); 24 | disp('___________________________________________________'); 25 | disp(' '); 26 | disp(' __ ____ ____ ____ _____ _ '); 27 | disp(' / _)( _ \( _ \ ( _ \ |_ _| | ) '); 28 | disp(' ( (_ )___/ )___/ )___/ | | | \ '); 29 | disp(' \__)(__) (__) (__) |_| |__)'); 30 | disp(' '); 31 | 32 | splash = 'Thank you for using the CPP PTB - version %s. '; 33 | fprintf(splash, version); 34 | fprintf('\n\n'); 35 | 36 | fprintf('Current list of contributors includes\n'); 37 | for iCont = 1:numel(contributors) 38 | fprintf(' %s\n', contributors{iCont}); 39 | end 40 | fprintf('\b\n\n'); 41 | 42 | % fprintf('Please cite using the following DOI: \n %s\n\n', DOI_URL) 43 | 44 | fprintf('For bug report, suggestions or contributions see our repo: \n %s\n\n', repoURL); 45 | 46 | disp('___________________________________________________'); 47 | disp('___________________________________________________'); 48 | 49 | fprintf('\n\n'); 50 | 51 | end 52 | -------------------------------------------------------------------------------- /src/utils/printScreen.m: -------------------------------------------------------------------------------- 1 | function frame = printScreen(win, filename, frame) 2 | % 3 | 4 | % (C) Copyright 2020 CPP_PTB developers 5 | 6 | image_array = Screen('GetImage', win); 7 | 8 | imagesc(image_array); 9 | box off; 10 | axis off; 11 | set(gca, 'position', [0 0 1 1], 'units', 'normalized'); 12 | 13 | print(gcf, [filename sprintf('%04.0f', frame) '.jpeg'], '-djpeg'); 14 | 15 | frame = frame + 1; 16 | 17 | end 18 | -------------------------------------------------------------------------------- /src/utils/setUpRand.m: -------------------------------------------------------------------------------- 1 | function setUpRand() 2 | % 3 | % Resets the seed of the random number generator. 4 | % Will "adapt" depending on the Matlab / Otave version. 5 | % 6 | % USAGE:: 7 | % 8 | % setUpRand() 9 | % 10 | % For an alternative from PTB see ``ClockRandSeed()`` 11 | % 12 | % 13 | 14 | % (C) Copyright 2010-2020 Sam Schwarzkopf 15 | % (C) Copyright 2020 CPP_PTB developers 16 | 17 | seed = sum(100 * clock); 18 | 19 | try 20 | % Use the recommended method in modern Matlab 21 | RandStream.setGlobalStream(RandStream('mt19937ar', 'seed', seed)); 22 | disp('Using modern randomizer...'); 23 | catch 24 | 25 | try 26 | % Use the recommended method in Matlab R2012a. 27 | rng('shuffle'); 28 | disp('Using less modern randomizer...'); 29 | catch 30 | 31 | try 32 | % Use worse methods for Octave or old versions of Matlab (e.g. 7.1.0.246 (R14) SP3). 33 | rand('twister', seed); 34 | randn('twister', seed); 35 | disp('Using Octave or outdated randomizer...'); 36 | catch 37 | % For very old Matlab versions these are the only methods you can use. 38 | % These are supposed to be flawed although you will probably not 39 | % notice any effect of this for most situations. 40 | rand('state', seed); 41 | randn('state', seed); 42 | disp('Using "flawed" randomizer...'); 43 | end 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /src/video/createFramesTextureStructure.m: -------------------------------------------------------------------------------- 1 | function [video, cfg] = createFramesTextureStructure(video, cfg) 2 | % creates a structure with the textures of the provided images/video frames 3 | % 4 | % input cfg needs to have the following: 5 | % cfg.screen.win : the window where the video will be played 6 | % 7 | % input video needs to have the following: 8 | % video.path : the path, as a string, starting from the current directory, 9 | % where the images are stores 10 | % video.name : the name of the video frames. 11 | % All images should be named the same and followed by an 12 | % increasing number, as an index to the frames 13 | % video.frame.format : the image format, e.g. '.png', '.jpeg', etc. 14 | % It can be any format supported by imread % 15 | % video.frame.number : the number of frames/images you want to present 16 | % 17 | % new in the output is video.frames % 18 | % a structure with the provided images stored as textures % 19 | % 20 | 21 | % (C) Copyright 2020 CPP_PTB developers 22 | 23 | % structure to store the video frames 24 | video.frames = struct; 25 | % fill it with the images the PTB way 26 | for s = 1:video.frame.number 27 | video.frames(s).readImage = imread(fullfile(cd, ... 28 | video.path, ... 29 | strcat(video.name, ... 30 | num2str(s), ... 31 | video.frame.format))); 32 | video.frames(s).videoFrameTexture = Screen('MakeTexture', ... 33 | cfg.screen.win, ... 34 | video.frames(s).readImage); 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /src/video/playVideoFrames.m: -------------------------------------------------------------------------------- 1 | function playVideoFrames(video, cfg) 2 | % 3 | % Plays the provided image textures at the desidred frame rate 4 | % The images should render as a video 5 | % 6 | % input cfg needs to have the following: 7 | % cfg.screen.win : the window where the video will be played 8 | % cfg.color.background : the desired background color 9 | % 10 | % input video needs to have the following: 11 | % video.frame.number : the number of frames/images you want to present 12 | % video.frame.rate : the frame rate (fps) at which 13 | % you would like your video to be presented 14 | % video.frames : a structure with the images textures, 15 | % as output of createFramesTextureStructure 16 | % 17 | % 18 | 19 | % (C) Copyright 2020 CPP_PTB developers 20 | 21 | % calculate the duration of a single frame based on the provided desired frame rate % 22 | video.frame.duration = 1 / video.frame.rate; 23 | 24 | % Draw background onto full screen and get first flip timestamp 25 | % before images/movie presentation % 26 | Screen('FillRect', cfg.screen.win, cfg.color.background); 27 | [~, ~, lastEventTime] = Screen('Flip', cfg.screen.win); 28 | 29 | % frames presentation loop 30 | for f = 1:video.frame.number 31 | 32 | Screen('DrawTexture', ... 33 | cfg.screen.win, ... 34 | video.frames(f).videoFrameTexture, [], [], 0); 35 | [~, ~, lastEventTime] = Screen('Flip', ... 36 | cfg.screen.win, ... 37 | lastEventTime + video.frame.duration); 38 | 39 | end 40 | 41 | % Clear video from screen % 42 | Screen('FillRect', cfg.screen.win, cfg.color.background); 43 | [~, ~, lastEventTime] = Screen('Flip', cfg.screen.win); 44 | 45 | end 46 | -------------------------------------------------------------------------------- /src/waitFor.m: -------------------------------------------------------------------------------- 1 | function waitFor(cfg, timeToWait) 2 | % 3 | % Will either wait for a certain amount of time or a number of triggers. 4 | % 5 | % USAGE:: 6 | % 7 | % waitFor(cfg, timeToWait) 8 | % 9 | 10 | % (C) Copyright 2020 CPP_PTB developers 11 | 12 | WaitSecs(timeToWait); 13 | 14 | end 15 | -------------------------------------------------------------------------------- /src/waitForTrigger.m: -------------------------------------------------------------------------------- 1 | function [lastTriggerTimeStamp] = waitForTrigger(varargin) 2 | % 3 | % Counts a certain number of triggers coming from the scanner before returning. 4 | % 5 | % USAGE: 6 | % 7 | % [lastTriggerTimeStamp] = waitForTrigger([cfg,] ... 8 | % [deviceNumber,] ... 9 | % [quietMode,] ... 10 | % [nbTriggersToWait]) 11 | % 12 | % :param cfg: 13 | % :type cfg: struct 14 | % :param deviceNumber: device number of the keyboard or trigger box in MRI 15 | % :type deviceNumber: integer 16 | % :param quietMode: a boolean to make sure nothing is printed on the screen or the prompt 17 | % :type quietMode: boolean 18 | % :param nbTriggersToWait: number of triggers to wait 19 | % :type nbTriggersToWait: integer 20 | % 21 | % :returns: 22 | % 23 | % - :lastTriggerTimeStamp: (optional) it can be used as experimentStart 24 | % timestamp (``cfg.experimentStart``) 25 | % 26 | % If you are not using the quietMode, it flips and waits for half a TR before starting to 27 | % check for the next trigger (unless this was the last trigger to wait for and in 28 | % this case it returns immediately). 29 | % 30 | % Will print the count down in the command line and on the PTB window if one is 31 | % opened. 32 | % 33 | % If the fMRI sequence RT is provided (``cgf.MRI.repetitionTime``) then it will wait 34 | % for half a RT before starting to check for next trigger, otherwise it will 35 | % wait 500 ms. 36 | % 37 | % When no deviceNumber is set then it will check the default device: this is 38 | % probably only useful in debug as you will want to make sure you get the 39 | % triggers coming from the scanner in a real case scenario. 40 | % 41 | 42 | % (C) Copyright 2020 CPP_PTB developers 43 | 44 | [cfg, nbTriggersToWait, deviceNumber, quietMode] = checkInputs(varargin); 45 | 46 | triggerCounter = 0; 47 | 48 | if strcmpi(cfg.testingDevice, 'mri') 49 | 50 | msg = ['Experiment starting in ', ... 51 | num2str(nbTriggersToWait - triggerCounter), '...']; 52 | 53 | talkToMe(cfg, msg, quietMode); 54 | 55 | while triggerCounter < nbTriggersToWait 56 | 57 | keyCode = []; %#ok 58 | 59 | [~, lastTriggerTimeStamp, keyCode] = KbCheck(deviceNumber); 60 | 61 | if strcmp(KbName(keyCode), cfg.mri.triggerKey) 62 | 63 | triggerCounter = triggerCounter + 1; 64 | 65 | msg = sprintf(' Trigger %i', triggerCounter); 66 | 67 | talkToMe(cfg, msg, quietMode); 68 | 69 | % we only wait if this is not the last trigger 70 | if triggerCounter < nbTriggersToWait 71 | pauseBetweenTriggers(cfg); 72 | end 73 | 74 | end 75 | end 76 | end 77 | end 78 | 79 | function [cfg, nbTriggersToWait, deviceNumber, quietMode] = checkInputs(varargin) 80 | 81 | varargin = varargin{1}; 82 | 83 | if numel(varargin) < 1 || isempty(varargin{1}) || ~isstruct(varargin{1}) 84 | error('First input must be a cfg structure.'); 85 | elseif isstruct(varargin{1}) 86 | cfg = varargin{1}; 87 | end 88 | 89 | if numel(varargin) < 3 || isempty(varargin{3}) 90 | quietMode = false; 91 | else 92 | quietMode = varargin{3}; 93 | end 94 | 95 | if numel(varargin) < 2 || isempty(varargin{2}) 96 | deviceNumber = -1; 97 | if ~quietMode 98 | fprintf('Will wait for triggers on the main keyboard device.\n'); 99 | end 100 | else 101 | deviceNumber = varargin{2}; 102 | end 103 | 104 | if numel(varargin) < 4 || isempty(varargin{4}) 105 | nbTriggersToWait = cfg.mri.triggerNb; 106 | else 107 | nbTriggersToWait = varargin{4}; 108 | end 109 | 110 | end 111 | 112 | function talkToMe(cfg, msg, quietMode) 113 | 114 | if ~quietMode 115 | 116 | fprintf([msg, ' \n']); 117 | 118 | if isfield(cfg, 'screen') && isfield(cfg.screen, 'win') 119 | 120 | DrawFormattedText(cfg.screen.win, msg, ... 121 | 'center', 'center', cfg.text.color); 122 | 123 | Screen('Flip', cfg.screen.win); 124 | 125 | end 126 | 127 | end 128 | 129 | end 130 | 131 | function pauseBetweenTriggers(cfg) 132 | % we pause between triggers otherwise KbWait and KbPressWait might be too fast and could 133 | % catch several triggers in one go. 134 | 135 | waitTime = 0.5; 136 | if isfield(cfg, 'mri') && isfield(cfg.mri, 'repetitionTime') && ~isempty(cfg.mri.repetitionTime) 137 | waitTime = cfg.mri.repetitionTime / 2; 138 | end 139 | 140 | WaitSecs(waitTime); 141 | 142 | end 143 | 144 | % function [MyPort] = WaitForScanTrigger(Parameters) 145 | % 146 | % %% Opening IOPort 147 | % PortSettings = sprintf('BaudRate=115200 InputBufferSize=10000 ReceiveTimeout=60'); 148 | % PortSpec = FindSerialPort([], 1); 149 | % 150 | % % Open port portSpec with portSettings, return handle: 151 | % MyPort = IOPort('OpenSerialPort', PortSpec, PortSettings); 152 | % 153 | % % Start asynchronous background data collection and timestamping. Use 154 | % % blocking mode for reading data -- easier on the system: 155 | % AsyncSetup = sprintf('BlockingBackgroundRead=1 ReadFilterFlags=0 StartBackgroundRead=1'); 156 | % IOPort('ConfigureSerialPort', MyPort, AsyncSetup); 157 | % 158 | % % Read once to warm up 159 | % WaitSecs(1); 160 | % IOPort('Read', MyPort); 161 | % 162 | % nTrig = 0; 163 | % 164 | % %% waiting for dummie triggers from the scanner 165 | % while nTrig <= Parameters.Dummies 166 | % 167 | % [PktData, TReceived] = IOPort('Read', MyPort); 168 | % 169 | % % it is checked if something was received via trigger_port 170 | % % oldtrigger is there so 'number' is only updated when something new is 171 | % % received via trigger_port (normally you receive a "small series" of data at 172 | % % a time) 173 | % if isempty(PktData) 174 | % TReceived = 0; 175 | % end 176 | % 177 | % if TReceived && (oldtrigger == 0) 178 | % Number = 1; 179 | % else 180 | % Number = 0; 181 | % end 182 | % 183 | % oldtrigger = TReceived; 184 | % 185 | % if Number 186 | % nTrig = nTrig + 1; 187 | % Number = 0; %#ok 188 | % end 189 | % 190 | % end 191 | % 192 | % end 193 | % 194 | -------------------------------------------------------------------------------- /tests/miss_hit.cfg: -------------------------------------------------------------------------------- 1 | # style guide (https://florianschanda.github.io/miss_hit/style_checker.html) 2 | regex_script_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*" 3 | 4 | # camelCase for helper functions 5 | # test_nameOfTheTestedFunction_comment_for_each_test 6 | regex_function_name: "[a-z]+([A-Z]{1}[a-z]+)*|test_([a-z]+([A-Z0-9]*[a-z0_9]*)+)(_[a-z0_9]*)*" 7 | 8 | # all uppercase or camelCase 9 | regex_parameter_name: "test_suite|[A-Z]*|[a-z]+([A-Z]{1}[a-z]+)*" 10 | -------------------------------------------------------------------------------- /tests/test_checkAbortGetResponse.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_checkAbortGetResponse %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_checkAbortGetResponseBasic() 12 | 13 | responseEvents(1).keyName = 'a'; 14 | responseEvents(2).keyName = '2'; 15 | responseEvents(3).keyName = 'ESCAPE'; 16 | 17 | cfg.keyboard.escapeKey = 'ESCAPE'; 18 | 19 | assertExceptionThrown(@()checkAbortGetResponse(responseEvents, cfg), ... 20 | 'getResponse:abortRequested'); 21 | 22 | end 23 | -------------------------------------------------------------------------------- /tests/test_checkCppPtbCfg.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_checkCppPtbCfg %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_checkCppPtbCfg_basic() 12 | 13 | % set up 14 | cfg = checkCppPtbCfg(); 15 | 16 | % test data 17 | expected = cppPtbDefaults('all'); 18 | expected.eyeTracker.do = false; 19 | expected.debug.do = 1; 20 | expected.skipSyncTests = 1; 21 | expected.hideCursor = false; 22 | 23 | % test 24 | checkAllFields(cfg, expected); 25 | 26 | end 27 | 28 | function test_setDefaultsPtb_no_debug() 29 | 30 | % set up 31 | cfg.debug.do = false; 32 | cfg = checkCppPtbCfg(cfg); 33 | 34 | % test data 35 | expected = cppPtbDefaults('all'); 36 | expected.debug.do = 0; 37 | expected.skipSyncTests = 0; 38 | expected.hideCursor = true; 39 | 40 | % test 41 | checkAllFields(cfg, expected); 42 | 43 | end 44 | 45 | function test_setDefaultsPtb_overwrite() 46 | 47 | % set up 48 | cfg.screen.monitorWidth = 36; 49 | cfg = checkCppPtbCfg(cfg); 50 | 51 | % test data 52 | expected = cppPtbDefaults('all'); 53 | expected.screen.monitorWidth = 36; 54 | expected.eyeTracker.do = false; 55 | expected.skipSyncTests = 1; 56 | expected.hideCursor = false; 57 | 58 | % test 59 | checkAllFields(cfg, expected); 60 | 61 | end 62 | 63 | function test_setDefaultsPtb_audio() 64 | 65 | % set up 66 | cfg.audio.do = 1; 67 | cfg = checkCppPtbCfg(cfg); 68 | 69 | % test data 70 | expected = cppPtbDefaults('all'); 71 | expected.audio = struct('do', true, ... 72 | 'devIdx', [], ... 73 | 'playbackMode', 1, ... 74 | 'fs', 44100, ... 75 | 'channels', 2, ... 76 | 'initVolume', 1, ... 77 | 'requestedLatency', 3, ... 78 | 'repeat', 1, ... 79 | 'startCue', 0, ... 80 | 'waitForDevice', 1); 81 | 82 | expected.audio.pushSize = expected.audio.fs * 0.010; 83 | 84 | expected.audio.requestOffsetTime = 1; 85 | expected.audio.reqsSampleOffset = expected.audio.requestOffsetTime * ... 86 | expected.audio.fs; 87 | 88 | expected.eyeTracker.do = false; 89 | expected.skipSyncTests = 1; 90 | expected.hideCursor = false; 91 | 92 | % test 93 | checkAllFields(cfg, expected); 94 | 95 | end 96 | 97 | function test_setDefaultsPtb_mri() 98 | 99 | % set up 100 | cfg.testingDevice = 'mri'; 101 | cfg = checkCppPtbCfg(cfg); 102 | 103 | % test data 104 | expected = cppPtbDefaults('all'); 105 | expected.testingDevice = 'mri'; 106 | expected.bids.mri.RepetitionTime = []; 107 | expected.pacedByTriggers.do = false; 108 | expected.eyeTracker.do = false; 109 | expected.skipSyncTests = 1; 110 | expected.hideCursor = false; 111 | 112 | % test 113 | checkAllFields(cfg, expected); 114 | 115 | end 116 | 117 | function checkAllFields(cfg, expected) 118 | fields = fieldnames(expected); 119 | for i = 1:numel(fields) 120 | % fields{i} 121 | assertEqual(cfg.(fields{i}), expected.(fields{i})); 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /tests/test_computeFOV.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_computeFOV %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_computeFOVBasic() 12 | 13 | cfg.screen.monitorWidth = 25; 14 | cfg.screen.monitorDistance = 50; 15 | 16 | FOV = computeFOV(cfg); 17 | 18 | expectedFOV = 28.072; 19 | 20 | assertElementsAlmostEqual(expectedFOV, FOV, 'absolute', 1e-3); 21 | 22 | end 23 | 24 | function test_computeFOVError() 25 | 26 | cfg.screen.monitorDistance = 1.2; % error as distance is most likely in meter 27 | 28 | assertExceptionThrown(@()computeFOV(cfg), ... 29 | 'computeFOV:wrongDistanceToScreen'); 30 | 31 | end 32 | -------------------------------------------------------------------------------- /tests/test_computeRadialMotionDirection.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_computeRadialMotionDirection %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_computeRadialMotionDirectionBasic() 12 | 13 | %% set up 14 | 15 | cfg.dot.matrixWidth = 100; % in pixels 16 | cfg.timing.eventDuration = 2; 17 | 18 | dots.direction = 666; 19 | dots.positions = [ 20 | 100, 100 / 2; ... % middle of right side 21 | 100, 100; ... % top right corner 22 | 100 / 2, 100; ... 23 | 0, 100 / 2; ... 24 | 0, 0; ... 25 | 100 / 2, 0]; 26 | 27 | angleMotion = computeRadialMotionDirection(dots.positions, cfg.dot.matrixWidth, dots); 28 | 29 | expectedDirection = [ 30 | 0; ... right 31 | 45; ... up-right 32 | 90; ... up 33 | 180; ... left 34 | -135; ... down left 35 | -90]; % down 36 | 37 | %% test 38 | 39 | assertEqual(angleMotion, expectedDirection); 40 | 41 | end 42 | -------------------------------------------------------------------------------- /tests/test_decomposeMotion.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_decomposeMotion %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_decomposeMotionBasic() 12 | 13 | [horVector, vertVector] = decomposeMotion(0); 14 | expectedOutput = [1, 0]; 15 | assertEqual(expectedOutput, [horVector, vertVector]); 16 | 17 | [horVector, vertVector] = decomposeMotion(90); 18 | expectedOutput = [0, -1]; 19 | assertElementsAlmostEqual(expectedOutput, [horVector, vertVector]); 20 | 21 | [horVector, vertVector] = decomposeMotion(180); 22 | expectedOutput = [-1, 0]; 23 | assertElementsAlmostEqual(expectedOutput, [horVector, vertVector]); 24 | 25 | [horVector, vertVector] = decomposeMotion(270); 26 | expectedOutput = [0, 1]; 27 | assertElementsAlmostEqual(expectedOutput, [horVector, vertVector]); 28 | 29 | [horVector, vertVector] = decomposeMotion(360); 30 | expectedOutput = [1, 0]; 31 | assertElementsAlmostEqual(expectedOutput, [horVector, vertVector]); 32 | 33 | end 34 | -------------------------------------------------------------------------------- /tests/test_degToPix.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_degToPix %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_degToPixBasic() 12 | 13 | fixation.width = 2; 14 | cfg.screen.ppd = 10; 15 | 16 | fixation = degToPix('width', fixation, cfg); 17 | 18 | expectedStruct.width = 2; 19 | expectedStruct.widthPix = 20; 20 | 21 | assertEqual(expectedStruct, fixation); 22 | 23 | end 24 | -------------------------------------------------------------------------------- /tests/test_generateNewDotPositions.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_generateNewDotPositions %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_generateNewDotPositionsBasic() 12 | 13 | dotMatrixWidth = 400; 14 | dotNumber = 200; 15 | 16 | newPositions = generateNewDotPositions(dotMatrixWidth, dotNumber); 17 | 18 | assertEqual([200, 2], size(newPositions)); 19 | 20 | assertTrue(all(all([newPositions(:) <= 400, newPositions(:) >= 0]))); 21 | 22 | end 23 | -------------------------------------------------------------------------------- /tests/test_initDots.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_initDots %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_initDotsBasic() 12 | 13 | %% set up 14 | 15 | % % Dot life time in seconds 16 | % cfg.dot.lifeTime 17 | % % Number of dots 18 | % cfg.dot.number 19 | % Proportion of coherent dots. 20 | % cfg.dot.coherence 21 | % 22 | % % Direction (an angle in degrees) 23 | % thisEvent.direction 24 | % % Speed expressed in pixels per frame 25 | % thisEvent.speed 26 | 27 | cfg.design.motionType = 'translation'; 28 | cfg.dot.number = 10; 29 | cfg.dot.coherence = 1; % proportion 30 | cfg.dot.lifeTime = 0.250; % in seconds 31 | cfg.dot.matrixWidth = 50; % in pixels 32 | cfg.timing.eventDuration = 1; % in seconds 33 | cfg.screen.ifi = 0.01; % in seconds 34 | 35 | thisEvent.direction = 0; 36 | thisEvent.speedPix = 10; 37 | 38 | [dots] = initDots(cfg, thisEvent); 39 | 40 | %% Undeterministic output 41 | assertTrue(all(dots.positions(:) >= 0)); 42 | assertTrue(all(dots.positions(:) <= 50)); 43 | assertTrue(all(dots.time(:) >= 0)); 44 | assertTrue(all(dots.time(:) <= 1 / 0.01)); 45 | 46 | %% Deterministic output : data to test against 47 | expectedStructure.lifeTime = 25; 48 | expectedStructure.isSignal = ones(10, 1); 49 | expectedStructure.speeds = repmat([1 0], 10, 1) * 10; 50 | expectedStructure.speedPixPerFrame = 10; 51 | expectedStructure.direction = 0; 52 | 53 | % remove undeterministic output 54 | dots = rmfield(dots, 'time'); 55 | dots = rmfield(dots, 'positions'); 56 | 57 | %% test 58 | assertEqual(dots, expectedStructure); 59 | 60 | end 61 | 62 | function test_initDotsStatic() 63 | 64 | cfg.design.motionType = 'translation'; 65 | cfg.dot.number = 10; 66 | cfg.dot.coherence = 1; % proportion 67 | cfg.dot.lifeTime = 0.250; % in seconds 68 | cfg.dot.matrixWidth = 50; % in pixels 69 | cfg.timing.eventDuration = 1; % in seconds 70 | cfg.screen.ifi = 0.01; % in seconds 71 | 72 | thisEvent.direction = -1; 73 | thisEvent.speedPix = 10; 74 | 75 | [dots] = initDots(cfg, thisEvent); 76 | 77 | % remove undeterministic output 78 | dots = rmfield(dots, 'time'); 79 | dots = rmfield(dots, 'positions'); 80 | 81 | %% data to test against 82 | expectedStructure.lifeTime = Inf; 83 | expectedStructure.isSignal = ones(10, 1); 84 | expectedStructure.speeds = zeros(10, 2); 85 | expectedStructure.speedPixPerFrame = 0; 86 | expectedStructure.direction = -1; 87 | 88 | %% test 89 | assertEqual(dots, expectedStructure); 90 | 91 | end 92 | 93 | function test_initDotsRadial() 94 | 95 | %% set up 96 | 97 | % % Dot life time in seconds 98 | % cfg.dot.lifeTime 99 | % % Number of dots 100 | % cfg.dot.number 101 | % Proportion of coherent dots. 102 | % cfg.dot.coherence 103 | % 104 | % % Direction (an angle in degrees) 105 | % thisEvent.direction 106 | % % Speed expressed in pixels per frame 107 | % thisEvent.speed 108 | 109 | cfg.design.motionType = 'radial'; 110 | cfg.dot.number = 10; 111 | cfg.dot.coherence = 1; % proportion 112 | cfg.dot.lifeTime = 0.250; % in seconds 113 | cfg.dot.matrixWidth = 50; % in pixels 114 | cfg.timing.eventDuration = 1; % in seconds 115 | cfg.screen.ifi = 0.01; % in seconds 116 | 117 | thisEvent.direction = 666; 118 | thisEvent.speedPix = 10; 119 | 120 | [dots] = initDots(cfg, thisEvent); 121 | 122 | %% Deterministic output : data to test against 123 | expectedStructure.lifeTime = 25; 124 | expectedStructure.isSignal = ones(10, 1); 125 | expectedStructure.speedPixPerFrame = 10; 126 | expectedStructure.direction = 666; 127 | 128 | directionAllDots = setDotDirection( ... 129 | dots.positions, ... 130 | cfg, ... 131 | expectedStructure, ... 132 | expectedStructure.isSignal); 133 | [horVector, vertVector] = decomposeMotion(directionAllDots); 134 | if strcmp(cfg.design.motionType, 'radial') 135 | vertVector = vertVector * -1; 136 | end 137 | expectedStructure.speeds = [horVector, vertVector] * expectedStructure.speedPixPerFrame; 138 | 139 | % remove undeterministic output 140 | dots = rmfield(dots, 'time'); 141 | dots = rmfield(dots, 'positions'); 142 | 143 | %% test 144 | assertEqual(dots, expectedStructure); 145 | 146 | end 147 | -------------------------------------------------------------------------------- /tests/test_initFixation.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_initFixation %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_initFixationBasic() 12 | 13 | cfg.screen.ppd = 10; 14 | cfg.fixation.type = 'cross'; 15 | cfg.fixation.width = 1; 16 | cfg.fixation.xDisplacement = 1; 17 | cfg.fixation.yDisplacement = 1; 18 | 19 | cfg = initFixation(cfg); 20 | 21 | cfg.fixation; 22 | 23 | expectedStruct.screen.ppd = 10; 24 | expectedStruct.fixation.type = 'cross'; 25 | expectedStruct.fixation.width = 1; 26 | expectedStruct.fixation.xDisplacement = 1; 27 | expectedStruct.fixation.yDisplacement = 1; 28 | expectedStruct.fixation.widthPix = 10; 29 | expectedStruct.fixation.xDisplacementPix = 10; 30 | expectedStruct.fixation.yDisplacementPix = 10; 31 | expectedStruct.fixation.xCoords = [5 15 10 10]; 32 | expectedStruct.fixation.yCoords = [10 10 5 15]; 33 | expectedStruct.fixation.allCoords = [5 15 10 10; 10 10 5 15]; 34 | 35 | assertEqual(expectedStruct, cfg); 36 | 37 | end 38 | 39 | function test_initFixationBestFixation() 40 | 41 | cfg.screen.ppd = 10; 42 | cfg.screen.center = [100 100]; 43 | cfg.fixation.type = 'bestFixation'; 44 | cfg.fixation.width = 1; 45 | cfg.fixation.xDisplacement = 1; 46 | cfg.fixation.yDisplacement = 1; 47 | 48 | cfg = initFixation(cfg); 49 | 50 | cfg.fixation; 51 | 52 | expectedStruct.screen.ppd = 10; 53 | expectedStruct.screen.center = [100 100]; 54 | expectedStruct.fixation.type = 'bestFixation'; 55 | expectedStruct.fixation.width = 1; 56 | expectedStruct.fixation.xDisplacement = 1; 57 | expectedStruct.fixation.yDisplacement = 1; 58 | expectedStruct.fixation.widthPix = 10; 59 | expectedStruct.fixation.xDisplacementPix = 10; 60 | expectedStruct.fixation.yDisplacementPix = 10; 61 | expectedStruct.fixation.xCoords = [5 15 10 10]; 62 | expectedStruct.fixation.yCoords = [10 10 5 15]; 63 | expectedStruct.fixation.allCoords = [5 15 10 10; 10 10 5 15]; 64 | expectedStruct.fixation.outerOval = [95 95 105 105]; 65 | expectedStruct.fixation.innerOval = [100 - 10 / 6, 100 - 10 / 6, 100 + 10 / 6, 100 + 10 / 6]; 66 | 67 | assertEqual(expectedStruct, cfg); 68 | 69 | end 70 | -------------------------------------------------------------------------------- /tests/test_pixToDeg.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_pixToDeg %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_pixToDegBasic() 12 | 13 | fixation.widthPix = 20; 14 | cfg.screen.ppd = 10; 15 | 16 | fixation = pixToDeg('widthPix', fixation, cfg); 17 | 18 | expectedStruct.widthDegVA = 2; 19 | expectedStruct.widthPix = 20; 20 | 21 | assertEqual(expectedStruct, fixation); 22 | 23 | end 24 | -------------------------------------------------------------------------------- /tests/test_repeatShuffleConditions.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_repeatShuffleConditions %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_repeatShuffleConditionsBasic() 12 | 13 | baseVector = [1 2 3 4]; 14 | nbRepeats = 2; 15 | 16 | shuffledRepeats = repeatShuffleConditions(baseVector, nbRepeats); 17 | 18 | % make sure no condition is repeated twice 19 | assertFalse(any(diff(shuffledRepeats, [], 2) == 0)); 20 | assertTrue(length(shuffledRepeats) == (nbRepeats * length(baseVector))); 21 | 22 | end 23 | -------------------------------------------------------------------------------- /tests/test_reseedDots.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_reseedDots %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_reseedDotsBasic() 12 | 13 | dotNb = 5; 14 | 15 | cfg.design.motionType = 'translation'; 16 | cfg.timing.eventDuration = 1; % in seconds 17 | cfg.screen.ifi = 0.01; % in seconds 18 | 19 | cfg.dot.matrixWidth = 1000; % in pixels 20 | cfg.dot.number = dotNb; 21 | cfg.dot.sizePix = 20; 22 | cfg.dot.proportionKilledPerFrame = 0; 23 | 24 | cfg.fixation.widthPix = 5; 25 | 26 | dots.lifeTime = 100; 27 | dots.speedPixPerFrame = 3; 28 | dots.direction = 90; 29 | dots.isSignal = true(dotNb, 1); 30 | dots.speeds = ones(dotNb, 2); 31 | 32 | dots.positions = [ ... 33 | 300, 10 % OK 34 | 750, 1010 % out of frame 35 | -1040, 50 % out of frame 36 | 300, 300 % OK 37 | 500, 500 % on the fixation cross 38 | ]; 39 | 40 | originalTime = [ ... 41 | 6; ... OK 42 | 4; ... OK 43 | 56; ... OK 44 | 300; ... % exceeded its life time 45 | 50]; % OK 46 | dots.time = originalTime; 47 | 48 | dots = reseedDots(dots, cfg); 49 | 50 | assertEqual(dots.time(1), originalTime(1)); 51 | assertTrue(all(dots.time(2:end) ~= originalTime(2:end))); 52 | 53 | end 54 | -------------------------------------------------------------------------------- /tests/test_seedDots.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_seedDots %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_seedDotsBasic() 12 | 13 | %% set up 14 | 15 | cfg.dot.matrixWidth = 400; 16 | cfg.design.motionType = 'translation'; 17 | cfg.timing.eventDuration = 1; % in seconds 18 | cfg.screen.ifi = 0.01; % in seconds 19 | 20 | nbDots = 10; 21 | isSignal = [true(5, 1); false(nbDots - 5, 1)]; 22 | 23 | dots.direction = 0; 24 | dots.speedPixPerFrame = 10; 25 | 26 | [positions, speeds, time] = seedDots(dots, cfg, isSignal); 27 | 28 | %% Deterministic output 29 | assertEqual(size(positions), [nbDots, 2]); 30 | assertTrue(all(all([ ... 31 | positions(:) <= cfg.dot.matrixWidth, ... 32 | positions(:) >= 0]))); 33 | 34 | assertTrue(all(time(:) >= 0)); 35 | assertTrue(all(time(:) <= 1 / 0.01)); 36 | 37 | assertEqual(speeds(1:5, :), repmat([10 0], 5, 1)); 38 | 39 | end 40 | -------------------------------------------------------------------------------- /tests/test_setDefaultFields.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_setDefaultFields %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_setDefaultsWrite() 12 | 13 | %% set up 14 | structure = struct(); 15 | 16 | fieldsToSet.field = 1; 17 | 18 | structure = setDefaultFields(structure, fieldsToSet); 19 | 20 | %% data to test against 21 | expectedStructure.field = 1; 22 | 23 | %% test 24 | assertEqual(expectedStructure, structure); 25 | 26 | end 27 | 28 | function test_setDefaultsNoOverwrite() 29 | 30 | % set up 31 | structure.field.subfield_1 = 3; 32 | 33 | fieldsToSet.field.subfield_1 = 1; 34 | fieldsToSet.field.subfield_2 = 1; 35 | 36 | structure = setDefaultFields(structure, fieldsToSet); 37 | 38 | % data to test against 39 | expectedStructure.field.subfield_1 = 3; 40 | expectedStructure.field.subfield_2 = 1; 41 | 42 | % test 43 | assert(isequal(expectedStructure, structure)); 44 | 45 | end 46 | -------------------------------------------------------------------------------- /tests/test_setDotDirection.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_setDotDirection %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_setDotDirectionInit() 12 | % create 5 coherent dots with direction == 362 (that should give 2 in the 13 | % end) 14 | % also creates additional dots with random direction between 0 and 360 15 | 16 | nbDots = 10; 17 | 18 | cfg.dot.matrixWidth = 400; 19 | cfg.design.motionType = 'translation'; 20 | 21 | dots.direction = 362; 22 | dots.isSignal = [true(5, 1); false(nbDots - 5, 1)]; 23 | 24 | positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); 25 | 26 | directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); 27 | 28 | assertEqual(directionAllDots(1:5), 2 * ones(5, 1)); 29 | assertGreaterThan(directionAllDots, zeros(size(directionAllDots))); 30 | assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); 31 | 32 | end 33 | 34 | function test_setDotDirectionReturn() 35 | % make sure that if the directions are already set it only changes that of 36 | % the noise dots 37 | % input has 4 signal dots with set directions also has additional noise dots 38 | % with negative direction 39 | 40 | nbDots = 8; 41 | 42 | cfg.dot.matrixWidth = 400; 43 | cfg.design.motionType = 'translation'; 44 | 45 | dots.direction = [ ... 46 | [362; 2; -362; -2]; ... 47 | -20 * ones(4, 1)]; 48 | dots.isSignal = [true(4, 1); false(nbDots - 4, 1)]; 49 | 50 | positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); 51 | 52 | directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); 53 | 54 | assertEqual(directionAllDots(1:4), [2 2 358 358]'); 55 | assertGreaterThan(directionAllDots, zeros(size(directionAllDots))); 56 | assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); 57 | assertTrue(all(directionAllDots(5:end) ~= -20 * ones(4, 1))); 58 | 59 | end 60 | -------------------------------------------------------------------------------- /tests/test_setTargetPositionInSequence.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_setTargetPositionInSequence %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | 5 | initEnv(); 6 | 7 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 8 | test_functions = localfunctions(); %#ok<*NASGU> 9 | catch % no problem; early Matlab versions can use initTestSuite fine 10 | end 11 | initTestSuite; 12 | 13 | end 14 | 15 | function test_setTargetPositionInSequenceBasic() 16 | 17 | seqLength = 12; 18 | nbTarget = 3; 19 | forbiddenPos = [1 5 10]; 20 | 21 | % Create a hundred draws of target positiona and ensure that 22 | % - the forbidden position are never drawn 23 | % - the interval between target is superior to 1 24 | for i = 1:100 25 | chosenPositions(i, :) = setTargetPositionInSequence(seqLength, nbTarget, forbiddenPos); 26 | end 27 | 28 | assertFalse(any(ismember(chosenPositions(:), forbiddenPos))); 29 | 30 | interval = abs(diff(chosenPositions, [], 2)); 31 | assertTrue(all(interval(:) > 1)); 32 | 33 | end 34 | 35 | function initEnv 36 | 37 | installlist = {'io', 'statistics'}; 38 | 39 | if isOctave 40 | 41 | for ii = 1:length(installlist) 42 | 43 | packageName = installlist{ii}; 44 | 45 | try 46 | % Try loading Octave packages 47 | disp(['loading ' packageName]); 48 | pkg('load', packageName); 49 | 50 | catch 51 | tryInstallFromForge(packageName); 52 | end 53 | end 54 | 55 | end 56 | 57 | end 58 | 59 | function tryInstallFromForge(packageName) 60 | 61 | errorcount = 1; 62 | while errorcount % Attempt twice in case installation fails 63 | try 64 | pkg('install', '-forge', packageName); 65 | pkg('load', packageName); 66 | errorcount = 0; 67 | catch err 68 | errorcount = errorcount + 1; 69 | if errorcount > 2 70 | error(err.message); 71 | end 72 | end 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /tests/test_utils.m: -------------------------------------------------------------------------------- 1 | function test_suite = test_utils %#ok<*STOUT> 2 | % 3 | % (C) Copyright 2020 CPP_PTB developers 4 | try % assignment of 'localfunctions' is necessary in Matlab >= 2016 5 | test_functions = localfunctions(); %#ok<*NASGU> 6 | catch % no problem; early Matlab versions can use initTestSuite fine 7 | end 8 | initTestSuite; 9 | end 10 | 11 | function test_utilsBasic() 12 | 13 | printCreditsCppPtb(); 14 | 15 | end 16 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | v1.3.0 2 | --------------------------------------------------------------------------------