I'm trying to link the node id of every face in a tetrahedron with it's corresponding tetra id.
tetras = [1 2 3 4 % Tetra 1
5 6 7 8] % Tetra 2
For tetra 1, there are four faces:
faces = [1 2 3; 1 2 4; 1 3 4; 2 3 4] % Notice these are sorted
Then I'd like to store these in a data structure:
tet_for_face = cell(8,8,8) % 8 allows for the maximum node id
tet_for_face{1,2,3} = 1;
tet_for_face{1,2,4} = 1;
tet_for_face{1,3,4} = 1;
tet_for_face{2,3,4} = 1;
This means that I can find the tetra ID of any particular face in O(1):
tet_for_face{2,3,3}
ans = []
tet_for_face{2,3,4}
ans = 1
The problem with this approach is that it requires contiguous memory. As my mesh gets larger, I run out of memory:
cell(1000, 1000, 1000)
??? Error using ==> cell
Out of memory. Type HELP MEMORY for your options.
I've also played around with using nested cells:
tet = cell(num_nodes, 1);
tet2 = cellfun(#(x) cell(num_nodes, 1), tet, 'UniformOutput', 0);
tet3 = cellfun(#(x) cellfun(#(y) cell(num_nodes, 1), x, 'UniformOutput', 0), tet2, 'UniformOutput', 0);
tet3{2}{3}{4} = 1;
...
Although this works for small meshes, and doesn't require contiguous memory (AFAIK), it has a nasty habit of crashing MATLAB with N=1000.
Any ideas?
After a bit of playing with sparse arrays (which can only be 1D or 2D, not 3D), and not getting anywhere, I decided to go with containers.Map (HashMap).
I used string keys, and the fastest way I found of producing them I found was using sprintf (rather than int2str or mat2str)
Sample code:
tet = containers.Map;
for tetra_id in tetras
for face in faces
face_key = sprintf('%d ', face);
tet(face_key) = tetra_id;
This gives me a map like so:
tet('1 2 3') = 1
You can use sparse matrices to deal with many problems arising with meshes. It depends on what you want to do with this data structure in practice, but here is one example:
% tetras and faces are transposed - column-wise storage
tetras = [1 2 3 4; 5 6 7 8]';
faces = [1 2 3; 1 2 4; 1 3 4; 2 3 4]';
ntetras = size(tetras, 2);
nfaces = size(faces, 2);
nfacenodes = size(faces, 1);
% construct face definitions for all tetras
tetras_faces = reshape(tetras(faces, :), nfacenodes, ntetras*nfaces);
% assign the faces to tetras keeping the face id within the tetra, if you need it
enum_faces = repmat(1:ntetras*nfaces, nfacenodes, 1);
% create a sparse matrix connecting tetra faces to tetras.
% Every column contains 3 non-zeros - 1 for every node in a face
% The number of matrix columns is ntetras*nfaces - 4 columns for every element.
A = sparse(tetras_faces, enum_faces, 1);
Now to extract the information you need you can multiply A by a vector holding the information about the face you are looking for:
v = sparse(ntetras*nfaces, 1);
v([1 2 3]) = 1;
tetra_id = ceil(find(A*v==nfacenodes)/nfaces)
Note that this is just an example. You can extract much more useful information this way, and you could perform more sophisticated searches using matrix-matrix multiplication instead of matrix-vector multiplication.
Related
I would like your help to make more efficient (maybe, by vectorising) the Matlab code below. The code below does essentially the following: take a row vector A; consider the maximum elements of such a row vector and let, for example, be i and j their positions; construct two columns vectors, the first with all zeros but a 1 positioned at i, the second with all zeros but a 1 positioned at j.
This is my attempt with loops, but it looks more complicated than needed.
clear
rng default
A=[3 2 3];
max_idx=ismember(A,max(A));
vertex=cell(size(A,2),1);
for j=1:size(max_idx,2)
if max_idx(j)>0
position=find(max_idx(j));
vertex_temp=zeros(size(A,2),1);
vertex_temp(position)=1;
vertex{j}=vertex_temp;
else
vertex{j}=[];
end
end
vertex=vertex(~cellfun('isempty',vertex));
Still using a for loop, but more readable:
A = [3 2 3];
% find max indices
max_idx = find(A == max(A));
vertex = cell(numel(max_idx),1);
for k = 1:numel(max_idx)
vertex{k} = zeros(size(A,2),1); % init zeros
vertex{k}(max_idx(k)) = 1; % set value in vector to 1
end
If you really wanted to avoid a for loop, you could probably also use something like this:
A=[3 2 3];
max_idx = find(A==max(A));
outMat = zeros(numel(A), numel(max_idx));
outMat((0:(numel(max_idx)-1)) * numel(A) + max_idx) = 1;
then optionally, if you want them in separate cells rather than columns of a matrix:
outCell = mat2cell(outMat, numel(A), ones(1,numel(max_idx)))';
However, I think this might be less simple and readable than the existing answers.
Is there a specific reason you want a cell array rather than a matrix?
If you can have it all in one vector:
A = [3 2 3]
B_rowvec = A == max(A)
B_colvec = B_rowvec'
If you need them separated into separate vectors:
A = [3 2 3]
Nmaxval = sum(A==max(A))
outmat = zeros(length(A),Nmaxval)
for i = 1:Nmaxval
outmat(find(A==max(A),i),i)=1;
end
outvec1 = outmat(:,1)
outvec2 = outmat(:,2)
Basically, the second input for find will specify which satisfactory instance of the first input you want.
so therefore
example = [ 1 2 3 1 2 3 1 2 3 ]
first = find(example == 1, 1) % returns 1
second = find(example == 1, 2) % returns 4
third = find(example == 1, 3) % returns 7
I have a 3d matrix H(i,j,k) with dimensions (i=1:m,j=1:n,k=1:o). I will use a simple case with m=n=o = 2:
H(:,:,1) =[1 2; 3 4];
H(:,:,2) =[5 6; 7 8];
I want to filter this matrix and project it to an (m,n) matrix by selecting for each j in 1:n a different k in 1:0.
For instance, I would like to retrieve (j,k) = {(1,2), (2,1)}, resulting in matrix G:
G = [5 2; 7 4];
This can be achieved with a for loop:
filter = [2 1]; % meaning filter (j,k) = {(1,2), (2,1)}
for i = 1:length(filter)
G(:,i) = squeeze(H(:,i,filter(i)));
end
But I'm wondering if it is possible to avoid the for loop via some smart indexing.
You can create all the linear indices to get such an output with the expansion needed for the first dimension with bsxfun. The implementation would look like this -
szH = size(H)
offset = (filter-1)*szH(1)*szH(2) + (0:numel(filter)-1)*szH(1)
out = H(bsxfun(#plus,[1:szH(1)].',offset))
How does it work
(filter-1)*szH(1)*szH(2) and (0:numel(filter)-1)*szH(1) gets the linear indices considering only the third and second dimension elements respectively. Adding these two gives us the offset linear indices.
Add the first dimenion linear indices 1:szH(1) to offset array in elementwise fashion with bsxfun to give us the actual linear indices, which when indexed into H would be the output.
Sample run -
H(:,:,1) =
1 2
3 4
H(:,:,2) =
5 6
7 8
filter =
2 1
out =
5 2
7 4
I have a matrix with constant consecutive values randomly distributed throughout the matrix. I want the indices of the consecutive values, and further, I want a matrix of the same size as the original matrix, where the number of consecutive values are stored in the indices of the consecutive values. For Example
original_matrix = [1 1 1;2 2 3; 1 2 3];
output_matrix = [3 3 3;2 2 0;0 0 0];
I have struggled mightily to find a solution to this problem. It has relevance for meteorological data quality control. For example, if I have a matrix of temperature data from a number of sensors, and I want to know what days had constant consecutive values, and how many days were constant, so I can then flag the data as possibly faulty.
temperature matrix is number of days x number of stations and I want an output matrix that is also number of days x number of stations, where the consecutive values are flagged as described above.
If you have a solution to that, please provide! Thank you.
For this kind of problems, I made my own utility function runlength:
function RL = runlength(M)
% calculates length of runs of consecutive equal items along columns of M
% work along columns, so that you can use linear indexing
% find locations where items change along column
jumps = diff(M) ~= 0;
% add implicit jumps at start and end
ncol = size(jumps, 2);
jumps = [true(1, ncol); jumps; true(1, ncol)];
% find linear indices of starts and stops of runs
ijump = find(jumps);
nrow = size(jumps, 1);
istart = ijump(rem(ijump, nrow) ~= 0); % remove fake starts in last row
istop = ijump(rem(ijump, nrow) ~= 1); % remove fake stops in first row
rl = istop - istart;
assert(sum(rl) == numel(M))
% make matrix of 'derivative' of runlength
% don't need last row, but needs same size as jumps for indices to be valid
dRL = zeros(size(jumps));
dRL(istart) = rl;
dRL(istop) = dRL(istop) - rl;
% remove last row and 'integrate' to get runlength
RL = cumsum(dRL(1:end-1,:));
It only works along columns since it uses linear indexing. Since you want do something similar along rows, you need to transpose back and forth, so you could use it for your case like so:
>> original = [1 1 1;2 2 3; 1 2 3];
>> original = original.'; % transpose, since runlength works along columns
>> output = runlength(original);
>> output = output.'; % transpose back
>> output(output == 1) = 0; % see hitzg's comment
>> output
output =
3 3 3
2 2 0
0 0 0
I have a 3 dimensional (or higher) array that I want to aggregate by another vector. The specific application is to take daily observations of spatial data and average them to get monthly values. So, I have an array with dimensions <Lat, Lon, Day> and I want to create an array with dimensions <Lat, Lon, Month>.
Here is a mock example of what I want. Currently, I can get the correct output using a loop, but in practice, my data is very large, so I was hoping for a more efficient solution than the second loop:
% Make the mock data
A = [1 2 3; 4 5 6];
X = zeros(2, 3, 9);
for j = 1:9
X(:, :, j) = A;
A = A + 1;
end
% Aggregate the X values in groups of 3 -- This is the part I would like help on
T = [1 1 1 2 2 2 3 3 3];
X_agg = zeros(2, 3, 3);
for i = 1:3
X_agg(:,:,i) = mean(X(:,:,T==i),3);
end
In 2 dimensions, I would use accumarray, but that does not accept higher dimension inputs.
Before getting to your answer let's first rewrite your code in a more general way:
ag = 3; % or agg_size
X_agg = zeros(size(X)./[1 1 ag]);
for i = 1:ag
X_agg(:,:,i) = mean(X(:,:,(i-1)*ag+1:i*ag), 3);
end
To avoid using the for loop one idea is to reshape your X matrix to something that you can use the mean function directly on.
splited_X = reshape(X(:), [size(X_agg), ag]);
So now splited_X(:,:,:,i) is the i-th part
that contains all the matrices that should be aggregated which is X(:,:,(i-1)*ag+1:i*ag)) (like above)
Now you just need to find the mean in the 3rd dimension of splited_X:
temp = mean(splited_X, 3);
However this results in a 4D matrix (where its 3rd dimension size is 1). You can again turn it into 3D matrix using reshape function:
X_agg = reshape(temp, size(X_agg))
I have not tried it to see how much more efficient it is, but it should do better for large matrices since it doesn't use for loops.
I'm a total beginner to matlab and I'm currently writing a script for extracting data from a thermographic video.
Firstly the video is cut in separate frames. The first frame is opened as a sample picture to define the coordinates of sampling points. The goal is then to select the rgb values of those defined coordinates from a set of frames and save them into a matrix.
Now I have a problem separating the matrix to n smaller matrices.
e.g I'm defining the number of points to be selected to n=2 , with a picture count of 31. Now it returns a matrix stating the rgb codes for 31 pictures, each at 2 points, in a 62x3 double matrix...
Now I want to extract the 1st, 3rd, 5th....etc... row to a new matrix...this should be done in a loop, according to the number of n points...e.g 5 points on each picture equals 5 matrices, containing values of 31 pictures....
this is an extract of my code to analyse the pictures, it returns the matrix 'values'
files = dir('*.jpg');
num_files = numel(files);
images = cell(1, num_files);
cal=imread(files(1).name);
n = input('number of selection points?: ');
imshow(cal);
[x,y] = ginput(n);
eval(get(1,'CloseRequestFcn'))
%# x = input('x-value?: '); manual x selection
%# y = input('y-value?: '); manual y selection
for k = 1:num_files
images{k} = imread(files(k).name);
end
matrix=cell2mat(images);
count=(0:size(matrix,1):size(matrix,1)*num_files);
for k = 1:num_files
a(k)= mat2cell(impixel(matrix,x+count(k),y));
end
values = cat(1,a{:})
Easy fix
Do you mean that if you have:
n = 2;
k = 2; % for example
matrix = [1 2 3;
4 5 6;
7 8 9;
8 7 6];
you want it to become
b{1} = [1 2 3;
7 8 9];
b{2} = [4 5 6;
8 7 6];
This can be easily done with:
for ii = 1:n
b{ii} = matrix(1:n:end,:);
end
Better fix
Of course it's also possible to just reshape your data matrix and use that instead of the smaller matrices: (continuing with my sample data ^^)
>> d=reshape(matrix',3,2,[]);
>> squeeze(d(:,1,:))
ans =
1 7
2 8
3 9
>> squeeze(d(:,2,:))
ans =
4 8
5 7
6 6
Good practice
Or, my preferred choice: save the data immediately in an easy to access way. Here I think it will be an matrix of size: [num_files x num_points x 3]
If you want all the first points:
rgb_data(:,1,:)
only the red channel of those points:
rgb_data(:,1,1)
and so on.
I think this is possible with this:
rgb_data = zeros(num_files, num_points, 3);
for kk = 1:num_files
rgb_data(kk,:,:) = impixel(images{kk},x+count(k),y);
end
But I don't understand the complete meaning of your code (eg: why matrix=cell2mat(images) ??? and then of course:
count=(0:size(matrix,1):size(matrix,1)*num_files);
is just count=0:num_files;
so I'm not sure what would come out of impixel(matrix,x+count(k),y) and I used images{k} :)