├── .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 | [](https://cpp-ptb.readthedocs.io/en/stable/?badge=stable)
2 | [](https://doi.org/10.5281/zenodo.4007672)
3 | [](https://github.com/cpp-lln-lab/CPP_PTB/actions/workflows/run_tests.yml)
4 | [](https://codecov.io/gh/cpp-lln-lab/CPP_PTB)
5 | [](#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 |
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 |
--------------------------------------------------------------------------------