From 52bc2d0576da80efe28b6399da87c9d0ff056d15 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 07:52:57 +0200 Subject: [PATCH 1/6] update CPP_BIDS submodule --- lib/CPP_BIDS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CPP_BIDS b/lib/CPP_BIDS index b5e6c7a..3a1269e 160000 --- a/lib/CPP_BIDS +++ b/lib/CPP_BIDS @@ -1 +1 @@ -Subproject commit b5e6c7af678ec288bdfc6117ee52f1c82cabac84 +Subproject commit 3a1269e76a8bcd01c63a8c652fbd02c0bb141ab0 From 960637558b3c54b4c62cfd202493d9d438b9aba6 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 13:22:03 +0200 Subject: [PATCH 2/6] update to use textures for aperture and dots also includes - some refactoring - specifying dot density in number per visual angle square and total number - using new CPP_PTB sub-functions --- initEnv.m | 2 +- setParameters.m | 26 +++--- subfun/doDotMo.m | 171 ++++++++++++++++++++-------------------- visualLocTanslational.m | 45 +++++------ 4 files changed, 115 insertions(+), 129 deletions(-) diff --git a/initEnv.m b/initEnv.m index d0ab070..90560f8 100644 --- a/initEnv.m +++ b/initEnv.m @@ -86,7 +86,7 @@ function addDependencies() pth = fileparts(mfilename('fullpath')); - addpath(fullfile(pth, 'lib', 'CPP_BIDS')); + addpath(fullfile(pth, 'lib', 'CPP_BIDS', 'src')); addpath(fullfile(pth, 'lib', 'CPP_PTB')); addpath(fullfile(pth, 'subfun')); diff --git a/setParameters.m b/setParameters.m index 15094a1..a70aae4 100644 --- a/setParameters.m +++ b/setParameters.m @@ -40,7 +40,7 @@ % Time between events in secs cfg.ISI = 0.1; % Number of seconds before the motion stimuli are presented - cfg.onsetDelay = 5; + cfg.onsetDelay = 1; % Number of seconds after the end all the stimuli before ending the run cfg.endDelay = 1; @@ -51,20 +51,21 @@ cfg.eventDuration = 1; % second % speed in visual angles - cfg.dot.speed = 8; + cfg.dot.speed = 1; % Coherence Level (0-1) - cfg.dot.coh = 1; - % Maximum number dots per frame - cfg.dot.maxNbPerFrame = 300; + cfg.dot.coh = .7; + % nb dots per visual angle square. + cfg.dot.density = 5; + % Dot life time in seconds cfg.dot.lifeTime = 1; % Dot Size (dot width) in visual angles. cfg.dot.size = 0.1; cfg.dot.color = cfg.color.white; - cfg.dot.dontClear = 0; % Diameter/length of side of aperture in Visual angles - cfg.diameterAperture = 8; + cfg.aperture.type = 'circle'; + cfg.aperture.width = []; % if left empty it will take the screen height %% Task(s) @@ -74,14 +75,11 @@ cfg.task.instruction = '1-Detect the RED fixation cross\n \n\n'; % Fixation cross (in pixels) - % Set the length of the lines of the fixation cross - cfg.fixation.dimensionPix = 10; - % Set the line width for our fixation cross - cfg.fixation.lineWidthPix = 4; - cfg.fixation.xDisplacement = 0; % Manual displacement of the fixation cross - cfg.fixation.yDisplacement = 0; % Manual displacement of the fixation cross - cfg.fixation.color = cfg.color.white; + cfg.fixation.type = 'cross'; cfg.fixation.colorTarget = cfg.color.red; + cfg.fixation.color = cfg.color.white; + cfg.fixation.width = .15; + cfg.fixation.lineWidthPix = 2; cfg.target.maxNbPerBlock = 2; cfg.target.duration = 0.15; % In secs diff --git a/subfun/doDotMo.m b/subfun/doDotMo.m index 8bcd721..0482dd2 100644 --- a/subfun/doDotMo.m +++ b/subfun/doDotMo.m @@ -16,146 +16,143 @@ % 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. - + %% Get parameters - dontClear = cfg.dot.dontClear; - direction = thisEvent.direction(1); isTarget = thisEvent.target(1); - speed = thisEvent.speed(1); - - coh = cfg.dot.coh; - ndots = cfg.dot.maxNbPerFrame; - dotSizePix = cfg.dot.sizePix; + + dotLifeTime = cfg.dot.lifeTime; - dotColor = cfg.dot.color; - + targetDuration = cfg.target.duration; - + % thisEvent = deg2Pix('speed', thisEvent, cfg); % dotSpeedPix = logFile.iEventSpeedPix; - - diamAperturePix = cfg.diameterAperturePix; - diamAperture = cfg.diameterAperture; - + + coh = cfg.dot.coh; + speed = thisEvent.speed(1); % Check if it is a static or motion block if direction == -1 - - % dotSpeedPix = 0; - + speed = 0; - + coh = 1; + dotLifeTime = cfg.eventDuration; end - + %% initialize variables - + % 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 that contains the square aperture - % [1,1] is the bottom / right of the square that contains the square aperture - xy = rand(ndots, 2); - + % [0,0] is the top / left of the square + % [1,1] is the bottom / right of the square + dotPositions = rand(cfg.dot.number, 2); + % Set a N x 2 matrix that gives jump size in pixels % pix/sec * sec/frame = pix / frame dxdy = repmat( ... - speed * 10 / (diamAperture * 10) * (3 / cfg.screen.monitorRefresh) * ... - [cos(pi * direction / 180.0) -sin(pi * direction / 180.0)], ndots, 1); - + speed * 10 / (cfg.aperture.width * 10) * (3 / cfg.screen.monitorRefresh) * ... + [cos(pi * direction / 180.0) -sin(pi * direction / 180.0)], cfg.dot.number, 1); + % dxdy = repmat(... % dotSpeedPix / Cfg.ifi ... % * (cos(pi*direction/180) - sin(pi*direction/180)), ... % ndots, 1); - + % Create a ones vector to update to dotlife time of each dot - dotTime = ones(size(xy, 1), 1); - - % Set for how many frames to show the dots - continueShow = floor(cfg.eventDuration / cfg.screen.ifi); - + dotTime = ones(size(dotPositions, 1), 1); + % Covert the dotLifeTime from seconds to frames dotLifeTime = ceil(dotLifeTime / cfg.screen.ifi); + + % Set for how many frames this event will last + framesLeft = floor(cfg.eventDuration / cfg.screen.ifi); %% Start the dots presentation - vbl = Screen('Flip', cfg.screen.win, 0, dontClear); + vbl = Screen('Flip', cfg.screen.win); onset = vbl; - - while continueShow - + + while framesLeft + % L are the dots that will be moved - L = rand(ndots, 1) < coh; - + L = rand(cfg.dot.number, 1) < coh; + % Move the selected dots - xy(L, :) = xy(L, :) + dxdy(L, :); - + dotPositions(L, :) = dotPositions(L, :) + dxdy(L, :); + % If not 100% coherence, we get new random locations for the other dots if sum(~L) > 0 - xy(~L, :) = rand(sum(~L), 2); + dotPositions(~L, :) = rand(sum(~L), 2); end - + % Create a logical vector to detect any dot that has: % - an xy position inferior to 0 % - an xy position superior to 1 % - has exceeded its liftime - N = any([xy > 1, xy < 0, dotTime > dotLifeTime], 2) ; - + N = any([dotPositions > 1, dotPositions < 0, dotTime > dotLifeTime], 2) ; + % If there is any such dot we relocate it to a new random position % and change its lifetime to 1 if any(N) - xy(N, :) = rand(sum(N), 2); + dotPositions(N, :) = rand(sum(N), 2); dotTime(N, 1) = 1; end - - % Convert the dot position to pixels - xy_pix = floor(xy * diamAperturePix); - + + %% Convert the dot position to pixels + % We expand that square so that its side is equal to the whole + % screen width. + % With no aperture the whole screen is filled with dots. + dotPositionsPix = floor(dotPositions * cfg.screen.winRect(3)); + % This assumes 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 aperture size to both the x and y direction. - xy_pix = (xy_pix - diamAperturePix / 2)'; - - % NaN out-of-circle dots - % We use Pythagore's theorem to figure out which dots are out of the - % circle - outCircle = sqrt(xy_pix(1, :).^2 + xy_pix(2, :).^2) + ... - dotSizePix / 2 > (diamAperturePix / 2); - xy_pix(:, outCircle) = NaN; - - %% PTB draws the dots stimulation - - % Draw the fixation cross - color = cfg.fixation.color; + dotPositionsPix = (dotPositionsPix - cfg.screen.winRect(3) / 2)'; + + thisEvent.dot.positions = dotPositionsPix; + + %% make textures + dotTexture('make', cfg, thisEvent); + + apertureTexture('make', cfg, thisEvent); + + %% draw evetything and flip screen + + dotTexture('draw', cfg, thisEvent); + + apertureTexture('draw', cfg, thisEvent); + % If this frame shows a target we change the color + thisFixation.fixation = cfg.fixation; + thisFixation.screen = cfg.screen; if GetSecs < (onset + targetDuration) && isTarget == 1 - color = cfg.fixation.colorTarget; + thisFixation.fixation.color = cfg.fixation.colorTarget; end - drawFixationCross(cfg, color); - - % Draw the dots - Screen('DrawDots', cfg.screen.win, xy_pix, dotSizePix, dotColor, cfg.screen.center, 2); - - Screen('DrawingFinished', cfg.screen.win, dontClear); - - vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi, dontClear); - + drawFixation(thisFixation); + + Screen('DrawingFinished', cfg.screen.win); + + vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi); + %% Update counters - + % Check for end of loop - continueShow = continueShow - 1; - + framesLeft = framesLeft - 1; + % Add one frame to the dot lifetime to each dot dotTime = dotTime + 1; - + end - + %% Erase last dots - - drawFixationCross(cfg, cfg.fixation.color); - - Screen('DrawingFinished', cfg.screen.win, dontClear); - - vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi, dontClear); - + + drawFixation(cfg); + + Screen('DrawingFinished', cfg.screen.win); + + vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi); + duration = vbl - onset; - + end + diff --git a/visualLocTanslational.m b/visualLocTanslational.m index 5d61be9..e9c4ced 100644 --- a/visualLocTanslational.m +++ b/visualLocTanslational.m @@ -25,16 +25,6 @@ cfg = userInputs(cfg); cfg = createFilename(cfg); -disp(cfg); - -% REFACTOR -% Prepare for fixation Cross -cfg.xCoords = [-cfg.fixation.dimensionPix cfg.fixation.dimensionPix 0 0] + ... - cfg.fixation.xDisplacement; -cfg.yCoords = [0 0 -cfg.fixation.dimensionPix cfg.fixation.dimensionPix] + ... - cfg.fixation.yDisplacement; -cfg.allCoords = [cfg.xCoords; cfg.yCoords]; - %% Experiment % Safety loop: close the screen if code crashes @@ -44,8 +34,11 @@ [cfg] = initPTB(cfg); % Convert some values from degrees to pixels - cfg = degToPix('diameterAperture', cfg, cfg); cfg.dot = degToPix('size', cfg.dot, cfg); + + % dots are displayed on a square with a length in visual angle equal to the + % field of view + cfg.dot.number = cfg.dot.density * cfg.screen.FOV^2; [el] = eyeTracker('Calibration', cfg); @@ -56,28 +49,24 @@ % Prepare for the output logfiles with all logFile.extraColumns = cfg.extraColumns; logFile = saveEventsFile('open', cfg, logFile); - - % Wait for space key to be pressed - pressSpaceForMe(); - + + % prepare textures + cfg = apertureTexture('init', cfg); + cfg = dotTexture('init', cfg); + + disp(cfg); + + standByScreen(cfg); + % prepare the KbQueue to collect responses getResponse('init', cfg.keyboard.responseBox, cfg); getResponse('start', cfg.keyboard.responseBox); - % Show instructions - DrawFormattedText(cfg.screen.win, cfg.task.instruction, ... - 'center', 'center', cfg.text.color); - Screen('Flip', cfg.screen.win); - % Wait for Trigger from Scanner waitForTrigger(cfg); - % Show the fixation cross - drawFixationCross(cfg, cfg.fixation.color); - Screen('Flip', cfg.screen.win); - %% Experiment Start - cfg.experimentStart = GetSecs; + cfg = getExperimentStart(cfg); WaitSecs(cfg.onsetDelay); @@ -152,14 +141,14 @@ % End of the run for the BOLD to go down WaitSecs(cfg.endDelay); + cfg = getExperimentEnd(cfg); + % Close the logfiles saveEventsFile('close', cfg, logFile); getResponse('stop', cfg.keyboard.responseBox); getResponse('release', cfg.keyboard.responseBox); - totalExperimentTime = GetSecs - cfg.experimentStart; - eyeTracker('Shutdown', cfg); % save the whole workspace @@ -171,6 +160,8 @@ else save(matFile, '-v7.3'); end + + farewellScreen(cfg); cleanUp(); From 90a36c08a24d957c11198119dbd783995bd63658 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 13:22:18 +0200 Subject: [PATCH 3/6] update CPP_PTB submodule --- lib/CPP_PTB | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CPP_PTB b/lib/CPP_PTB index a7653ed..ac6fc16 160000 --- a/lib/CPP_PTB +++ b/lib/CPP_PTB @@ -1 +1 @@ -Subproject commit a7653ed5055ea7ae591450d49804f074e6e76edd +Subproject commit ac6fc160bfb535a08b4f7afbe35a4e48a35b2184 From 3f84d262663e3b57cf5bdc2aca20c565beee51a7 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 16:10:17 +0200 Subject: [PATCH 4/6] update some default parameters --- setParameters.m | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/setParameters.m b/setParameters.m index a70aae4..1583bcf 100644 --- a/setParameters.m +++ b/setParameters.m @@ -23,26 +23,26 @@ cfg.eyeTracker.do = false; cfg.audio.do = false; - cfg = setMonitor(cfg, cfg); + cfg = setMonitor(cfg); % Keyboards - cfg = setKeyboards(cfg, cfg); + cfg = setKeyboards(cfg); % MRI settings - cfg = setMRI(cfg, cfg); + cfg = setMRI(cfg); %% Experiment Design cfg.names = {'static', 'motion'}; cfg.possibleDirections = [-1 1]; % 1 motion , -1 static cfg.numBlocks = size(cfg.possibleDirections, 2); cfg.numRepetitions = 1; % AT THE MOMENT IT IS NOT SET IN THE MAIN SCRIPT - cfg.IBI = 0; % 8; + cfg.IBI = .5; % 8; % Time between events in secs - cfg.ISI = 0.1; + cfg.ISI = 0.5; % Number of seconds before the motion stimuli are presented - cfg.onsetDelay = 1; + cfg.onsetDelay = .1; % Number of seconds after the end all the stimuli before ending the run - cfg.endDelay = 1; + cfg.endDelay = .1; %% Visual Stimulation @@ -66,6 +66,7 @@ % Diameter/length of side of aperture in Visual angles cfg.aperture.type = 'circle'; cfg.aperture.width = []; % if left empty it will take the screen height + cfg.aperture.xPos = 0; %% Task(s) @@ -78,11 +79,13 @@ cfg.fixation.type = 'cross'; cfg.fixation.colorTarget = cfg.color.red; cfg.fixation.color = cfg.color.white; - cfg.fixation.width = .15; + cfg.fixation.width = 1; cfg.fixation.lineWidthPix = 2; + cfg.fixation.xDisplacement = 0; + cfg.fixation.yDisplacement = 0; cfg.target.maxNbPerBlock = 2; - cfg.target.duration = 0.15; % In secs + cfg.target.duration = 0.05; % In secs cfg.extraColumns = {'direction', 'speed', 'target', 'event', 'block'}; end @@ -122,11 +125,11 @@ cfg.text.color = cfg.color.white; % Monitor parameters - cfg.screen.monitorWidth = 42; % in cm - cfg.screen.monitorDistance = 134; % distance from the screen in cm + cfg.screen.monitorWidth = 50; % in cm + cfg.screen.monitorDistance = 40; % distance from the screen in cm if strcmpi(cfg.testingDevice, 'mri') - cfg.screen.monitorWidth = 42; - cfg.screen.monitorDistance = 134; + cfg.screen.monitorWidth = 50; + cfg.screen.monitorDistance = 40; end end From f2f8870d3b454e85841996c0a710febcfca77771 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 18:30:00 +0200 Subject: [PATCH 5/6] update dot algo --- setParameters.m | 16 ++++--- subfun/doDotMo.m | 102 +++++----------------------------------- subfun/expDesign.m | 7 +-- subfun/initializeDots.m | 50 ++++++++++++++++++++ subfun/updateDots.m | 26 ++++++++++ visualLocTanslational.m | 8 +++- 6 files changed, 108 insertions(+), 101 deletions(-) create mode 100644 subfun/initializeDots.m create mode 100644 subfun/updateDots.m diff --git a/setParameters.m b/setParameters.m index 1583bcf..a8d30d7 100644 --- a/setParameters.m +++ b/setParameters.m @@ -50,17 +50,21 @@ cfg.numEventsPerBlock = 12; cfg.eventDuration = 1; % second - % speed in visual angles - cfg.dot.speed = 1; + % speed in visual angles / second + cfg.dot.speed = 15; % Coherence Level (0-1) - cfg.dot.coh = .7; + cfg.dot.coherence = 1; % nb dots per visual angle square. - cfg.dot.density = 5; + cfg.dot.density = .25; % Dot life time in seconds - cfg.dot.lifeTime = 1; + cfg.dot.lifeTime = 10; + + % proportion of dots killed per frame + cfg.dot.proportionKilledPerFrame = .05; + % Dot Size (dot width) in visual angles. - cfg.dot.size = 0.1; + cfg.dot.size = 1; cfg.dot.color = cfg.color.white; % Diameter/length of side of aperture in Visual angles diff --git a/subfun/doDotMo.m b/subfun/doDotMo.m index 0482dd2..6223e1e 100644 --- a/subfun/doDotMo.m +++ b/subfun/doDotMo.m @@ -6,110 +6,34 @@ % % 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: % - % - % 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. + % The dots are drawn on a square with a width equals to the width of the + % screen + % We then draw an aperture on top to hide the certain dots. %% Get parameters - direction = thisEvent.direction(1); - isTarget = thisEvent.target(1); - - dotLifeTime = cfg.dot.lifeTime; - - targetDuration = cfg.target.duration; - - % thisEvent = deg2Pix('speed', thisEvent, cfg); - % dotSpeedPix = logFile.iEventSpeedPix; - - coh = cfg.dot.coh; - speed = thisEvent.speed(1); - % Check if it is a static or motion block - if direction == -1 - - speed = 0; - coh = 1; - - dotLifeTime = cfg.eventDuration; - end - - %% initialize variables - - % 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 - dotPositions = rand(cfg.dot.number, 2); - - % Set a N x 2 matrix that gives jump size in pixels - % pix/sec * sec/frame = pix / frame - dxdy = repmat( ... - speed * 10 / (cfg.aperture.width * 10) * (3 / cfg.screen.monitorRefresh) * ... - [cos(pi * direction / 180.0) -sin(pi * direction / 180.0)], cfg.dot.number, 1); - - % dxdy = repmat(... - % dotSpeedPix / Cfg.ifi ... - % * (cos(pi*direction/180) - sin(pi*direction/180)), ... - % ndots, 1); - - % Create a ones vector to update to dotlife time of each dot - dotTime = ones(size(dotPositions, 1), 1); - - % Covert the dotLifeTime from seconds to frames - dotLifeTime = ceil(dotLifeTime / cfg.screen.ifi); + dots = initializeDots(cfg, thisEvent); % Set for how many frames this event will last framesLeft = floor(cfg.eventDuration / cfg.screen.ifi); - + %% Start the dots presentation vbl = Screen('Flip', cfg.screen.win); onset = vbl; while framesLeft - % L are the dots that will be moved - L = rand(cfg.dot.number, 1) < coh; - - % Move the selected dots - dotPositions(L, :) = dotPositions(L, :) + dxdy(L, :); - - % If not 100% coherence, we get new random locations for the other dots - if sum(~L) > 0 - dotPositions(~L, :) = rand(sum(~L), 2); - end - - % Create a logical vector to detect any dot that has: - % - an xy position inferior to 0 - % - an xy position superior to 1 - % - has exceeded its liftime - N = any([dotPositions > 1, dotPositions < 0, dotTime > dotLifeTime], 2) ; + [dots] = updateDots(dots, cfg); - % If there is any such dot we relocate it to a new random position - % and change its lifetime to 1 - if any(N) - dotPositions(N, :) = rand(sum(N), 2); - dotTime(N, 1) = 1; - end - - %% Convert the dot position to pixels - % We expand that square so that its side is equal to the whole - % screen width. - % With no aperture the whole screen is filled with dots. - dotPositionsPix = floor(dotPositions * cfg.screen.winRect(3)); - - % This assumes that zero is at the top left, but we want it to be + %% 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 aperture size to both the x and y direction. - dotPositionsPix = (dotPositionsPix - cfg.screen.winRect(3) / 2)'; - - thisEvent.dot.positions = dotPositionsPix; + % adding half of the screen width in pixel to both the x and y direction. + thisEvent.dot.positions = (dots.positions - cfg.screen.winWidth / 2)'; %% make textures dotTexture('make', cfg, thisEvent); @@ -125,7 +49,7 @@ % If this frame shows a target we change the color thisFixation.fixation = cfg.fixation; thisFixation.screen = cfg.screen; - if GetSecs < (onset + targetDuration) && isTarget == 1 + if thisEvent.target(1) && GetSecs < (onset + cfg.target.duration) thisFixation.fixation.color = cfg.fixation.colorTarget; end drawFixation(thisFixation); @@ -139,9 +63,6 @@ % Check for end of loop framesLeft = framesLeft - 1; - % Add one frame to the dot lifetime to each dot - dotTime = dotTime + 1; - end %% Erase last dots @@ -155,4 +76,3 @@ duration = vbl - onset; end - diff --git a/subfun/expDesign.m b/subfun/expDesign.m index dffad21..c7c5c6c 100644 --- a/subfun/expDesign.m +++ b/subfun/expDesign.m @@ -38,7 +38,8 @@ % Set directions for static and motion condition motionDirections = [0 90 180 270]; - staticDirections = [-1 -1 -1 -1]; + staticDirections = [0 90 180 270]; +% staticDirections = [-1 -1 -1 -1]; %% Check inputs @@ -59,7 +60,7 @@ % Get the parameters names = cfg.names; numRepetitions = cfg.numRepetitions; - speedEvent = cfg.dot.speed; + dotsSpeed = cfg.dot.speedPixPerFrame; numEventsPerBlock = cfg.numEventsPerBlock; maxNumFixTargPerBlock = cfg.target.maxNbPerBlock; @@ -93,7 +94,7 @@ cfg.design.blockNames = cell(nrBlocks, 1); cfg.design.directions = zeros(nrBlocks, numEventsPerBlock); - cfg.design.speeds = ones(nrBlocks, numEventsPerBlock) * speedEvent; + cfg.design.speeds = ones(nrBlocks, numEventsPerBlock) * dotsSpeed; cfg.design.fixationTargets = zeros(nrBlocks, numEventsPerBlock); for iMotionBlock = 1:numRepetitions diff --git a/subfun/initializeDots.m b/subfun/initializeDots.m new file mode 100644 index 0000000..a8b5755 --- /dev/null +++ b/subfun/initializeDots.m @@ -0,0 +1,50 @@ +function [dots] = initializeDots(cfg, thisEvent) + + direction = thisEvent.direction(1); + + dots.lifeTime = cfg.dot.lifeTime; + + speedPixPerFrame = thisEvent.speed(1); + + % decide which dots are signal dots (1) and those are noise dots (0) + dots.isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence; + + % for static dots + if direction == -1 + speedPixPerFrame = 0; + dots.lifeTime = cfg.eventDuration; + dots.isSignal = ones(cfg.dot.number, 1); + end + + % Convert from seconds to frames + dots.lifeTime = ceil(dots.lifeTime / cfg.screen.ifi); + + % 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 = rand(cfg.dot.number, 2) * cfg.screen.winWidth; + + % Set a N x 2 matrix that speed in X and Y + dots.speeds = nan(cfg.dot.number, 2); + + % Coherent dots + [horVector, vertVector] = decompMotion(direction); + dots.speeds(dots.isSignal,:) = ... + repmat([horVector, vertVector], sum(dots.isSignal), 1); + + % If not 100% coherence, we get new random direction for the other dots + direction = rand(sum(~dots.isSignal), 1) * 360; + [horVector, vertVector] = decompMotion(direction); + dots.speeds(~dots.isSignal, :) = [horVector, vertVector]; + + % So far we were working wiht unit vectors convert that speed in pixels per + % frame + dots.speeds = dots.speeds * speedPixPerFrame; + + % Create a vector to update to dotlife time of each dot + % Not all set to one so the dots will die at different times + dots.time = floor(rand(cfg.dot.number, 1) * cfg.eventDuration / cfg.screen.ifi); + +end + diff --git a/subfun/updateDots.m b/subfun/updateDots.m new file mode 100644 index 0000000..35ab733 --- /dev/null +++ b/subfun/updateDots.m @@ -0,0 +1,26 @@ +function [dots] = updateDots(dots, cfg) + + % Move the selected dots + dots.positions = dots.positions + dots.speeds; + + % Create a logical vector to detect any dot that has: + % - an xy position inferior to 0 + % - an x position superior to winWidth + % - an x position superior to winHeight + % - has exceeded its liftime + N = any([... + dots.positions > cfg.screen.winWidth, ... + dots.positions < 0, ... + dots.time > dots.lifeTime, ... + rand(cfg.dot.number,1) < cfg.dot.proportionKilledPerFrame ], 2) ; + + % 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, :) = rand(sum(N), 2) * cfg.screen.winWidth; + dots.time(N, 1) = 1; + end + + % Add one frame to the dot lifetime to each dot + dots.time = dots.time + 1; +end diff --git a/visualLocTanslational.m b/visualLocTanslational.m index e9c4ced..4012f09 100644 --- a/visualLocTanslational.m +++ b/visualLocTanslational.m @@ -35,10 +35,16 @@ % Convert some values from degrees to pixels cfg.dot = degToPix('size', cfg.dot, cfg); + cfg.dot = degToPix('speed', cfg.dot, cfg); + + % Get dot speeds in pixels per frame + cfg.dot.speedPixPerFrame = cfg.dot.speedPix / cfg.screen.monitorRefresh; + + cfg.aperture = degToPix('xPos', cfg.aperture, cfg); % dots are displayed on a square with a length in visual angle equal to the % field of view - cfg.dot.number = cfg.dot.density * cfg.screen.FOV^2; + cfg.dot.number = round(cfg.dot.density * (cfg.screen.winWidth / cfg.screen.ppd)^2); [el] = eyeTracker('Calibration', cfg); From 75ccb24c41c820169f2b8542bb9555259c5692ca Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Sun, 2 Aug 2020 18:30:10 +0200 Subject: [PATCH 6/6] update submodule --- lib/CPP_PTB | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CPP_PTB b/lib/CPP_PTB index ac6fc16..ae4b3af 160000 --- a/lib/CPP_PTB +++ b/lib/CPP_PTB @@ -1 +1 @@ -Subproject commit ac6fc160bfb535a08b4f7afbe35a4e48a35b2184 +Subproject commit ae4b3af956bb4a608586fa2d9fd856b311e59159