function dots = MakeDotArray(side,number_dots,dot_diameter,mode)
% Finds the centers of a set of dots within a larger allowed circle. The
% density = number_dots / side^2 (density is number of dots per square
% pixel) This script will have to be called twice if you want subject to
% choose between two dot arrays.
%
% Input arguments:
% SIDE diameter of allowed circle where the dots can appear (likely
% constrained by the minumum side of the monitor) NUMBER_DOTS number of
% dots in the array DOT_DIAMETER diameter in pixels of each dot MODE can
% be 'centers' or 'sides', defaults to centers; 'centers' gives the x,y
% centers of the dots, while 'sides' gives xmin,ymin,xmax,ymax, as used
% in psychtoolbox for placing dots
%
% Output argument:
% DOTS
% -For 'centers' mode: [2 x NUMBER_DOTS] matrix of (x,y) pixel
% coordinates of the centers of the dots
% -For 'sides' mode: [4 x NUMBER_DOTS] matrix of (x,y) pixel
% coordinates representing [xmin ymin xmax ymax]
%
% EG Gaffin-Cahn
% 2013
%
try
% error message when the density of the dots is too high to find a valid new location
density_error = 'Density of the dot array too high! You may have to allow more space, or shrink the dot size or number';
% parameters
bounds = [side, side]; % height and width of allowed area circle
area_center = bounds/2; % x,y coordinates of center of allowed area circle
% initialize the circle where the dots area allowed (in LOGICAL format)
allowed_area = MakeCircle(side-dot_diameter/2, area_center, bounds);
% do this for each dot you are creating
for i = 1:number_dots
if all(~allowed_area(:))
error(density_error)
end
% get the (x,y) center of the current dot (choose randomly)
center = find(mnrnd(1, allowed_area(:)/sum(allowed_area(:))));
% convert the center coordinate to (x,y) from an index
[center(1) center(2)] = ind2sub(size(allowed_area),center);
% further restrict the allowed area
allowed_area = allowed_area & ~MakeCircle(dot_diameter*2,center,bounds);
% add to growing list of the (x,y) centers of the dots
dot_centers(:,i) = center;
end
% just for good measure
% it's ok if some overlap, the actual dots won't overlap, this is just the
% allowed area for any next dots
% % imshow(double(allowed_area));
catch me
[ST,~] = dbstack;
if ~isequal(me.message, density_error) || length(ST) == get(0,'RecursionLimit') - 5
warning OFF BACKTRACE
warning OFF VERBOSE
warning(me.message);
else % if density is too high, try again
dot_centers = MakeDotArray(side,number_dots,dot_diameter);
end
end
if nargin < 4 || isequal(mode,'centers')
dots = dot_centers;
else
r = dot_diameter / 2;
dots([1,3],:) = [dot_centers(1,:)-r; dot_centers(1,:)+r];
dots([2,4],:) = [dot_centers(2,:)-r; dot_centers(2,:)+r];
end
function circle = MakeCircle(diameter,center,bounds)
% Arguments:
% diameter of circle (or dot)
% center (x,y) coordinates of circle or dot
% width and height of allowed bounds for dots to appear in
% For a [bounds x bounds] matrix, pixel2origin(n,m,:) is the (x,y) vector
% from the origin (0,0) to (n,m).
[pixel2origin(:,:,2), pixel2origin(:,:,1)] = meshgrid(1:bounds(2),1:bounds(1));
% origin2center is the (x,y) vector from the origin to the dot center
origin2center = -center;
% scroll through x, then y
for i = 1:2
% center2pixel is the vector addition of pixel2origin and
% origin2center, meaning that center2pixel is the (x,y) vector from the
% center of the dot to the current pixel at (m,n)
center2pixel(:,:,i) = pixel2origin(:,:,i) + origin2center(i);
end
% convert vector to magnitude only
absolute_distance = sqrt(sum(center2pixel.^2,3));
% circle is a logical array where TRUEs are where the distances are less
% than the radius
circle = absolute_distance <= diameter/2;