简体   繁体   中英

Construct 3D lattice graph in MATLAB

I was interested in an extension of this question/answer ( https://stackoverflow.com/a/3283732/2371031 ) to extend the 4-connected case into a third dimension.

Question 1: given an X x Y x Z dimensional matrix, how does one construct the 6-connection adjacency matrix (or edge list)?

Question 2: given the adjacency matrix (or edge list), how does one compute the edge weights as some function (eg, mean) of the values of connected nodes?

Question 3: How does one solve Question 1 and 2 for very large matrices without running out of memory? Sparse matrices or edge lists seem like probable solutions, but how to implement?

% simple case
X = 2; Y = 2; Z = 2;
mat = reshape([1:(X*Y*Z)],X,Y,Z);
>> mat

mat(:,:,1) =

     1     3
     2     4


mat(:,:,2) =

     5     7
     6     8


% large case
X = 256; Y = 256; Z = 1000;
mat = reshape([1:(X*Y*Z)],X,Y,Z);

This approach uses ndgrid to find each node's neighbor in one direction. For example, in dimension 2 we find the (valid) neighbor to the right of each node.

在此处输入图像描述

Here, the blue nodes are the source of the edge, and the red nodes to their right are the target nodes. So the grids for the source and target nodes are offset by 1 from each other.

% 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.
[m, n, p] = size(M);

% Initialize Source and Target node vectors and corresponding Weight
S = T = W = [];

% List neighboring nodes where S(i) < T(i)
% Neighbors along dimension 1
[X1, X2, X3] = ndgrid(1:m-1, 1:n, 1:p);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];

[X1, X2, X3] = ndgrid(2:m, 1:n, 1:p);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];

% Neighbors along dimension 2
[X1, X2, X3] = ndgrid(1:m, 1:n-1, 1:p);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];

[X1, X2, X3] = ndgrid(1:m, 2:n, 1:p);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];

% Neighbors along dimension 3
[X1, X2, X3] = ndgrid(1:m, 1:n, 1:p-1);
S = [S; sub2ind(size(M), X1(:), X2(:), X3(:))];

[X1, X2, X3] = ndgrid(1:m, 1:n, 2:p);
T = [T; sub2ind(size(M), X1(:), X2(:), X3(:))];

% Calculate the weight for each edge 
W = mean(M([S, T]), 2);

%% Adjacency matrix A is for an undirected graph
%%    therefore only edges where S(i) < T(i) are listed.
%%    If the backward edges are also necessary:
% tempS = S;
% S = [S;T];
% T = [T;tempS];
% W = [W;W];

% [S T W] gives the graph's edge list.
% If the sparse adjacency matrix is required:
A = sparse(S, T, W);

Running this in Octave with your large testcase:

X = 256; Y = 256; Z = 1000;
M = reshape([1:(X*Y*Z)],X,Y,Z);

takes about 45 seconds for the (undirected) edge list, and an additional 20 seconds or so to generate the sparse adjacency matrix. Since I use Octave, I can't test which one will be faster in generating the graph.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM