diff --git a/README.md b/README.md index 44a5abf..4691521 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,30 @@ [![Build Status](https://travis-ci.com/cpp-lln-lab/localizer_visual_motion.svg?branch=master)](https://travis-ci.com/cpp-lln-lab/localizer_visual_motion) - - -- 1. [Requirements](#Requirements) -- 2. [Installation](#Installation) -- 3. [Structure and function details](#Structureandfunctiondetails) - _ 3.1. [visualLocTranslational](#visualLocTranslational) - _ 3.2. [setParameters](#setParameters) - _ 3.3. [subfun/doDotMo](#subfundoDotMo) - _ 3.3.1. [Input:](#Input:) - _ 3.3.2. [Output:](#Output:) - _ 3.4. [subfun/expDesign](#subfunexpDesign) - _ 3.4.1. [EVENTS](#EVENTS) - _ 3.4.2. [TARGETS:](#TARGETS:) - _ 3.4.3. [Input:](#Input:-1) - _ 3.4.4. [Output:](#Output:-1) - - - + +- [fMRI localizers for visual motion](#fmri-localizers-for-visual-motion) + - [Translational Motion](#translational-motion) + - [Requirements](#requirements) + - [Installation](#installation) + - [Structure and function details](#structure-and-function-details) + - [visualLocTranslational](#visualloctranslational) + - [setParameters](#setparameters) + - [Let the scanner pace the experiment](#let-the-scanner-pace-the-experiment) + - [subfun/doDotMo](#subfundodotmo) + - [Input](#input) + - [Output](#output) + - [subfun/expDesign](#subfunexpdesign) + - [EVENTS](#events) + - [TARGETS](#targets) + - [Input](#input-1) + - [Output](#output-1) + # fMRI localizers for visual motion -# Translational Motion +## Translational Motion -## 1. Requirements +## Requirements Make sure that the following toolboxes are installed and added to the matlab / octave path. @@ -45,7 +42,7 @@ For instructions see the following links: | [Matlab](https://www.mathworks.com/products/matlab.html) | >=2017 | | or [octave](https://www.gnu.org/software/octave/) | >=4.? | -## 2. Installation +## 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. @@ -54,9 +51,9 @@ You can install it all with git by doing. git clone --recurse-submodules https://github.com/cpp-lln-lab/localizer_visual_motion.git ``` -## 3. Structure and function details +## Structure and function details -### 3.1. visualLocTranslational +### visualLocTranslational Running this script will show blocks of motion dots (soon also moving gratings) and static dots. Motion blocks will show dots(/gratings) moving in one of four directions (up-, down-, left-, and right-ward) @@ -64,7 +61,7 @@ By default it is run in `Debug mode` meaning that it does not run care about sub Any details of the experiment can be changed in `setParameters.m` (e.g., experiment mode, motion stimuli details, exp. design, etc.) -### 3.2. setParameters +### setParameters `setParameters.m` is the core engine of the experiment. It contains the following tweakable sections: @@ -105,40 +102,40 @@ if cfg.pacedByTriggers.do end ``` -### 3.3. subfun/doDotMo +### subfun/doDotMo -#### 3.3.1. Input: +#### Input - `cfg`: PTB/machine configurations returned by `setParameters` and `initPTB` - `expParameters`: parameters returned by `setParameters` - `logFile`: structure that stores the experiment logfile to be saved -#### 3.3.2. Output: +#### Output - Event `onset` - Event `duration` 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. -### 3.4. subfun/expDesign +### subfun/expDesign Creates the sequence of blocks and the events in them. The conditions are consecutive static and motion blocks (Gives better results than randomised). It can be run as a stand alone without inputs to display a visual example of possible design. -#### 3.4.1. EVENTS +#### EVENTS The `numEventsPerBlock` should be a multiple of the number of "base" listed in the `motionDirections` and `staticDirections` (4 at the moment). -#### 3.4.2. TARGETS: +#### TARGETS - If there are 2 targets per block we make sure that they are at least 2 events apart. - Targets cannot be on the first or last event of a block -#### 3.4.3. Input: +#### Input - `expParameters`: parameters returned by `setParameters` - `displayFigs`: a boolean to decide whether to show the basic design matrix of the design -#### 3.4.4. Output: +#### Output - `expParameters.designBlockNames` is a cell array `(nr_blocks, 1)` with the name for each block - `expParameters.designDirections` is an array `(nr_blocks, numEventsPerBlock)` with the direction to present in a given block diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7d8b3af --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +# Documentation \ No newline at end of file diff --git a/initEnv.m b/initEnv.m index bf4ec9c..0420ce7 100644 --- a/initEnv.m +++ b/initEnv.m @@ -1,18 +1,19 @@ -% -% 1 - Check if version requirements -% are satisfied and the packages are -% are installed/loaded: -% Octave > 4 -% - image -% - optim -% - struct -% - statistics -% -% MATLAB >= R2015b -% -% 2 - Add project to the O/M path +% (C) Copyright 2020 Agah Karakuzu +% (C) Copyright 2019 CPP BIDS SPM-pipeline developpers function initEnv + % 1 - Check if version requirements + % are satisfied and the packages are + % are installed/loaded: + % Octave > 4 + % - image + % - optim + % - struct + % - statistics + % + % MATLAB >= R2015b + % + % 2 - Add project to the O/M path octaveVersion = '4.0.3'; matlabVersion = '8.6.0'; @@ -56,8 +57,8 @@ if numel(dir(libDirectory)) <= 2 % Means that the external is empty error(['Git submodules are not cloned!', ... - 'Try this in your terminal:', ... - ' git submodule update --recursive ']); + 'Try this in your terminal:', ... + ' git submodule update --recursive ']); else addDependencies(); end diff --git a/lib/CPP_BIDS b/lib/CPP_BIDS index 962c947..bfa76ac 160000 --- a/lib/CPP_BIDS +++ b/lib/CPP_BIDS @@ -1 +1 @@ -Subproject commit 962c947fe38094da9561eeba5daa44993505f2c0 +Subproject commit bfa76acd7ad6796dbe2d353a097c9c1db94cade9 diff --git a/lib/CPP_PTB b/lib/CPP_PTB index e7be247..0334054 160000 --- a/lib/CPP_PTB +++ b/lib/CPP_PTB @@ -1 +1 @@ -Subproject commit e7be247a039cfe5ed95122d5045328f770023935 +Subproject commit 03340548820285460bde9e27396a2595bb2e54af diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..ca16653 --- /dev/null +++ b/lib/README.md @@ -0,0 +1 @@ +# External libraries and dependencies \ No newline at end of file diff --git a/miss_hit.cfg b/miss_hit.cfg index 5a5f311..df98a43 100644 --- a/miss_hit.cfg +++ b/miss_hit.cfg @@ -1,8 +1,10 @@ # style guide (https://florianschanda.github.io/miss_hit/style_checker.html) line_length: 100 regex_function_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" -suppress_rule: "copyright_notice" exclude_dir: "lib" +copyright_entity: "Mohamed Rezk" +copyright_entity: "Agah Karakuzu" +copyright_entity: "CPP visual motion localizer developpers" # metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html) metric "cnest": limit 4 diff --git a/setParameters.m b/setParameters.m index 6179c47..7714aeb 100644 --- a/setParameters.m +++ b/setParameters.m @@ -1,3 +1,5 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + function [cfg] = setParameters() % VISUAL LOCALIZER @@ -9,21 +11,21 @@ % setParamters.m file is % change that if you want the data to be saved somewhere else cfg.dir.output = fullfile( ... - fileparts(mfilename('fullpath')), '..', ... - 'output'); + fileparts(mfilename('fullpath')), '..', ... + 'output'); %% Debug mode settings cfg.debug.do = false; % 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.debug.transpWin = true; % To test with trasparent full size screen cfg.verbose = false; %% Engine parameters cfg.testingDevice = 'mri'; - cfg.eyeTracker.do = true; + cfg.eyeTracker.do = false; cfg.audio.do = false; cfg = setMonitor(cfg); @@ -34,18 +36,24 @@ % MRI settings cfg = setMRI(cfg); - cfg.pacedByTriggers.do = true; + cfg.pacedByTriggers.do = false; %% Experiment Design - % cfg.design.motionType = 'translation'; + % cfg.design.localizer = 'MT_MST'; + % cfg.design.motionType = 'radial'; cfg.design.motionType = 'translation'; + cfg.design.motionDirections = [0 0 180 180]; cfg.design.names = {'static'; 'motion'}; cfg.design.nbRepetitions = 8; cfg.design.nbEventsPerBlock = 12; % DO NOT CHANGE + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + cfg.design.names = {'fixation_right'; 'fixation_left'}; + end + %% Timing % FOR 7T: if you want to create localizers on the fly, the following must be @@ -65,6 +73,10 @@ % Number of seconds after the end all the stimuli before ending the run cfg.timing.endDelay = 3.6; + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + cfg.timing.IBI = 3.6; + end + % reexpress those in terms of repetition time if cfg.pacedByTriggers.do @@ -81,6 +93,11 @@ cfg.timing.onsetDelay = 0; % Number of seconds after the end all the stimuli before ending the run cfg.timing.endDelay = 2; + + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + cfg.timing.IBI = 2; + end + end %% Visual Stimulation @@ -92,11 +109,11 @@ % Number of dots per visual angle square. cfg.dot.density = 1; % Dot life time in seconds - cfg.dot.lifeTime = 10; + cfg.dot.lifeTime = .15; % proportion of dots killed per frame - cfg.dot.proportionKilledPerFrame = 0; + cfg.dot.proportionKilledPerFrame = 0.005; % Dot Size (dot width) in visual angles. - cfg.dot.size = .1; + cfg.dot.size = .2; cfg.dot.color = cfg.color.white; % Diameter/length of side of aperture in Visual angles @@ -104,9 +121,18 @@ cfg.aperture.width = []; % if left empty it will take the screen height cfg.aperture.xPos = 0; + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + cfg.aperture.type = 'circle'; + cfg.aperture.width = 7; % if left empty it will take the screen height + cfg.aperture.xPos = 7; + end + %% Task(s) cfg.task.name = 'visual localizer'; + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + cfg.task.name = 'mt mst localizer'; + end % Instruction cfg.task.instruction = '1-Detect the RED fixation cross\n \n\n'; @@ -115,7 +141,7 @@ cfg.fixation.type = 'cross'; cfg.fixation.colorTarget = cfg.color.red; cfg.fixation.color = cfg.color.white; - cfg.fixation.width = .5; + cfg.fixation.width = .25; cfg.fixation.lineWidthPix = 3; cfg.fixation.xDisplacement = 0; cfg.fixation.yDisplacement = 0; @@ -123,16 +149,24 @@ cfg.target.maxNbPerBlock = 1; cfg.target.duration = 0.05; % In secs - cfg.extraColumns = {'direction', 'speed', 'target', 'event', 'block', 'keyName'}; + cfg.extraColumns = { ... + 'direction', ... + 'speed', ... + 'target', ... + 'event', ... + 'block', ... + 'keyName', ... + 'fixationPosition', ... + 'aperturePosition'}; end function cfg = setKeyboards(cfg) cfg.keyboard.escapeKey = 'ESCAPE'; cfg.keyboard.responseKey = { ... - 'r', 'g', 'y', 'b', ... - 'd', 'n', 'z', 'e', ... - 't'}; % dnze rgyb + 'r', 'g', 'y', 'b', ... + 'd', 'n', 'z', 'e', ... + 't'}; cfg.keyboard.keyboard = []; cfg.keyboard.responseBox = []; @@ -145,7 +179,7 @@ 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.triggerNb = 1; cfg.mri.repetitionTime = 1.8; diff --git a/subfun/assignConditions.m b/subfun/assignConditions.m new file mode 100644 index 0000000..74ef6ae --- /dev/null +++ b/subfun/assignConditions.m @@ -0,0 +1,20 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function [conditionNamesVector, CONDITON1_INDEX, CONDITON2_INDEX] = assignConditions(cfg) + + [~, nbRepet] = getDesignInput(cfg); + + conditionNamesVector = repmat(cfg.design.names, nbRepet, 1); + + % Get the index of each condition + nameCondition1 = 'static'; + nameCondition2 = 'motion'; + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + nameCondition1 = 'fixation_right'; + nameCondition2 = 'fixation_left'; + end + + CONDITON1_INDEX = find(strcmp(conditionNamesVector, nameCondition1)); + CONDITON2_INDEX = find(strcmp(conditionNamesVector, nameCondition2)); + +end diff --git a/subfun/diplayDesign.m b/subfun/diplayDesign.m new file mode 100644 index 0000000..34929bb --- /dev/null +++ b/subfun/diplayDesign.m @@ -0,0 +1,81 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function diplayDesign(cfg, displayFigs) + + %% Visualize the design matrix + if displayFigs + + close all; + + figure(1); + + % Shows blocks (static and motion) and events (motion direction) order + directions = cfg.design.directions; + directions(directions == -1) = -90; + + subplot(3, 1, 1); + imagesc(directions); + + labelAxesBlock(); + + caxis([-90 - 37, 270 + 37]); + myColorMap = lines(5); + colormap(myColorMap); + + title('Block (static and motion) & Events (motion direction)'); + + % Shows the fixation targets design in each event (1 or 0) + fixationTargets = cfg.design.fixationTargets; + + subplot(3, 1, 2); + imagesc(fixationTargets); + labelAxesBlock(); + title('Fixation Targets design'); + colormap(gray); + + % Shows the fixation targets position distribution in the block across + % the experimet + [~, itargetPosition] = find(fixationTargets == 1); + + subplot(3, 1, 3); + hist(itargetPosition); + labelAxesFreq(); + title('Fixation Targets position distribution'); + + figure(2); + + [motionDirections] = getDirectionBaseVectors(cfg); + motionDirections = unique(motionDirections); + + for iMotion = 1:length(motionDirections) + + [~, position] = find(directions == motionDirections(iMotion)); + + subplot(2, 2, iMotion); + hist(position); + scaleAxes(); + labelAxesFreq(); + title(num2str(motionDirections(iMotion))); + + end + + end + +end + +function labelAxesBlock() + % an old viking saying because they really cared about their axes + ylabel('Block seq.', 'Fontsize', 8); + xlabel('Events', 'Fontsize', 8); +end + +function labelAxesFreq() + % an old viking saying because they really cared about their axes + ylabel('Number of targets', 'Fontsize', 8); + xlabel('Events', 'Fontsize', 8); +end + +function scaleAxes() + xlim([1 12]); + ylim([0 5]); +end diff --git a/subfun/doDotMo.m b/subfun/doDotMo.m index 5978421..4a4a312 100644 --- a/subfun/doDotMo.m +++ b/subfun/doDotMo.m @@ -1,4 +1,7 @@ -function [onset, duration] = doDotMo(cfg, thisEvent) +% (C) Copyright 2018 Mohamed Rezk +% (C) Copyright 2020 CPP visual motion localizer developpers + +function [onset, duration] = doDotMo(cfg, thisEvent, thisFixation) % Draws the stimulation of static/moving in 4 directions dots or static % % DIRECTIONS @@ -30,12 +33,14 @@ [dots] = updateDots(dots, cfg); %% Center the dots + % We assumed that zero is at the top left, but we want it to be % in the center, so shift the dots up and left, which just means % adding half of the screen width in pixel to both the x and y direction. thisEvent.dot.positions = (dots.positions - cfg.dot.matrixWidth / 2)'; %% make textures + dotTexture('make', cfg, thisEvent); apertureTexture('make', cfg, thisEvent); @@ -46,9 +51,6 @@ apertureTexture('draw', cfg, thisEvent); - % If this frame shows a target we change the color of the cross - thisFixation.fixation = cfg.fixation; - thisFixation.screen = cfg.screen; if thisEvent.target(1) && GetSecs < (onset + cfg.target.duration) thisFixation.fixation.color = cfg.fixation.colorTarget; end @@ -67,7 +69,7 @@ %% Erase last dots - drawFixation(cfg); + drawFixation(thisFixation); Screen('DrawingFinished', cfg.screen.win); diff --git a/subfun/expDesign.m b/subfun/expDesign.m index 912915e..1678d83 100644 --- a/subfun/expDesign.m +++ b/subfun/expDesign.m @@ -1,3 +1,5 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + function [cfg] = expDesign(cfg, displayFigs) % Creates the sequence of blocks and the events in them % @@ -62,8 +64,8 @@ fprintf('\n\nCreating design.\n\n'); - [NB_BLOCKS, NB_REPETITIONS, NB_EVENTS_PER_BLOCK, MAX_TARGET_PER_BLOCK] = getInput(cfg); - [~, STATIC_INDEX, MOTION_INDEX] = assignConditions(cfg); + [NB_BLOCKS, NB_REPETITIONS, NB_EVENTS_PER_BLOCK, MAX_TARGET_PER_BLOCK] = getDesignInput(cfg); + [~, CONDITON1_INDEX, CONDITON2_INDEX] = assignConditions(cfg); if mod(NB_REPETITIONS, MAX_TARGET_PER_BLOCK) ~= 0 error('number of repetitions must be a multiple of max number of targets'); @@ -73,8 +75,8 @@ targetPerCondition = repmat(RANGE_TARGETS, 1, NB_REPETITIONS / MAX_TARGET_PER_BLOCK); numTargetsForEachBlock = zeros(1, NB_BLOCKS); - numTargetsForEachBlock(STATIC_INDEX) = shuffle(targetPerCondition); - numTargetsForEachBlock(MOTION_INDEX) = shuffle(targetPerCondition); + numTargetsForEachBlock(CONDITON1_INDEX) = shuffle(targetPerCondition); + numTargetsForEachBlock(CONDITON2_INDEX) = shuffle(targetPerCondition); %% Give the blocks the names with condition and design the task in each event while 1 @@ -92,9 +94,9 @@ nbTarget = numTargetsForEachBlock(iBlock); chosenPosition = setTargetPositionInSequence( ... - NB_EVENTS_PER_BLOCK, ... - nbTarget, ... - [1 NB_EVENTS_PER_BLOCK]); + NB_EVENTS_PER_BLOCK, ... + nbTarget, ... + [1 NB_EVENTS_PER_BLOCK]); fixationTargets(iBlock, chosenPosition) = 1; @@ -123,147 +125,3 @@ diplayDesign(cfg, displayFigs); end - -function cfg = setDirections(cfg) - - [MOTION_DIRECTIONS, STATIC_DIRECTIONS] = getDirectionBaseVectors(cfg); - - [NB_BLOCKS, NB_REPETITIONS, NB_EVENTS_PER_BLOCK] = getInput(cfg); - - [~, STATIC_INDEX, MOTION_INDEX] = assignConditions(cfg); - - if mod(NB_EVENTS_PER_BLOCK, length(MOTION_DIRECTIONS)) ~= 0 - error('Number of events/block not a multiple of number of motion/static direction'); - end - - % initialize - directions = zeros(NB_BLOCKS, NB_EVENTS_PER_BLOCK); - - % Create a vector for the static condition - NB_REPEATS_BASE_VECTOR = NB_EVENTS_PER_BLOCK / length(STATIC_DIRECTIONS); - - static_directions = repmat( ... - STATIC_DIRECTIONS, ... - 1, NB_REPEATS_BASE_VECTOR); - - for iMotionBlock = 1:NB_REPETITIONS - - % Set motion direction and static order - directions(MOTION_INDEX(iMotionBlock), :) = ... - repeatShuffleConditions(MOTION_DIRECTIONS, NB_REPEATS_BASE_VECTOR); - directions(STATIC_INDEX(iMotionBlock), :) = static_directions; - - end - - cfg.design.directions = directions; - -end - -function [MOTION_DIRECTIONS, STATIC_DIRECTIONS] = getDirectionBaseVectors(cfg) - - % CONSTANTS - % Set directions for static and motion condition - - MOTION_DIRECTIONS = cfg.design.motionDirections; - STATIC_DIRECTIONS = repmat(-1, size(MOTION_DIRECTIONS)); - -end - -function [nbBlocks, nbRepet, nbEventsBlock, maxTargBlock] = getInput(cfg) - nbRepet = cfg.design.nbRepetitions; - nbEventsBlock = cfg.design.nbEventsPerBlock; - maxTargBlock = cfg.target.maxNbPerBlock; - nbBlocks = length(cfg.design.names) * nbRepet; -end - -function [conditionNamesVector, STATIC_INDEX, MOTION_INDEX] = assignConditions(cfg) - - [~, nbRepet] = getInput(cfg); - - conditionNamesVector = repmat(cfg.design.names, nbRepet, 1); - - % Get the index of each condition - STATIC_INDEX = find(strcmp(conditionNamesVector, 'static')); - MOTION_INDEX = find(strcmp(conditionNamesVector, 'motion')); - -end - -function diplayDesign(cfg, displayFigs) - - %% Visualize the design matrix - if displayFigs - - close all; - - figure(1); - - % Shows blocks (static and motion) and events (motion direction) order - directions = cfg.design.directions; - directions(directions == -1) = -90; - - subplot(3, 1, 1); - imagesc(directions); - - labelAxesBlock(); - - caxis([-90 - 37, 270 + 37]); - myColorMap = lines(5); - colormap(myColorMap); - - title('Block (static and motion) & Events (motion direction)'); - - % Shows the fixation targets design in each event (1 or 0) - fixationTargets = cfg.design.fixationTargets; - - subplot(3, 1, 2); - imagesc(fixationTargets); - labelAxesBlock(); - title('Fixation Targets design'); - colormap(gray); - - % Shows the fixation targets position distribution in the block across - % the experimet - [~, itargetPosition] = find(fixationTargets == 1); - - subplot(3, 1, 3); - hist(itargetPosition); - labelAxesFreq(); - title('Fixation Targets position distribution'); - - figure(2); - - [motionDirections] = getDirectionBaseVectors(cfg); - motionDirections = unique(motionDirections); - - for iMotion = 1:length(motionDirections) - - [~, position] = find(directions == motionDirections(iMotion)); - - subplot(2, 2, iMotion); - hist(position); - scaleAxes(); - labelAxesFreq(); - title(num2str(motionDirections(iMotion))); - - end - - end - -end - -function labelAxesBlock() - % an old viking saying because they really cared about their axes - ylabel('Block seq.', 'Fontsize', 8); - xlabel('Events', 'Fontsize', 8); -end - -function labelAxesFreq() - % an old viking saying because they really cared about their axes - ylabel('Number of targets', 'Fontsize', 8); - xlabel('Events', 'Fontsize', 8); -end - -function scaleAxes() - xlim([1 12]); - ylim([0 5]); -end diff --git a/subfun/getDesignInput.m b/subfun/getDesignInput.m new file mode 100644 index 0000000..7e177bc --- /dev/null +++ b/subfun/getDesignInput.m @@ -0,0 +1,8 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function [nbBlocks, nbRepet, nbEventsBlock, maxTargBlock] = getDesignInput(cfg) + nbRepet = cfg.design.nbRepetitions; + nbEventsBlock = cfg.design.nbEventsPerBlock; + maxTargBlock = cfg.target.maxNbPerBlock; + nbBlocks = length(cfg.design.names) * nbRepet; +end diff --git a/subfun/getDirectionBaseVectors.m b/subfun/getDirectionBaseVectors.m new file mode 100644 index 0000000..b63d0ea --- /dev/null +++ b/subfun/getDirectionBaseVectors.m @@ -0,0 +1,17 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function [CONDITION1_DIRECTIONS, CONDITION2_DIRECTIONS] = getDirectionBaseVectors(cfg) + + % CONSTANTS + + % Set directions for static and motion condition + CONDITION1_DIRECTIONS = cfg.design.motionDirections; + CONDITION2_DIRECTIONS = repmat(-1, size(CONDITION1_DIRECTIONS)); % static + + % for for the MT / MST localizer + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + CONDITION1_DIRECTIONS = cfg.design.motionDirections; + CONDITION2_DIRECTIONS = cfg.design.motionDirections; + end + +end diff --git a/subfun/postInitializationSetup.m b/subfun/postInitializationSetup.m index 1bbaa4e..fdafac3 100644 --- a/subfun/postInitializationSetup.m +++ b/subfun/postInitializationSetup.m @@ -1,3 +1,5 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + function varargout = postInitializationSetup(varargin) % varargout = postInitializatinSetup(varargin) @@ -20,8 +22,8 @@ % 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); + (cfg.dot.matrixWidth / cfg.screen.ppd)^2); - varargout = cfg; + varargout = {cfg}; end diff --git a/subfun/preSaveSetup.m b/subfun/preSaveSetup.m new file mode 100644 index 0000000..d13c65f --- /dev/null +++ b/subfun/preSaveSetup.m @@ -0,0 +1,33 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function varargout = preSaveSetup(varargin) + % varargout = postInitializatinSetup(varargin) + + % generic function to prepare structures before saving + + [thisEvent, thisFixation, iBlock, iEvent, duration, onset, cfg, logFile] = ... + deal(varargin{:}); + + thisEvent.event = iEvent; + thisEvent.block = iBlock; + thisEvent.keyName = 'n/a'; + thisEvent.duration = duration; + thisEvent.onset = onset - cfg.experimentStart; + thisEvent.fixationPosition = thisFixation.fixation.xDisplacement; + thisEvent.aperturePosition = cfg.aperture.xPos * sign(cfg.aperture.xPosPix); + + % % this value should be in degrees / second in the log file + % % highlights that the way speed is passed around could be + % % simplified. + % % + % thisEvent.speed + % % + + % Save the events txt logfile + % we save event by event so we clear this variable every loop + thisEvent.fileID = logFile.fileID; + thisEvent.extraColumns = logFile.extraColumns; + + varargout = {thisEvent}; + +end diff --git a/subfun/preTrialSetup.m b/subfun/preTrialSetup.m new file mode 100644 index 0000000..2c8a49a --- /dev/null +++ b/subfun/preTrialSetup.m @@ -0,0 +1,37 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function varargout = preTrialSetup(varargin) + % varargout = postInitializatinSetup(varargin) + + % generic function to prepare some structure before each trial + + [cfg, iBlock, iEvent] = deal(varargin{:}); + + % set direction, speed of that event and if it is a target + thisEvent.trial_type = cfg.design.blockNames{iBlock}; + thisEvent.direction = cfg.design.directions(iBlock, iEvent); + thisEvent.speed = cfg.design.speeds(iBlock, iEvent); + thisEvent.target = cfg.design.fixationTargets(iBlock, iEvent); + + % If this frame shows a target we change the color of the cross + thisFixation.fixation = cfg.fixation; + thisFixation.screen = cfg.screen; + + switch thisEvent.trial_type + case 'fixation_right' + cfg.aperture.xPosPix = -abs(cfg.aperture.xPosPix); + + thisFixation.fixation.xDisplacement = cfg.aperture.xPos; + thisFixation = initFixation(thisFixation); + + case 'fixation_left' + cfg.aperture.xPosPix = +abs(cfg.aperture.xPosPix); + + thisFixation.fixation.xDisplacement = -cfg.aperture.xPos; + thisFixation = initFixation(thisFixation); + + end + + varargout = {thisEvent, thisFixation, cfg}; + +end diff --git a/subfun/saveResponsesAndTriggers.m b/subfun/saveResponsesAndTriggers.m index 61f8e5a..bca56bd 100644 --- a/subfun/saveResponsesAndTriggers.m +++ b/subfun/saveResponsesAndTriggers.m @@ -1,3 +1,5 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + function saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString) if isfield(responseEvents(1), 'onset') && ~isempty(responseEvents(1).onset) diff --git a/subfun/setDirections.m b/subfun/setDirections.m new file mode 100644 index 0000000..c6c4a04 --- /dev/null +++ b/subfun/setDirections.m @@ -0,0 +1,52 @@ +% (C) Copyright 2020 CPP visual motion localizer developpers + +function cfg = setDirections(cfg) + + [CONDITION1_DIRECTIONS, CONDITION2_DIRECTIONS] = getDirectionBaseVectors(cfg); + + [NB_BLOCKS, NB_REPETITIONS, NB_EVENTS_PER_BLOCK] = getDesignInput(cfg); + + [~, CONDITON1_INDEX, CONDITON2_INDEX] = assignConditions(cfg); + + if mod(NB_EVENTS_PER_BLOCK, length(CONDITION1_DIRECTIONS)) ~= 0 + error('Number of events/block not a multiple of number of motion/static direction'); + end + + % initialize + directions = zeros(NB_BLOCKS, NB_EVENTS_PER_BLOCK); + + % Create a vector for the static condition + NB_REPEATS_BASE_VECTOR = NB_EVENTS_PER_BLOCK / length(CONDITION2_DIRECTIONS); + + static_directions = repmat( ... + CONDITION2_DIRECTIONS, ... + 1, NB_REPEATS_BASE_VECTOR); + + for iMotionBlock = 1:NB_REPETITIONS + + if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + + % Set motion direction for MT/MST localizer + + directions(CONDITON1_INDEX(iMotionBlock), :) = ... + repeatShuffleConditions(CONDITION1_DIRECTIONS, NB_REPEATS_BASE_VECTOR); + + directions(CONDITON2_INDEX(iMotionBlock), :) = ... + repeatShuffleConditions(CONDITION1_DIRECTIONS, NB_REPEATS_BASE_VECTOR); + + else + + % Set motion direction and static order + + directions(CONDITON2_INDEX(iMotionBlock), :) = ... + repeatShuffleConditions(CONDITION1_DIRECTIONS, NB_REPEATS_BASE_VECTOR); + + directions(CONDITON1_INDEX(iMotionBlock), :) = static_directions; + + end + + end + + cfg.design.directions = directions; + +end diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..da470b5 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,5 @@ +# Unit tests folder + +To be run using mox unit. + +Code coverage can be estimated with Mocov. \ No newline at end of file diff --git a/tests/test_expDesign.m b/tests/test_expDesign.m index a224c55..5ef89da 100644 --- a/tests/test_expDesign.m +++ b/tests/test_expDesign.m @@ -28,8 +28,9 @@ function test_exDesignBasic() % make sure that we have the right number of blocks of the right length assertTrue(all(size(cfg.design.directions) == [ ... - cfg.design.nbRepetitions * numel(cfg.design.names), ... - cfg.design.nbEventsPerBlock])); + cfg.design.nbRepetitions * ... + numel(cfg.design.names), ... + cfg.design.nbEventsPerBlock])); % check that we do not have more than the required number of targets per % block @@ -62,8 +63,9 @@ function test_exDesignBasicOtherSetUp() % make sure that we have the right number of blocks of the right length assertTrue(all(size(cfg.design.directions) == [ ... - cfg.design.nbRepetitions * numel(cfg.design.names), ... - cfg.design.nbEventsPerBlock])); + cfg.design.nbRepetitions * ... + numel(cfg.design.names), ... + cfg.design.nbEventsPerBlock])); % check that we do not have more than the required number of targets per % block diff --git a/visualLocTranslational.m b/visualLocTranslational.m index 0951228..4e40173 100644 --- a/visualLocTranslational.m +++ b/visualLocTranslational.m @@ -1,17 +1,14 @@ -%% Visual hMT localizer using translational motion in four directions -% (up- down- left and right-ward) +% (C) Copyright 2018 Mohamed Rezk +% (C) Copyright 2020 CPP visual motion localizer developpers -% by Mohamed Rezk 2018 -% adapted by MarcoB and RemiG 2020 - -%% +%% Visual motion localizer getOnlyPress = 1; more off; % Clear all the previous stuff -% clc; clear; +clc; if ~ismac close all; clear Screen; @@ -37,7 +34,11 @@ [el] = eyeTracker('Calibration', cfg); + % if isfield(cfg.design, 'localizer') && strcmpi(cfg.design.localizer, 'MT_MST') + % [cfg] = expDesignMtMst(cfg); + % else [cfg] = expDesign(cfg); + % end % Prepare for the output logfiles with all logFile.extraColumns = cfg.extraColumns; @@ -79,50 +80,33 @@ % Check for experiment abortion from operator checkAbort(cfg, cfg.keyboard.keyboard); - % set direction, speed of that event and if it is a target - thisEvent.trial_type = cfg.design.blockNames{iBlock}; - thisEvent.direction = cfg.design.directions(iBlock, iEvent); - thisEvent.speed = cfg.design.speeds(iBlock, iEvent); - thisEvent.target = cfg.design.fixationTargets(iBlock, iEvent); + [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); + cfg, ... + cfg.keyboard.responseBox, ... + cfg.pacedByTriggers.quietMode, ... + cfg.pacedByTriggers.nbTriggers); end % play the dots and collect onset and duraton of the event - [onset, duration] = doDotMo(cfg, thisEvent); - - thisEvent.event = iEvent; - thisEvent.block = iBlock; - thisEvent.keyName = 'n/a'; - thisEvent.duration = duration; - thisEvent.onset = onset - cfg.experimentStart; - - % % this value should be in degrees / second in the log file - % % highlights that the way speed is passed around could be - % % simplified. - % % - % thisEvent.speed - % % - - % Save the events txt logfile - % we save event by event so we clear this variable every loop - thisEvent.fileID = logFile.fileID; - thisEvent.extraColumns = logFile.extraColumns; - + [onset, duration] = doDotMo(cfg, thisEvent, thisFixation); + + thisEvent = preSaveSetup( ... + thisEvent, ... + thisFixation, ... + iBlock, iEvent, ... + duration, onset, ... + cfg, ... + logFile); saveEventsFile('save', cfg, thisEvent); - clear thisEvent; - % collect the responses and appends to the event structure for % saving in the tsv file responseEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... - getOnlyPress); + getOnlyPress); triggerString = ['trigger_' cfg.design.blockNames{iBlock}]; saveResponsesAndTriggers(responseEvents, cfg, logFile, triggerString); @@ -133,11 +117,22 @@ eyeTracker('StopRecordings', cfg); + % "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); + waitFor(cfg, cfg.timing.IBI); % trigger monitoring triggerEvents = getResponse('check', cfg.keyboard.responseBox, cfg, ... - getOnlyPress); + getOnlyPress); triggerString = 'trigger_baseline'; saveResponsesAndTriggers(triggerEvents, cfg, logFile, triggerString);