Skip to content

Commit 6225b66

Browse files
authored
Merge pull request #18 from Remi-Gau/remi-use_aperture
Implement textures for dots / apertures. Fixes dot motion procedure.
2 parents 83d83f4 + 75ccb24 commit 6225b66

File tree

9 files changed

+194
-198
lines changed

9 files changed

+194
-198
lines changed

initEnv.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
function addDependencies()
8787

8888
pth = fileparts(mfilename('fullpath'));
89-
addpath(fullfile(pth, 'lib', 'CPP_BIDS'));
89+
addpath(fullfile(pth, 'lib', 'CPP_BIDS', 'src'));
9090
addpath(fullfile(pth, 'lib', 'CPP_PTB'));
9191
addpath(fullfile(pth, 'subfun'));
9292

setParameters.m

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,48 +23,54 @@
2323
cfg.eyeTracker.do = false;
2424
cfg.audio.do = false;
2525

26-
cfg = setMonitor(cfg, cfg);
26+
cfg = setMonitor(cfg);
2727

2828
% Keyboards
29-
cfg = setKeyboards(cfg, cfg);
29+
cfg = setKeyboards(cfg);
3030

3131
% MRI settings
32-
cfg = setMRI(cfg, cfg);
32+
cfg = setMRI(cfg);
3333

3434
%% Experiment Design
3535
cfg.names = {'static', 'motion'};
3636
cfg.possibleDirections = [-1 1]; % 1 motion , -1 static
3737
cfg.numBlocks = size(cfg.possibleDirections, 2);
3838
cfg.numRepetitions = 1; % AT THE MOMENT IT IS NOT SET IN THE MAIN SCRIPT
39-
cfg.IBI = 0; % 8;
39+
cfg.IBI = .5; % 8;
4040
% Time between events in secs
41-
cfg.ISI = 0.1;
41+
cfg.ISI = 0.5;
4242
% Number of seconds before the motion stimuli are presented
43-
cfg.onsetDelay = 5;
43+
cfg.onsetDelay = .1;
4444
% Number of seconds after the end all the stimuli before ending the run
45-
cfg.endDelay = 1;
45+
cfg.endDelay = .1;
4646

4747
%% Visual Stimulation
4848

4949
% Number of events per block (should not be changed)
5050
cfg.numEventsPerBlock = 12;
5151
cfg.eventDuration = 1; % second
5252

53-
% speed in visual angles
54-
cfg.dot.speed = 8;
53+
% speed in visual angles / second
54+
cfg.dot.speed = 15;
5555
% Coherence Level (0-1)
56-
cfg.dot.coh = 1;
57-
% Maximum number dots per frame
58-
cfg.dot.maxNbPerFrame = 300;
56+
cfg.dot.coherence = 1;
57+
% nb dots per visual angle square.
58+
cfg.dot.density = .25;
59+
5960
% Dot life time in seconds
60-
cfg.dot.lifeTime = 1;
61+
cfg.dot.lifeTime = 10;
62+
63+
% proportion of dots killed per frame
64+
cfg.dot.proportionKilledPerFrame = .05;
65+
6166
% Dot Size (dot width) in visual angles.
62-
cfg.dot.size = 0.1;
67+
cfg.dot.size = 1;
6368
cfg.dot.color = cfg.color.white;
64-
cfg.dot.dontClear = 0;
6569

6670
% Diameter/length of side of aperture in Visual angles
67-
cfg.diameterAperture = 8;
71+
cfg.aperture.type = 'circle';
72+
cfg.aperture.width = []; % if left empty it will take the screen height
73+
cfg.aperture.xPos = 0;
6874

6975
%% Task(s)
7076

@@ -74,17 +80,16 @@
7480
cfg.task.instruction = '1-Detect the RED fixation cross\n \n\n';
7581

7682
% Fixation cross (in pixels)
77-
% Set the length of the lines of the fixation cross
78-
cfg.fixation.dimensionPix = 10;
79-
% Set the line width for our fixation cross
80-
cfg.fixation.lineWidthPix = 4;
81-
cfg.fixation.xDisplacement = 0; % Manual displacement of the fixation cross
82-
cfg.fixation.yDisplacement = 0; % Manual displacement of the fixation cross
83-
cfg.fixation.color = cfg.color.white;
83+
cfg.fixation.type = 'cross';
8484
cfg.fixation.colorTarget = cfg.color.red;
85+
cfg.fixation.color = cfg.color.white;
86+
cfg.fixation.width = 1;
87+
cfg.fixation.lineWidthPix = 2;
88+
cfg.fixation.xDisplacement = 0;
89+
cfg.fixation.yDisplacement = 0;
8590

8691
cfg.target.maxNbPerBlock = 2;
87-
cfg.target.duration = 0.15; % In secs
92+
cfg.target.duration = 0.05; % In secs
8893

8994
cfg.extraColumns = {'direction', 'speed', 'target', 'event', 'block'};
9095
end
@@ -124,11 +129,11 @@
124129
cfg.text.color = cfg.color.white;
125130

126131
% Monitor parameters
127-
cfg.screen.monitorWidth = 42; % in cm
128-
cfg.screen.monitorDistance = 134; % distance from the screen in cm
132+
cfg.screen.monitorWidth = 50; % in cm
133+
cfg.screen.monitorDistance = 40; % distance from the screen in cm
129134

130135
if strcmpi(cfg.testingDevice, 'mri')
131-
cfg.screen.monitorWidth = 42;
132-
cfg.screen.monitorDistance = 134;
136+
cfg.screen.monitorWidth = 50;
137+
cfg.screen.monitorDistance = 40;
133138
end
134139
end

subfun/doDotMo.m

Lines changed: 54 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -6,156 +6,73 @@
66
%
77
% Input:
88
% - cfg: PTB/machine configurations returned by setParameters and initPTB
9-
% - expParameters: parameters returned by setParameters
10-
% - logFile: structure that stores the experiment logfile to be saved
119
%
1210
% Output:
1311
% -
1412
%
15-
% The dots are drawn on a square that contains the round aperture, then any
16-
% dots outside of the aperture is turned into a NaN so effectively the
17-
% actual number of dots on the screen at any given time is not the one that you input but a
18-
% smaller number (nDots / Area of aperture) on average.
19-
13+
% The dots are drawn on a square with a width equals to the width of the
14+
% screen
15+
% We then draw an aperture on top to hide the certain dots.
16+
2017
%% Get parameters
21-
dontClear = cfg.dot.dontClear;
22-
23-
direction = thisEvent.direction(1);
24-
isTarget = thisEvent.target(1);
25-
speed = thisEvent.speed(1);
26-
27-
coh = cfg.dot.coh;
28-
ndots = cfg.dot.maxNbPerFrame;
29-
dotSizePix = cfg.dot.sizePix;
30-
dotLifeTime = cfg.dot.lifeTime;
31-
dotColor = cfg.dot.color;
32-
33-
targetDuration = cfg.target.duration;
34-
35-
% thisEvent = deg2Pix('speed', thisEvent, cfg);
36-
% dotSpeedPix = logFile.iEventSpeedPix;
37-
38-
diamAperturePix = cfg.diameterAperturePix;
39-
diamAperture = cfg.diameterAperture;
40-
41-
% Check if it is a static or motion block
42-
if direction == -1
43-
44-
% dotSpeedPix = 0;
45-
46-
speed = 0;
47-
48-
dotLifeTime = cfg.eventDuration;
49-
end
50-
51-
%% initialize variables
52-
53-
% Set an array of dot positions [xposition, yposition]
54-
% These can never be bigger than 1 or lower than 0
55-
% [0,0] is the top / left of the square that contains the square aperture
56-
% [1,1] is the bottom / right of the square that contains the square aperture
57-
xy = rand(ndots, 2);
58-
59-
% Set a N x 2 matrix that gives jump size in pixels
60-
% pix/sec * sec/frame = pix / frame
61-
dxdy = repmat( ...
62-
speed * 10 / (diamAperture * 10) * (3 / cfg.screen.monitorRefresh) * ...
63-
[cos(pi * direction / 180.0) -sin(pi * direction / 180.0)], ndots, 1);
64-
65-
% dxdy = repmat(...
66-
% dotSpeedPix / Cfg.ifi ...
67-
% * (cos(pi*direction/180) - sin(pi*direction/180)), ...
68-
% ndots, 1);
69-
70-
% Create a ones vector to update to dotlife time of each dot
71-
dotTime = ones(size(xy, 1), 1);
72-
73-
% Set for how many frames to show the dots
74-
continueShow = floor(cfg.eventDuration / cfg.screen.ifi);
75-
76-
% Covert the dotLifeTime from seconds to frames
77-
dotLifeTime = ceil(dotLifeTime / cfg.screen.ifi);
78-
18+
19+
dots = initializeDots(cfg, thisEvent);
20+
21+
% Set for how many frames this event will last
22+
framesLeft = floor(cfg.eventDuration / cfg.screen.ifi);
23+
7924
%% Start the dots presentation
80-
vbl = Screen('Flip', cfg.screen.win, 0, dontClear);
25+
vbl = Screen('Flip', cfg.screen.win);
8126
onset = vbl;
82-
83-
while continueShow
84-
85-
% L are the dots that will be moved
86-
L = rand(ndots, 1) < coh;
87-
88-
% Move the selected dots
89-
xy(L, :) = xy(L, :) + dxdy(L, :);
90-
91-
% If not 100% coherence, we get new random locations for the other dots
92-
if sum(~L) > 0
93-
xy(~L, :) = rand(sum(~L), 2);
94-
end
95-
96-
% Create a logical vector to detect any dot that has:
97-
% - an xy position inferior to 0
98-
% - an xy position superior to 1
99-
% - has exceeded its liftime
100-
N = any([xy > 1, xy < 0, dotTime > dotLifeTime], 2) ;
101-
102-
% If there is any such dot we relocate it to a new random position
103-
% and change its lifetime to 1
104-
if any(N)
105-
xy(N, :) = rand(sum(N), 2);
106-
dotTime(N, 1) = 1;
107-
end
108-
109-
% Convert the dot position to pixels
110-
xy_pix = floor(xy * diamAperturePix);
111-
112-
% This assumes that zero is at the top left, but we want it to be
27+
28+
while framesLeft
29+
30+
[dots] = updateDots(dots, cfg);
31+
32+
%% Center the dots
33+
% We assumed that zero is at the top left, but we want it to be
11334
% in the center, so shift the dots up and left, which just means
114-
% adding half of the aperture size to both the x and y direction.
115-
xy_pix = (xy_pix - diamAperturePix / 2)';
116-
117-
% NaN out-of-circle dots
118-
% We use Pythagore's theorem to figure out which dots are out of the
119-
% circle
120-
outCircle = sqrt(xy_pix(1, :).^2 + xy_pix(2, :).^2) + ...
121-
dotSizePix / 2 > (diamAperturePix / 2);
122-
xy_pix(:, outCircle) = NaN;
123-
124-
%% PTB draws the dots stimulation
125-
126-
% Draw the fixation cross
127-
color = cfg.fixation.color;
35+
% adding half of the screen width in pixel to both the x and y direction.
36+
thisEvent.dot.positions = (dots.positions - cfg.screen.winWidth / 2)';
37+
38+
%% make textures
39+
dotTexture('make', cfg, thisEvent);
40+
41+
apertureTexture('make', cfg, thisEvent);
42+
43+
%% draw evetything and flip screen
44+
45+
dotTexture('draw', cfg, thisEvent);
46+
47+
apertureTexture('draw', cfg, thisEvent);
48+
12849
% If this frame shows a target we change the color
129-
if GetSecs < (onset + targetDuration) && isTarget == 1
130-
color = cfg.fixation.colorTarget;
50+
thisFixation.fixation = cfg.fixation;
51+
thisFixation.screen = cfg.screen;
52+
if thisEvent.target(1) && GetSecs < (onset + cfg.target.duration)
53+
thisFixation.fixation.color = cfg.fixation.colorTarget;
13154
end
132-
drawFixationCross(cfg, color);
133-
134-
% Draw the dots
135-
Screen('DrawDots', cfg.screen.win, xy_pix, dotSizePix, dotColor, cfg.screen.center, 2);
136-
137-
Screen('DrawingFinished', cfg.screen.win, dontClear);
138-
139-
vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi, dontClear);
140-
55+
drawFixation(thisFixation);
56+
57+
Screen('DrawingFinished', cfg.screen.win);
58+
59+
vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi);
60+
14161
%% Update counters
142-
62+
14363
% Check for end of loop
144-
continueShow = continueShow - 1;
145-
146-
% Add one frame to the dot lifetime to each dot
147-
dotTime = dotTime + 1;
148-
64+
framesLeft = framesLeft - 1;
65+
14966
end
150-
67+
15168
%% Erase last dots
152-
153-
drawFixationCross(cfg, cfg.fixation.color);
154-
155-
Screen('DrawingFinished', cfg.screen.win, dontClear);
156-
157-
vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi, dontClear);
158-
69+
70+
drawFixation(cfg);
71+
72+
Screen('DrawingFinished', cfg.screen.win);
73+
74+
vbl = Screen('Flip', cfg.screen.win, vbl + cfg.screen.ifi);
75+
15976
duration = vbl - onset;
160-
77+
16178
end

subfun/expDesign.m

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838

3939
% Set directions for static and motion condition
4040
motionDirections = [0 90 180 270];
41-
staticDirections = [-1 -1 -1 -1];
41+
staticDirections = [0 90 180 270];
42+
% staticDirections = [-1 -1 -1 -1];
4243

4344
%% Check inputs
4445

@@ -59,7 +60,7 @@
5960
% Get the parameters
6061
names = cfg.names;
6162
numRepetitions = cfg.numRepetitions;
62-
speedEvent = cfg.dot.speed;
63+
dotsSpeed = cfg.dot.speedPixPerFrame;
6364
numEventsPerBlock = cfg.numEventsPerBlock;
6465
maxNumFixTargPerBlock = cfg.target.maxNbPerBlock;
6566

@@ -93,7 +94,7 @@
9394

9495
cfg.design.blockNames = cell(nrBlocks, 1);
9596
cfg.design.directions = zeros(nrBlocks, numEventsPerBlock);
96-
cfg.design.speeds = ones(nrBlocks, numEventsPerBlock) * speedEvent;
97+
cfg.design.speeds = ones(nrBlocks, numEventsPerBlock) * dotsSpeed;
9798
cfg.design.fixationTargets = zeros(nrBlocks, numEventsPerBlock);
9899

99100
for iMotionBlock = 1:numRepetitions

0 commit comments

Comments
 (0)