Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/moxunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 39 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
[![](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)

[![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)

<!-- vscode-markdown-toc -->
* 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)

<!-- vscode-markdown-toc-config
numbering=true
autoSave=true
/vscode-markdown-toc-config -->
<!-- /vscode-markdown-toc -->

# fMRI localizers for visual motion

# Translational Motion

## Requirements
## 1. <a name='Requirements'></a>Requirements

Make sure that the following toolboxes are installed and added to the matlab / octave path.

Expand All @@ -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. <a name='Installation'></a>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.
Expand All @@ -25,17 +51,17 @@ 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. <a name='Structureandfunctiondetails'></a>Structure and function details

### visualLocTranslational
### 3.1. <a name='visualLocTranslational'></a>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)

By default it is run in `Debug mode` meaning that it does not run care about subjID, run n., fMRI triggers, Eye Tracker, etc..

Any details of the experiment can be changed in `setParameters.m` (e.g., experiment mode, motion stimuli details, exp. design, etc.)

### setParameters
### 3.2. <a name='setParameters'></a>setParameters

`setParameters.m` is the core engine of the experiment. It contains the following tweakable sections:

Expand All @@ -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. <a name='subfundoDotMo'></a>subfun/doDotMo

#### Input:
#### 3.3.1. <a name='Input:'></a>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. <a name='Output:'></a>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. <a name='subfunexpDesign'></a>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. <a name='EVENTS'></a>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. <a name='TARGETS:'></a>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. <a name='Input:-1'></a>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. <a name='Output:-1'></a>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
Expand Down
49 changes: 30 additions & 19 deletions initEnv.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,28 @@
octaveVersion = '4.0.3';
matlabVersion = '8.6.0';

installlist = {'io', 'statistics', 'image'};

if isOctave

% Exit if min version is not satisfied
if ~compare_versions(OCTAVE_VERSION, octaveVersion, '>=')
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

Expand Down Expand Up @@ -72,17 +66,34 @@

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)
cacheval = (exist ('OCTAVE_VERSION', 'builtin') > 0);
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()
Expand Down
8 changes: 7 additions & 1 deletion miss_hit.cfg
Original file line number Diff line number Diff line change
@@ -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"

# 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
1 change: 1 addition & 0 deletions setParameters.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 29 additions & 64 deletions subfun/expDesign.m
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
Expand Down
Loading