Answer 1: Rather than building up a huge matrix of edges and weights... iteratively build the graph directly in the loop using the edge list, looping over each dimension (rows, cols, planes). This one does 64x64x10 pretty quick (seconds), but for the large example (256x256x1000) runs MUCH longer. Would be nice to work out a faster version.
X=256;Y=256;Z=1000;
% input matrix of 0-1 values
mat = rand(X,Y,Z);
% matrix of indices for input matrix coordinates
imat = reshape([1:(X*Y*Z)],X,Y,Z);
% initialize empty graph
g = graph();
xdim = size(imat,1);
ydim = size(imat,2);
zdim = size(imat,3);
% create edge list with weights
disp('calculating start/end nodes for edges and associated weights...')
for y = 1:(ydim-1) % loop through all "horizontal" (column) connections in each z plane
% example:
% 1 - 3
% 2 - 4
for z = 1:zdim
nodes1 = imat(:,y,z);
nodes2 = imat(:,y+1,z); % grab column y+1 nodes of plane z
wts = ((1-mat(:,y,z)) + (1-mat(:,y+1,z))) ./ 2; % average of 1-value for each node
g = addedge(g,nodes1,nodes2,wts); % add to graph
end
end
for x = 1:(xdim-1) % loop through all "vertical" (row) connections within each z plane
% example:
% 1 3
% | |
% 2 4
for z = 1:zdim
nodes1 = imat(x,:,z);
nodes2 = imat(x+1,:,z); % grab row x+1 nodes of plane z
wts = ((1-mat(x,:,z)) + (1-mat(x+1,:,z))) ./ 2; % average of 1-value for each node
g = addedge(g,nodes1,nodes2,wts); % add to graph
end
end
for z = 1:(zdim-1) % loop through all "deep" connections across z planes
% example:
% 5 7
% / /
% 1 3
for x = 1:xdim
nodes1 = imat(x,:,z);
nodes2 = imat(x,:,z+1); % grab row x nodes of plane z+1
wts = ((1-mat(x,:,z)) + (1-mat(x,:,z+1))) ./ 2; % average of 1-value for each node
g = addedge(g,nodes1,nodes2,wts); % add to graph
end
end
disp('done.')
figure
spy(adjacency(g))
Answer 2: extends the linked answer's 4-connected case into a third dimension (https://stackoverflow.com/a/3283732/2371031):
disp("calculating adjacency matrix...")
X=3; Y=3; Z=2;
mat = reshape([1:(X*Y*Z)],X,Y,Z);
[x, y, z] = size(mat); % Get the matrix size
diagVec1 = repmat(repmat([ones(y-1, 1); 0], x, 1), z, 1); % Make the first diagonal vector
% (for y-planar connections)
diagVec1 = diagVec1(1:end-1); % Remove the last value
diagVec2 = repmat([ones(y*(x-1), 1); zeros(x,1)], z, 1); % Make the second diagonal vector
% (for x-planar connections)
diagVec2 = diagVec2(1:end-x); % drop the last x values (zeros)
diagVec3 = ones(x*y*(z-1), 1); % make the third diagonal vector
% (for z-planar connections)
adj = diag(diagVec1, 1)+diag(diagVec2, y)+diag(diagVec3, x*y); % Add the diagonals to a zero matrix
adj = adj+adj.'; % Add the matrix to a transposed copy of
% itself to make it symmetric
disp("done.")
figure
spy(adj)
For the large case Matlab complains with an error:
Error using diag
Requested 65536000x65536000 (32000000.0GB) array exceeds maximum array size preference. Creation of
arrays greater than this limit may take a long time and cause MATLAB to become unresponsive.
Error in blah (line X)
adj = diag(diagVec1, 1) + diag(diagVec2, y) + diag(diagVec3, x*y); % Add the diagonals to a zero matrix
Update
Answer 3:
(updated version of @beaker's answer)
I was curious whether I could get an improved shortest path result by using the 26-connected case (all 9 linearly or diagonally adjacent points in the superior plane, 9 in the inferior plane, and 8 in the coincident plane).
% 3x3x3 'cube'
X=3;Y=3;Z=3;
% input matrix of 0-1 values
M_wts = rand(X,Y,Z);
M_wts = 1-M_wts;
% matrix of indices for input matrix coordinates
M = reshape([1:(X*Y*Z)],X,Y,Z);
% initialize empty graph
g = graph();
%% Given a 3d matrix M, generate a graph representing
% the 6-connected neighbors.
% Node numbers are equal to node indices in M.
% Edge weights are given by mean of adjacent node values.
[x, y, z] = size(M);
% Initialize Source and Target node vectors and corresponding Weight
S = [];
T = [];
%% 6-connected case:
% List neighboring nodes where S(i) < T(i)
% Neighbors along dimension 1 (x/lateral)
[X1, X2, X3] = ndgrid(1:x-1, 1:y, 1:z);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 1:y, 1:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 2 (y/vertical)
[X1, X2, X3] = ndgrid(1:x, 1:y-1, 1:z);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x, 2:y, 1:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (z/planar)
[X1, X2, X3] = ndgrid(1:x, 1:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x, 1:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
%%
% Calculate the weight for each edge
W = mean(M_wts([S, T]), 2);
g = graph(S, T, W);
gplot = plot(g,'EdgeLabel',g.Edges.Weight);
layout(gplot,'force3')
view(3)
%% diagonal connections
% Neighbors within dimension 3 (diagonal xy1)
[X1, X2, X3] = ndgrid(1:x-1, 1:y-1, 1:z);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 2:y, 1:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors within dimension 3 (diagonal xy2)
[X1, X2, X3] = ndgrid(1:x-1, 2:y, 1:z);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 1:y-1, 1:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
%% across-plane diagonal connections
% Neighbors along dimension 3 (across-plane diagonal xz1)
[X1, X2, X3] = ndgrid(1:x, 1:y-1, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x, 2:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal xz2)
[X1, X2, X3] = ndgrid(1:x, 2:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x, 1:y-1, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal yz1)
[X1, X2, X3] = ndgrid(1:x-1, 1:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 1:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal yz2)
[X1, X2, X3] = ndgrid(2:x, 1:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x-1, 1:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal xyz1)
[X1, X2, X3] = ndgrid(1:x-1, 1:y-1, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 2:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal xyz2)
[X1, X2, X3] = ndgrid(2:x, 1:y-1, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x-1, 2:y, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal xyz3)
[X1, X2, X3] = ndgrid(1:x-1, 2:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(2:x, 1:y-1, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
% Neighbors along dimension 3 (across-plane diagonal xyz4)
[X1, X2, X3] = ndgrid(2:x, 2:y, 1:z-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];
[X1, X2, X3] = ndgrid(1:x-1, 1:y-1, 2:z);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];
%% 3D plot it
% Calculate the weight for each edge
W = mean(M_wts([S, T]), 2);
g = graph(S, T, W);
gplot = plot(g,'EdgeLabel',g.Edges.Weight);
layout(gplot,'force3')
view(3)