find and index equal values in multidimensional matrix - matlab

I am producing a three dimensional matrix with two rows(e.g. a 2x1000x10 matrix). These contain x- and y-coordinates for dot motion paths, where the first line contains the x-coordinates and the second line the y-coordinates. The number of columns (here: 1000) depends on the length of the motion paths. The number of levels in the third dimension depends on the number of dots (here: 10).
I want to know if some dots are going to overlap, i.e. if any x- and y-coordinates across the third dimension at the same time point t, i.e. any combination of (:, t, :), are identical. So the comparison will be within the matrix.
i = [1:size(matrix, 3)];
j = [1:size(matrix, 3)];
t = [1:size(matrix, 2)];
crash = any(coordinates(:, t, i)==coordinates(:, t, j))
would be trivial because i and j can be the same and thus say that the point is equal to itself.
Do you know how I can detect equal value combinations across matrix dimensions? How do I index them if they exist?

I think this does what you want. To find out if there's a crash:
t = bsxfun(#eq, coordinates, permute(coordinates, [1 2 4 3]));
crash = any(sum(any(all(t,1),2),4)>1,3);
We first use bsxfun to compute a 4D array (t) that tests for equality in the same coordinate (1st dim) and the same position along path (2nd dim), where the 3rd and 4th dims run over all pairs of dots. A crash occurs if both coordinates (all(,...,1)) are equal for any column (any(...,2)) in more than one "other" dot (sum(...,4)>1) for any "reference" dot (any(...,3)). We need to specify "more than one" because any dot is at least equal to itself (for a given position and coordinate).
Example:
>> coordinates = randi(9,2,5,4)
coordinates(:,:,1) =
7 4 3 3 8
7 1 4 2 4
coordinates(:,:,2) =
8 7 8 4 8
4 4 7 2 9
coordinates(:,:,3) =
3 4 7 8 5
7 8 2 9 8
coordinates(:,:,4) =
6 2 7 8 5
2 4 8 3 1
>> t = bsxfun(#eq, coordinates, permute(coordinates, [1 2 4 3]));
>> crash = any(sum(any(all(t,1),2),4)>1,3)
crash =
0
>> coordinates(:,2,4) = coordinates(:,2,1) %// 4th dot equal to 1st dot in column 2
coordinates(:,:,1) =
7 4 3 3 8
7 1 4 2 4
coordinates(:,:,2) =
8 7 8 4 8
4 4 7 2 9
coordinates(:,:,3) =
3 4 7 8 5
7 8 2 9 8
coordinates(:,:,4) =
6 4 7 8 5
2 1 8 3 1
>> t = bsxfun(#eq, coordinates, permute(coordinates, [1 2 4 3]));
>> crash = any(sum(any(all(t,1),2),4)>1,3)
crash =
1
To find the coordinates of the crash:
t = bsxfun(#eq, coordinates, permute(coordinates, [1 2 4 3]));
[m,n,p] = size(coordinates);
ii = find(squeeze(all(bsxfun(#times, t, ...
bsxfun(#ne, reshape(1:p, 1,1,[]), reshape(1:p, 1,1,1,[]) )),1)));
[col, dot_ref, dot_oth] = ind2sub([n p p], ii);
In the latter example, this gives
col =
2
2
dot_ref =
4
1
dot_oth =
1
4
which tells you that the dot 4 equals dot 1 in column 2; and then of course that dot 1 equals dot 4 in column 2.
This computes the same t as above. It then multiplies by a logical mask along the third and fourth dimensions (bsxfun(#ne, reshape(1:p, 1,1,[]), reshape(1:p, 1,1,1,[]) )) to avoid detecting each dot as similar to itself. Finally, it requires that all coordinates (all(...,1)) be the same in order to declare two dots as crashing, and applies find to locate those dots. The result (ii) is a linear index into a 3D array of dimensions path length × number of dots × number of dots, which is translated by ind2sub into position along path and identities of crashing dots.

A=[1 2 ;3 4];B=[5 6;3 9];
A==B
ans =
0 0
1 0
This works in 2D, but also in 3D. You can find the location of the equal numbers by using find:
C=permute(A,[3 2 1]);
D=permute(B,[3 2 1]);
find(C==D)
ans =
3
where 3 is a linear index to the location of the equal number.
for ii = 1:size(A,3)-1
for jj = ii+1:size(A,3)
[r{ii,jj},c{ii,jj},v{ii,jj}] = find(squeeze(A(:,:,ii))==squeeze(A(:,:,jj)));
end
end
This will loop over your matrices in the third dimension, i.e. your physical dots. It will squeeze out the 2D matrix per dot and check that against every other dot's 2D matrix. If it finds any, it will store the row, column and value at the intersection in the cells r, c and v, where the indices correspond to the dot numbers, i.e. r{2,4} will give the row numbers of intersections between dot 2 and 4.

Related

Merging sorted pairs

I have two (or more but if solved for two, it's solved for any number) 2-by-N matrices which represent points with an x (the first row) and y (the second row) coordinates. The points are always sorted in the increasing x coordinate. What I want to do is I want to merge these two matrices into one 3-by-N matrix so that if two points (one from each matrix) have the same x coordinate, they would form one column in the new matrix, the first row being the x coordinate and the second and third row being the two y coordinates. However, if there is a point in one matrix that has x coordinate different than all other points in the second matrix, I still want to have full 3-element column that is placed such that the x coordinates are still sorted and the missing value from the other matrix is replaced by the nearest value with lower x coordinate (or NaN if there is none).
Better to explain by example.
First matrix:
1 3 5 7 % x coordinate
1 2 3 4 % y coordinate
Second matrix:
2 3 4 7 8 % x coordinate
5 6 7 8 9 % y coordinate
Desired result:
1 2 3 4 5 7 8 % x coordinate
1 1 2 2 3 4 4 % y coordinate from first matrix
NaN 5 6 7 7 8 9 % y coordinate from second matrix
My question is, how can I do it effectively in matlab/octave and numpy? (Effectively because I can always do it "manually" with loops but that doesn't seem right.)
You can do it with interp1 and the keyword 'previous' for strategy (you can also choose 'nearest' if you do not care if it is larger or smaller) and 'extrap' for allowing extrapolation.
Define the matrices
a=[...
1 3 5 7;...
1 2 3 4];
b=[...
2 3 4 7 8;...
5 6 7 8 9];
Then find the interpolation points
x = unique([a(1,:),b(1,:)]);
And interpolate
[x ; interp1(a(1,:),a(2,:),x,'previous','extrap') ; interp1(b(1,:),b(2,:),x,'previous','extrap') ]
Timeit results:
I tested the algorithms on
n = 1e6;
a = cumsum(randi(3,2,n),2);
b = cumsum(randi(2,2,n),2);
and got:
Wolfie: 1.7473 s
Flawr: 0.4927 s
Mine: 0.2757 s
This verions uses set operations:
a=[...
1 3 5 7;...
1 2 3 4];
b=[...
2 3 4 7 8;...
5 6 7 8 9];
% compute union of x coordinates
c = union(a(1,:),b(1,:));
% find indices of x of a and b coordinates in c
[~,~,ia] = intersect(a(1,:),c);
[~,~,ib] = intersect(b(1,:),c);
% create output matrix
d = NaN(3,numel(c));
d(1,:) = c;
d(2,ia) = a(2,:);
d(3,ib) = b(2,:);
% fill NaNs
m = isnan(d);
m(:,1) = false;
i = find(m(:,[2:end,1])); %if you have multiple consecutive nans you have to repeat these two steps
d(m) = d(i);
disp(d);
Try it online!
Your example:
a = [1 3 5 7; 1 2 3 4];
b = [2 3 4 7 8; 5 6 7 8 9];
% Get the combined (unique, sorted) `x` coordinates
output(1,:) = unique([a(1,:), b(1,:)]);
% Initialise y values to NaN
output(2:3, :) = NaN;
% Add x coords from `a` and `b`
output(2, ismember(output(1,:),a(1,:))) = a(2,:);
output(3, ismember(output(1,:),b(1,:))) = b(2,:);
% Replace NaNs in columns `2:end` with the previous value.
% A simple loop has the advantage of capturing multiple consecutive NaNs.
for ii = 2:size(output,2)
colNaN = isnan(output(:, ii));
output(colNaN, ii) = output(colNaN, ii-1);
end
If you have more than 2 matrices (as suggested in your question) then I'd advise
Store them in a cell array, and loop over them to do the calls to ismember, instead of having one code line per matrix hardcoded.
The NaN replacement loop is already vectorised for any number of rows.
This is the generic solution for any number of matrices, demonstrated with a and b:
mats = {a, b};
cmats = horzcat(mats);
output(1, :) = unique(cmats(1,:));
output(2:numel(mats)+1, :) = NaN;
for ii = 1:size(mats)
output(ii+1, ismember(output(1,:), mats{ii}(1,:))) = mats{ii}(2,:);
end
for ii = 2:size(output,2)
colNaN = isnan(output(:,ii));
output(colNaN, ii) = output(colNaN, ii-1);
end

How to vertically stack blocks of an nD array?

I have a 5D array A and want to vertically stack the blocks
A(x,y,z,:,:) for all x, y and z. I have done the following:
A = rand(128, 128, 64, 20, 3);
a = 1;
for z = 1:Z
for y = 1:Y
for x = 1:X
res(a:a+T-1,:) = squeeze(A(x,y,z,:,:));
a = a + T;
end
end
end
While correct, it appears that this computation is quite slow for my purpose.
Can anyone figure out how to achieve the same goal more efficiently?
You can do this via reshape and permute. First, understand how reshape works.
c = randi(9,[2 2 3])
c(:,:,1) =
5 8
2 6
c(:,:,2) =
4 4
5 1
c(:,:,3) =
3 2
2 3
Let us reshape c into a 2 x 6 matrix.
ans =
5 8 4 4 3 2
2 6 5 1 2 3
Now a 6 x 2 matrix.
ans =
5 4
2 1
8 3
6 2
4 2
5 3
See that while creating the reshaped matrix, MATLAB starts traversing the first dimension of c and puts it along the first dimension (i.e. along rows in the first column) in the reshaped matrix. Specifically,
c(1,1,1) = 5
c(2,1,1) = 2
c(1,2,1) = 8
c(2,2,1) = 6
c(1,1,2) = 4
c(2,1,2) = 5 % stop since you would have filled 6 rows
which forms the first column of the reshaped matrix.
Coming back to your example,
the first column of the reshaped matrix will be along the rows of your 20x3 submatrix i.e. the fourth dimension. The first row will be along your columns of your 20x3 submatrix i.e. along the fifth dimension. However, when you traverse from (1,1) to (1,2) in your reshaped matrix, you will have to accomodate all the elements from your first three dimensions (i.e. 128, 128, 64, why?, because you first traverse your x dimension, then y and then finally z dimension). So your answer is, you want to move along dimensions: 4,1,2,3,5 in that order.
Answer:
sz_A = size(A);
p = sz_A(4)*prod(sz_A(1:3))
reshaped_A = reshape(permute(A,[4, 1, 2, 3, 5]), [p size(A,5)])

dot product of matrix columns

I have a 4x8 matrix which I want to select two different columns of it then derive dot product of them and then divide to norm values of that selected columns, and then repeat this for all possible two different columns and save the vectors in a new matrix. can anyone provide me a matlab code for this purpose?
The code which I supposed to give me the output is:
A=[1 2 3 4 5 6 7 8;1 2 3 4 5 6 7 8;1 2 3 4 5 6 7 8;1 2 3 4 5 6 7 8;];
for i=1:8
for j=1:7
B(:,i)=(A(:,i).*A(:,j+1))/(norm(A(:,i))*norm(A(:,j+1)));
end
end
I would approach this a different way. First, create two matrices where the corresponding columns of each one correspond to a unique pair of columns from your matrix.
Easiest way I can think of is to create all possible combinations of pairs, and eliminate the duplicates. You can do this by creating a meshgrid of values where the outputs X and Y give you a pairing of each pair of vectors and only selecting out the lower triangular part of each matrix offsetting by 1 to get the main diagonal just one below the diagonal.... so do this:
num_columns = size(A,2);
[X,Y] = meshgrid(1:num_columns);
X = X(tril(ones(num_columns),-1)==1); Y = Y(tril(ones(num_columns),-1)==1);
In your case, here's what the grid of coordinates looks like:
>> [X,Y] = meshgrid(1:num_columns)
X =
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
Y =
1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5
6 6 6 6 6 6 6 6
7 7 7 7 7 7 7 7
8 8 8 8 8 8 8 8
As you can see, if we select out the lower triangular part of each matrix excluding the diagonal, you will get all combinations of pairs that are unique, which is what I did in the last parts of the code. Selecting the lower-part is important because by doing this, MATLAB selects out values column-wise, and traversing the columns of the lower-triangular part of each matrix gives you the exact orderings of each pair of columns in the right order (i.e. 1-2, 1-3, ..., 1-7, 2-3, 2-4, ..., etc.)
The point of all of this is that can then use X and Y to create two new matrices that contain the columns located at each pair of X and Y, then use dot to apply the dot product to each matrix column-wise. We also need to divide the dot product by the multiplication of the magnitudes of the two vectors respectively. You can't use MATLAB's built-in function norm for this because it will compute the matrix norm for matrices. As such, you have to sum over all of the rows for each column respectively for each of the two matrices then multiply both of the results element-wise then take the square root - this is the last step of the process:
matrix1 = A(:,X);
matrix2 = A(:,Y);
B = dot(matrix1, matrix2, 1) ./ sqrt(sum(matrix1.^2,1).*sum(matrix2.^2,1));
I get this for B:
>> B
B =
Columns 1 through 11
1 1 1 1 1 1 1 1 1 1 1
Columns 12 through 22
1 1 1 1 1 1 1 1 1 1 1
Columns 23 through 28
1 1 1 1 1 1
Well.. this isn't useful at all. Why is that? What you are actually doing is finding the cosine angle between two vectors, and since each vector is a scalar multiple of another, the angle that separates each vector is in fact 0, and the cosine of 0 is 1.
You should try this with different values of A so you can see for yourself that it works.
To make this code compatible for copying and pasting, here it is:
%// Define A here:
A = repmat(1:8, 4, 1);
%// Code to produce dot products here
num_columns = size(A,2);
[X,Y] = meshgrid(1:num_columns);
X = X(tril(ones(num_columns),-1)==1); Y = Y(tril(ones(num_columns),-1)==1);
matrix1 = A(:,X);
matrix2 = A(:,Y);
B = dot(matrix1, matrix2, 1) ./ sqrt(sum(matrix1.^2,1).*sum(matrix2.^2,1));
Minor Note
If you have a lot of columns in A, this may be very memory intensive. You can get your original code to work with loops, but you need to change what you're doing at each column.
You can do something like this:
num_columns = nchoosek(size(A,2),2);
B = zeros(1, num_columns);
counter = 1;
for ii = 1 : size(A,2)
for jj = ii+1 : size(A,2)
B(counter) = dot(A(:,ii), A(:,jj), 1) / (norm(A(:,ii))*norm(A(:,jj)));
counter = counter + 1;
end
end
Note that we can use norm because we're specifying vectors for each of the inputs into the function. We first preallocate a matrix B that will contain the dot products of all possible combinations. Then, we go through each pair of combinations - take note that the inner for loop starts from the outer most for loop index added with 1 so you don't look at any duplicates. We take the dot product of the corresponding columns referenced by positions ii and jj and store the results in B. I need an external counter so we can properly access the right slot to place our result in for each pair of columns.

Group matrix values into separate matrices based on values of another matrix

I am reading in images with imread which results in 768x1024x3 matrix with R,G,B values of each pixel.
I have a function that takes in an image and returns matrix of segment labels for each pixel so this matrix is 768x1024. the labels are just numbers 1,2,3,4 depending on how many different segments the function finds.
Now I want to calculate the average Red, Green and Blue value in each segment of the image. So I want to use the indices from the segment label matrix to find group all R,G,B values into separate arrays and then be able to calculate the mean.
Is there any smart way to do this? use the indices of each 1 value in the segment matrix to get the values from the imread matrix and group the segments into different arrays? I though of using for loops and brute force through this but is there a better way to do this?
Here's a code that you will get you everything without looping.
Code
%// img is your input RGB image (NxMx3)
%// L is your label matrix (NxM)
t1 = bsxfun(#eq,L,permute(unique(L),[3 2 1]));
t2 = bsxfun(#times,permute(img,[ 1 2 4 3]),t1);
t2(t2==0)=nan;
out = squeeze(nanmean(nanmean(t2)))
%// out is the desired output matrix that is (NLx3),
%// where NL is the number of labels. Thus, the mean of labels is
%// along the rows and the corresponding values for R, G and B are in the three
%// columns of it.
Explanation
Let's test out with some random values for img -
img = randi(9,3,4,3)
Giving us -
img(:,:,1) =
9 7 5 3
7 7 2 4
1 6 7 9
img(:,:,2) =
8 6 6 4
4 9 3 9
3 9 8 1
img(:,:,3) =
5 4 4 5
7 2 5 3
2 3 1 3
Some assumed values for L that goes from 1 to 8
L = [1 3 3 4;
4 5 8 8;
5 6 7 2]
The code output is -
out =
9 8 5
9 1 3
6 6 4
5 4 6
4 6 2
6 9 3
7 8 1
3 6 4
Let's see how to make sense of the output.
Looking at the input, let's choose the label 8, which is at locations (2nd row,3rd col) and (2nd row,4th col). The corresponding R values at these locations in img are [2 4], and thus the R mean/average value must be 3. Similarly for G it must be from [3 9], that is 6 and again for B would be from [5 3], that is 4.
Let's look at the 8th row of out that represents the label-8, we have [3 6 4], which are the mean values as calculated earlier. Similarly other mean values could be interpreted from out.
Edited to handle all channels at once.
Let img be your RGB image and labels the labels array.
You can mask the RGB image with the labels like this:
% create a 3-channels mask:
labelsRGB=repmat(labels, 1, 1, 3);
Segment1=img.*(labelsRGB==1);
The average values in the segment labeled as 1 is then:
avg=mean(mean(Segment1, 1), 2);
Get the average for re in avg(1), the average for green in avg(2), etc.
Idem for the other segments.
Here goes a general alternative.
In this case you do not need to loop over the different segments to get the average of each.
%simulated image and label
img=rand(10,12,3);
labeled=[ones(10,3),ones(10,3)*2,ones(10,3)*3,ones(10,3)*4];
% actual code for the mean
red_mean = regionprops(labeled, img(:,:,1), 'MeanIntensity')

reconstruct time series from given matrix

suppose that we are creating following matrix from given signal
function [ x ]=create_matrix1(b,l)
n = length(b);
m = n-l+1;
x = zeros(m,l);
for i=1:m
x(i,:)=b(i:i+l-1);
end;
end
with some window length,for example
X=[2;1;3;4;5;7]
X =
2
1
3
4
5
7
>> B=create_matrix1(X,3)
B =
2 1 3
1 3 4
3 4 5
4 5 7
if we have given matrix and windows length ,how can i reconstruct original signal?let say i know that windows length is 3,thanks in advance,i think i should sum elements on anti diagonal and divide by number of elements in this anti diagonal ,but how can i do it by code?thanks in advance
Your original vector is located along the top and right edge of your matrix B and can be reconstructed like so:
>> X_reconstructed = [B(1,1:end-1).'; B(:,end)]
X_reconstructed =
2
1
3
4
5
7
In case the matrix B is some noisy matrix and you actually want to do the averages along the diagonals:
>> BB = fliplr(B);
>> X_mean = arrayfun(#(i) mean(diag(BB,i)), size(B,2)-1:-1:-size(B,1)+1).'
X_mean =
2
1
3
4
5
7