diff --git a/.github/workflows/moxunit.yml b/.github/workflows/moxunit.yml
index 16a9ee9..511d918 100644
--- a/.github/workflows/moxunit.yml
+++ b/.github/workflows/moxunit.yml
@@ -19,7 +19,7 @@ jobs:
uses: joergbrech/moxunit-action@v1.1
with:
tests: tests
- src: src
+ src: subfun
with_coverage: true
cover_xml_file: coverage.xml
- name: Code coverage
diff --git a/README.md b/README.md
index fda12ab..187ba53 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,34 @@
+[](https://github.com/cpp-lln-lab/localizer_visual_motion/actions)
+
+
+[](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
# Translational Motion
-## Requirements
+## 1. Requirements
Make sure that the following toolboxes are installed and added to the matlab / octave path.
@@ -16,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.? |
-## Installation
+## 2. 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.
@@ -25,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
```
-## Structure and function details
+## 3. Structure and function details
-### visualLocTranslational
+### 3.1. 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)
@@ -35,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.)
-### setParameters
+### 3.2. setParameters
`setParameters.m` is the core engine of the experiment. It contains the following tweakable sections:
@@ -51,34 +77,34 @@ Any details of the experiment can be changed in `setParameters.m` (e.g., experim
- Instructions
- Task #1 parameters
-### subfun/doDotMo
+### 3.3. subfun/doDotMo
-#### Input:
+#### 3.3.1. 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
-#### Output:
+#### 3.3.2. 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.
-### subfun/expDesign
+### 3.4. 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.
-#### EVENTS
+#### 3.4.1. EVENTS
The `numEventsPerBlock` should be a multiple of the number of "base" listed in the `motionDirections` and `staticDirections` (4 at the moment).
-#### TARGETS:
+#### 3.4.2. 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
-#### Input:
+#### 3.4.3. Input:
- `expParameters`: parameters returned by `setParameters`
- `displayFigs`: a boolean to decide whether to show the basic design matrix of the design
-#### Output:
+#### 3.4.4. 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
- `0 90 180 270` indicate the angle
diff --git a/initEnv.m b/initEnv.m
index bd40b17..ec6eefe 100644
--- a/initEnv.m
+++ b/initEnv.m
@@ -17,6 +17,8 @@
octaveVersion = '4.0.3';
matlabVersion = '8.6.0';
+ installlist = {'io', 'statistics', 'image'};
+
if isOctave
% Exit if min version is not satisfied
@@ -24,27 +26,19 @@
error('Minimum required Octave version: %s', octaveVersion);
end
- installlist = {'statistics', 'image'};
for ii = 1:length(installlist)
+
+ packageName = installlist{ii};
+
try
% Try loading Octave packages
- disp(['loading ' installlist{ii}]);
- pkg('load', installlist{ii});
+ disp(['loading ' packageName]);
+ pkg('load', packageName);
catch
- errorcount = 1;
- while errorcount % Attempt twice in case installation fails
- try
- pkg('install', '-forge', installlist{ii});
- pkg('load', installlist{ii});
- errorcount = 0;
- catch err
- errorcount = errorcount + 1;
- if errorcount > 2
- error(err.message);
- end
- end
- end
+
+ tryInstallFromForge(packageName);
+
end
end
@@ -72,10 +66,8 @@
end
-%%
-%% Return: true if the environment is Octave.
-%%
function retval = isOctave
+ % Return: true if the environment is Octave.
persistent cacheval % speeds up repeated calls
if isempty (cacheval)
@@ -83,6 +75,25 @@
end
retval = cacheval;
+
+end
+
+function tryInstallFromForge(packageName)
+
+ errorcount = 1;
+ while errorcount % Attempt twice in case installation fails
+ try
+ pkg('install', '-forge', packageName);
+ pkg('load', packageName);
+ errorcount = 0;
+ catch err
+ errorcount = errorcount + 1;
+ if errorcount > 2
+ error(err.message);
+ end
+ end
+ end
+
end
function addDependencies()
diff --git a/lib/CPP_BIDS b/lib/CPP_BIDS
index c23210f..febf3e6 160000
--- a/lib/CPP_BIDS
+++ b/lib/CPP_BIDS
@@ -1 +1 @@
-Subproject commit c23210f5b35faa1505ca2cc35c92c3168564f98a
+Subproject commit febf3e648d088544421c4270f5a248eae1b8a8da
diff --git a/lib/CPP_PTB b/lib/CPP_PTB
index 7861863..e7be247 160000
--- a/lib/CPP_PTB
+++ b/lib/CPP_PTB
@@ -1 +1 @@
-Subproject commit 78618636295d4e52036d04c210f6ec5c6c4e68eb
+Subproject commit e7be247a039cfe5ed95122d5045328f770023935
diff --git a/miss_hit.cfg b/miss_hit.cfg
index 1ea7cd0..5a5f311 100644
--- a/miss_hit.cfg
+++ b/miss_hit.cfg
@@ -1,5 +1,11 @@
+# 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"
-exclude_dir: "Visual-loc_radial"
\ No newline at end of file
+
+# metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html)
+metric "cnest": limit 4
+metric "file_length": limit 500
+metric "cyc": limit 15
+metric "parameters": limit 5
\ No newline at end of file
diff --git a/setParameters.m b/setParameters.m
index 0b99eef..48dada5 100644
--- a/setParameters.m
+++ b/setParameters.m
@@ -39,6 +39,7 @@
% cfg.design.motionType = 'translation';
% cfg.design.motionType = 'radial';
cfg.design.motionType = 'translation';
+ cfg.design.motionDirections = [0 0 180 180];
cfg.design.names = {'static'; 'motion'};
cfg.design.nbRepetitions = 10;
cfg.design.nbEventsPerBlock = 12; % DO NOT CHANGE
diff --git a/subfun/expDesign.m b/subfun/expDesign.m
index 14a13b0..912915e 100644
--- a/subfun/expDesign.m
+++ b/subfun/expDesign.m
@@ -22,10 +22,10 @@
% TARGETS
%
% Pseudorandomization rules:
- % (1) If there are 2 targets per block we make sure that they are at least 2
+ % (1) If there are more than 1 target 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) Targets can not be present more than 2 times in the same event
+ % (3) Targets can not be present more than NB_REPETITIONS - 1 times in the same event
% position across blocks.
%
% Input:
@@ -57,16 +57,20 @@
% Set variables here for a dummy test of this function
if nargin < 1 || isempty(cfg)
- error('give me something to work with')
+ error('give me something to work with');
end
-
- fprintf('\n\nCreating design.\n\n')
+
+ 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);
- RANGE_TARGETS = [1 MAX_TARGET_PER_BLOCK];
- targetPerCondition = repmat(RANGE_TARGETS, 1, NB_REPETITIONS / 2);
+ if mod(NB_REPETITIONS, MAX_TARGET_PER_BLOCK) ~= 0
+ error('number of repetitions must be a multiple of max number of targets');
+ end
+
+ RANGE_TARGETS = 1:MAX_TARGET_PER_BLOCK;
+ targetPerCondition = repmat(RANGE_TARGETS, 1, NB_REPETITIONS / MAX_TARGET_PER_BLOCK);
numTargetsForEachBlock = zeros(1, NB_BLOCKS);
numTargetsForEachBlock(STATIC_INDEX) = shuffle(targetPerCondition);
@@ -85,33 +89,19 @@
% - targets cannot be on the first or last event of a block
% - no more than 2 target in the same event order
- chosenTarget = [];
-
- tmpTarget = numTargetsForEachBlock(iBlock);
-
- switch tmpTarget
-
- case 1
-
- chosenTarget = randsample(2:NB_EVENTS_PER_BLOCK - 1, tmpTarget, false);
-
- case 2
-
- targetDifference = 0;
-
- while abs(targetDifference) <= 2
- chosenTarget = randsample(2:NB_EVENTS_PER_BLOCK - 1, tmpTarget, false);
- targetDifference = diff(chosenTarget);
- end
+ nbTarget = numTargetsForEachBlock(iBlock);
- end
+ chosenPosition = setTargetPositionInSequence( ...
+ NB_EVENTS_PER_BLOCK, ...
+ nbTarget, ...
+ [1 NB_EVENTS_PER_BLOCK]);
- fixationTargets(iBlock, chosenTarget) = 1;
+ fixationTargets(iBlock, chosenPosition) = 1;
end
% Check rule 3
- if max(sum(fixationTargets)) < 3
+ if max(sum(fixationTargets)) < NB_REPETITIONS - 1
break
end
@@ -150,26 +140,17 @@
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_EVENTS_PER_BLOCK / length(STATIC_DIRECTIONS));
+ 1, NB_REPEATS_BASE_VECTOR);
for iMotionBlock = 1:NB_REPETITIONS
- % Check that we never have twice the same direction
- while 1
- tmp = [ ...
- shuffle(MOTION_DIRECTIONS), ...
- shuffle(MOTION_DIRECTIONS), ...
- shuffle(MOTION_DIRECTIONS)];
-
- if ~any(diff(tmp, [], 2) == 0)
- break
- end
- end
-
% Set motion direction and static order
- directions(MOTION_INDEX(iMotionBlock), :) = tmp;
+ directions(MOTION_INDEX(iMotionBlock), :) = ...
+ repeatShuffleConditions(MOTION_DIRECTIONS, NB_REPEATS_BASE_VECTOR);
directions(STATIC_INDEX(iMotionBlock), :) = static_directions;
end
@@ -183,15 +164,8 @@
% CONSTANTS
% Set directions for static and motion condition
- STATIC_DIRECTIONS = [-1 -1 -1 -1];
-
- switch cfg.design.motionType
- case 'translation'
- MOTION_DIRECTIONS = [0 0 180 180];
- case 'radial'
- STATIC_DIRECTIONS = [666 -666 666 -666];
- MOTION_DIRECTIONS = [666 -666 666 -666];
- end
+ MOTION_DIRECTIONS = cfg.design.motionDirections;
+ STATIC_DIRECTIONS = repmat(-1, size(MOTION_DIRECTIONS));
end
@@ -202,25 +176,16 @@
nbBlocks = length(cfg.design.names) * nbRepet;
end
-function [condition, STATIC_INDEX, MOTION_INDEX] = assignConditions(cfg)
+function [conditionNamesVector, STATIC_INDEX, MOTION_INDEX] = assignConditions(cfg)
[~, nbRepet] = getInput(cfg);
- condition = repmat(cfg.design.names, nbRepet, 1);
+ conditionNamesVector = repmat(cfg.design.names, nbRepet, 1);
% Get the index of each condition
- STATIC_INDEX = find(strcmp(condition, 'static'));
- MOTION_INDEX = find(strcmp(condition, 'motion'));
-
-end
+ STATIC_INDEX = find(strcmp(conditionNamesVector, 'static'));
+ MOTION_INDEX = find(strcmp(conditionNamesVector, 'motion'));
-function shuffled = shuffle(unshuffled)
- % in case PTB is not in the path
- try
- shuffled = Shuffle(unshuffled);
- catch
- shuffled = unshuffled(randperm(length(unshuffled)));
- end
end
function diplayDesign(cfg, displayFigs)
diff --git a/tests/miss_hit.cfg b/tests/miss_hit.cfg
new file mode 100644
index 0000000..f59bdb0
--- /dev/null
+++ b/tests/miss_hit.cfg
@@ -0,0 +1,10 @@
+# style guide (https://florianschanda.github.io/miss_hit/style_checker.html)
+line_length: 100
+regex_function_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*"
+suppress_rule: "copyright_notice"
+
+# metrics limit for the code quality (https://florianschanda.github.io/miss_hit/metrics.html)
+metric "cnest": limit 4
+metric "file_length": limit 500
+metric "cyc": limit 15
+metric "parameters": limit 5
\ No newline at end of file
diff --git a/tests/test_exDesign.m b/tests/test_exDesign.m
deleted file mode 100644
index f5bdceb..0000000
--- a/tests/test_exDesign.m
+++ /dev/null
@@ -1,25 +0,0 @@
-function test_suite = test_exDesign %#ok<*STOUT>
- 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_checkCfgDefault()
-
- initEnv();
-
- % cfg.design.motionType = 'translation';
- cfg.design.motionType = 'translation';
- cfg.design.names = {'static'; 'motion'};
- cfg.design.nbRepetitions = 10;
- cfg.design.nbEventsPerBlock = 12;
- cfg.dot.speedPixPerFrame = 4;
- cfg.target.maxNbPerBlock = 1;
- displayFigs = 1;
-
- [cfg] = expDesign(cfg, displayFigs)
-
-
-end
\ No newline at end of file
diff --git a/tests/test_expDesign.m b/tests/test_expDesign.m
new file mode 100644
index 0000000..a224c55
--- /dev/null
+++ b/tests/test_expDesign.m
@@ -0,0 +1,75 @@
+function test_suite = test_expDesign %#ok<*STOUT>
+ 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_exDesignBasic()
+
+ initEnv();
+
+ cfg.design.motionType = 'translation';
+ cfg.design.motionDirections = [0 0 180 180];
+ cfg.design.names = {'static'; 'motion'};
+ cfg.design.nbRepetitions = 6;
+ cfg.design.nbEventsPerBlock = 12;
+ cfg.dot.speedPixPerFrame = 4;
+ cfg.target.maxNbPerBlock = 3;
+ displayFigs = 0;
+
+ [cfg] = expDesign(cfg, displayFigs);
+
+ % make sure we don't have the same directions one after the other
+ directions = cfg.design.directions(strcmp(cfg.design.blockNames, 'motion'), :);
+ repeatedDirections = all(diff(directions, [], 2) == 0);
+ assertTrue(all(repeatedDirections == 0));
+
+ % 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]));
+
+ % check that we do not have more than the required number of targets per
+ % block
+ assertTrue(all(sum(cfg.design.fixationTargets, 2) <= cfg.target.maxNbPerBlock));
+
+ % make sure that targets are not presented too often in the same position
+ assertTrue(all(sum(cfg.design.fixationTargets) < cfg.design.nbRepetitions - 1));
+
+end
+
+function test_exDesignBasicOtherSetUp()
+
+ initEnv();
+
+ cfg.design.motionType = 'translation';
+ cfg.design.motionDirections = [0 90 180 270];
+ cfg.design.names = {'static'; 'motion'};
+ cfg.design.nbRepetitions = 9;
+ cfg.design.nbEventsPerBlock = 8;
+ cfg.dot.speedPixPerFrame = 4;
+ cfg.target.maxNbPerBlock = 3;
+ displayFigs = 0;
+
+ [cfg] = expDesign(cfg, displayFigs);
+
+ % make sure we don't have the same directions one after the other
+ directions = cfg.design.directions(strcmp(cfg.design.blockNames, 'motion'), :);
+ repeatedDirections = all(diff(directions, [], 2) == 0);
+ assertTrue(all(repeatedDirections == 0));
+
+ % 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]));
+
+ % check that we do not have more than the required number of targets per
+ % block
+ assertTrue(all(sum(cfg.design.fixationTargets, 2) <= cfg.target.maxNbPerBlock));
+
+ % make sure that targets are not presented too often in the same position
+ assertTrue(all(sum(cfg.design.fixationTargets) < cfg.design.nbRepetitions - 1));
+
+end