diff --git a/.github/workflows/update_submodules.yml b/.github/workflows/update_submodules.yml index 3505a04..ee2be70 100644 --- a/.github/workflows/update_submodules.yml +++ b/.github/workflows/update_submodules.yml @@ -30,8 +30,6 @@ name: update submodules on: push: branches: - - main - - master - dev schedule: - cron: "0 0 1 * *" diff --git a/.gitignore b/.gitignore index 7c5694d..6e55f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,9 @@ test_report.log */logfiles/* */*/*/logfiles/* *.tsv -*.mat output +source ## virtual env env/ @@ -19,6 +19,10 @@ venv/ ## visual studio code .vscode +# exclude temp files from tests and coverage +test_report.log +*coverage* + ## MATLAB / OCTAVE gitignore template # From : https://github.com/github/gitignore/blob/master/Global/MATLAB.gitignore diff --git a/.gitmodules b/.gitmodules index dee3ca7..a0e66af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,8 @@ [submodule "lib/CPP_PTB"] path = lib/CPP_PTB url = https://github.com/cpp-lln-lab/CPP_PTB.git - branch = master + branch = dev [submodule "lib/CPP_BIDS"] path = lib/CPP_BIDS url = https://github.com/cpp-lln-lab/CPP_BIDS.git - branch = master + branch = dev diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..9ba1bff --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,57 @@ +cff-version: 1.2.0 + +title: "CPP localizer visual motion" + +version: 0.4.0dev + +abstract: A localizer for visual motion sensitive brain regions using RDK. + +message: "If you use this software, please cite it as below." + +repository-code: "https://github.com/cpp-lln-lab/localizer_visual_motion.git" + +identifiers: + - description: This is the collection of archived snapshots of all releases + type: doi + value: "10.5281/zenodo.4007674" + +contact: + - affiliation: "Université catholique de Louvain" + email: remi.gau@uclouvain.be + family-names: Gau + given-names: Rémi + +authors: + - family-names: "Gau" + given-names: "Rémi" + orcid: "https://orcid.org/0000-0002-1535-9767" + affiliation: "Université catholique de Louvain" + + - family-names: "Barilari" + given-names: "Marco" + orcid: "https://orcid.org/0000-0002-3313-3120" + affiliation: "Université catholique de Louvain" + + - family-names: "Battal" + given-names: "Ceren" + orcid: "https://orcid.org/0000-0002-9844-7630" + affiliation: "Université catholique de Louvain" + + - family-names: "Rezk" + given-names: "Mohamed" + orcid: "https://orcid.org/0000-0002-1866-8645" + affiliation: "Université catholique de Louvain" + + - family-names: "Shahzad" + given-names: "Iqra" + orcid: "https://orcid.org/0000-0002-8724-7668" + affiliation: "Université catholique de Louvain" + +license: MIT license + +keywords: + - BIDS + - brain imaging data structure + - neuroscience + - MATLAB + - Octave diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d630a2e --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +# TODO make more general to use the local matlab version +MATLAB = /usr/local/MATLAB/R2017a/bin/matlab +ARG = -nodisplay -nosplash -nodesktop + +.PHONY: clean manual fix_submodule +clean: clean + rm -rf version.txt + +fix_submodule: + git submodule update --init --recursive && git submodule update --recursive + +lint: + mh_style --fix && mh_metric --ci && mh_lint + +test: + $(MATLAB) $(ARG) -r "runTests; exit()" + +version.txt: CITATION.cff + grep -w "^version" CITATION.cff | sed "s/version: /v/g" > version.txt + +validate_cff: CITATION.cff + cffconvert --validate diff --git a/README.md b/README.md index 1a1a1b1..fbc4ce4 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,28 @@ [![](https://img.shields.io/badge/Octave-CI-blue?logo=Octave&logoColor=white)](https://github.com/cpp-lln-lab/localizer_visual_motion/actions) ![](https://github.com/cpp-lln-lab/localizer_visual_motion/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/cpp-lln-lab/localizer_visual_motion/branch/master/graph/badge.svg)](https://codecov.io/gh/cpp-lln-lab/localizer_visual_motion) - [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) - - - [fMRI localizers for visual motion](#fmri-localizers-for-visual-motion) - [Requirements](#requirements) - [Installation](#installation) - - [Structure and function details](#structure-and-function-details) - - [visualMotionLocalizer](#visualmotionlocalizer) - - [setParameters](#setparameters) - - [Let the scanner pace the experiment](#let-the-scanner-pace-the-experiment) - - [subfun/doDotMo](#subfundodotmo) - - [Input](#input) - - [Output](#output) - - [subfun/design/expDesign](#subfundesignexpdesign) - - [Events](#events) - - [Pseudorandomization rules:](#pseudorandomization-rules) - - [Input:](#input-1) - - [Output:](#output-1) + - [Set up and running](#set-up-and-running) - [Contributors ✨](#contributors-) - # fMRI localizers for visual motion +Running this script will show blocks of motion dots and static dots. Motion +blocks will show: + +- dots moving in one of four directions (up-, down-, left-, and right-ward) (MT+ + localizer) +- or dots moving inward and outward in the peripheral of the screen (MT/MST + localizer). + ## Requirements -Make sure that the following toolboxes are installed and added to the matlab / octave path. See the next section on how to install the submodule toolboxes. +Make sure that the following toolboxes are installed and added to the matlab / +octave path. See the next section on how to install the submodule toolboxes. For instructions see the following links: @@ -42,129 +36,50 @@ For instructions see the following links: ## Installation -The CPP_BIDS and CPP_PTB dependencies are already set up as submodule to this repository. -You can install it all with git by doing. +The CPP_BIDS and CPP_PTB dependencies are already set up as submodules to this +repository. You can install it all with git by doing. ```bash git clone --recurse-submodules https://github.com/cpp-lln-lab/localizer_visual_motion.git ``` -## Structure and function details - -### visualMotionLocalizer - -Running this script will show blocks of motion dots and static dots. Motion blocks will show dots moving in one of four directions (up-, down-, left-, and right-ward) (MT+ localizer) or dots moving inward and outward in the peripheral of the screen (MT/MST localizer). - -Run in `Debug mode` (see `setParameters.m`) it does not care about subjID, run n., Eye Tracker (soon, at the moment it needs to be set off manually), etc.. - -Any details of the experiment can be changed in `setParameters.m` (e.g., experiment mode, motion stimuli details, exp. design, etc.) +## Set up and running -### setParameters +In the `main.m` script, you are meant -`setParameters.m` is the core engine of the experiment. It contains the following tweakable sections: +- to set your configuration (`cfg`) +- call `initEnv()` to add the relevant folders to the MATLAB path +- call `cfg = checkParameters(cfg)` to set up any default configuration you did + not set. +- call `visualMotionLocalizer(cfg)` to run the localizer. -- Debug mode setting -- MRI settings -- Engine parameters: - - Monitor parameters - - Monitor parameters for PsychToolBox -- Keyboards -- Experiment Design -- Visual Stimulation -- Task(s) - - Instructions - - Task #1 parameters - -#### Let the scanner pace the experiment - -Set `cfg.pacedByTriggers.do` to `true` and you can then set all the details in this `if` block +The minimalist script would thus look like: ```matlab -% Time is here in terms of `repetition time (TR)` (i.e. MRI volumes) -if cfg.pacedByTriggers.do +clc; +clear; - cfg.pacedByTriggers.quietMode = true; - cfg.pacedByTriggers.nbTriggers = 1; +%% Run MT+ localizer - cfg.timing.eventDuration = cfg.mri.repetitionTime / 2 - 0.04; % second +cfg.design.localizer = 'MT'; +initEnv(); - % Time between blocs in secs - cfg.timing.IBI = 0; - % Time between events in secs - cfg.timing.ISI = 0; - % Number of seconds before the motion stimuli are presented - cfg.timing.onsetDelay = 0; - % Number of seconds after the end all the stimuli before ending the run - cfg.timing.endDelay = 2; +cfg = checkParameters(cfg); -end +% Run +visualMotionLocalizer(cfg); ``` -### subfun/doDotMo - -Wrapper function that present the dot stimulation (static or motion) per event. - -#### Input - -- `cfg`: PTB/machine and experiment configurations returned by `setParameters` and `initPTB` -- `logFile`: structure that stores the experiment logfile to be saved -- `thisEvent`: structure that stores information about the event to present regarding the dots (static or motion, direction, etc.) -- `thisFixation`: structure that stores information about the fixation cross task to present -- `dots`: [...] -- `iEvent`: index of the event of the block at the moment of the presentation - -#### Output - -- Event `onset` -- Event `duration` -- `dots`: [...] - -> NB: The dots are drawn on a square that contains the round aperture, then any dots outside of the aperture is turned into a NaN so effectively the actual number of dots on the screen at any given time is not the one that you input but a smaller number (nDots / Area of aperture) on average. - -### subfun/design/expDesign - -This function and its companions creates the sequence of blocks (static/motion) and the events (the single directions) for MT+ and MT/MST localizers. The conditions are consecutive static and motion blocks (fixed in this order gives better results than randomised). - -It can be run as a stand alone without inputs and displays a visual example of the possible design. See `getMockConfig` to set up the mock configuration. - -It computes the directions to display and the task(s), at the moment: -1. detection of change in the color of the fixation target -2. detection of different speed of the moving dots [ W I P - if selected as a task it will give the same null output as if not selected ie no difference in speed] - -#### Events - -The ``nbEventsPerBlock`` should be a multiple of the number of motion directions requested in ``motionDirections`` (which should be more than 1) e.g.: -- MT localizer: `cfg.design.motionDirections = [ 0 90 180 270 ]; % right down left up` -- MT_MST localizer: `cfg.design.motionDirections = [666 -666]; % outward inward` - -#### Pseudorandomization rules: - -- Directions: -1. Directions are all presented in random orders in `numEventsPerBlock/nDirections` consecutive chunks. This evenly distribute the directions across the block. -2. No same consecutive direction - -- Color change detection of the fixation cross: -1. If there are 2 targets per block we make sure that they are at least 2 events apart. -2. Targets cannot be on the first or last event of a block. -3. No less than 1 target per event position in the whole run - -#### Input: -- `cfg`: parameters returned by setParameters -- `displayFigs`: a boolean to decide whether to show the basic design matrix of the design +Type `help checkParameters` and see the [README in docs](./docs/README.md) to +get more information about the configuration options. -#### Output: -- `cfg.design.blockNames`: cell array (nbBlocks, 1) with the condition name for each block -- `cfg.design.nbBlocks`: integer for th etotal number of blocks in the run -- `cfg.design.directions`: array (nbBlocks, nbEventsPerBlock) with the direction to present in a given event of a block. - - 0 90 180 270 indicate the angle for translational motion direction - - 666 -666 indicate in/out-ward direction in radial motion - - -1 indicates static -- `cfg.design.speeds`: array (nbBlocks, nbEventsPerBlock) indicate the dots speed in each event, the target is represented by a higher/lower value -- `cfg.design.fixationTargets`: array (nbBlocks, numEventsPerBlock) showing for each event if it should be accompanied by a target +Run in debug mode (set `cfg.debug.do = true`) it does not care about subjID, run +n., Eye Tracker... ## Contributors ✨ -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): +Thanks goes to these wonderful people +([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -184,4 +99,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! +This project follows the +[all-contributors](https://github.com/all-contributors/all-contributors) +specification. Contributions of any kind welcome! diff --git a/cfgMST.m b/cfgMST.m new file mode 100644 index 0000000..f2eb762 --- /dev/null +++ b/cfgMST.m @@ -0,0 +1,44 @@ +function cfg = cfgMST(cfg) + % + % (C) Copyright 2020 CPP visual motion localizer developers + + cfg.design.localizer = 'MT_MST'; + + cfg.design.nbRepetitions = 10; + + cfg.design.nbEventsPerBlock = 10; + + % in Liege is 0.6 + % on macs is 0.3 + cfg.timing.eventDuration = 0.6; % 0.6 seconds + + %% variable FOV info + % in case the field of view is not properly centered or obstructed + % + % see https://github.com/cpp-lln-lab/estimate_visual_FOV.git + % + % set up configuration: ensure that the following fields are the same + % as when you ran the estimate_visual_FOV script + % + % cfg.testingDevice + % cfg.screen.monitorDistance + % cfg.screen.monitorWidth + + % fixation cross displacement in degrees of visual angles + % this will also shift the whole FOV relative to the center of the screen + % Note: negative values will move things to the left and up. + cfg.fixation.xDisplacement = -3.676540; + cfg.fixation.yDisplacement = -0.499724; + + % determines position of the fixation cross on the right / left + % should be a bit less than the: ( width of FOV ) / 2 + cfg.design.xDisplacementFixation = 3; + + % determines position of the dots on the left / + % should be a bit less than the: ( width of FOV ) / 2 + cfg.design.xDisplacementAperture = 7; + + % determines the width of the dot circle + cfg.aperture.width = 7; + +end diff --git a/cfgMT.m b/cfgMT.m new file mode 100644 index 0000000..7702270 --- /dev/null +++ b/cfgMT.m @@ -0,0 +1,48 @@ +function cfg = cfgMT(cfg) + % + % (C) Copyright 2020 CPP visual motion localizer developers + + cfg.design.localizer = 'MT'; + + cfg.pacedByTriggers.do = false; + + cfg.timing.triggerIBI = 4; + + cfg.mri.triggerNb = 5; + + % Time between events in secs + cfg.timing.ISI = 0; + % Time between blocs in secs + cfg.timing.IBI = 5.4; + % Number of seconds before the motion stimuli are presented + cfg.timing.onsetDelay = 0; + % Number of seconds after the end all the stimuli before ending the run + cfg.timing.endDelay = 0; + + cfg.design.nbRepetitions = 15; + + cfg.design.nbEventsPerBlock = 12; + + % in Liege is 0.79 (tsry less) + % in mcas is 0.43 + cfg.timing.eventDuration = 0.75; % .86 second + + %% variable FOV info + % in case the field of view is not properly centered or obstructed + % + % see https://github.com/cpp-lln-lab/estimate_visual_FOV.git + % + % set up configuration: ensure that the following fields are the same + % as when you ran the estimate_visual_FOV script + % + % cfg.testingDevice + % cfg.screen.monitorDistance + % cfg.screen.monitorWidth + + % fixation cross displacement in degrees of visual angles + % this will also shift the whole FOV relative to the center of the screen + % Note: negative values will move things to the left and up. + cfg.fixation.xDisplacement = -3.676540; + cfg.fixation.yDisplacement = -0.499724; + +end diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 25f8d45..0000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -# Documentation diff --git a/docs/configurations.md b/docs/configurations.md new file mode 100644 index 0000000..18dae77 --- /dev/null +++ b/docs/configurations.md @@ -0,0 +1,133 @@ +# Configurations + +- [Configurations](#configurations) + - [For MT localiser](#for-mt-localiser) + - [Let the scanner pace the experiment](#let-the-scanner-pace-the-experiment) + +## For MT localiser + +These are all the options that can be tweaked. + +```matlab +cfg.aperture.type = 'none'; +cfg.aperture.width = []; +cfg.aperture.xPos = 0.000000; +cfg.audio.do = 0; +cfg.bids.MRI.Instructions = '1-Detect the RED fixation cross'; +cfg.bids.MRI.TaskDescription = ''; +cfg.bids.mri.RepetitionTime = []; +cfg.color.background = 0.000000, 0.000000, 0.000000; +cfg.color.black = 0.000000, 0.000000, 0.000000; +cfg.color.blue = 0.000000, 255.000000, 0.000000; +cfg.color.green = 0.000000, 0.000000, 255.000000; +cfg.color.grey = 127.500000, 127.500000, 127.500000; +cfg.color.red = 255.000000, 0.000000, 0.000000; +cfg.color.white = 255.000000, 255.000000, 255.000000; +cfg.debug.do = 1; +cfg.debug.smallWin = 1; +cfg.debug.transpWin = 1; +cfg.design.localizer = 'MT'; +cfg.design.motionDirections = 0.000000, 0.000000, 180.000000, 180.000000; +cfg.design.motionType = 'translation'; +cfg.design.names{1} = 'static'; +cfg.design.names{2} = 'motion'; +cfg.design.nbEventsPerBlock = 12.000000; +cfg.design.nbRepetitions = 12.000000; +cfg.dot.coherence = 1.000000; +cfg.dot.color = 255.000000, 255.000000, 255.000000; +cfg.dot.density = 1.000000; +cfg.dot.lifeTime = 0.400000; +cfg.dot.proportionKilledPerFrame = 0.000000; +cfg.dot.size = 0.200000; +cfg.dot.speed = 15.000000; +cfg.dot.staticReSeed = 1; +cfg.extraColumns{1} = 'direction'; +cfg.extraColumns{2} = 'speedDegVA'; +cfg.extraColumns{3} = 'target'; +cfg.extraColumns{4} = 'event'; +cfg.extraColumns{5} = 'block'; +cfg.extraColumns{6} = 'keyName'; +cfg.extraColumns{7} = 'fixationPosition'; +cfg.extraColumns{8} = 'aperturePosition'; +cfg.eyeTracker.do = 0; +cfg.fixation.color = 255.000000, 255.000000, 255.000000; +cfg.fixation.colorTarget = 255.000000, 0.000000, 0.000000; +cfg.fixation.lineWidthPix = 3.000000; +cfg.fixation.type = 'cross'; +cfg.fixation.width = 0.250000; +cfg.fixation.xDisplacement = 0.000000; +cfg.fixation.yDisplacement = 0.000000; +cfg.hideCursor = 0; +cfg.keyboard.escapeKey = 'ESCAPE'; +cfg.keyboard.keyboard = []; +cfg.keyboard.responseBox = []; +cfg.keyboard.responseKey{1} = 'r'; +cfg.keyboard.responseKey{2} = 'g'; +cfg.keyboard.responseKey{3} = 'y'; +cfg.keyboard.responseKey{4} = 'b'; +cfg.keyboard.responseKey{5} = 'd'; +cfg.keyboard.responseKey{6} = 'n'; +cfg.keyboard.responseKey{7} = 'z'; +cfg.keyboard.responseKey{8} = 'e'; +cfg.keyboard.responseKey{9} = 't'; +cfg.mri.repetitionTime = 1.800000; +cfg.mri.triggerKey = 't'; +cfg.mri.triggerNb = 5.000000; +cfg.pacedByTriggers.do = 0; +cfg.screen.monitorDistance = 95.000000; +cfg.screen.monitorWidth = 25.000000; +cfg.screen.resolution{1} = []; +cfg.screen.resolution{2} = []; +cfg.screen.resolution{3} = []; +cfg.skipSyncTests = 1.000000; +cfg.suffix.acquisition = ''; +cfg.target.duration = 0.100000; +cfg.target.maxNbPerBlock = 1.000000; +cfg.target.type = 'fixation_cross'; +cfg.task.instruction = '1-Detect the RED fixation cross'; +cfg.task.name = 'visual localizer'; +cfg.task.taskDescription = ''; +cfg.testingDevice = 'mri'; +cfg.text.color = 255.000000, 255.000000, 255.000000; +cfg.text.font = 'Courier New'; +cfg.text.size = 18.000000; +cfg.text.style = 1.000000; +cfg.timing.IBI = 4.000000; +cfg.timing.ISI = 0.100000; +cfg.timing.endDelay = 5.000000; +cfg.timing.eventDuration = 0.300000; +cfg.timing.onsetDelay = 5.000000; +cfg.verbose = 1.000000; +``` + +## + +Type help `expDesign` to get information on how to tweak your experiment design. + +## Let the scanner pace the experiment + + + +Set `cfg.pacedByTriggers.do` to `true` and you can then set all the details in +this `if` block + +```matlab +% Time is here in terms of `repetition time (TR)` (i.e. MRI volumes) +if cfg.pacedByTriggers.do + + cfg.pacedByTriggers.quietMode = true; + cfg.pacedByTriggers.nbTriggers = 1; + + cfg.timing.eventDuration = cfg.mri.repetitionTime / 2 - 0.04; % second + + % Time between blocs in secs + cfg.timing.IBI = 0; + % Time between events in secs + cfg.timing.ISI = 0; + % Number of seconds before the motion stimuli are presented + cfg.timing.onsetDelay = 0; + % Number of seconds after the end all the stimuli before ending the run + cfg.timing.endDelay = 2; + +end +``` diff --git a/initEnv.m b/initEnv.m index 7685413..a6c42bc 100644 --- a/initEnv.m +++ b/initEnv.m @@ -1,5 +1,5 @@ % (C) Copyright 2020 Agah Karakuzu -% (C) Copyright 2019 CPP visual motion localizer developpers +% (C) Copyright 2019 CPP visual motion localizer developers function initEnv % 1 - Check if version requirements @@ -15,6 +15,8 @@ % % 2 - Add project to the O/M path + more off; + octaveVersion = '4.0.3'; matlabVersion = '8.6.0'; @@ -102,6 +104,7 @@ function addDependencies() pth = fileparts(mfilename('fullpath')); addpath(fullfile(pth, 'lib', 'CPP_BIDS')); addpath(genpath(fullfile(pth, 'lib', 'CPP_PTB', 'src'))); + addpath(genpath(fullfile(pth, 'lib', 'CPP_PTB', 'lib'))); addpath(genpath(fullfile(pth, 'subfun'))); checkCppBidsDependencies(); diff --git a/lib/CPP_BIDS b/lib/CPP_BIDS index 466e6ef..052381b 160000 --- a/lib/CPP_BIDS +++ b/lib/CPP_BIDS @@ -1 +1 @@ -Subproject commit 466e6efa0b6d0c83c0c8a26e915c6ee13be68c15 +Subproject commit 052381b32ce037d1adb90eb512717d486a26628a diff --git a/lib/CPP_PTB b/lib/CPP_PTB index 25825c2..91e4d87 160000 --- a/lib/CPP_PTB +++ b/lib/CPP_PTB @@ -1 +1 @@ -Subproject commit 25825c21bdba77740501c1c73aed9df5ae0ce340 +Subproject commit 91e4d87c3ecb73047ea37f4568143608302a3b92 diff --git a/main.m b/main.m new file mode 100644 index 0000000..c402f87 --- /dev/null +++ b/main.m @@ -0,0 +1,40 @@ +% set your options here and then call visualMotionLocalizer(cfg) +% +% (C) Copyright 2020 CPP visual motion localizer developers + +clc; +clear; + +initEnv(); + +cfg.subject.subjectGrp = 'pilot'; +cfg.subject.sessionNb = 1; +cfg.subject.askGrpSess = [true false]; + +cfg.verbose = 1; + +cfg.debug.do = false; +cfg.debug.transpWin = false; +cfg.debug.smallWin = false; + +cfg.audio.devIdx = 5; + +cfg.eyeTracker.do = true; + +% in liege 7.5 +% on Marco's mac 15 +cfg.dot.speed = 7.5; + +%% Run MT+ localizer +% cfg = cfgMT(cfg); + +%% Run MT/MST localizer +cfg = cfgMST(cfg); + +cfg = checkParameters(cfg); + +% to view all the options that are set +% unfold(cfg); + +% run +visualMotionLocalizer(cfg); diff --git a/miss_hit.cfg b/miss_hit.cfg index 147dd54..84e5fda 100644 --- a/miss_hit.cfg +++ b/miss_hit.cfg @@ -8,7 +8,9 @@ regex_script_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" exclude_dir: "lib" copyright_entity: "Mohamed Rezk" copyright_entity: "Agah Karakuzu" -copyright_entity: "CPP visual motion localizer developpers" +copyright_entity: "R.F. Tap" +copyright_entity: "Remi Gau" +copyright_entity: "CPP visual motion localizer developers" # metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html) metric "cnest": limit 4 diff --git a/runTests.m b/runTests.m new file mode 100644 index 0000000..ad48f59 --- /dev/null +++ b/runTests.m @@ -0,0 +1,38 @@ +function runTests() + % + % (C) Copyright 2022 CPP visual motion localizer developers + + % Elapsed time is ??? seconds. + + tic; + + initEnv(); + + if bids.internal.is_github_ci + fprintf(1, '\nThis is github CI\n'); + else + fprintf(1, '\nThis is not github CI\n'); + end + + fprintf('\nHome is %s\n', getenv('HOME')); + + warning('OFF'); + + folderToCover = fullfile(pwd, 'subfun'); + testFolder = fullfile(pwd, 'tests'); + + success = moxunit_runtests(testFolder, ... + '-verbose', '-recursive', '-with_coverage', ... + '-cover', folderToCover, ... + '-cover_xml_file', 'coverage.xml', ... + '-cover_html_dir', fullfile(pwd, 'coverage_html')); + + if success + system('echo 0 > test_report.log'); + else + system('echo 1 > test_report.log'); + end + + toc; + +end diff --git a/setParameters.m b/setParameters.m deleted file mode 100755 index c2c7b83..0000000 --- a/setParameters.m +++ /dev/null @@ -1,252 +0,0 @@ -% (C) Copyright 2020 CPP visual motion localizer developpers - -function [cfg] = setParameters() - - % VISUAL LOCALIZER - - % Initialize the general configuration variables structure - cfg = struct(); - - % by default the data will be stored in an output folder created where the - % setParamters.m file is - % change that if you want the data to be saved somewhere else - cfg.dir.output = fullfile( ... - fileparts(mfilename('fullpath')), 'output'); - - %% Debug mode settings - - cfg.debug.do = true; % To test the script out of the scanner, skip PTB sync - cfg.debug.smallWin = false; % To test on a part of the screen, change to 1 - cfg.debug.transpWin = false; % To test with trasparent full size screen - - cfg.skipSyncTests = 0; - - cfg.verbose = 1; - - %% Engine parameters - - cfg.testingDevice = 'mri'; - cfg.eyeTracker.do = false; - cfg.audio.do = false; - - cfg = setMonitor(cfg); - - % Keyboards - cfg = setKeyboards(cfg); - - % MRI settings - cfg = setMRI(cfg); - % cfg.suffix.acquisition = ''; - - cfg.pacedByTriggers.do = false; - - %% Experiment Design - - % switching this on to MT or MT/MST with use: - % - MT: translational motion on the whole screen - % - alternates static and motion (left or right) blocks - % - MST: radial motion centered in a circle aperture that is on the opposite - % side of the screen relative to the fixation - % - alternates fixaton left and fixation right - % cfg.design.localizer = 'MT'; - cfg.design.localizer = 'MT_MST'; - - cfg.design.motionType = 'translation'; - cfg.design.motionDirections = [0 0 180 180]; - cfg.design.names = {'static'; 'motion'}; - - % if you have static and motion and `nbRepetions` = 4, this will return 8 blocks (for MT/MST - % localizer && 2 hemifield it is 8 blocks per hemifield), i.e. how many times each condition - % will be repeated - cfg.design.nbRepetitions = 12; - cfg.design.nbEventsPerBlock = 12; - - %% Timing - - % FOR 7T: if you want to create localizers on the fly, the following must be - % multiples of the scanner sequence TR - % - % IBI - % block length = (cfg.eventDuration + cfg.ISI) * cfg.design.nbEventsPerBlock - - cfg.timing.eventDuration = 0.30; % second - - % Time between blocs in secs - cfg.timing.IBI = 0; - % Time between events in secs - cfg.timing.ISI = 0; - % Number of seconds before the motion stimuli are presented - cfg.timing.onsetDelay = 0; - % Number of seconds after the end all the stimuli before ending the run - cfg.timing.endDelay = 3.6; - - % reexpress those in terms of repetition time - if cfg.pacedByTriggers.do - - cfg.pacedByTriggers.quietMode = true; - cfg.pacedByTriggers.nbTriggers = 1; - - cfg.timing.eventDuration = cfg.mri.repetitionTime / 2 - 0.04; % second - - % Time between blocs in nb of triggers (remember to consider the nb trigger to wait + 1) - cfg.timing.triggerIBI = 4; - % Time between blocks in secs - cfg.timing.IBI = 0; - % Time between events in secs - cfg.timing.ISI = 0; - % Number of seconds before the motion stimuli are presented - cfg.timing.onsetDelay = 0; - % Number of seconds after the end all the stimuli before ending the run - cfg.timing.endDelay = 2; - - end - - %% Visual Stimulation - - % Speed in visual angles / second - cfg.dot.speed = 15; - % Coherence Level (0-1) - cfg.dot.coherence = 1; - % Number of dots per visual angle square. - cfg.dot.density = 1; - % Dot life time in seconds - cfg.dot.lifeTime = 0.4; - % proportion of dots killed per frame - cfg.dot.proportionKilledPerFrame = 0; - % Dot Size (dot width) in visual angles. - cfg.dot.size = .2; - cfg.dot.color = cfg.color.white; - % Static dots should change position at each event or not - cfg.dot.staticReSeed = true; - - % Diameter/length of side of aperture in Visual angles - cfg.aperture.type = 'none'; - cfg.aperture.width = []; % if left empty it will take the screen height - cfg.aperture.xPos = 0; - - %% Task(s) - - cfg.task.name = 'visual localizer'; - - % Instruction - cfg.task.instruction = '1-Detect the RED fixation cross\n \n\n'; - - % Fixation cross (in pixels) - cfg.fixation.type = 'cross'; - cfg.fixation.color = cfg.color.white; - cfg.fixation.width = .25; - cfg.fixation.lineWidthPix = 3; - cfg.fixation.xDisplacement = 0; - cfg.fixation.yDisplacement = 0; - - % target - cfg.target.maxNbPerBlock = 1; - cfg.target.duration = 0.1; % In secs - cfg.target.type = 'fixation_cross'; - % 'fixation_cross' : the fixation cross changes color - % 'static_repeat' : static dots are in the same position as previous trials - - cfg.fixation.colorTarget = cfg.fixation.color; - if strcmp(cfg.target.type, 'fixation_cross') - cfg.fixation.colorTarget = cfg.color.red; - end - - cfg.extraColumns = { ... - 'direction', ... - 'speedDegVA', ... - 'target', ... - 'event', ... - 'block', ... - 'keyName', ... - 'fixationPosition', ... - 'aperturePosition'}; - - %% orverrireds the relevant fields in case we use the MT / MST localizer - cfg = setParametersMtMst(cfg); - -end - -function cfg = setKeyboards(cfg) - cfg.keyboard.escapeKey = 'ESCAPE'; - cfg.keyboard.responseKey = { ... - 'r', 'g', 'y', 'b', ... - 'd', 'n', 'z', 'e', ... - 't'}; - cfg.keyboard.keyboard = []; - cfg.keyboard.responseBox = []; - - if strcmpi(cfg.testingDevice, 'mri') - cfg.keyboard.keyboard = []; - cfg.keyboard.responseBox = []; - end -end - -function cfg = setMRI(cfg) - % letter sent by the trigger to sync stimulation and volume acquisition - cfg.mri.triggerKey = 't'; - cfg.mri.triggerNb = 5; - - cfg.mri.repetitionTime = 1.8; - - cfg.bids.MRI.Instructions = 'Detect the RED fixation cross'; - cfg.bids.MRI.TaskDescription = []; - -end - -function cfg = setMonitor(cfg) - - % Monitor parameters for PTB - cfg.color.white = [255 255 255]; - cfg.color.black = [0 0 0]; - cfg.color.red = [255 0 0]; - cfg.color.grey = mean([cfg.color.black; cfg.color.white]); - cfg.color.background = cfg.color.black; - cfg.text.color = cfg.color.white; - - % Monitor parameters - cfg.screen.monitorWidth = 50; % in cm - cfg.screen.monitorDistance = 40; % distance from the screen in cm - - if strcmpi(cfg.testingDevice, 'mri') - cfg.screen.monitorWidth = 25; - cfg.screen.monitorDistance = 95; - end - -end - -function cfg = setParametersMtMst(cfg) - - if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') - - cfg.task.name = 'mt mst localizer'; - - cfg.design.motionType = 'radial'; - cfg.design.motionDirections = [666 -666]; - cfg.design.names = {'motion'}; - % cfg.design.names = {'static'; 'motion'}; - cfg.design.fixationPosition = {'fixation_left'}; - % cfg.design.fixationPosition = {'fixation_right'; 'fixation_left'}; - cfg.design.xDisplacementFixation = 7; - cfg.design.xDisplacementAperture = 3; - - % inward and outward are presented as separated event - cfg.design.nbEventsPerBlock = cfg.design.nbEventsPerBlock * 2; - - cfg.timing.IBI = 4; - - cfg.timing.changeFixationPosition = 10; - - % reexpress those in terms of repetition time - if cfg.pacedByTriggers.do - - cfg.timing.IBI = 2; - - end - - cfg.aperture.type = 'circle'; - cfg.aperture.width = 7; % if left empty it will take the screen height - cfg.aperture.xPos = cfg.design.xDisplacementAperture; - - end - -end diff --git a/subfun/defaults/checkParameters.m b/subfun/defaults/checkParameters.m new file mode 100755 index 0000000..9bffb73 --- /dev/null +++ b/subfun/defaults/checkParameters.m @@ -0,0 +1,266 @@ +function [cfg] = checkParameters(cfg) + % + % Check that all parameters are sets. If not it uses the defaults. + % + % ``cfg.design.localizer``: switching this to ``MT`` (default) or ``MT_MST`` + % + % - ``MT``: translational motion on the whole screen + % + % - alternates static and motion (left or right) blocks + % + % - ``MT_MST``: radial motion centered in a circle aperture that is on the opposite + % side of the screen relative to the fixation + % + % - (default) alternates fixaton left and fixation right + % + % ``cfg.dir.output``: by default the data will be stored in an output folder created in the root + % dir of this repo. Change that if you want the data to be saved somewhere + % else. + % + % (C) Copyright 2020 CPP visual motion localizer developers + + % Initialize the general configuration variables structure + if nargin < 1 + cfg.design.localizer = 'MT'; + end + + root_dir = fullfile(fileparts(mfilename('fullpath')), '..', '..'); + if which('bids.internal.file_utils') + root_dir = bids.internal.file_utils(root_dir, 'cpath'); + end + + % the "source" subfolder will be added by createFilename of CPP_BIDS + fieldsToSet.dir.output = fullfile(root_dir, 'output'); + + %% Engine parameters + fieldsToSet.testingDevice = 'mri'; + fieldsToSet.eyeTracker.do = false; + + fieldsToSet = setMonitor(fieldsToSet); + + fieldsToSet = setKeyboards(fieldsToSet); + + %% Experiment Design + + % if you have static and motion and `nbRepetions` = 4, this will return 8 blocks (for MT/MST + % localizer && 2 hemifield it is 8 blocks per hemifield), i.e. how many times each condition + % will be repeated + fieldsToSet.design.nbRepetitions = 12; + fieldsToSet.design.nbEventsPerBlock = 12; + + %% Timing + + % block length = (cfg.eventDuration + cfg.ISI) * cfg.design.nbEventsPerBlock + fieldsToSet.timing.eventDuration = 0.6; % second + + % Time between events in secs + fieldsToSet.timing.ISI = 0.1; + % Number of seconds before the motion stimuli are presented + fieldsToSet.timing.onsetDelay = 5; + % Number of seconds after the end all the stimuli before ending the run + fieldsToSet.timing.endDelay = 5; + + %% Visual Stimulation + fieldsToSet.dot = cppPtbDefaults('dot'); + fieldsToSet.dot.color = fieldsToSet.color.white; + % Static dots should change position at each event or not + fieldsToSet.dot.staticReSeed = true; + + %% Task(s) + + % target + % 'fixation_cross' : the fixation cross changes color + % 'static_repeat' : static dots are in the same position as previous trials + fieldsToSet.target.type = 'fixation_cross'; + + % Fixation cross (in pixels) + fieldsToSet.fixation = cppPtbDefaults('fixation'); + fieldsToSet.fixation.color = fieldsToSet.color.white; + fieldsToSet.fixation.width = .25; + fieldsToSet.fixation.lineWidthPix = 3; + + fieldsToSet.extraColumns = {'direction', ... + 'speedDegVA', ... + 'target', ... + 'event', ... + 'block', ... + 'keyName', ... + 'fixationPosition', ... + 'aperturePosition'}; + + fieldsToSet.audio.do = false; + + cfg = setDefaultFields(cfg, fieldsToSet); + + cfg = setParametersMtMst(cfg); + + cfg = setMRI(cfg); + + cfg = setTarget(cfg); + + cfg = checkCppPtbCfg(cfg); + + if cfg.verbose == 2 + unfold(cfg); + end + +end + +function fieldsToSet = setKeyboards(fieldsToSet) + + fieldsToSet.keyboard = cppPtbDefaults('keyboard'); + + fieldsToSet.keyboard.responseKey = {'r', 'g', 'y', 'b', ... + 'd', 'n', 'z', 'e', ... + 't'}; + +end + +function cfg = setMRI(cfg) + + % letter sent by the trigger to sync stimulation and volume acquisition + fieldsToSet.mri.triggerKey = 't'; + + fieldsToSet.mri.triggerNb = 5; + + fieldsToSet.mri.repetitionTime = 1.8; + + fieldsToSet.suffix.acq = ''; + + fieldsToSet.pacedByTriggers.do = false; + + cfg = setDefaultFields(cfg, fieldsToSet); + + cfg = setPacedByTrigger(cfg); + +end + +function fieldsToSet = setMonitor(fieldsToSet) + + % Monitor parameters for PTB + fieldsToSet.color = cppPtbDefaults('color'); + fieldsToSet.color.background = fieldsToSet.color.black; + fieldsToSet.text.color = fieldsToSet.color.white; + + % Monitor parameters + fieldsToSet.screen.monitorWidth = 50; % in cm + fieldsToSet.screen.monitorDistance = 40; % distance from the screen in cm + if strcmpi(fieldsToSet.testingDevice, 'mri') + fieldsToSet.screen.monitorWidth = 25; + fieldsToSet.screen.monitorDistance = 95; + end + +end + +function cfg = setParametersMtMst(cfg) + + switch lower(cfg.design.localizer) + + case 'mt_mst' + + fieldsToSet.task.name = 'mt mst localizer'; + + fieldsToSet.design.motionType = 'radial'; + fieldsToSet.design.motionDirections = [666 -666]; + fieldsToSet.design.names = {'motion'}; + % {'static'; 'motion'} + fieldsToSet.design.fixationPosition = {'fixation_right'; 'fixation_left'}; + % {'fixation_right'; 'fixation_left'}; + fieldsToSet.design.xDisplacementFixation = 7; + fieldsToSet.design.xDisplacementAperture = 3; + + % inward and outward are presented as separated event + fieldsToSet.design.nbEventsPerBlock = cfg.design.nbEventsPerBlock * 2; + + % time between events in secs + fieldsToSet.timing.ISI = 0; + fieldsToSet.timing.IBI = 10; + fieldsToSet.timing.changeFixationPosition = 10; + + fieldsToSet.aperture.type = 'circle'; + fieldsToSet.aperture.width = 7; % if left empty it will take the screen height + fieldsToSet.aperture.xPos = 0; + + case 'mt' + + fieldsToSet.task.name = 'visual localizer'; + + fieldsToSet.design.motionType = 'translation'; + fieldsToSet.design.motionDirections = [0 0 180 180]; + fieldsToSet.design.names = {'static'; 'motion'}; + + % Time between blocs in secs + fieldsToSet.timing.IBI = 4; + + % Diameter/length of side of aperture in Visual angles + fieldsToSet.aperture.type = 'none'; + fieldsToSet.aperture.width = []; % if left empty it will take the screen height + fieldsToSet.aperture.xPos = 0; + + end + + cfg = setDefaultFields(cfg, fieldsToSet); + +end + +function cfg = setPacedByTrigger(cfg) + + % reexpress those in terms of repetition time + if cfg.pacedByTriggers.do + + fieldsToSet.pacedByTriggers.quietMode = true; + fieldsToSet.pacedByTriggers.nbTriggers = 1; + + fieldsToSet.timing.eventDuration = cfg.mri.repetitionTime / 2 - 0.04; % second + + % Time in nb of volumes between blocs in nb of triggers + % (remember to consider the nb trigger to wait + 1) + fieldsToSet.timing.triggerIBI = 4; + + % Time between blocks in secs + cfg.timing.IBI = 0; + + % Time between events in secs + cfg.timing.ISI = 0; + + % Number of seconds before the motion stimuli are presented + cfg.timing.onsetDelay = 0; + + % Number of seconds after the end all the stimuli before ending the run + cfg.timing.endDelay = 0; + + cfg = setDefaultFields(cfg, fieldsToSet); + + end + +end + +function cfg = setTarget(cfg) + % 'fixation_cross' : the fixation cross changes color + % 'static_repeat' : static dots are in the same position as previous trials + + fieldsToSet.target.maxNbPerBlock = 1; + + if strcmp(cfg.target.type, 'fixation_cross') + cfg.task.instruction = '1-Detect the RED fixation cross\n \n\n'; + cfg.task.taskDescription = ''; + cfg.fixation.colorTarget = cfg.color.red; + + fieldsToSet.target.duration = 0.1; % In secs + + elseif strcmp(cfg.target.type, 'static_repeat') + cfg.task.instruction = '1-Detect when the dots are in the same position\n \n\n'; + cfg.task.taskDescription = ''; + cfg.fixation.colorTarget = cfg.fixation.color; + + else + error('cfg.target.type must be ''fixation_cross'' or ''static_repeat'''); + + end + + cfg.bids.MRI.Instructions = cfg.task.instruction; + cfg.bids.MRI.TaskDescription = cfg.task.taskDescription; + + cfg = setDefaultFields(cfg, fieldsToSet); + +end diff --git a/subfun/design/diplayDesign.m b/subfun/design/diplayDesign.m index f971cf3..6ab0f7d 100644 --- a/subfun/design/diplayDesign.m +++ b/subfun/design/diplayDesign.m @@ -3,7 +3,7 @@ function diplayDesign(cfg, displayFigs) % diplayDesign(cfg, displayFigs) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers %% Visualize the design matrix if displayFigs diff --git a/subfun/design/expDesign.m b/subfun/design/expDesign.m index 428c2b8..d26eae9 100644 --- a/subfun/design/expDesign.m +++ b/subfun/design/expDesign.m @@ -1,62 +1,67 @@ function [cfg] = expDesign(cfg) % - % Creates the sequence of blocks and the events in them + % This function and its companions creates the sequence of blocks (static/motion) + % and the events (the single directions) for MT+ and MT/MST localizers. % - % The conditions are consecutive static and motion blocks. - % It gives better results than randomised. + % The conditions are consecutive static and motion blocks (fixed in this order gives + % better results than randomised). + % + % USAGE + % + % [cfg] = expDesign(cfg) + % + % + % Output: + % + % - ``cfg.design.blockNames``: cell array (nbBlocks, 1) with the condition name for each block + % - ``cfg.design.nbBlocks``: integer for the total number of blocks in the run + % - ``cfg.design.directions``: array (nbBlocks, nbEventsPerBlock) with the direction + % to present in a given event of a block. + % - ``cfg.design.blockFixationPosition``: [MT_MST] array (nbBlocks, 1) + % with the position in the hemifiled where to show the fixation cross + % - 0 90 180 270 : indicate the angle for translational motion direction + % - 666 -666 : indicate in/out-ward direction in radial motion + % - -1 : indicates static + % - ``cfg.design.speeds``: array (nbBlocks, nbEventsPerBlock) indicate the dots speed + % in each event, the target is represented by a higher/lower value + % - ``cfg.design.fixationTargets``: array (nbBlocks, numEventsPerBlock) showing + % for each event if it should be accompanied by a target % % It can be run as a stand alone without inputs and display a visual example of the - % possible design. See `getMockConfig` to set up the mock configuration. + % possible design. See ``getMockConfig`` to set up the mock configuration. % % It computes the directions to display and the task(s), at the moment: - % (1) detection of change in the color of the fixation target - % (2) detection of different speed of the moving dots - % [ W I P - if selected as a task it will give the same null output - % as if not selected ie no difference in speed ] + % + % 1. detection of change in the color of the fixation target + % 2. detection of different speed of the moving dots + % [ WIP - if selected as a task it will give the same null output + % as if not selected ie no difference in speed ] % % EVENTS + % % The ``nbEventsPerBlock`` should be a multiple of the number of motion directions requested in - % ``motionDirections`` (which should be more than 1) e.g.: + % ``motionDirections`` (which should be more than 1) for example:: % - % MT localizer: ``cfg.design.motionDirections = [ 0 90 180 270 ]; % right down left up`` - % MT_MST localizer: ``cfg.design.motionDirections = [666 -666]; % outward inward`` + % MT localizer: ``cfg.design.motionDirections = [ 0 90 180 270 ]; % right down left up`` + % MT_MST localizer: ``cfg.design.motionDirections = [666 -666]; % outward inward`` % % Pseudorandomization rules: % % - Directions: - % (1) Directions are all presented in random orders in `numEventsPerBlock/nDirections` - % consecutive chunks. This evenly distribute the directions across the - % block. - % (2) No same consecutive direction + % + % 1. Directions are all presented in random orders in `numEventsPerBlock/nDirections` + % consecutive chunks. This evenly distribute the directions across the + % block. + % 2. No same consecutive direction % % - Color change detection of the fixation cross: - % (1) If there are 2 targets per block we make sure that they are at least 2 events apart. - % (2) Targets cannot be on the first or last event of a block. - % (3) No less than 1 target per event position in the whole run % - % Input: - % - cfg: parameters returned by setParameters - % - displayFigs: a boolean to decide whether to show the basic design - % matrix of the design + % 1. If there are 2 targets per block we make sure that they are at least 2 events apart. + % 2. Targets cannot be on the first or last event of a block. + % 3. No less than 1 target per event position in the whole run % - % Output: - % - cfg.design.blockNames: cell array (nbBlocks, 1) with the condition name for each block - % - cfg.design.nbBlocks: integer for th etotal number of blocks in the run - % - cfg.design.directions: array (nbBlocks, nbEventsPerBlock) with the direction - % to present in a given event of a block. - % - cfg.design.blockFixationPosition: [MT_MST] array (nbBlocks, 1) - % with the position in the hemifiled - % where to show the fixation cross - % - 0 90 180 270 indicate the angle for translational motion direction - % - 666 -666 indicate in/out-ward direction in radial motion - % - -1 indicates static - % - cfg.design.speeds: array (nbBlocks, nbEventsPerBlock) indicate the dots speed - % in each event, the target is represented by a higher/lower value - % - cfg.design.fixationTargets: array (nbBlocks, numEventsPerBlock) showing - % for each event if it should be accompanied by a target - % - % - % (C) Copyright 2020 CPP visual motion localizer developpers + % + % (C) Copyright 2020 CPP visual motion localizer developers %% Check inputs diff --git a/subfun/design/getDesignInput.m b/subfun/design/getDesignInput.m index 2cee9b5..0b52de7 100644 --- a/subfun/design/getDesignInput.m +++ b/subfun/design/getDesignInput.m @@ -3,7 +3,7 @@ % [nbRepetitions, nbEventsPerBlock, maxNbPerBlock, nbBlocks] = getDesignInput(cfg) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers nbRepetitions = cfg.design.nbRepetitions; diff --git a/subfun/design/getDirectionBaseVectors.m b/subfun/design/getDirectionBaseVectors.m index 3c13d13..6548d1f 100644 --- a/subfun/design/getDirectionBaseVectors.m +++ b/subfun/design/getDirectionBaseVectors.m @@ -3,7 +3,7 @@ % [directionsCondition1, directionsCondition2] = getDirectionBaseVectors(cfg) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers % Set directions for static and motion condition diff --git a/subfun/design/getMockConfig.m b/subfun/design/getMockConfig.m index e0c6218..81ff764 100644 --- a/subfun/design/getMockConfig.m +++ b/subfun/design/getMockConfig.m @@ -7,7 +7,7 @@ % this may make the tests on GitHub fail, so be careful. % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers if nargin < 1 isMT = true; diff --git a/subfun/design/setBlocksConditions.m b/subfun/design/setBlocksConditions.m index eccae83..d00b09d 100644 --- a/subfun/design/setBlocksConditions.m +++ b/subfun/design/setBlocksConditions.m @@ -3,7 +3,7 @@ % [conditionNamesVector, idxCondition1, idxCondition2] = setBlocksConditions(cfg) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers nbRepetitions = getDesignInput(cfg); diff --git a/subfun/design/setDirections.m b/subfun/design/setDirections.m index 3eccf1f..2c4017b 100644 --- a/subfun/design/setDirections.m +++ b/subfun/design/setDirections.m @@ -9,7 +9,7 @@ % condition2 = 'motion'; % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers % Get the directions we should work with [directionsCondition1, directionsCondition2] = getDirectionBaseVectors(cfg); diff --git a/subfun/design/setFixationPosition.m b/subfun/design/setFixationPosition.m index 22a80e2..3b3ccb3 100644 --- a/subfun/design/setFixationPosition.m +++ b/subfun/design/setFixationPosition.m @@ -6,7 +6,7 @@ % in a matrix of size ``nbBlocks`` by ``nbEventsPerBlock`` % % - % (C) Copyright 2022 CPP visual motion localizer developpers + % (C) Copyright 2022 CPP visual motion localizer developers % Get the parameter to compute the design with [~, ~, ~, nbBlocks] = getDesignInput(cfg); diff --git a/subfun/design/setFixationTargets.m b/subfun/design/setFixationTargets.m index 9284a94..cfcd73f 100644 --- a/subfun/design/setFixationTargets.m +++ b/subfun/design/setFixationTargets.m @@ -11,7 +11,7 @@ % If the fixation target task is not required, it outputs a matrix with only zeros % % - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers % Get the parameter to compute the design with [nbRepetitions, nbEventsPerBlock, maxNbPerBlock, nbBlocks] = getDesignInput(cfg); diff --git a/subfun/design/setSpeedTargets.m b/subfun/design/setSpeedTargets.m index 386233d..c421571 100644 --- a/subfun/design/setSpeedTargets.m +++ b/subfun/design/setSpeedTargets.m @@ -10,7 +10,7 @@ % set speed in ``cfg.dot.speed`` (computed in pixels per frame). % % - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers [~, nbEventsPerBlock, ~, nbBlocks] = getDesignInput(cfg); speeds = ones(nbBlocks, nbEventsPerBlock) * cfg.dot.speedPixPerFrame; diff --git a/subfun/doDotMo.m b/subfun/doDotMo.m index ad02761..7d53701 100644 --- a/subfun/doDotMo.m +++ b/subfun/doDotMo.m @@ -1,17 +1,28 @@ function [onset, duration, dots] = doDotMo(cfg, thisEvent, thisFixation, dots, iEvent) % - % [onset, duration, dots] = doDotMo(cfg, thisEvent, thisFixation, dots, iEvent) + % Wrapper function that present the dot stimulation (static or motion) per event. % - % Draws the stimulation of static/moving in 4 directions dots or static + % USAGE:: % - % DIRECTIONS - % 0=Right; 90=Up; 180=Left; 270=down + % [onset, duration, dots] = doDotMo(cfg, thisEvent, thisFixation, dots, iEvent) % - % Input: - % - cfg: PTB/machine configurations returned by setParameters and initPTB + % :param cfg: PTB/machine configurations returned. see ``checkParameters`` + % :type cfg: structure + % + % :param thisEvent: structure that stores information about the event to present + % regarding the dots (static or motion, direction, etc.) + % :type thisEvent: + % + % :param thisFixation: structure that stores information about the fixation cross + % task to present + % :type thisFixation: + % + % :param dots: + % :type dots: + % + % :param iEvent: index of the event of the block at the moment of the presentation + % :type iEvent: % - % Output: - % - % % The dots are drawn on a square with a width equals to the width of the % screen @@ -19,7 +30,7 @@ % % % (C) Copyright 2018 Mohamed Rezk - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers %% Get parameters if isempty(dots) @@ -46,6 +57,8 @@ %% make textures + % If staticReSeed is true, then change the seed of the static dots only + % for the first event if cfg.dot.staticReSeed && ... strcmpi(thisEvent.trial_type, 'static') && ... iEvent ~= 1 @@ -85,7 +98,9 @@ drawFixation(thisFixation); - if cfg.dot.staticReSeed && strcmpi(thisEvent.trial_type, 'static') + if cfg.dot.staticReSeed && ... + strcmpi(thisEvent.trial_type, 'static') && ... + iEvent ~= cfg.design.nbEventsPerBlock dotTexture('draw', cfg, thisEvent); diff --git a/subfun/postInitializationSetup.m b/subfun/postInitializationSetup.m index ab1d7fc..07fbf17 100644 --- a/subfun/postInitializationSetup.m +++ b/subfun/postInitializationSetup.m @@ -6,22 +6,21 @@ % initialized % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers - cfg.dot.matrixWidth = cfg.screen.winWidth; + cfg = postInitDots(cfg); - % Convert some values from degrees to pixels - cfg.dot = degToPix('size', cfg.dot, cfg); - cfg.dot = degToPix('speed', cfg.dot, cfg); + % TODO transfer those if blocks into 'degToPix' (and similarly into pixToDeg) + % TODO this kind of generic post initialization can be done systematically at end of initPTB + if isfield(cfg.fixation, 'xDisplacement') + cfg.fixation = degToPix('xDisplacement', cfg.fixation, cfg); + end + if isfield(cfg.fixation, 'yDisplacement') + cfg.fixation = degToPix('yDisplacement', cfg.fixation, cfg); + end - % Get dot speeds in pixels per frame - cfg.dot.speedPixPerFrame = cfg.dot.speedPix / cfg.screen.monitorRefresh; - - cfg.aperture = degToPix('xPos', cfg.aperture, cfg); - - % dots are displayed on a square with a length in visual angle equal to the - % field of view - cfg.dot.number = round(cfg.dot.density * ... - (cfg.dot.matrixWidth / cfg.screen.ppd)^2); + if isfield(cfg.design, 'xDisplacementAperture') + cfg.design = degToPix('xDisplacementAperture', cfg.design, cfg); + end end diff --git a/subfun/preSaveSetup.m b/subfun/preSaveSetup.m index dbb688c..aecd103 100755 --- a/subfun/preSaveSetup.m +++ b/subfun/preSaveSetup.m @@ -4,7 +4,7 @@ % duration, onset, cfg, logFile) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers % generic function to prepare structures before saving diff --git a/subfun/preTrialSetup.m b/subfun/preTrialSetup.m index ce3cbd3..4eb02c4 100644 --- a/subfun/preTrialSetup.m +++ b/subfun/preTrialSetup.m @@ -3,7 +3,7 @@ % [thisEvent, thisFixation, cfg] = postInitializatinSetup(cfg, iBlock, iEvent) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers % generic function to prepare some structure before each trial @@ -34,26 +34,47 @@ thisEvent.fixationPosition = cfg.design.blockFixationPosition{iBlock}; + % This is necessary because where the dot aperture is drawn is set in cfg + % So we "reset" that pixel value from the value in degrees + cfg.aperture.xPos = cfg.design.xDisplacementAperture; + cfg.aperture = degToPix('xPos', cfg.aperture, cfg); + switch thisEvent.fixationPosition case 'fixation_right' cfg.aperture.xPosPix = -abs(cfg.aperture.xPosPix); - - thisEvent.dotCenterXPosPix = cfg.aperture.xPosPix; - thisFixation.fixation.xDisplacement = cfg.design.xDisplacementFixation; - thisFixation = initFixation(thisFixation); case 'fixation_left' cfg.aperture.xPosPix = +abs(cfg.aperture.xPosPix); + thisFixation.fixation.xDisplacement = -cfg.design.xDisplacementFixation; - thisEvent.dotCenterXPosPix = cfg.aperture.xPosPix; + otherwise - thisFixation.fixation.xDisplacement = -cfg.design.xDisplacementFixation; - thisFixation = initFixation(thisFixation); + error('WTF'); + + end + if isfield(cfg.fixation, 'xDisplacementPix') + cfg.aperture.xPosPix = cfg.aperture.xPosPix + cfg.fixation.xDisplacementPix; + thisFixation.fixation.xDisplacement = thisFixation.fixation.xDisplacement + ... + cfg.fixation.xDisplacement; end + if isfield(cfg.fixation, 'yDisplacementPix') + cfg.aperture.yPosPix = cfg.fixation.yDisplacementPix; + thisEvent.dotCenterYPosPix = cfg.aperture.yPosPix; + end + + thisEvent.dotCenterXPosPix = cfg.aperture.xPosPix; + + if isfield(cfg.fixation, 'yDisplacementPix') + end + + thisFixation.fixation.allCoords; + + thisFixation = initFixation(thisFixation); + end varargout = {thisEvent, thisFixation, cfg}; diff --git a/subfun/saveResponsesAndTriggers.m b/subfun/saveResponsesAndTriggers.m index 5da624b..02bdbd5 100755 --- a/subfun/saveResponsesAndTriggers.m +++ b/subfun/saveResponsesAndTriggers.m @@ -3,7 +3,7 @@ function saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString) % saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString) % % - % (C) Copyright 2020 CPP visual motion localizer developpers + % (C) Copyright 2020 CPP visual motion localizer developers if isfield(responseEvents(1), 'onset') && ~isempty(responseEvents(1).onset) diff --git a/subfun/visualMotionLocalizer.m b/subfun/visualMotionLocalizer.m new file mode 100755 index 0000000..b169ffe --- /dev/null +++ b/subfun/visualMotionLocalizer.m @@ -0,0 +1,207 @@ +function visualMotionLocalizer(cfg) + % + % (C) Copyright 2018 Mohamed Rezk + % (C) Copyright 2020 CPP visual motion localizer developers + + if nargin < 1 + cfg.design.localizer = 'MT'; + end + + getOnlyPress = 1; + + % Clear all the previous stuff + clc; + if ~ismac + close all; + clear Screen; + end + + % make sure we got access to all the required functions and inputs + initEnv(); + + % set and load all the parameters to run the experiment + cfg = checkParameters(cfg); + cfg = userInputs(cfg); + cfg = createFilename(cfg); + + %% Experiment + + % Safety loop: close the screen if code crashes + try + + cfg = initPTB(cfg); + + cfg = postInitializationSetup(cfg); + + cfg.el = eyeTracker('Calibration', cfg); + + cfg = expDesign(cfg); + + % Prepare for the output logfiles with all + logFile.extraColumns = cfg.extraColumns; + logFile = saveEventsFile('init', cfg, logFile); + logFile = saveEventsFile('open', cfg, logFile); + + % prepare textures + cfg = apertureTexture('init', cfg); + cfg = dotTexture('init', cfg); + + if cfg.verbose == 2 + unfold(cfg); + end + + % Show experiment instruction + standByScreen(cfg); + + % prepare the KbQueue to collect responses + getResponse('init', cfg.keyboard.responseBox, cfg); + + waitForTrigger(cfg); + + % Start + eyeTracker('StartRecording', cfg); + + cfg = getExperimentStart(cfg); + + getResponse('start', cfg.keyboard.responseBox); + + waitFor(cfg, cfg.timing.onsetDelay); + + for iBlock = 1:cfg.design.nbBlocks + + fprintf('\n - Running Block %.0f \n', iBlock); + + eyeTracker('Message', cfg, ['start_block-', num2str(iBlock)]); + + dots = []; + previousEvent.target = 0; + + for iEvent = 1:cfg.design.nbEventsPerBlock + + checkAbort(cfg, cfg.keyboard.keyboard); + + [thisEvent, thisFixation, cfg] = preTrialSetup(cfg, iBlock, iEvent); + + % we wait for a trigger every 2 events + if cfg.pacedByTriggers.do && mod(iEvent, 2) == 1 + waitForTrigger(cfg, ... + cfg.keyboard.responseBox, ... + cfg.pacedByTriggers.quietMode, ... + cfg.pacedByTriggers.nbTriggers); + end + + eyeTracker('Message', cfg, ... + ['start_trial-', num2str(iEvent), '_', thisEvent.trial_type]); + + % we only reuse the dots position for targets that consists of + % presenting static dots with the same position as those of the + % previous trial + % + % TODO does not take into account what to do if 3 or more targets in a row + if strcmp(cfg.target.type, 'static_repeat') && ... + strcmp(thisEvent.trial_type, 'static') && ... + thisEvent.target == 1 && ... + thisEvent.target == previousEvent.target + else + dots = []; + end + + % play the dots and collect onset and duraton of the event + [onset, duration, dots] = doDotMo(cfg, thisEvent, thisFixation, dots, iEvent); + + thisEvent = preSaveSetup(thisEvent, ... + thisFixation, ... + iBlock, iEvent, ... + duration, onset, ... + cfg, ... + logFile); + + saveEventsFile('save', cfg, thisEvent); + + % collect the responses and appends to the event structure for + % saving in the tsv file + responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... + getOnlyPress); + + triggerString = ['trigger_' cfg.design.blockNames{iBlock}]; + saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString); + + eyeTracker('Message', cfg, ... + ['end_trial-', num2str(iEvent), '_', thisEvent.trial_type]); + + previousEvent = thisEvent; + + waitFor(cfg, cfg.timing.ISI); + + end + + % "prepare" cross for the baseline block + % if MT / MST this allows us to set the cross at the position of the next block + if iBlock < cfg.design.nbBlocks + nextBlock = iBlock + 1; + else + nextBlock = cfg.design.nbBlocks; + end + + [~, thisFixation] = preTrialSetup(cfg, nextBlock, 1); + drawFixation(thisFixation); + Screen('Flip', cfg.screen.win); + + eyeTracker('Message', cfg, ['end_block-', num2str(iBlock)]); + + waitFor(cfg, cfg.timing.IBI); + + % IBI trigger paced + if cfg.pacedByTriggers.do + waitForTrigger(cfg, ... + cfg.keyboard.responseBox, ... + cfg.pacedByTriggers.quietMode, ... + cfg.timing.triggerIBI); + end + + if isfield(cfg.design, 'localizer') && ... + strcmpi(cfg.design.localizer, 'MT_MST') && ... + iBlock == cfg.design.nbBlocks / 2 + + waitFor(cfg, cfg.timing.changeFixationPosition); + + end + + % trigger monitoring + triggerEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... + getOnlyPress); + + triggerString = 'trigger_baseline'; + saveResponsesAndTriggers(triggerEvents, cfg, logFile, triggerString); + + end + + % End of the run for the BOLD to go down + waitFor(cfg, cfg.timing.endDelay); + + cfg = getExperimentEnd(cfg); + + eyeTracker('StopRecordings', cfg); + + % Close the logfiles + saveEventsFile('close', cfg, logFile); + + getResponse('stop', cfg.keyboard.responseBox); + getResponse('release', cfg.keyboard.responseBox); + + eyeTracker('Shutdown', cfg); + + createJson(cfg, cfg); + + farewellScreen(cfg); + + cleanUp(); + + catch + + cleanUp(); + psychrethrow(psychlasterror); + + end + +end diff --git a/tests/data/config_MT.mat b/tests/data/config_MT.mat new file mode 100644 index 0000000..aa67118 Binary files /dev/null and b/tests/data/config_MT.mat differ diff --git a/tests/data/config_MT_MST.mat b/tests/data/config_MT_MST.mat new file mode 100644 index 0000000..af1b90d Binary files /dev/null and b/tests/data/config_MT_MST.mat differ diff --git a/tests/test_checkParameters.m b/tests/test_checkParameters.m new file mode 100644 index 0000000..17e81e0 --- /dev/null +++ b/tests/test_checkParameters.m @@ -0,0 +1,171 @@ +function test_suite = test_checkParameters %#ok<*STOUT> + % (C) Copyright 2021 CPP visual motion localizer developers + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end + +function test_checkParameters_output_dir() + + % failure expected when run with runTests + + % set up + cfg.design.localizer = 'MT'; + cfg = checkParameters(cfg); + + % test + if ~isGithubMoxunitAction + cfg.dir.output = bids.internal.file_utils(cfg.dir.output, 'cpath'); + assertEqual(cfg.dir.output, ... + bids.internal.file_utils(fullfile(fileparts(mfilename('fullpath')), ... + '..', ... + 'output'), ... + 'cpath')); + + end + +end + +function test_checkParameters_no_debug_fullscreen() + + % set up + cfg.design.localizer = 'MT'; + cfg.debug.do = false; + cfg.debug.transpWin = 0; + cfg.debug.smallWin = 0; + + cfg = checkParameters(cfg); + + % prepare expected results + cfg = removeDirFieldForGithubAction(cfg); + + load(fullfile(fileparts(mfilename('fullpath')), 'data', 'config_MT.mat'), 'expected'); + + expected.debug.do = false; + expected.debug.transpWin = 0; + expected.debug.smallWin = 0; + expected.skipSyncTests = 0; + + % test + assertEqual(cfg.debug, expected.debug); + assertEqual(cfg.skipSyncTests, expected.skipSyncTests); + +end + +function test_checkParameters_no_debug() + + % set up + cfg.design.localizer = 'MT'; + cfg.debug.do = false; + + cfg = checkParameters(cfg); + + % prepare expected results + cfg = removeDirFieldForGithubAction(cfg); + + load(fullfile(fileparts(mfilename('fullpath')), 'data', 'config_MT.mat'), 'expected'); + + expected.debug.do = false; + expected.debug.transpWin = 1; + expected.debug.smallWin = 1; + expected.skipSyncTests = 0; + + % test + assertEqual(cfg.debug, expected.debug); + assertEqual(cfg.skipSyncTests, expected.skipSyncTests); + +end + +function test_checkParameters_debug() + + cfg.design.localizer = 'MT'; + cfg.debug.do = true; + + cfg = checkParameters(cfg); + + % prepare expected results + cfg = removeDirFieldForGithubAction(cfg); + + load(fullfile(fileparts(mfilename('fullpath')), 'data', 'config_MT.mat'), 'expected'); + + expected.debug.do = true; + expected.debug.transpWin = 1; + expected.debug.smallWin = 1; + expected.skipSyncTests = 1; + expected.hideCursor = 1; + + % test + assertEqual(cfg.debug, expected.debug); + assertEqual(cfg.skipSyncTests, expected.skipSyncTests); + +end + +function test_checkParameters_MT() + + cfg.design.localizer = 'MT'; + + cfg = checkParameters(cfg); + + % prepare expected results + cfg = removeDirFieldForGithubAction(cfg); + + fileToLoad = fullfile(fileparts(mfilename('fullpath')), 'data', 'config_MT.mat'); + % uncomment for update default config .mat + % expected = cfg; + % save(fileToLoad, 'expected'); + load(fileToLoad, 'expected'); + + % test + checkAllFields(cfg, expected); + assertEqual(cfg, expected); + +end + +function test_checkParameters_MT_MST() + + cfg.design.localizer = 'MT_MST'; + + cfg = checkParameters(cfg); + + % prepare expected results + cfg = removeDirFieldForGithubAction(cfg); + + fileToLoad = fullfile(fileparts(mfilename('fullpath')), 'data', 'config_MT_MST.mat'); + % uncomment for update default config .mat + % expected = cfg; + % save(fileToLoad, 'expected'); + load(fileToLoad, 'expected'); + + % test + checkAllFields(cfg, expected); + assertEqual(cfg, expected); + +end + +function checkAllFields(cfg, expected) + fields = fieldnames(expected); + for i = 1:numel(fields) + assertEqual(cfg.(fields{i}), expected.(fields{i})); + end +end + +function cfg = removeDirFieldForGithubAction(cfg) + cfg = rmfield(cfg, 'dir'); +end + +function [isGithub, pth] = isGithubMoxunitAction() + % (C) Copyright 2021 Remi Gau + isGithub = false; + + GITHUB_WORKSPACE = getenv('HOME'); + + if strcmp(GITHUB_WORKSPACE, '/github/home') + + isGithub = true; + pth = GITHUB_WORKSPACE; + + end + +end diff --git a/tests/test_expDesign.m b/tests/test_expDesign.m index 38f0368..c57f453 100644 --- a/tests/test_expDesign.m +++ b/tests/test_expDesign.m @@ -1,5 +1,5 @@ function test_suite = test_expDesign %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_getDesignInput.m b/tests/test_getDesignInput.m index 830c35f..3fa7381 100644 --- a/tests/test_getDesignInput.m +++ b/tests/test_getDesignInput.m @@ -1,5 +1,5 @@ function test_suite = test_getDesignInput %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_getDirectionBaseVectors.m b/tests/test_getDirectionBaseVectors.m index c16303c..c49eb19 100644 --- a/tests/test_getDirectionBaseVectors.m +++ b/tests/test_getDirectionBaseVectors.m @@ -1,5 +1,5 @@ function test_suite = test_getDirectionBaseVectors %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_setBlocksConditions.m b/tests/test_setBlocksConditions.m index 23e7820..ee2dbc5 100644 --- a/tests/test_setBlocksConditions.m +++ b/tests/test_setBlocksConditions.m @@ -1,5 +1,5 @@ function test_suite = test_setBlocksConditions %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_setDirections.m b/tests/test_setDirections.m index 8388e5c..d7952c0 100644 --- a/tests/test_setDirections.m +++ b/tests/test_setDirections.m @@ -1,5 +1,5 @@ function test_suite = test_setDirections %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_setFixationTargets.m b/tests/test_setFixationTargets.m index cfe8e8f..5a6aaf8 100644 --- a/tests/test_setFixationTargets.m +++ b/tests/test_setFixationTargets.m @@ -1,5 +1,5 @@ function test_suite = test_setFixationTargets %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/tests/test_setSpeedTargets.m b/tests/test_setSpeedTargets.m index c294634..30a5547 100644 --- a/tests/test_setSpeedTargets.m +++ b/tests/test_setSpeedTargets.m @@ -1,5 +1,5 @@ function test_suite = test_setSpeedTargets %#ok<*STOUT> - % (C) Copyright 2021 CPP visual motion localizer developpers + % (C) Copyright 2021 CPP visual motion localizer developers try % assignment of 'localfunctions' is necessary in Matlab >= 2016 test_functions = localfunctions(); %#ok<*NASGU> catch % no problem; early Matlab versions can use initTestSuite fine diff --git a/version.txt b/version.txt index fb7a04c..b043aa6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.4.0 +v0.5.0 diff --git a/visualMotionLocalizer.m b/visualMotionLocalizer.m deleted file mode 100755 index fad8952..0000000 --- a/visualMotionLocalizer.m +++ /dev/null @@ -1,211 +0,0 @@ -% (C) Copyright 2018 Mohamed Rezk -% (C) Copyright 2020 CPP visual motion localizer developpers - -%% Visual motion localizer - -getOnlyPress = 1; - -more off; - -% Clear all the previous stuff -clc; -if ~ismac - close all; - clear Screen; -end - -% make sure we got access to all the required functions and inputs -initEnv(); - -% set and load all the parameters to run the experiment -cfg = setParameters; -cfg = userInputs(cfg); -cfg = createFilename(cfg); - -%% Experiment - -% Safety loop: close the screen if code crashes -try - - %% Init the experiment - [cfg] = initPTB(cfg); - - cfg = postInitializationSetup(cfg); - - [el] = eyeTracker('Calibration', cfg); - - [cfg] = expDesign(cfg); - - % Prepare for the output logfiles with all - logFile.extraColumns = cfg.extraColumns; - logFile = saveEventsFile('init', cfg, logFile); - logFile = saveEventsFile('open', cfg, logFile); - - % prepare textures - cfg = apertureTexture('init', cfg); - cfg = dotTexture('init', cfg); - - disp(cfg); - - % Show experiment instruction - standByScreen(cfg); - - % prepare the KbQueue to collect responses - getResponse('init', cfg.keyboard.responseBox, cfg); - - % Wait for Trigger from Scanner - waitForTrigger(cfg); - - %% Experiment Start - - eyeTracker('StartRecording', cfg); - - cfg = getExperimentStart(cfg); - - getResponse('start', cfg.keyboard.responseBox); - - waitFor(cfg, cfg.timing.onsetDelay); - - %% For Each Block - - for iBlock = 1:cfg.design.nbBlocks - - fprintf('\n - Running Block %.0f \n', iBlock); - - eyeTracker('Message', cfg, ['start_block-', num2str(iBlock)]); - - dots = []; - previousEvent.target = 0; - - % For each event in the block - for iEvent = 1:cfg.design.nbEventsPerBlock - - % Check for experiment abortion from operator - checkAbort(cfg, cfg.keyboard.keyboard); - - [thisEvent, thisFixation, cfg] = preTrialSetup(cfg, iBlock, iEvent); - - % we wait for a trigger every 2 events - if cfg.pacedByTriggers.do && mod(iEvent, 2) == 1 - waitForTrigger( ... - cfg, ... - cfg.keyboard.responseBox, ... - cfg.pacedByTriggers.quietMode, ... - cfg.pacedByTriggers.nbTriggers); - end - - eyeTracker('Message', cfg, ... - ['start_trial-', num2str(iEvent), '_', thisEvent.trial_type]); - - % we only reuse the dots position for targets that consists of - % presenting static dots with the same position as those of the - % previous trial - % - % TODO does not take into account what to do if 3 or more targets in a row - if strcmp(cfg.target.type, 'static_repeat') && ... - strcmp(thisEvent.trial_type, 'static') && ... - thisEvent.target == 1 && ... - thisEvent.target == previousEvent.target - else - dots = []; - end - - % play the dots and collect onset and duraton of the event - [onset, duration, dots] = doDotMo(cfg, thisEvent, thisFixation, dots, iEvent); - - thisEvent = preSaveSetup( ... - thisEvent, ... - thisFixation, ... - iBlock, iEvent, ... - duration, onset, ... - cfg, ... - logFile); - - saveEventsFile('save', cfg, thisEvent); - - % collect the responses and appends to the event structure for - % saving in the tsv file - responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... - getOnlyPress); - - triggerString = ['trigger_' cfg.design.blockNames{iBlock}]; - saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString); - - eyeTracker('Message', cfg, ... - ['end_trial-', num2str(iEvent), '_', thisEvent.trial_type]); - - previousEvent = thisEvent; - - waitFor(cfg, cfg.timing.ISI); - - end - - % "prepare" cross for the baseline block - % if MT / MST this allows us to set the cross at the position of the next block - if iBlock < cfg.design.nbBlocks - nextBlock = iBlock + 1; - else - nextBlock = cfg.design.nbBlocks; - end - - [~, thisFixation] = preTrialSetup(cfg, nextBlock, 1); - drawFixation(thisFixation); - Screen('Flip', cfg.screen.win); - - eyeTracker('Message', cfg, ['end_block-', num2str(iBlock)]); - - waitFor(cfg, cfg.timing.IBI); - - % IBI trigger paced - if cfg.pacedByTriggers.do - waitForTrigger( ... - cfg, ... - cfg.keyboard.responseBox, ... - cfg.pacedByTriggers.quietMode, ... - cfg.timing.triggerIBI); - end - - if isfield(cfg.design, 'localizer') && ... - strcmpi(cfg.design.localizer, 'MT_MST') && ... - iBlock == cfg.design.nbBlocks / 2 - - waitFor(cfg, cfg.timing.changeFixationPosition); - - end - - % trigger monitoring - triggerEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... - getOnlyPress); - - triggerString = 'trigger_baseline'; - saveResponsesAndTriggers(triggerEvents, cfg, logFile, triggerString); - - end - - % End of the run for the BOLD to go down - waitFor(cfg, cfg.timing.endDelay); - - cfg = getExperimentEnd(cfg); - - eyeTracker('StopRecordings', cfg); - - % Close the logfiles - saveEventsFile('close', cfg, logFile); - - getResponse('stop', cfg.keyboard.responseBox); - getResponse('release', cfg.keyboard.responseBox); - - eyeTracker('Shutdown', cfg); - - createJson(cfg, cfg); - - farewellScreen(cfg); - - cleanUp(); - -catch - - cleanUp(); - psychrethrow(psychlasterror); - -end