Skip to content

Commit 26f41c6

Browse files
committed
adapt initDots for radial motion
- add tests - refactor - rename decomMotion
1 parent a8bc6b5 commit 26f41c6

File tree

5 files changed

+232
-34
lines changed

5 files changed

+232
-34
lines changed

computeRadialMotionDirection.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function direction = computeRadialMotionDirection(cfg, positions, direction)
2+
if strcmp(cfg.design.motionType, 'radial')
3+
4+
cartesianCoordinates = positions - cfg.screen.winWidth / 2;
5+
6+
[angle, ~] = cart2pol(cartesianCoordinates(:,1), cartesianCoordinates(:,2));
7+
angle = angle / pi * 180;
8+
9+
if direction == -666
10+
angle = angle - 180;
11+
end
12+
13+
direction = angle;
14+
15+
end
16+
end

decompMotion.m renamed to decomposeMotion.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function [horVector, vertVector] = decompMotion(angleMotion)
1+
function [horVector, vertVector] = decomposeMotion(angleMotion)
22
% decompose angle of start motion into horizontal and vertical vector
33
horVector = cos(pi * angleMotion / 180);
44
vertVector = -sin(pi * angleMotion / 180);

initDots.m

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,82 @@
11
function [dots] = initDots(cfg, thisEvent)
2-
2+
% [dots] = initDots(cfg, thisEvent)
3+
%
4+
% % Dot life time in seconds
5+
% cfg.dot.lifeTime
6+
% % Number of dots
7+
% cfg.dot.number
8+
% Proportion of coherent dots.
9+
% cfg.dot.coherence
10+
%
11+
% % Direction (an angle in degrees)
12+
% thisEvent.direction
13+
% % Speed expressed in pixels per frame
14+
% thisEvent.speed
15+
%
16+
%
17+
18+
% TODO
19+
% bound direction between 0 and 360 ??
20+
321
direction = thisEvent.direction(1);
4-
5-
dots.lifeTime = cfg.dot.lifeTime;
6-
22+
723
speedPixPerFrame = thisEvent.speed(1);
824

25+
lifeTime = cfg.dot.lifeTime;
26+
927
% decide which dots are signal dots (1) and those are noise dots (0)
10-
dots.isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence;
28+
isSignal = rand(cfg.dot.number, 1) < cfg.dot.coherence;
1129

1230
% for static dots
1331
if direction == -1
1432
speedPixPerFrame = 0;
15-
dots.lifeTime = Inf;
16-
dots.isSignal = true(cfg.dot.number, 1);
33+
lifeTime = Inf;
34+
isSignal = true(cfg.dot.number, 1);
1735
end
18-
19-
% Convert from seconds to frames
20-
dots.lifeTime = ceil(dots.lifeTime / cfg.screen.ifi);
21-
22-
% Set an array of dot positions [xposition, yposition]
36+
37+
%% Set an array of dot positions [xposition, yposition]
2338
% These can never be bigger than 1 or lower than 0
2439
% [0,0] is the top / left of the square
2540
% [1,1] is the bottom / right of the square
26-
dots.positions = rand(cfg.dot.number, 2) * cfg.screen.winWidth;
27-
28-
% Set a N x 2 matrix that speed in X and Y
29-
dots.speeds = nan(cfg.dot.number, 2);
41+
positions = rand(cfg.dot.number, 2) * cfg.screen.winWidth;
3042

31-
% Coherent dots
32-
[horVector, vertVector] = decompMotion(direction);
33-
dots.speeds(dots.isSignal, :) = ...
34-
repmat([horVector, vertVector], sum(dots.isSignal), 1);
43+
%% Set vertical and horizontal speed for all dots
44+
directionAllDots = setDotDirection(cfg, positions, direction, isSignal);
45+
46+
[horVector, vertVector] = decomposeMotion(directionAllDots);
47+
speeds = [horVector, vertVector];
48+
49+
% we were working with unit vectors. we now switch to pixels
50+
speeds = speeds * speedPixPerFrame;
3551

36-
% Random direction for the non coherent dots
37-
if any(~dots.isSignal)
38-
randomDirection = rand(sum(~dots.isSignal), 1) * 360;
39-
[horVector, vertVector] = decompMotion(randomDirection);
40-
dots.speeds(~dots.isSignal, :) = [horVector, vertVector];
41-
end
42-
43-
% So far we were working wiht unit vectors convert that speed in pixels per
44-
% frame
45-
dots.speeds = dots.speeds * speedPixPerFrame;
46-
47-
% Create a vector to update to dotlife time of each dot
52+
%% Create a vector to update to dotlife time of each dot
4853
% Not all set to 1 so the dots will die at different times
4954
% The maximum value is the duraion of the event in frames
50-
dots.time = floor(rand(cfg.dot.number, 1) * cfg.eventDuration / cfg.screen.ifi);
55+
time = floor(rand(cfg.dot.number, 1) * cfg.eventDuration / cfg.screen.ifi);
56+
57+
%% Convert from seconds to frames
58+
lifeTime = ceil(lifeTime / cfg.screen.ifi);
59+
60+
%%
61+
dots.lifeTime = lifeTime;
62+
dots.time = time;
63+
dots.speeds = speeds;
64+
dots.isSignal = isSignal;
65+
dots.positions = positions;
66+
end
5167

68+
function directionAllDots = setDotDirection(cfg, positions, direction, isSignal)
69+
70+
directionAllDots = nan(cfg.dot.number, 1);
71+
72+
direction = computeRadialMotionDirection(cfg, positions, direction);
73+
74+
% Coherent dots
75+
directionAllDots(isSignal) = direction;
76+
% Random direction for the non coherent dots
77+
directionAllDots(~isSignal) = rand(sum(~isSignal), 1) * 360;
78+
directionAllDots = rem(directionAllDots,360);
79+
5280
end
81+
82+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
function test_suite = test_computeRadialMotionDirection %#ok<*STOUT>
2+
try % assignment of 'localfunctions' is necessary in Matlab >= 2016
3+
test_functions = localfunctions(); %#ok<*NASGU>
4+
catch % no problem; early Matlab versions can use initTestSuite fine
5+
end
6+
initTestSuite;
7+
end
8+
9+
function test_computeRadialMotionDirectionBasic()
10+
11+
%% set up
12+
13+
cfg.design.motionType = 'radial';
14+
cfg.screen.winWidth = 100; % in pixels
15+
direction = 666;
16+
17+
positions = [
18+
100, 100/2; ... % middle of right side
19+
100, 100; ... % top right corner
20+
100/2, 100; ...
21+
0, 100/2; ...
22+
0, 0; ...
23+
100/2, 0];
24+
25+
direction = computeRadialMotionDirection(cfg, positions, direction);
26+
27+
expectedDirection = [
28+
0; ... right
29+
45; ... up-right
30+
90; ... up
31+
180; ... left
32+
-135; ... down left
33+
-90]; % down
34+
35+
36+
%% test
37+
assertEqual(expectedDirection, direction);
38+
39+
end

tests/test_initDots.m

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
function test_suite = test_initDots %#ok<*STOUT>
2+
try % assignment of 'localfunctions' is necessary in Matlab >= 2016
3+
test_functions = localfunctions(); %#ok<*NASGU>
4+
catch % no problem; early Matlab versions can use initTestSuite fine
5+
end
6+
initTestSuite;
7+
end
8+
9+
function test_initDotsBasic()
10+
11+
%% set up
12+
13+
% % Dot life time in seconds
14+
% cfg.dot.lifeTime
15+
% % Number of dots
16+
% cfg.dot.number
17+
% Proportion of coherent dots.
18+
% cfg.dot.coherence
19+
%
20+
% % Direction (an angle in degrees)
21+
% thisEvent.direction
22+
% % Speed expressed in pixels per frame
23+
% thisEvent.speed
24+
25+
cfg.design.motionType = 'translation';
26+
cfg.dot.number = 10;
27+
cfg.dot.coherence = 1; % proportion
28+
cfg.dot.lifeTime = 0.250; % in seconds
29+
cfg.screen.winWidth = 2000; % in pixels
30+
cfg.eventDuration = 1; % in seconds
31+
cfg.screen.ifi = 0.01; % in seconds
32+
33+
thisEvent.direction = 0;
34+
thisEvent.speed = 10;
35+
36+
[dots] = initDots(cfg, thisEvent);
37+
38+
%% Undeterministic ouput
39+
assertTrue(all(dots.positions(:) >= 0));
40+
assertTrue(all(dots.positions(:) <= 2000));
41+
assertTrue(all(dots.time(:) >= 0));
42+
assertTrue(all(dots.time(:) <= 1 / 0.01));
43+
44+
%% Deterministic output : data to test against
45+
expectedStructure.lifeTime = 25;
46+
expectedStructure.isSignal = ones(10, 1);
47+
expectedStructure.speeds = repmat([1 0], 10, 1) * 10;
48+
49+
% remove undeterministic output
50+
dots = rmfield(dots, 'time');
51+
dots = rmfield(dots, 'positions');
52+
53+
%% test
54+
assertEqual(expectedStructure, dots);
55+
56+
end
57+
58+
function test_initDotsStatic()
59+
60+
cfg.design.motionType = 'translation';
61+
cfg.dot.number = 10;
62+
cfg.dot.coherence = 1; % proportion
63+
cfg.dot.lifeTime = 0.250; % in seconds
64+
cfg.screen.winWidth = 2000; % in pixels
65+
cfg.eventDuration = 1; % in seconds
66+
cfg.screen.ifi = 0.01; % in seconds
67+
68+
thisEvent.direction = -1;
69+
thisEvent.speed = 10;
70+
71+
[dots] = initDots(cfg, thisEvent);
72+
73+
% remove undeterministic output
74+
dots = rmfield(dots, 'time');
75+
dots = rmfield(dots, 'positions');
76+
77+
%% data to test against
78+
expectedStructure.lifeTime = Inf;
79+
expectedStructure.isSignal = ones(10, 1);
80+
expectedStructure.speeds = zeros(10, 2);
81+
82+
%% test
83+
assertEqual(expectedStructure, dots);
84+
85+
end
86+
87+
88+
function test_initDotsRadial()
89+
90+
cfg.design.motionType = 'radial';
91+
cfg.dot.number = 10;
92+
cfg.dot.coherence = 1; % proportion
93+
cfg.dot.lifeTime = 0.250; % in seconds
94+
cfg.screen.winWidth = 2000; % in pixels
95+
cfg.eventDuration = 1; % in seconds
96+
cfg.screen.ifi = 0.01; % in seconds
97+
98+
thisEvent.direction = 666; % outward motion
99+
thisEvent.speed = 10;
100+
101+
[dots] = initDots(cfg, thisEvent);
102+
103+
%% data to test against
104+
XY = dots.positions - 2000/2;
105+
angle = cart2pol(XY(:,1), XY(:,2));
106+
angle = angle / pi * 180;
107+
[horVector, vertVector] = decomposeMotion(angle);
108+
speeds = [horVector, vertVector] * 10;
109+
110+
%% test
111+
assertEqual(speeds, dots.speeds);
112+
113+
end

0 commit comments

Comments
 (0)