Efficiently calculating the pixels crossed by a ray bundle
Show older comments
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
6 Comments
Bruno Luong
on 24 Oct 2023
Edited: Bruno Luong
on 24 Oct 2023
Your code won't run:
Unrecognized field name "z".
Error in xxx (line 22)
[array.X, array.Y, array.Z] = ndgrid(coordaxis.x,coordaxis.y,coordaxis.z);
This is clearly an implementation of Siddon's algorithm. I hope this is not being done for the purposes of tomographic forward projection. There are already freely available GPU-accelerated libraries for that, e.g.,
although not for the case of circular pixels.
Bruno Luong
on 24 Oct 2023
Edited: Bruno Luong
on 24 Oct 2023
I did not read your new code to see what changes from the old thread, but there is no assumption in the ols thread that the pixels ("voxel"?) are square. They can have random positions and the distance to rays are computed using euclidian distance. So when you threshold it it detect all the rays that cross the circle disk that is centered about the position.
Not sure what changes here.
Matt J
on 24 Oct 2023
Since tx and ty are already sorted, using sort() in this line,
t = sort( [tx; ty], 1);
Dominik Rhiem
on 24 Oct 2023
Dominik Rhiem
on 24 Oct 2023
Accepted Answer
More Answers (1)
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 Comments
Dominik Rhiem
on 24 Oct 2023
Dominik Rhiem
on 24 Oct 2023
I don't understand everything in your code, but you should be able to find, using t, the actual (x,y) locations where the lines intersect the squares.
x = x_r + t(:).*vectors(1,:);
y = y_r + t(:).*vectors(2,:);
Assuming you have normalized your coordinates so that the grid lines area at integer coordinates,
i=floor(x);
j=floor(y);
should give the discrete (i,j) voxel coordinates of the line-square intersections.
Dominik Rhiem
on 25 Oct 2023
Bruno Luong
on 25 Oct 2023
Edited: Bruno Luong
on 25 Oct 2023
@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.
Categories
Find more on Matrix Indexing in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!