From 2b20116a872c7682ecf00b941c2155c1567a9221 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 23 Sep 2020 10:52:19 +0200 Subject: [PATCH 01/15] add function for pix to degree conversion --- src/utils/degToPix.m | 13 +++++++++++++ src/utils/pixToDeg.m | 29 +++++++++++++++++++++++++++++ tests/test_pixToDeg.m | 21 +++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/utils/pixToDeg.m create mode 100644 tests/test_pixToDeg.m diff --git a/src/utils/degToPix.m b/src/utils/degToPix.m index 9780d02..5a38b38 100644 --- a/src/utils/degToPix.m +++ b/src/utils/degToPix.m @@ -4,6 +4,19 @@ % For a given field value in degrees of visual angle in the structure, % this computes its value in pixel using the pixel per degree value of the cfg structure % and returns a structure with an additional field with Pix suffix holding that new value. + % + % + % USAGE: + % ------ + % fixation.width = 2; + % cfg.screen.ppd = 10; + % + % fixation = degToPix('width', fixation, cfg); + % + % Returns: + % ------- + % fixation.widthPix = 20; + % deg = getfield(structure, fieldName); %#ok diff --git a/src/utils/pixToDeg.m b/src/utils/pixToDeg.m new file mode 100644 index 0000000..0d6b9e1 --- /dev/null +++ b/src/utils/pixToDeg.m @@ -0,0 +1,29 @@ +% 2020 CPP BIDS SPM-pipeline developpers + +function structure = pixToDeg(fieldName, structure, cfg) + % structure = pixToDeg(fieldName, structure, cfg) + % + % For a given field value in pixel in the structure, + % this computes its value in degrees of viual angle using the pixel per + % degree value of the cfg structure and returns a structure with an + % additional field holding that new value and with a fieldname with any + % 'Pix' suffix removed and replaced with the 'DegVA' suffix . + % + % USAGE: + % ------ + % fixation.widthPix = 20; + % cfg.screen.ppd = 10; + % + % fixation = degToPix('widthPix', fixation, cfg); + % + % Returns: + % ------- + % fixation.widthDegVA = 2; + % + + pix = getfield(structure, fieldName); %#ok + + structure = setfield(structure, [strrep(fieldName, 'Pix', '') 'DegVA'], ... + floor(pix / cfg.screen.ppd)); %#ok + +end \ No newline at end of file diff --git a/tests/test_pixToDeg.m b/tests/test_pixToDeg.m new file mode 100644 index 0000000..847b233 --- /dev/null +++ b/tests/test_pixToDeg.m @@ -0,0 +1,21 @@ +function test_suite = test_pixToDeg %#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_pixToDegBasic() + + fixation.widthPix = 20; + cfg.screen.ppd = 10; + + fixation = pixToDeg('widthPix', fixation, cfg); + + expectedStruct.widthDegVA = 2; + expectedStruct.widthPix = 20; + + assertEqual(expectedStruct, fixation); + +end From 17107b77df857fbb756b70f97db41ad5a86b71c9 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 23 Sep 2020 11:17:13 +0200 Subject: [PATCH 02/15] mh fix --- src/utils/degToPix.m | 4 ++-- src/utils/pixToDeg.m | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils/degToPix.m b/src/utils/degToPix.m index 5a38b38..bb5fac5 100644 --- a/src/utils/degToPix.m +++ b/src/utils/degToPix.m @@ -10,11 +10,11 @@ % ------ % fixation.width = 2; % cfg.screen.ppd = 10; - % + % % fixation = degToPix('width', fixation, cfg); % % Returns: - % ------- + % ------- % fixation.widthPix = 20; % diff --git a/src/utils/pixToDeg.m b/src/utils/pixToDeg.m index 0d6b9e1..5efcba5 100644 --- a/src/utils/pixToDeg.m +++ b/src/utils/pixToDeg.m @@ -1,23 +1,23 @@ -% 2020 CPP BIDS SPM-pipeline developpers +% 2020 CPP BIDS SPM-pipeline developpers function structure = pixToDeg(fieldName, structure, cfg) % structure = pixToDeg(fieldName, structure, cfg) % % For a given field value in pixel in the structure, - % this computes its value in degrees of viual angle using the pixel per - % degree value of the cfg structure and returns a structure with an - % additional field holding that new value and with a fieldname with any + % this computes its value in degrees of viual angle using the pixel per + % degree value of the cfg structure and returns a structure with an + % additional field holding that new value and with a fieldname with any % 'Pix' suffix removed and replaced with the 'DegVA' suffix . % % USAGE: % ------ % fixation.widthPix = 20; % cfg.screen.ppd = 10; - % + % % fixation = degToPix('widthPix', fixation, cfg); % % Returns: - % ------- + % ------- % fixation.widthDegVA = 2; % @@ -26,4 +26,4 @@ structure = setfield(structure, [strrep(fieldName, 'Pix', '') 'DegVA'], ... floor(pix / cfg.screen.ppd)); %#ok -end \ No newline at end of file +end From ba72db41bb8c6731af7dadd29ac992202536982f Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 25 Sep 2020 17:05:59 +0200 Subject: [PATCH 03/15] refactor dot functions refactor reseedDots function refactor dot functions --- src/dot/computeCartCoord.m | 7 ++-- src/dot/computeRadialMotionDirection.m | 4 +- src/dot/dotTexture.m | 3 +- src/dot/generateNewDotPositions.m | 4 +- src/dot/initDots.m | 41 +++++--------------- src/dot/reseedDots.m | 19 +++++---- src/dot/seedDots.m | 23 +++++++++++ src/dot/setDotDirection.m | 53 ++++++++++++++------------ tests/test_generateNewDotPositions.m | 4 +- tests/test_initDots.m | 6 +-- tests/test_reseedDots.m | 40 +++++++++---------- tests/test_seedDots.m | 38 ++++++++++++++++++ tests/test_setDotDirection.m | 50 +++++++++++++++++++----- 13 files changed, 183 insertions(+), 109 deletions(-) create mode 100644 src/dot/seedDots.m create mode 100644 tests/test_seedDots.m diff --git a/src/dot/computeCartCoord.m b/src/dot/computeCartCoord.m index 063944d..7224254 100644 --- a/src/dot/computeCartCoord.m +++ b/src/dot/computeCartCoord.m @@ -1,7 +1,8 @@ -function cartesianCoordinates = computeCartCoord(positions, cfg) +function cartesianCoordinates = computeCartCoord(positions, dotMatrixWidth) + cartesianCoordinates = ... - [positions(:, 1) - cfg.dot.matrixWidth / 2, ... % x coordinate - positions(:, 2) - cfg.dot.matrixWidth / 2]; % y coordinate + [positions(:, 1) - dotMatrixWidth / 2, ... % x coordinate + positions(:, 2) - dotMatrixWidth / 2]; % y coordinate % cartesianCoordinates = positions; end diff --git a/src/dot/computeRadialMotionDirection.m b/src/dot/computeRadialMotionDirection.m index ef47ed8..0c5f7bf 100644 --- a/src/dot/computeRadialMotionDirection.m +++ b/src/dot/computeRadialMotionDirection.m @@ -1,6 +1,6 @@ -function angleMotion = computeRadialMotionDirection(cfg, dots) +function angleMotion = computeRadialMotionDirection(positions, dotMatrixWidth, dots) - cartesianCoordinates = computeCartCoord(dots.positions, cfg); + cartesianCoordinates = computeCartCoord(positions, dotMatrixWidth); [angleMotion, ~] = cart2pol(cartesianCoordinates(:, 1), cartesianCoordinates(:, 2)); angleMotion = angleMotion / pi * 180; diff --git a/src/dot/dotTexture.m b/src/dot/dotTexture.m index c775a03..b4036bf 100644 --- a/src/dot/dotTexture.m +++ b/src/dot/dotTexture.m @@ -4,7 +4,8 @@ case 'init' cfg.dot.texture = Screen('MakeTexture', cfg.screen.win, ... - cfg.color.background(1) * ones(cfg.screen.winRect([4 3]))); + cfg.color.background(1) * ... + ones(cfg.screen.winRect([4 3]))); case 'make' diff --git a/src/dot/generateNewDotPositions.m b/src/dot/generateNewDotPositions.m index 92bf28f..d59bb04 100644 --- a/src/dot/generateNewDotPositions.m +++ b/src/dot/generateNewDotPositions.m @@ -1,5 +1,5 @@ -function newPositions = generateNewDotPositions(cfg, dotNumber) +function newPositions = generateNewDotPositions(dotMatrixWidth, nbDots) - newPositions = rand(dotNumber, 2) * cfg.dot.matrixWidth; + newPositions = rand(nbDots, 2) * dotMatrixWidth; end diff --git a/src/dot/initDots.m b/src/dot/initDots.m index 2c247cd..5ade937 100644 --- a/src/dot/initDots.m +++ b/src/dot/initDots.m @@ -24,47 +24,26 @@ dots.direction = thisEvent.direction(1); - speedPixPerFrame = thisEvent.speed(1); - - lifeTime = cfg.dot.lifeTime; - % decide which dots are signal dots (1) and those are noise dots (0) dots.isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence; + + dots.speedPixPerFrame = thisEvent.speed(1); + lifeTime = cfg.dot.lifeTime; % for static dots if dots.direction == -1 - speedPixPerFrame = 0; - lifeTime = Inf; dots.isSignal = true(cfg.dot.number, 1); + dots.speedPixPerFrame = 0; + lifeTime = Inf; end - %% Set an array of dot positions [xposition, yposition] - % These can never be bigger than 1 or lower than 0 - % [0,0] is the top / left of the square - % [1,1] is the bottom / right of the square - dots.positions = generateNewDotPositions(cfg, cfg.dot.number); - - %% Set vertical and horizontal speed for all dots - dots = setDotDirection(cfg, dots); - - [horVector, vertVector] = decomposeMotion(dots.directionAllDots); - speeds = [horVector, vertVector]; - - % we were working with unit vectors. we now switch to pixels - speeds = speeds * speedPixPerFrame; - - %% Create a vector to update to dotlife time of each dot - % Not all set to 1 so the dots will die at different times - % The maximum value is the duraion of the event in frames - time = floor(rand(cfg.dot.number, 1) * cfg.timing.eventDuration / cfg.screen.ifi); + % set position and directions fo the dots + [dots.positions, dots.speeds, dots.time] = ... + seedDots(dots, cfg, dots.isSignal); %% Convert from seconds to frames lifeTime = ceil(lifeTime / cfg.screen.ifi); - - %% dots.lifeTime = lifeTime; - dots.time = time; - dots.speeds = speeds; - dots.speedPixPerFrame = speedPixPerFrame; - + + end diff --git a/src/dot/reseedDots.m b/src/dot/reseedDots.m index 7c2c750..1e9ec05 100644 --- a/src/dot/reseedDots.m +++ b/src/dot/reseedDots.m @@ -5,7 +5,7 @@ fixationWidthPix = cfg.fixation.widthPix; end - cartesianCoordinates = computeCartCoord(dots.positions, cfg); + cartesianCoordinates = computeCartCoord(dots.positions, cfg.dot.matrixWidth); [~, radius] = cart2pol(cartesianCoordinates(:, 1), cartesianCoordinates(:, 2)); % Create a logical vector to detect any dot that has: @@ -26,15 +26,14 @@ % If there is any such dot we relocate it to a new random position % and change its lifetime to 1 if any(N) - - dots.positions(N, :) = generateNewDotPositions(cfg, sum(N)); - - dots = setDotDirection(cfg, dots); - - [horVector, vertVector] = decomposeMotion(dots.directionAllDots); - dots.speeds = [horVector, vertVector] * dots.speedPixPerFrame; - - dots.time(N, 1) = 1; + + isSignal = dots.isSignal(N); + + [positions, speeds, time] = seedDots(dots, cfg, isSignal); + + dots.positions(N, :) = positions; + dots.speeds(N, :) = speeds; + dots.time(N, 1) = time; end diff --git a/src/dot/seedDots.m b/src/dot/seedDots.m new file mode 100644 index 0000000..b835ec8 --- /dev/null +++ b/src/dot/seedDots.m @@ -0,0 +1,23 @@ +function [positions, speeds, time] = seedDots(dots, cfg, isSignal) + + nbDots = numel(isSignal); + + %% Set an array of dot positions [xposition, yposition] + % These can never be bigger than 1 or lower than 0 + % [0,0] is the top / left of the square + % [1,1] is the bottom / right of the square + positions = generateNewDotPositions(cfg.dot.matrixWidth, nbDots); + + %% Set vertical and horizontal speed for all dots + directionAllDots = setDotDirection(positions, cfg, dots, isSignal); + [horVector, vertVector] = decomposeMotion(directionAllDots); + + % we were working with unit vectors. we now switch to pixels + speeds = [horVector, vertVector] * dots.speedPixPerFrame; + + %% Create a vector to update to dotlife time of each dot + % Not all set to 1 so the dots will die at different times + % The maximum value is the duraion of the event in frames + time = floor(rand(nbDots, 1) * cfg.timing.eventDuration / cfg.screen.ifi); + +end \ No newline at end of file diff --git a/src/dot/setDotDirection.m b/src/dot/setDotDirection.m index 0df6fe0..a9f08df 100644 --- a/src/dot/setDotDirection.m +++ b/src/dot/setDotDirection.m @@ -1,4 +1,4 @@ -function dots = setDotDirection(cfg, dots) +function directionAllDots = setDotDirection(positions, cfg, dots, isSignal) % dots = setDotDirection(cfg, dots) % % creates some new direction for the dots @@ -9,30 +9,35 @@ % all the other dots get a random value between 0 and 360. % % all directions are in end expressed between 0 and 360 - - directionAllDots = nan(cfg.dot.number, 1); - - % Coherent dots - - if numel(dots.direction) == 1 - dots.direction = ones(sum(dots.isSignal), 1) * dots.direction; - elseif numel(dots.direction) ~= sum(dots.isSignal) - error(['dots.direction must have one element' ... - 'or as many element as there are coherent dots']); + + directionAllDots = dots.direction; + + % when we initialiaze the direction for all the dots + % after that dots.direction will be a vector + if numel(directionAllDots) == 1 + + directionAllDots(isSignal) = ones(sum(isSignal), 1) * dots.direction; + end - - directionAllDots(dots.isSignal) = dots.direction; - + + %% Coherent dots if strcmp(cfg.design.motionType, 'radial') - angleMotion = computeRadialMotionDirection(cfg, dots); - directionAllDots(dots.isSignal) = angleMotion; - end - - % Random direction for the non coherent dots - - directionAllDots(~dots.isSignal) = rand(sum(~dots.isSignal), 1) * 360; - directionAllDots = rem(directionAllDots, 360); - - dots.directionAllDots = directionAllDots; + + angleMotion = computeRadialMotionDirection(positions, cfg.dot.matrixWidth, dots); + + directionAllDots(isSignal) = angleMotion; + end + + %% Random direction for the non coherent dots + directionAllDots(~isSignal) = rand(sum(~isSignal), 1) * 360; + + %% Express the direction in the 0 to 360 range + directionAllDots = mod(directionAllDots, 360); + + % ensure we return a colum vector + if size(directionAllDots, 1)==1 + directionAllDots = directionAllDots'; + end + end diff --git a/tests/test_generateNewDotPositions.m b/tests/test_generateNewDotPositions.m index f6a2721..d47e285 100644 --- a/tests/test_generateNewDotPositions.m +++ b/tests/test_generateNewDotPositions.m @@ -8,10 +8,10 @@ function test_generateNewDotPositionsBasic() - cfg.dot.matrixWidth = 400; + dotMatrixWidth = 400; dotNumber = 200; - newPositions = generateNewDotPositions(cfg, dotNumber); + newPositions = generateNewDotPositions(dotMatrixWidth, dotNumber); assertEqual([200, 2], size(newPositions)); diff --git a/tests/test_initDots.m b/tests/test_initDots.m index 7f92d6e..991c76b 100644 --- a/tests/test_initDots.m +++ b/tests/test_initDots.m @@ -47,8 +47,7 @@ function test_initDotsBasic() expectedStructure.isSignal = ones(10, 1); expectedStructure.speeds = repmat([1 0], 10, 1) * 10; expectedStructure.speedPixPerFrame = 10; - expectedStructure.direction = zeros(10, 1); - expectedStructure.directionAllDots = zeros(10, 1); + expectedStructure.direction = 0; % remove undeterministic output dots = rmfield(dots, 'time'); @@ -84,8 +83,7 @@ function test_initDotsStatic() expectedStructure.isSignal = ones(10, 1); expectedStructure.speeds = zeros(10, 2); expectedStructure.speedPixPerFrame = 0; - expectedStructure.direction = -1 * ones(10, 1); - expectedStructure.directionAllDots = -1 * ones(10, 1); + expectedStructure.direction = -1; %% test assertEqual(expectedStructure, dots); diff --git a/tests/test_reseedDots.m b/tests/test_reseedDots.m index 6f3da93..7ed3abf 100644 --- a/tests/test_reseedDots.m +++ b/tests/test_reseedDots.m @@ -7,47 +7,45 @@ end function test_reseedDotsBasic() + + dotNb = 5; - cfg.screen.winWidth = 2000; + cfg.design.motionType = 'translation'; + cfg.timing.eventDuration = 1; % in seconds + cfg.screen.ifi = 0.01; % in seconds - cfg.design.motionType = 'radial'; - - cfg.dot.matrixWidth = 50; % in pixels - cfg.dot.number = 5; + cfg.dot.matrixWidth = 1000; % in pixels + cfg.dot.number = dotNb; cfg.dot.sizePix = 20; cfg.dot.proportionKilledPerFrame = 0; - cfg.fixation.widthPix = 20; + cfg.fixation.widthPix = 5; dots.lifeTime = 100; dots.speedPixPerFrame = 3; dots.direction = 90; - dots.isSignal = true(5, 1); + dots.isSignal = true(dotNb, 1); + dots.speeds = ones(dotNb, 2); dots.positions = [ ... - 49, 1 % OK - 490, 2043 % out of frame - -104, 392 % out of frame - 492, 402 % OK - 1000, 1000 % on the fixation cross + 300, 10 % OK + 750, 1010 % out of frame + -1040, 50 % out of frame + 300, 300 % OK + 500, 500 % on the fixation cross ]; - dots.time = [ ... + originalTime = [ ... 6; ... OK 4; ... OK 56; ... OK 300; ... % exceeded its life time 50]; % OK + dots.time = originalTime; dots = reseedDots(dots, cfg); - reseeded = [ ... - 6 - 1 - 1 - 1 - 1]; - - assertEqual(reseeded, dots.time); + assertEqual(dots.time(1), originalTime(1)); + assertTrue(all(dots.time(2:end) ~= originalTime(2:end))); end diff --git a/tests/test_seedDots.m b/tests/test_seedDots.m new file mode 100644 index 0000000..56ac733 --- /dev/null +++ b/tests/test_seedDots.m @@ -0,0 +1,38 @@ +function test_suite = test_seedDots %#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_seedDotsBasic() + + %% set up + + cfg.dot.matrixWidth = 400; + cfg.design.motionType = 'translation'; + cfg.timing.eventDuration = 1; % in seconds + cfg.screen.ifi = 0.01; % in seconds + + nbDots = 10; + isSignal = [true(5, 1); false(nbDots - 5, 1)]; + + dots.direction = 0; + dots.speedPixPerFrame = 10; + + [positions, speeds, time] = seedDots(dots, cfg, isSignal); + + %% Deterministic output + assertEqual(size(positions), [nbDots, 2]); + assertTrue(all(all([... + positions(:) <= cfg.dot.matrixWidth, ... + positions(:) >= 0]))); + + assertTrue(all(time(:) >= 0)); + assertTrue(all(time(:) <= 1 / 0.01)); + + assertEqual(speeds(1:5,:), repmat([10 0], 5, 1)); + +end + diff --git a/tests/test_setDotDirection.m b/tests/test_setDotDirection.m index f68fdd1..790bbe1 100644 --- a/tests/test_setDotDirection.m +++ b/tests/test_setDotDirection.m @@ -6,21 +6,53 @@ initTestSuite; end -function test_setDotDirectionBasic() +function test_setDotDirectionInit() % create 5 coherent dots with direction == 362 (that should give 2 in the % end) - % also creates 955 additonal dots with random direction between 0 and 360 - - cfg.dot.number = 1000; + % also creates additonal dots with random direction between 0 and 360 + + nbDots = 10; + + cfg.dot.matrixWidth = 400; cfg.design.motionType = 'translation'; + dots.direction = 362; + dots.isSignal = [true(5, 1); false(nbDots - 5, 1)]; + + + positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); + + directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); + + assertEqual(directionAllDots(1:5), 2 * ones(5, 1)); + assertGreaterThan(directionAllDots, zeros(size(directionAllDots)) ); + assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); - dots.isSignal = [true(5, 1); false(1000 - 5, 1)]; +end - dots = setDotDirection(cfg, dots); +function test_setDotDirectionReturn() + % make sure that if the directions are already set it only changes that of + % the noise dots + % input has 4 signal dots with set directions also has additonal noise dots with negative direction + + nbDots = 8; + + cfg.dot.matrixWidth = 400; + cfg.design.motionType = 'translation'; + + dots.direction = [... + [362; 2; -362; -2]; ... + -20 * ones(4, 1)]; + dots.isSignal = [true(4, 1); false(nbDots - 4, 1)]; + + + positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); + + directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); - assertTrue(all(dots.directionAllDots(1:5) == 2 * ones(5, 1))); - assertTrue(all(dots.directionAllDots >= 0)); - assertTrue(all(dots.directionAllDots <= 360)); + assertEqual(directionAllDots(1:4), [2 2 358 358]'); + assertGreaterThan(directionAllDots, zeros(size(directionAllDots)) ); + assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); + assertTrue(all(directionAllDots(5:end) ~= -20 * ones(4,1))) end From 619240538843c893b49a94e830254deb415d1b6c Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 25 Sep 2020 19:28:23 +0200 Subject: [PATCH 04/15] create a dot motion simulation --- src/dot/dotMotionSimulation.m | 104 ++++++++++++++++++++++++++++++++++ tests/test_initDots.m | 1 - 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/dot/dotMotionSimulation.m diff --git a/src/dot/dotMotionSimulation.m b/src/dot/dotMotionSimulation.m new file mode 100644 index 0000000..4e88f1e --- /dev/null +++ b/src/dot/dotMotionSimulation.m @@ -0,0 +1,104 @@ +function relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, doPlot) + % to simulate where the dots are more dense on the screen + % relativeDensityContrast : hard to get it below 0.10 + + close all + + if nargin<3 + + doPlot=1; + + end + + + if nargin<2 + + thisEvent.direction = 0; % degrees + thisEvent.speed = 1; % pix per frame + + end + + if nargin<1 + + cfg.design.motionType = 'translation'; + + cfg.dot.coherence = 1; % proportion + + cfg.dot.lifeTime = 10; % in seconds + + cfg.dot.matrixWidth = 250; % in pixels + + cfg.dot.proportionKilledPerFrame = 0; + + cfg.timing.eventDuration = 500; % in seconds + + end + + % interframe interval + cfg.screen.ifi = 0.016; % in seconds + + % size of the fixation is 1% of screen width + cfg.fixation.widthPix = ceil(cfg.dot.matrixWidth * 1 / 100); + + % dot size + cfg.dot.sizePix = 1; + + % We fill 25% of the screen with dots + cfg.dot.number = round(cfg.dot.matrixWidth^2 * 25 / 100); + + fprintf(1, '\n\nDot motion simulation:') + + nbFrames = ceil(cfg.timing.eventDuration/cfg.screen.ifi); + frameToReport = round(linspace(1, nbFrames, 20)); + + % to keep track of where the dots have been + dotDensity = zeros(cfg.dot.matrixWidth); + + [dots] = initDots(cfg, thisEvent); + dotDensity = updateDotDensity(dotDensity, dots); + + for iFrame = 1:nbFrames + + if any(frameToReport==iFrame) + fprintf(1, '.') + end + + [dots] = updateDots(dots, cfg); + + dotDensity = updateDotDensity(dotDensity, dots); + + end + + %% Post sim + % trim the edges (to avoid super high/low values + dotDensity = dotDensity(2:end-1, 2:end-1); + + relativeDensityContrast = (max(dotDensity(:)) - min(dotDensity(:))) / max(dotDensity(:)); + + if doPlot + imagesc(dotDensity) + axis square + title('dot density') + end + +end + + +function dotDensity = updateDotDensity(dotDensity, dots) + + x = round(dots.positions(:,1)); + x = avoidEdgeValues(x, size(dotDensity, 2)); + + y = round(dots.positions(:,2)); + y = avoidEdgeValues(y, size(dotDensity, 1)); + + ind = sub2ind(size(dotDensity), y, x); + + dotDensity(ind) = dotDensity(ind) + 1; + +end + +function x = avoidEdgeValues(x, dim) + x(x<1) = 1; + x(x>dim) = dim; +end \ No newline at end of file diff --git a/tests/test_initDots.m b/tests/test_initDots.m index 991c76b..75a50d8 100644 --- a/tests/test_initDots.m +++ b/tests/test_initDots.m @@ -27,7 +27,6 @@ function test_initDotsBasic() cfg.dot.coherence = 1; % proportion cfg.dot.lifeTime = 0.250; % in seconds cfg.dot.matrixWidth = 50; % in pixels - cfg.screen.winWidth = 2000; % in pixels cfg.timing.eventDuration = 1; % in seconds cfg.screen.ifi = 0.01; % in seconds From cea101ed2c4a9207dad766df50aa8beaa43c6d12 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 25 Sep 2020 19:31:18 +0200 Subject: [PATCH 05/15] mh fix --- src/dot/computeCartCoord.m | 2 +- src/dot/dotMotionSimulation.m | 108 +++++++++++++++++----------------- src/dot/initDots.m | 5 +- src/dot/reseedDots.m | 4 +- src/dot/seedDots.m | 14 +++-- src/dot/setDotDirection.m | 24 ++++---- src/utils/pixToDeg.m | 2 +- tests/test_reseedDots.m | 12 ++-- tests/test_seedDots.m | 17 +++--- tests/test_setDotDirection.m | 37 ++++++------ 10 files changed, 111 insertions(+), 114 deletions(-) diff --git a/src/dot/computeCartCoord.m b/src/dot/computeCartCoord.m index 7224254..2982138 100644 --- a/src/dot/computeCartCoord.m +++ b/src/dot/computeCartCoord.m @@ -1,5 +1,5 @@ function cartesianCoordinates = computeCartCoord(positions, dotMatrixWidth) - + cartesianCoordinates = ... [positions(:, 1) - dotMatrixWidth / 2, ... % x coordinate positions(:, 2) - dotMatrixWidth / 2]; % y coordinate diff --git a/src/dot/dotMotionSimulation.m b/src/dot/dotMotionSimulation.m index 4e88f1e..4c82f2c 100644 --- a/src/dot/dotMotionSimulation.m +++ b/src/dot/dotMotionSimulation.m @@ -1,104 +1,102 @@ function relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, doPlot) % to simulate where the dots are more dense on the screen % relativeDensityContrast : hard to get it below 0.10 - - close all - - if nargin<3 - - doPlot=1; - + + close all; + + if nargin < 3 + + doPlot = 1; + end - - - if nargin<2 - + + if nargin < 2 + thisEvent.direction = 0; % degrees thisEvent.speed = 1; % pix per frame - + end - - if nargin<1 - + + if nargin < 1 + cfg.design.motionType = 'translation'; - + cfg.dot.coherence = 1; % proportion - + cfg.dot.lifeTime = 10; % in seconds - + cfg.dot.matrixWidth = 250; % in pixels - + cfg.dot.proportionKilledPerFrame = 0; - + cfg.timing.eventDuration = 500; % in seconds end - + % interframe interval cfg.screen.ifi = 0.016; % in seconds - + % size of the fixation is 1% of screen width cfg.fixation.widthPix = ceil(cfg.dot.matrixWidth * 1 / 100); - + % dot size cfg.dot.sizePix = 1; - + % We fill 25% of the screen with dots cfg.dot.number = round(cfg.dot.matrixWidth^2 * 25 / 100); - - fprintf(1, '\n\nDot motion simulation:') - - nbFrames = ceil(cfg.timing.eventDuration/cfg.screen.ifi); + + fprintf(1, '\n\nDot motion simulation:'); + + nbFrames = ceil(cfg.timing.eventDuration / cfg.screen.ifi); frameToReport = round(linspace(1, nbFrames, 20)); - + % to keep track of where the dots have been dotDensity = zeros(cfg.dot.matrixWidth); - + [dots] = initDots(cfg, thisEvent); dotDensity = updateDotDensity(dotDensity, dots); for iFrame = 1:nbFrames - - if any(frameToReport==iFrame) - fprintf(1, '.') + + if any(frameToReport == iFrame) + fprintf(1, '.'); end - + [dots] = updateDots(dots, cfg); - + dotDensity = updateDotDensity(dotDensity, dots); - + end - + %% Post sim % trim the edges (to avoid super high/low values - dotDensity = dotDensity(2:end-1, 2:end-1); - + dotDensity = dotDensity(2:end - 1, 2:end - 1); + relativeDensityContrast = (max(dotDensity(:)) - min(dotDensity(:))) / max(dotDensity(:)); - + if doPlot - imagesc(dotDensity) - axis square - title('dot density') + imagesc(dotDensity); + axis square; + title('dot density'); end - -end +end function dotDensity = updateDotDensity(dotDensity, dots) - - x = round(dots.positions(:,1)); + + x = round(dots.positions(:, 1)); x = avoidEdgeValues(x, size(dotDensity, 2)); - - y = round(dots.positions(:,2)); + + y = round(dots.positions(:, 2)); y = avoidEdgeValues(y, size(dotDensity, 1)); - + ind = sub2ind(size(dotDensity), y, x); - + dotDensity(ind) = dotDensity(ind) + 1; - + end function x = avoidEdgeValues(x, dim) - x(x<1) = 1; - x(x>dim) = dim; -end \ No newline at end of file + x(x < 1) = 1; + x(x > dim) = dim; +end diff --git a/src/dot/initDots.m b/src/dot/initDots.m index 5ade937..94077b0 100644 --- a/src/dot/initDots.m +++ b/src/dot/initDots.m @@ -26,7 +26,7 @@ % decide which dots are signal dots (1) and those are noise dots (0) dots.isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence; - + dots.speedPixPerFrame = thisEvent.speed(1); lifeTime = cfg.dot.lifeTime; @@ -44,6 +44,5 @@ %% Convert from seconds to frames lifeTime = ceil(lifeTime / cfg.screen.ifi); dots.lifeTime = lifeTime; - - + end diff --git a/src/dot/reseedDots.m b/src/dot/reseedDots.m index 1e9ec05..d30e7a9 100644 --- a/src/dot/reseedDots.m +++ b/src/dot/reseedDots.m @@ -26,9 +26,9 @@ % If there is any such dot we relocate it to a new random position % and change its lifetime to 1 if any(N) - + isSignal = dots.isSignal(N); - + [positions, speeds, time] = seedDots(dots, cfg, isSignal); dots.positions(N, :) = positions; diff --git a/src/dot/seedDots.m b/src/dot/seedDots.m index b835ec8..1e1fecb 100644 --- a/src/dot/seedDots.m +++ b/src/dot/seedDots.m @@ -1,7 +1,9 @@ -function [positions, speeds, time] = seedDots(dots, cfg, isSignal) - +function [positions, speeds, time] = seedDots(varargin) + + [dots, cfg, isSignal] = deal(varargin{:}); + nbDots = numel(isSignal); - + %% Set an array of dot positions [xposition, yposition] % These can never be bigger than 1 or lower than 0 % [0,0] is the top / left of the square @@ -11,7 +13,7 @@ %% Set vertical and horizontal speed for all dots directionAllDots = setDotDirection(positions, cfg, dots, isSignal); [horVector, vertVector] = decomposeMotion(directionAllDots); - + % we were working with unit vectors. we now switch to pixels speeds = [horVector, vertVector] * dots.speedPixPerFrame; @@ -19,5 +21,5 @@ % Not all set to 1 so the dots will die at different times % The maximum value is the duraion of the event in frames time = floor(rand(nbDots, 1) * cfg.timing.eventDuration / cfg.screen.ifi); - -end \ No newline at end of file + +end diff --git a/src/dot/setDotDirection.m b/src/dot/setDotDirection.m index a9f08df..3d093b4 100644 --- a/src/dot/setDotDirection.m +++ b/src/dot/setDotDirection.m @@ -9,35 +9,35 @@ % all the other dots get a random value between 0 and 360. % % all directions are in end expressed between 0 and 360 - + directionAllDots = dots.direction; - + % when we initialiaze the direction for all the dots % after that dots.direction will be a vector if numel(directionAllDots) == 1 - + directionAllDots(isSignal) = ones(sum(isSignal), 1) * dots.direction; - + end - + %% Coherent dots if strcmp(cfg.design.motionType, 'radial') - + angleMotion = computeRadialMotionDirection(positions, cfg.dot.matrixWidth, dots); - + directionAllDots(isSignal) = angleMotion; end - + %% Random direction for the non coherent dots directionAllDots(~isSignal) = rand(sum(~isSignal), 1) * 360; - + %% Express the direction in the 0 to 360 range directionAllDots = mod(directionAllDots, 360); - + % ensure we return a colum vector - if size(directionAllDots, 1)==1 + if size(directionAllDots, 1) == 1 directionAllDots = directionAllDots'; end - + end diff --git a/src/utils/pixToDeg.m b/src/utils/pixToDeg.m index 5efcba5..a55978c 100644 --- a/src/utils/pixToDeg.m +++ b/src/utils/pixToDeg.m @@ -24,6 +24,6 @@ pix = getfield(structure, fieldName); %#ok structure = setfield(structure, [strrep(fieldName, 'Pix', '') 'DegVA'], ... - floor(pix / cfg.screen.ppd)); %#ok + floor(pix / cfg.screen.ppd)); %#ok end diff --git a/tests/test_reseedDots.m b/tests/test_reseedDots.m index 7ed3abf..6097bf5 100644 --- a/tests/test_reseedDots.m +++ b/tests/test_reseedDots.m @@ -7,7 +7,7 @@ end function test_reseedDotsBasic() - + dotNb = 5; cfg.design.motionType = 'translation'; @@ -36,11 +36,11 @@ function test_reseedDotsBasic() ]; originalTime = [ ... - 6; ... OK - 4; ... OK - 56; ... OK - 300; ... % exceeded its life time - 50]; % OK + 6; ... OK + 4; ... OK + 56; ... OK + 300; ... % exceeded its life time + 50]; % OK dots.time = originalTime; dots = reseedDots(dots, cfg); diff --git a/tests/test_seedDots.m b/tests/test_seedDots.m index 56ac733..782b60d 100644 --- a/tests/test_seedDots.m +++ b/tests/test_seedDots.m @@ -14,10 +14,10 @@ function test_seedDotsBasic() cfg.design.motionType = 'translation'; cfg.timing.eventDuration = 1; % in seconds cfg.screen.ifi = 0.01; % in seconds - + nbDots = 10; isSignal = [true(5, 1); false(nbDots - 5, 1)]; - + dots.direction = 0; dots.speedPixPerFrame = 10; @@ -25,14 +25,13 @@ function test_seedDotsBasic() %% Deterministic output assertEqual(size(positions), [nbDots, 2]); - assertTrue(all(all([... - positions(:) <= cfg.dot.matrixWidth, ... - positions(:) >= 0]))); - + assertTrue(all(all([ ... + positions(:) <= cfg.dot.matrixWidth, ... + positions(:) >= 0]))); + assertTrue(all(time(:) >= 0)); assertTrue(all(time(:) <= 1 / 0.01)); - - assertEqual(speeds(1:5,:), repmat([10 0], 5, 1)); -end + assertEqual(speeds(1:5, :), repmat([10 0], 5, 1)); +end diff --git a/tests/test_setDotDirection.m b/tests/test_setDotDirection.m index 790bbe1..12a80b4 100644 --- a/tests/test_setDotDirection.m +++ b/tests/test_setDotDirection.m @@ -10,22 +10,21 @@ function test_setDotDirectionInit() % create 5 coherent dots with direction == 362 (that should give 2 in the % end) % also creates additonal dots with random direction between 0 and 360 - + nbDots = 10; - + cfg.dot.matrixWidth = 400; cfg.design.motionType = 'translation'; - + dots.direction = 362; dots.isSignal = [true(5, 1); false(nbDots - 5, 1)]; - - + positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); - + directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); assertEqual(directionAllDots(1:5), 2 * ones(5, 1)); - assertGreaterThan(directionAllDots, zeros(size(directionAllDots)) ); + assertGreaterThan(directionAllDots, zeros(size(directionAllDots))); assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); end @@ -33,26 +32,26 @@ function test_setDotDirectionInit() function test_setDotDirectionReturn() % make sure that if the directions are already set it only changes that of % the noise dots - % input has 4 signal dots with set directions also has additonal noise dots with negative direction - + % input has 4 signal dots with set directions also has additonal noise dots + % with negative direction + nbDots = 8; - + cfg.dot.matrixWidth = 400; cfg.design.motionType = 'translation'; - - dots.direction = [... - [362; 2; -362; -2]; ... - -20 * ones(4, 1)]; + + dots.direction = [ ... + [362; 2; -362; -2]; ... + -20 * ones(4, 1)]; dots.isSignal = [true(4, 1); false(nbDots - 4, 1)]; - - + positions = generateNewDotPositions(cfg.dot.matrixWidth, numel(dots.isSignal)); - + directionAllDots = setDotDirection(positions, cfg, dots, dots.isSignal); assertEqual(directionAllDots(1:4), [2 2 358 358]'); - assertGreaterThan(directionAllDots, zeros(size(directionAllDots)) ); + assertGreaterThan(directionAllDots, zeros(size(directionAllDots))); assertLessThan(directionAllDots, 360 * ones(size(directionAllDots))); - assertTrue(all(directionAllDots(5:end) ~= -20 * ones(4,1))) + assertTrue(all(directionAllDots(5:end) ~= -20 * ones(4, 1))); end From 053cf1529f01005b51a1ee8385ffde17a7ad497e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 25 Sep 2020 20:06:43 +0200 Subject: [PATCH 06/15] add test for simulation --- src/dot/dotMotionSimulation.m | 36 +++++++++++++----------- tests/test_dotMotionSimulation.m | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 tests/test_dotMotionSimulation.m diff --git a/src/dot/dotMotionSimulation.m b/src/dot/dotMotionSimulation.m index 4c82f2c..25ca7ae 100644 --- a/src/dot/dotMotionSimulation.m +++ b/src/dot/dotMotionSimulation.m @@ -1,20 +1,20 @@ -function relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, doPlot) +function relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot) % to simulate where the dots are more dense on the screen % relativeDensityContrast : hard to get it below 0.10 close all; - if nargin < 3 - + if nargin < 4 doPlot = 1; + end + if nargin < 3 + nbEvents = 100; end if nargin < 2 - thisEvent.direction = 0; % degrees thisEvent.speed = 1; % pix per frame - end if nargin < 1 @@ -23,13 +23,13 @@ cfg.dot.coherence = 1; % proportion - cfg.dot.lifeTime = 10; % in seconds + cfg.dot.lifeTime = .1; % in seconds cfg.dot.matrixWidth = 250; % in pixels cfg.dot.proportionKilledPerFrame = 0; - cfg.timing.eventDuration = 500; % in seconds + cfg.timing.eventDuration = 1.8; % in seconds end @@ -48,23 +48,27 @@ fprintf(1, '\n\nDot motion simulation:'); nbFrames = ceil(cfg.timing.eventDuration / cfg.screen.ifi); - frameToReport = round(linspace(1, nbFrames, 20)); + % frameToReport = round(linspace(1, nbFrames, 20)); % to keep track of where the dots have been dotDensity = zeros(cfg.dot.matrixWidth); - [dots] = initDots(cfg, thisEvent); - dotDensity = updateDotDensity(dotDensity, dots); + for iEvent = 1:nbEvents - for iFrame = 1:nbFrames + [dots] = initDots(cfg, thisEvent); + dotDensity = updateDotDensity(dotDensity, dots); - if any(frameToReport == iFrame) - fprintf(1, '.'); - end + for iFrame = 1:nbFrames - [dots] = updateDots(dots, cfg); + % if any(frameToReport == iFrame) + % fprintf(1, '.'); + % end - dotDensity = updateDotDensity(dotDensity, dots); + [dots] = updateDots(dots, cfg); + + dotDensity = updateDotDensity(dotDensity, dots); + + end end diff --git a/tests/test_dotMotionSimulation.m b/tests/test_dotMotionSimulation.m new file mode 100644 index 0000000..83e715e --- /dev/null +++ b/tests/test_dotMotionSimulation.m @@ -0,0 +1,48 @@ +function test_suite = test_dotMotionSimulation %#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_dotMotionSimulationStatic() + + nbEvents = 1; + doPlot = false; + + thisEvent.direction = -1; % degrees + thisEvent.speed = 1; % pix per frame + + cfg.design.motionType = 'translation'; + cfg.dot.coherence = 1; % proportion + cfg.dot.lifeTime = .5; % in seconds + cfg.dot.matrixWidth = 250; % in pixels + cfg.dot.proportionKilledPerFrame = 0; + cfg.timing.eventDuration = 1.8; % in seconds + + relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); + +end + +function test_dotMotionSimulationTranslation() + % ensure that dog homogenity is not too low when we kill dots often enough + + nbEvents = 500; + doPlot = false; + + thisEvent.direction = 0; % degrees + thisEvent.speed = 1; % pix per frame + + cfg.design.motionType = 'translation'; + cfg.dot.coherence = 1; % proportion + cfg.dot.lifeTime = .1; % in seconds + cfg.dot.matrixWidth = 250; % in pixels + cfg.dot.proportionKilledPerFrame = 0; + cfg.timing.eventDuration = 1.8; % in seconds + + relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); + + assertLessThan(relativeDensityContrast, 0.5); + +end From 472ef0559b1c72807791b7af6ea1be44def2c865 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 25 Sep 2020 20:17:57 +0200 Subject: [PATCH 07/15] markdown linting --- README.md | 8 +- docs/{00_index.md => 00-index.md} | 0 docs/10-functions-description.md | 214 ++++++++++++++++++++++++++++++ docs/10_functions_description.md | 6 - 4 files changed, 216 insertions(+), 12 deletions(-) rename docs/{00_index.md => 00-index.md} (100%) create mode 100644 docs/10-functions-description.md diff --git a/README.md b/README.md index 1cf7da8..ec3fcf5 100644 --- a/README.md +++ b/README.md @@ -154,13 +154,9 @@ git submodule foreach --recursive 'git submodule update' Download the code. Unzip. And add to the matlab path. -Pick a specific version: +Pick a specific version from [here](https://github.com/cpp-lln-lab/CPP_PTB/releases). -https://github.com/cpp-lln-lab/CPP_PTB/releases - -Or take the latest commit (NOT RECOMMENDED): - -https://github.com/cpp-lln-lab/CPP_PTB/archive/master.zip +Or take [the latest commit](https://github.com/cpp-lln-lab/CPP_PTB/archive/master.zip) - NOT RECOMMENDED. ### Add CPP_PTB globally to the matlab path diff --git a/docs/00_index.md b/docs/00-index.md similarity index 100% rename from docs/00_index.md rename to docs/00-index.md diff --git a/docs/10-functions-description.md b/docs/10-functions-description.md new file mode 100644 index 0000000..ce76c4c --- /dev/null +++ b/docs/10-functions-description.md @@ -0,0 +1,214 @@ +# functions description + + + +- [functions description](#functions-description) + - [General functions](#general-functions) + - [initPTB](#initptb) + - [cleanUp](#cleanup) + - [getExperimentStart](#getexperimentstart) + - [getExperimentEnd](#getexperimentend) + - [degToPix](#degtopix) + - [computeFOV](#computefov) + - [eyeTracker](#eyetracker) + - [standByScreen](#standbyscreen) + - [waitForTrigger](#waitfortrigger) + - [waitFor](#waitfor) + - [readAndFilterLogfile](#readandfilterlogfile) + - [Keyboard functions: response collection and aborting experiment](#keyboard-functions-response-collection-and-aborting-experiment) + - [testKeyboards](#testkeyboards) + - [getResponse](#getresponse) + - [pressSpaceForme](#pressspaceforme) + - [checkAbort](#checkabort) + - [Fixations](#fixations) + - [drawFixationCross](#drawfixationcross) + - [Drawing dots](#drawing-dots) + - [Drawing apertures](#drawing-apertures) + - [Randomization](#randomization) + - [shuffle](#shuffle) + - [setTargetPositionInSequence](#settargetpositioninsequence) + - [repeatShuffleConditions](#repeatshuffleconditions) + + + +## General functions + +### initPTB + +This will initialize PsychToolBox. + +It is pretty much necessary to use this function to set up the stage for using +any other functions of CPP_PTB. + +- checks OS and PTB version +- set some defaults +- set the screen details + - the window opened takes the whole screen by default + - set in debug mode with window transparency if necessary + - can skip synch test if you ask for it (nicely) + - gets the flip interval + - computes the pixel per degree of visual angle +- set fixation cross details +- set font details +- keyboard +- hides cursor +- sound + +### cleanUp + +A wrapper function to close all windows, ports, show mouse cursor, close +keyboard queues and give you back access to the keyboards. + +### getExperimentStart + +Wrapper function that will show a fixation cross and collect a start timestamp +in `cfg.experimentStart` + +### getExperimentEnd + +Wrapper function that will show a fixation cross and display in the console the +whole experiment's duration in minutes and seconds + +### degToPix + +For a given field value in degrees of visual angle in the input `structure`, +this computes its value in pixel using the pixel per degree value of the `cfg` +structure and returns a structure with an additional field with Pix suffix +holding that new value. + +### computeFOV + +Gives you the width of the field on view in degress of visual angle based on the +screen width and distance to the screen in cm (taken from the `cfg`) + +### eyeTracker + +This will handle the Eye Tracker (EyeLink set up) and can be called to +initialize the connection and start the calibration, start/stop eye(s) movement +recordings and save the `*.edf` file (named with BIDS specification from +cpp-lln-lab/CPP_BIDS). + +There are several actions to perform: + +- Calibration: to initialize EyeLink and run calibration + - 'default calibration' (default) will run a calibration with 6 points + - 'custom calibration' (cfg.eyeTracker.defaultCalibration = 'false') will + run a calibration with 6 points but the experimenter can choose their + position on the screen +- StartRecording: to start eye movements recording +- Message: will add a tag (e.g. 'Block_n1') in the ET output file, the tag is + a string and it is input from `varargin` +- StopRecordings: to stop eye movements recornding +- Shutdown: to save the `.edf` file with BIDS compliant name, from + cpp-lln-lab/CPP_BIDS, in the output folder and shut the connection between + the stimulation computer and the EyeLink computer + +### standByScreen + +It shows a basic one-page instruction stored in `cfg.task.instruction` and wait +for `space` stroke. + +### waitForTrigger + +Counts a certain number of triggers coming from the mri/scanner before +returning. Requires number of triggers to wait for. + +This can also be used if you want to let the scanner pace the experiment and you +want to synch stimulus presentation to the scanner trigger. + +### waitFor + +A generic function that you can use to for a certain amount of time or a number +of triggers + +### readAndFilterLogfile + +Displays in the command window part of the `*events.tsv` file filtered by an +element (e.g. 'trigger'). It can take the last output produced (through `cfg`) +or any output BIDS compatible (through file path). + +## Keyboard functions: response collection and aborting experiment + +### testKeyboards + +Checks that the keyboards asked for are properly connected. + +If no key is pressed on the correct keyboard after the timeOut time this exits +with an error. + +### getResponse + +It is wrapper function to use `KbQueue` which is definitely what you should use +to collect responses. + +You can easily collect responses while running some other code at the same time. + +It will only take responses from one device which can simply be the "main +keyboard" (the default device that PTB will find) or another keyboard connected +to the computer or the response box that the participant is using. + +You can use it in a way so that it only takes responses from certain keys and +ignore others (like the triggers from an MRI scanner). + +If you want to know more on how to use it check its help section and the +`CPP_getResponseDemo.m`. + +In brief, there are several actions you can execute with this function. + +- init: initialize the buffer for key presses on a given device (you can also + specify the keys of interest that should be listened to). +- start: start listening to the key presses (carefully insert into your + script - where do you want to start buffering the responses). +- check: till that point, it will check the buffer for all key presses. - It + only reports presses on the keys of interest mentioned at initialization. - + It **can** also check for presses on the escape key and abort if the escape + key is part of the keys of interest. +- flush: empties the buffer of key presses in case you want to discard any + previous key presses. +- stop: stops buffering key presses. You can still restart by calling "start" + again. +- release: closes the buffer for good. + +### pressSpaceForme + +Use that to stop your script and only restart when the space bar is pressed. + +This can be useful if as an experimenter you want to have one final check on +some set up before giving the green light. + +### checkAbort + +A simple function that will quit your experiment if you press the key you have +defined in `cfg.keyboard.escapeKey`. + +## Fixations + +### drawFixationCross + +Define the parameters of the fixation cross in `cfg` and `expParameters` and +this does the rest. + +## Drawing dots + +## Drawing apertures + +## Randomization + +Functions that can be used to create random stimuli sequences. + +### shuffle + +Is just there to replace the Shuffle function from PTB in case it is not in the +path. Can be useful for testing or for continuous integration. + +### setTargetPositionInSequence + +For a sequence of length `seqLength` where we want to insert `nbTarget` targets, +this will return `nbTarget` random position in that sequence and make sure that +they are not in consecutive positions. + +### repeatShuffleConditions + +Given `baseConditionVector`, a vector of conditions (coded as numbers), this +will create a longer vector made of `nbRepeats` of this base vector and make +sure that a given condition is not repeated one after the other. diff --git a/docs/10_functions_description.md b/docs/10_functions_description.md index f0bfa39..d1d50ab 100644 --- a/docs/10_functions_description.md +++ b/docs/10_functions_description.md @@ -26,7 +26,6 @@ - 6.1. [shuffle](#shuffle) - 6.2. [setTargetPositionInSequence](#setTargetPositionInSequence) - 6.3. [repeatShuffleConditions](#repeatShuffleConditions) - - 6.4. [setUpRand](#setUprand) - -- 1. [ General functions](#Generalfunctions) - - 1.1. [initPTB](#initPTB) - - 1.2. [cleanUp](#cleanUp) - - 1.3. [getExperimentStart](#getExperimentStart) - - 1.4. [getExperimentEnd](#getExperimentEnd) - - 1.5. [degToPix](#degToPix) - - 1.6. [computeFOV](#computeFOV) - - 1.7. [eyeTracker](#eyeTracker) - - 1.8. [standByScreen](#standByScreen) - - 1.9. [waitForTrigger](#waitForTrigger) - - 1.10. [waitFor](#waitFor) - - 1.11. [readAndFilterLogfile](#readAndFilterLogfile) -- 2. [Keyboard functions: response collection and aborting experiment](#Keyboardfunctions:responsecollectionandabortingexperiment) - - 2.1. [testKeyboards](#testKeyboards) - - 2.2. [getResponse](#getResponse) - - 2.3. [pressSpaceForme](#pressSpaceForme) -- 3. [Fixations](#Fixations) - - 3.1. [drawFixationCross](#drawFixationCross) -- 4. [Drawing dots](#Drawingdots) -- 5. [Drawing apertures](#Drawingapertures) -- 6. [Randomization](#Randomization) - - 6.1. [shuffle](#shuffle) - - 6.2. [setTargetPositionInSequence](#setTargetPositionInSequence) - - 6.3. [repeatShuffleConditions](#repeatShuffleConditions) - - - - -## 1. General functions - -### 1.1. initPTB - -This will initialize PsychToolBox. - -It is pretty much necessary to use this function to set up the stage for using -any other functions of CPP_PTB. - -- checks OS and PTB version -- set some defaults -- set the screen details - - the window opened takes the whole screen by default - - set in debug mode with window transparency if necessary - - can skip synch test if you ask for it (nicely) - - gets the flip interval - - computes the pixel per degree of visual angle -- set fixation cross details -- set font details -- keyboard -- hides cursor -- sound - -### 1.2. cleanUp - -A wrapper function to close all windows, ports, show mouse cursor, close -keyboard queues and give you back access to the keyboards. - -### 1.3. getExperimentStart - -Wrapper function that will show a fixation cross and collect a start timestamp -in `cfg.experimentStart` - -### 1.4. getExperimentEnd - -Wrapper function that will show a fixation cross and display in the console the -whole experiment's duration in minutes and seconds - -### 1.5. degToPix - -For a given field value in degrees of visual angle in the input `structure`, -this computes its value in pixel using the pixel per degree value of the `cfg` -structure and returns a structure with an additional field with Pix suffix -holding that new value. - -### 1.6. computeFOV - -Gives you the width of the field on view in degress of visual angle based on the -screen width and distance to the screen in cm (taken from the `cfg`) - -### 1.7. eyeTracker - -This will handle the Eye Tracker (EyeLink set up) and can be called to -initialize the connection and start the calibration, start/stop eye(s) movement -recordings and save the `*.edf` file (named with BIDS specification from -cpp-lln-lab/CPP_BIDS). - -There are several actions to perform: - -- Calibration: to initialize EyeLink and run calibration - - 'default calibration' (default) will run a calibration with 6 points - - 'custom calibration' (cfg.eyeTracker.defaultCalibration = 'false') will - run a calibration with 6 points but the experimenter can choose their - position on the screen -- StartRecording: to start eye movements recording -- Message: will add a tag (e.g. 'Block_n1') in the ET output file, the tag is - a string and it is input from `varargin` -- StopRecordings: to stop eye movements recornding -- Shutdown: to save the `.edf` file with BIDS compliant name, from - cpp-lln-lab/CPP_BIDS, in the output folder and shut the connection between - the stimulation computer and the EyeLink computer - -### 1.8. standByScreen - -It shows a basic one-page instruction stored in `cfg.task.instruction` and wait -for `space` stroke. - -### 1.9. waitForTrigger - -Counts a certain number of triggers coming from the mri/scanner before -returning. Requires number of triggers to wait for. - -This can also be used if you want to let the scanner pace the experiment and you -want to synch stimulus presentation to the scanner trigger. - -### 1.10. waitFor - -A generic function that you can use to for a certain amount of time or a number -of triggers - -### 1.11. readAndFilterLogfile - -Displays in the command window part of the `*events.tsv` file filtered by an -element (e.g. 'trigger'). It can take the last output produced (through `cfg`) -or any output BIDS compatible (through file path). - -## 2. Keyboard functions: response collection and aborting experiment - -### 2.1. testKeyboards - -Checks that the keyboards asked for are properly connected. - -If no key is pressed on the correct keyboard after the timeOut time this exits -with an error. - -### 2.2. getResponse - -It is wrapper function to use `KbQueue` which is definitely what you should use -to collect responses. - -You can easily collect responses while running some other code at the same time. - -It will only take responses from one device which can simply be the "main -keyboard" (the default device that PTB will find) or another keyboard connected -to the computer or the response box that the participant is using. - -You can use it in a way so that it only takes responses from certain keys and -ignore others (like the triggers from an MRI scanner). - -If you want to know more on how to use it check its help section and the -`CPP_getResponseDemo.m`. - -In brief, there are several actions you can execute with this function. - -- init: initialize the buffer for key presses on a given device (you can also - specify the keys of interest that should be listened to). -- start: start listening to the key presses (carefully insert into your - script - where do you want to start buffering the responses). -- check: till that point, it will check the buffer for all key presses. - It - only reports presses on the keys of interest mentioned at initialization. - - It **can** also check for presses on the escape key and abort if the escape - key is part of the keys of interest. -- flush: empties the buffer of key presses in case you want to discard any - previous key presses. -- stop: stops buffering key presses. You can still restart by calling "start" - again. -- release: closes the buffer for good. - -### 2.3. pressSpaceForme - -Use that to stop your script and only restart when the space bar is pressed. - -This can be useful if as an experimenter you want to have one final check on -some set up before giving the green light. - -### checkAbort - -A simple function that will quit your experiment if you press the key you have -defined in `cfg.keyboard.escapeKey`. - -## 3. Fixations - -### 3.1. drawFixationCross - -Define the parameters of the fixation cross in `cfg` and `expParameters` and -this does the rest. - -## 4. Drawing dots - -## 5. Drawing apertures - -## 6. Randomization - -Functions that can be used to create random stimuli sequences. - -### 6.1. shuffle - -Is just there to replace the Shuffle function from PTB in case it is not in the -path. Can be useful for testing or for continuous integration. - -### 6.2. setTargetPositionInSequence - -For a sequence of length `seqLength` where we want to insert `nbTarget` targets, -this will return `nbTarget` random position in that sequence and make sure that -they are not in consecutive positions. - -### 6.3. repeatShuffleConditions - -Given `baseConditionVector`, a vector of conditions (coded as numbers), this -will create a longer vector made of `nbRepeats` of this base vector and make -sure that a given condition is not repeated one after the other. From 5a6dc1e6d9c156df27d2780e278a82bfe2e77481 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 30 Sep 2020 17:57:41 +0200 Subject: [PATCH 14/15] mh fix --- manualTests/test_radialMotion.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/manualTests/test_radialMotion.m b/manualTests/test_radialMotion.m index 86e4db3..6cb305a 100644 --- a/manualTests/test_radialMotion.m +++ b/manualTests/test_radialMotion.m @@ -18,6 +18,4 @@ cfg.timing.eventDuration = 1.8; % in seconds -relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot) - - +relativeDensityContrast = dotMotionSimulation(cfg, thisEvent, nbEvents, doPlot); From 9e6f7a2934bd7e05b5ba1b8edf876edcf1f0fb62 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Wed, 30 Sep 2020 18:00:02 +0200 Subject: [PATCH 15/15] fix dead link --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ec3fcf5..3a249e2 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,6 @@ In practice, we use the following regular expression for function names: > > - A quick [intro to regular expression](https://www.rexegg.com/) > - And many websites allow you to "design and test" your regular expression: -> - [regexr](https://regexr.com/) > - [regexper](https://regexper.com/#%5Ba-z%5D%2B%28%28%5BA-Z%5D%7C%5B0-9%5D%29%7B1%7D%5Ba-z%5D%2B%29) > - ...