Efficiently calculating the pixels crossed by a ray bundle
5 次查看(过去 30 天)
显示 更早的评论
I have created a script that calculates which pixels on a regular square grid are crossed by which ray(s) of a bundle. Up to the line "B(idx_lin) = 1;", the code is quite efficient, I would say. It's calculated on the GPU for the simple reason that there are easily thousands of rays, and these calculations are sped up quite a lot on the GPU.
Recently, however, I have tried to go from square pixels to circles (with the circle diameter being the pixel edge length and the circles lying in the pixels' centers), and I have unfortunately not found a solution for vectorisation yet, instead solving it with a loop, which is highly inefficient, especially on a GPU. I have tested it and the results are correct, i.e. the solution works, its performance is just bad. The basis for the function min_dist2_ray_pix_GPU can be found here, for the interested reader. The function simply calculates the minimum distance between all rays and all pixels supplied to it.
Without a preselection of pixels, this operation is on the order of n_rays * n_pix_x * n_pix_y. The code shown here performs an operation on the order n_rays * (n_pix_x + n_pix_y), which is far quicker, creating a preselection of pixels which is then supplied to the minimum distance calculation, making that calculation far quicker in theory, if it was vectorised.
In principle, the issue is that each ray now needs a different selection of pixels it passes through, in most cases not even the same number of pixels, which I don't know how to supply to the function all at once. This is quite easily solved in a loop, as shown, but it makes the code slow. So how can this be vectorised? Or is there maybe a wholly different approach yielding the same results which I overlooked?
%all of this is usually supplied to the function, but I explicitly write it
%here so that calculations are possible
x_r = 0;
y_r = -0.3;
x_r = repmat(x_r, 1, 1001);
y_r = repmat(y_r, 1, 1001);
vectors_dummy = [0;1];
vectors = zeros(2,1001);
angles = linspace(-30,30,1001) * pi/180;
vectors = rot_matrix_2d(vectors_dummy,angles);
vectors = squeeze(vectors).';
vox.dx = 0.002;
vox.dy = 0.002;
vox.xstart = -0.2;
vox.xend = 0.2;
vox.ystart = -0.2;
vox.yend = 0.2;
vox.Nx = 201;
vox.Ny = 201;
coordaxis.x = linspace(vox.xstart, vox.xend, vox.Nx);
coordaxis.y = linspace(vox.ystart, vox.yend, vox.Ny);
coordaxis.z = 0;
[array.X, array.Y, array.Z] = ndgrid(coordaxis.x,coordaxis.y,coordaxis.z);
vox_pos = zeros(3, length(array.X(:)));
vox_pos(1,:) = array.X(:);
vox_pos(2,:) = array.Y(:);
N_rays = size(vectors, 1);
v = gpuArray(vectors.');
x_r = gpuArray(x_r);
y_r = gpuArray(y_r);
%define outer grid around pixels
x2 = gpuArray(vox.xstart - vox.dx/2:vox.dx:vox.xend + vox.dx/2);
y2 = gpuArray(vox.ystart - vox.dy/2:vox.dy:vox.yend + vox.dy/2);
% Calculate t-values for the intersection points with all the grid lines
tx = ( x2(:) - x_r ) ./ v(1,:);
ty = ( y2(:) - y_r ) ./ v(2,:);
% Sort the t-values for each ray
t = sort( [tx; ty], 1);
M = size(t,1);
if exist('t_hit_min', 'var')
t(t>t_hit_min.') = NaN;
end
% Calculate the distance between subsequent intersection points
dist_t = diff(t);
% Define mid point between two intersection points
p = reshape([x_r; y_r], 2, 1, N_rays) + ...
reshape(t(1:end-1,:) + 1/2*dist_t, 1, M - 1, N_rays) .* reshape(v, 2, 1, N_rays);
% Define nearest grid points to mid points between two intersection points
idx_x = (round((p(1,:,:) - vox.xstart)/ vox.dx) + 1);
idx_y = (round((p(2,:,:) - vox.ystart)/ vox.dy) + 1);
idx_x(idx_x < 1 | idx_x > vox.Nx) = NaN;
idx_y(idx_y < 1 | idx_y > vox.Ny) = NaN;
%% Calculate matrix
mask = isfinite(idx_x) & isfinite(idx_y);
idx_ray = repmat(reshape(1:N_rays, 1, 1, []), 1, size(idx_x, 2), 1);
idx_lin = sub2ind([vox.Nx, vox.Ny, N_rays], idx_x(mask), idx_y(mask), idx_ray(mask));
%this operation is necessary, as in some cases, the same linear index can
%occur multiple times (which I don't fully understand)
idx_lin = unique(idx_lin);
%boolean is more efficient and also sufficient
B = false(vox.Nx*vox.Ny, N_rays, 'gpuArray');
B(idx_lin) = 1;
for i = 1:N_rays
idx_dummy = (B(:,i) == 1);
d2 = min_dist2_ray_pix_GPU(vox_pos(1:2,B(:,i)), vectors(i,:).', [x_r(i); y_r(i)]);
B(idx_dummy,i) = (d2 < (vox.dx/2)^2);
end
function rot_vec = rot_matrix_2d(vec, theta)
if length(theta)>1
theta = reshape(theta, 1, 1, length(theta));
end
R = [cos(theta) sin(theta) ; -sin(theta) cos(theta)];
rot_vec = pagemtimes(R, vec);
end
采纳的回答
Bruno Luong
2023-10-24
编辑:Bruno Luong
2023-10-25
I only add an altervative to the for-loop at the end.
On my PC the runtime if about 10 time faster.
%all of this is usually supplied to the function, but I explicitly write it
%here so that calculations are possible
x_r = 0;
y_r = -0.3;
x_r = repmat(x_r, 1, 1001);
y_r = repmat(y_r, 1, 1001);
vectors_dummy = [0;1];
% vectors = zeros(2,1001);
angles = linspace(-30,30,1001) * pi/180;
vectors = rot_matrix_2d(vectors_dummy,angles);
vectors = squeeze(vectors).'; % 1001 x 2
vox.dx = 0.002;
vox.dy = 0.002;
vox.xstart = -0.2;
vox.xend = 0.2;
vox.ystart = -0.2;
vox.yend = 0.2;
vox.Nx = 201;
vox.Ny = 201;
coordaxis.x = linspace(vox.xstart, vox.xend, vox.Nx);
coordaxis.y = linspace(vox.ystart, vox.yend, vox.Ny);
coordaxis.z = 0;
[array.X, array.Y, array.Z] = ndgrid(coordaxis.x,coordaxis.y,coordaxis.z);
vox_pos = zeros(3, length(array.X(:)));
vox_pos(1,:) = array.X(:);
vox_pos(2,:) = array.Y(:); % 3 x nvoxels
N_rays = size(vectors, 1);
v = gpuArray(vectors.'); % 2 x 1001
x_r = gpuArray(x_r); % 1 x 1001
y_r = gpuArray(y_r); % 1 x 1001
tic
%define outer grid around pixels
x2 = gpuArray(vox.xstart - vox.dx/2:vox.dx:vox.xend + vox.dx/2); % 1 x 202
y2 = gpuArray(vox.ystart - vox.dy/2:vox.dy:vox.yend + vox.dy/2); % 1 x 202
% Calculate t-values for the intersection points with all the grid lines
tx = ( x2(:) - x_r ) ./ v(1,:); % 202 x 1001
ty = ( y2(:) - y_r ) ./ v(2,:); % 202 x 1001
% Sort the t-values for each ray
t = sort( [tx; ty], 1); % 404 x 1001
M = size(t,1); % == 404
if exist('t_hit_min', 'var')
t(t>t_hit_min.') = NaN;
end
% Calculate the distance between subsequent intersection points
dist_t = diff(t); % 403 x 1001
% Define mid point between two intersection points
p = reshape([x_r; y_r], 2, 1, N_rays) + ...
reshape(t(1:end-1,:) + 1/2*dist_t, 1, M - 1, N_rays) .* reshape(v, 2, 1, N_rays); % 3 x 403 x 1001
% Define nearest grid points to mid points between two intersection points
idx_x = (round((p(1,:,:) - vox.xstart)/ vox.dx) + 1); % 1 x 403 x 1001
idx_y = (round((p(2,:,:) - vox.ystart)/ vox.dy) + 1); % 1 x 403 x 1001
idx_x(idx_x < 1 | idx_x > vox.Nx) = NaN;
idx_y(idx_y < 1 | idx_y > vox.Ny) = NaN;
%% Calculate matrix
mask = isfinite(idx_x) & isfinite(idx_y);
idx_ray = repmat(reshape(1:N_rays, 1, 1, []), 1, size(idx_x, 2), 1); % 1 x 403 x 1001
idx_lin = sub2ind([vox.Nx, vox.Ny, N_rays], idx_x(mask), idx_y(mask), idx_ray(mask)); %long column vector
%this operation is necessary, as in some cases, the same linear index can
%occur multiple times (which I don't fully understand)
idx_lin = unique(idx_lin);
%boolean is more efficient and also sufficient
B = false(vox.Nx*vox.Ny, N_rays, 'gpuArray');
B(idx_lin) = 1;
toc % Elapsed time is 0.086350 seconds.
BORG = B; % Just for debug
tic
for i = 1:N_rays
idx_dummy = (B(:,i) == 1);
d2 = min_dist2_ray_pix_GPU(vox_pos(1:2,B(:,i)), vectors(i,:).', [x_r(i); y_r(i)]);
B(idx_dummy,i) = (d2 < (vox.dx/2)^2);
end
toc % Elapsed time is 0.907424 seconds.
B1 = B; % Save result, Just for debug
B = BORG; % restore original array, Just for debug
tic
[idx_v,idx_r] = find(B);
VOX_POS = vox_pos(1:2, idx_v);
R_VEC = vectors(idx_r,:).';
R_POS = [x_r(idx_r); y_r(idx_r)];
d2 = nc_min_dist2_ray_pix_GPU(VOX_POS, R_VEC, R_POS);
B(sub2ind(size(B),idx_v,idx_r)) = (d2 < (vox.dx/2)^2);
toc % Elapsed time is 0.091884 seconds.
B2 = B; % Save result, Just for debug
% Check correctness
isequal(B1, B2)
%%
function rot_vec = rot_matrix_2d(vec, theta)
if length(theta)>1
theta = reshape(theta, 1, 1, length(theta));
end
R = [cos(theta) sin(theta) ; -sin(theta) cos(theta)];
rot_vec = pagemtimes(R, vec);
end
%%
% (Non cross) distance
function d2 = nc_min_dist2_ray_pix_GPU(VOX_POS, R_VEC, R_POS, t_hit_min)
% this function calculates the distance between a bundle of
% vectors/rays, which start at some point(s) ray_pos, and a bunch of
% pixels/voxels given by their positions vox_pos
% vectors: 2 x n
% vox_pos: 2 x n
% ray_pos: 2 x n
% if vectors which hit an object's edge are considered, the parameter t is
% given in t_hit_min, where edge = ray_pos + t * vector.
vox_pos = gpuArray(VOX_POS);
vectors = gpuArray(R_VEC);
ray_pos = gpuArray(R_POS);
vox_vec = vox_pos - ray_pos;
the_norm2 = sum(vectors.*vectors,1);
projections = sum(vectors./the_norm2 .* vox_vec,1); % divide on 2D array rather than3D array
%include safeguard: we only want rays in the positive directions
projections(projections<0) = Inf;
% this is a 2 x n_vec x n_pix array
dummy = vox_vec - projections .* vectors;
d2 = sum(dummy.*dummy,1);
%now we test whether a ray that hits an object hits the object or the pixel
%first
if nargin >= 4
t_hit_min = gpuArray(t_hit_min);
idx_dummy = projections < t_hit_min;
d2(~idx_dummy) = Inf;
end
end
%%
function d2 = min_dist2_ray_pix_GPU(vox_pos, vectors, ray_pos, t_hit_min)
% this function calculates the distance between a bundle of
% vectors/rays, which start at some point(s) ray_pos, and a bunch of
% pixels/voxels given by their positions vox_pos
% vectors: 2 x n_vec
% vox_pos: 2 x n_pix
% ray_pos: 2 x n_vec
% if vectors which hit an object's edge are considered, the parameter t is
% given in t_hit_min, where edge = ray_pos + t * vector.
n_vec = size(vectors,2);
n_pix = size(vox_pos,2);
vox_pos = gpuArray(vox_pos);
vectors = gpuArray(vectors);
ray_pos = gpuArray(ray_pos);
vox_vec = reshape(vox_pos, 2, 1, n_pix) - ray_pos;
the_norm2 = sum(vectors.*vectors,1);
projections = sum(vectors./the_norm2 .* vox_vec,1); % divide on 2D array rather than3D array
%include safeguard: we only want rays in the positive directions
projections(projections<0) = Inf;
% this is a 2 x n_vec x n_pix array
dummy = vox_vec - projections .* vectors;
d2 = sum(dummy.*dummy,1);
d2 = reshape(d2, n_vec, n_pix);
%now we test whether a ray that hits an object hits the object or the pixel
%first
if nargin >= 4
t_hit_min = gpuArray(t_hit_min);
projections = reshape(projections, n_vec, n_pix);
idx_dummy = projections < t_hit_min;
d2(~idx_dummy) = Inf;
end
end
更多回答(1 个)
Matt J
2023-10-24
编辑:Matt J
2023-10-24
I have tried to go from square pixels to circles,
I'm assuming the circles are inscribed inside the square pixels? If so, I would first use the code you already have to detect intersections with the square pixels. In order for a line to intersect a circle, it must first intersect the square that circumscribes it, which narrows down the list of candidate intersections.
Once you've narrowed the list (or even if you haven't), it is easy to test intersections of lines with a circles in a vectorized manner. Given a line whose equation is in the form A*x+B*y=C where norm([A,B])=1 and C>=0 and given a circle of radius R and centered at (P,Q), the line intersects the circle if,
abs(A*P+B*Q-C)<=R
which can easily be vectorized over multiple lines and circles.
5 个评论
Bruno Luong
2023-10-25
编辑:Bruno Luong
2023-10-25
@Dominik Rhiem Can you please post the final version of all the modifs integrated? I lost track of what ideas you use, and curious to see them.
另请参阅
类别
在 Help Center 和 File Exchange 中查找有关 Matrices and Arrays 的更多信息
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!