Combine index-based and logical addressing in Matlab - matlab

Consider a matrix X. I have to update a submatrix of X, X(row1:row2, col1:col2), with a matrix Z (of size row2-row1+1, col2-col1+1) but only on those positions where a logical matrix L (of size row2-row1+1, col2-col1+1) is true.
E.g. if
X=[ 1 2 3 4 5 6
11 12 13 14 15 16
21 22 23 24 25 26
31 32 33 34 34 36]
Z=[31 41
32 42]
L=[ 1 0
0 1]
row1 = 2; row2 = 3; col1 = 3; col2 = 4
then after the update I should get:
X=[ 1 2 3 4 5 6
11 12 31 14 15 16
21 22 23 42 25 26
31 32 33 34 34 36]
Currently I do the following:
Y = X(row1:row2, col1:col2);
Y(L) = Z(L);
X(row1:row2, col1:col2) = Y;
This code is in a tight loop and according to Matlab's (v2019a) profiler is the main bottleneck of my program. In the real code X is a 2000x1500x3 cube; row1, row2, col1, col2, Z and L change in the loop.
The question is whether it can be rewritten into a single / faster assignment.
Thanks.

Honestly, without seeing your actual code, I get the sense that your solution may be as fast as you can get. The reason I say that is because I tested a few different solutions by creating some random sample data closer to your actual problem. I assumed X is an image of type uint8 with size 2000-by-1500-by-3, Z is size N-by-N (i.e. we will only be modifying the first page of X), L is an N-by-N logical array, and the row and column indices are randomly chosen:
X = randi([0 255], 2000, 1500, 3, 'uint8');
N = 20; % Submatrix size
Z = randi([0 255], N, N, 'uint8');
L = (rand(N, N) > 0.5);
row1 = randi([1 2000-N]);
row2 = row1+N-1
col1 = randi([1 1500-N]);
col2 = col1+N-1;
I then tested 3 different solutions: your original solution, a solution using find and sub2ind to create a linear index for X, and a solution that creates a logical index for X:
% Original solution:
Y = X(row1:row2, col1:col2, 1);
Y(L) = Z(L);
X(row1:row2, col1:col2, 1) = Y;
% Linear index solution:
[rIndex, cIndex] = find(L);
X(sub2ind(size(X), rIndex+row1-1, cIndex+col1-1)) = Z(L);
% Logical index solution
[R, C, ~] = size(X);
fullL = false(R, C);
fullL(row1:row2, col1:col2) = L;
X(fullL) = Z(L);
I tested these repeatedly with randomly-generated sample data using timeit and found that your original solution is consistently the fastest. The linear index solution is very close, but slightly slower. The logical index solution takes more than twice as long.

Let's define some example data:
X = randi(9,5,6);
Y = 10+X;
row1 = 2;
row2 = 4;
col1 = 3;
col2 = 4;
L = logical([0 1; 0 0; 1 1]);
Then:
ind_subm = bsxfun(#plus, (row1:row2).',size(X,1)*((col1:col2)-1));
% linear index for submatrix
ind_subm_masked = ind_subm(L);
% linear index for masked submatrix
X(ind_subm_masked) = Y(ind_subm_masked);
Example results:
X before:
X =
6 2 1 7 9 6
3 3 3 5 5 7
6 3 8 6 5 4
7 4 1 3 3 4
2 5 9 5 5 9
L:
L =
3×2 logical array
0 1
0 0
1 1
X after:
X =
6 2 1 7 9 6
3 3 3 15 5 7
6 3 8 6 5 4
7 4 11 13 3 4
2 5 9 5 5 9

Related

Filtering 3D matrix above certain threshold?

I have two 3D matrices of the same size. Say, A contain values ranging from 0 to 1 and B contains certain discrete values.
I want to extract values from B matrix which are above certain threshold values in A?
Can anyone help me out?
Like this? Have a look at logical indexing.
% Input.
B = reshape(1:18, 2, 3, 3)
A = rand(size(B))
% Threshold.
thr = 0.75
% Output.
output = B(A > thr)
B =
ans(:,:,1) =
1 3 5
2 4 6
ans(:,:,2) =
7 9 11
8 10 12
ans(:,:,3) =
13 15 17
14 16 18
A =
ans(:,:,1) =
0.80533 0.24370 0.89180
0.90358 0.22422 0.69243
ans(:,:,2) =
0.119366 0.168337 0.771999
0.206004 0.065481 0.979772
ans(:,:,3) =
0.0057303 0.1469925 0.0556628
0.0454038 0.4122576 0.9847027
thr = 0.75000
output =
1
2
5
11
12
18

Create new matrix based on diagonal and antidiagonal of given matrix

I want to create matrix B from matrix A, with the following rules:
Non-diagonal elements A are like non-diagonal elements of B.
The main diagonal of A is the antidiagonal of B
The antidiagonal of A is the main diagonal of B.
For example:
A = [ 1 2 3 4;
7 8 9 10;
13 14 15 16;
19 20 21 22 ];
B = [ 4 2 3 1;
7 9 8 10;
13 15 14 16;
22 20 21 19 ];
How can I create B given A?
You can create all of the indices, then it's a single assignment.
% Get size of square matrix A
n = size(A,1);
% Indicies are 1:n^2 by default
idx = 1:n^2;
% Swap diagonal and antidiagonal indices
idx( [1:(n+1):n^2, n^2-n+1:1-n:n] ) = [n^2-n+1:1-n:n, 1:(n+1):n^2];
% Use the indexing array to create B from A, reshape to be n*n
B = reshape( A( idx ), n, n );
Output for your example A:
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
There are so many ways to reach that results, it is just an indexing exercise. Here is one (of the many) way to reach that result for any square matrix of size n:
%% input
A=[ 1 2 3 4 ;
7 8 9 10 ;
13 14 15 16 ;
19 20 21 22 ];
%% Calculate linear indices for the diagonal and antidiagonal
n=size(A,1) ;
idxdiag = 1:(n+1):n^2 ; % => idxdiag = [1 6 11 16]
idxantidiag = n:(n-1):n^2-1 ; % => idxantidiag = [4 7 10 13]
%% Generate B
B = A ; % start with a simple copy (for the non-diagonal elements)
% Method 1: direct indice assignment
B(idxdiag) = diag(fliplr(A)) ; % Assign diagonal elements of B
B(idxantidiag) = flipud(diag(A)) ; % Assign antidiagonal elements of B
% Method 2: summation
B([idxdiag idxantidiag]) = 0 ;
B = B + diag(diag(fliplr(A))) + fliplr(diag(diag(A))) ;
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
Both methods return exactly the same matrix B.
I suggest you familiarise yourself with the MATLAB function used to understand what is going on behind the scene:
fliplr
flipud
diag
and may be have a read at:
Matrix Indexing in MATLAB
I thought a little differently and came to a conclusion
A=[1 2 3 4;7 8 9 10;13 14 15 16; 19 20 21 22];;
n=size(A,1) ;
B=zeros(n,n) ;
for i=1:n
for j=1:n
if i==j
B(i,j)=A(i,n-i+1);
elseif j==n-i+1
B(i,j)=A(i,i);
else
B(i,j)=A(i,j);
end
end
end
B
Here's a variant using eye, find, and flip to generate linear indices:
ind1 = find(eye(size(A)));
ind2 = flip(find(flip(eye(size(A)))));
B = A;
B([ind1 ind2]) = B([ind2 ind1]);
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
And here's a variant of the above that uses just eye and flip to generate logical indices:
ind1 = eye(size(A), 'logical');
ind2 = flip(ind1);
B = A;
B(ind1) = flip(A(ind2));
B(ind2) = flip(A(ind1));
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19

creating diagonal matrix with selected elements

I have a 4x5 matrix called A from which I want to select randomly 3 rows, then 4 random columns and then select those elements which coincide in those selected rows and columns so that I have 12 selected elements.Then I want to create a diagonal matrix called B which will have entries either 1 or 0 so that multiplication of that B matrix with reshaped A matrix (20x1) will give me those selected 12 elements of A.
How can I create that B matrix? Here is my code:
A=1:20;
A=reshape(A,4,5);
Mr=4;
Ma=3;
Na=4;
Nr=5;
M=Ma*Mr;
[S1,S2]=size(A);
N=S1*S2;
y2=zeros(size(A));
k1=randperm(S1);
k1=k1(1:Ma);
k2=randperm(S2);
k2=k2(1:Mr);
y2(k1,k2)=A(k1,k2);
It's a little hard to understand what you want and your code isn't much help but I think I've a solution for you.
I create a matrix (vector) of zeros of the same size as A and then use bsxfun to determine the indexes in this vector (which will be the diagonal of B) that should be 1.
>> A = reshape(1:20, 4, 5);
>> R = [1 2 3]; % Random rows
>> C = [2 3 4 5]; % Random columns
>> B = zeros(size(A));
>> B(bsxfun(#plus, C, size(A, 1)*(R-1).')) = 1;
>> B = diag(B(:));
>> V = B*A(:);
>> V = V(V ~= 0)
V =
2
3
4
5
6
7
8
9
10
11
12
13
Note: There is no need for B = diag(B(:)); we could have simply used element by element multiplication in Matlab.
>> V = B(:).*A(:);
>> V = V(V ~= 0)
Note: This may be overly complex or very poorly put together and there is probably a better way of doing it. It's my first real attempt at using bsxfun on my own.
Here is a hack but since you are creating y2 you might as well just use it instead of creating the useless B matrix. The bsxfun answer is much better.
A=1:20;
A=reshape(A,4,5);
Mr=4;
Ma=3;
Na=4;
Nr=5;
M=Ma*Mr;
[S1,S2]=size(A);
N=S1*S2;
y2=zeros(size(A));
k1=randperm(S1);
k1=k1(1:Ma);
k2=randperm(S2);
k2=k2(1:Mr);
y2(k1,k2)=A(k1,k2);
idx = reshape(y2 ~= 0, numel(y2), []);
B = diag(idx);
% "diagonal" matrix 12x20
B = B(all(B==0,2),:) = [];
output = B * A(:)
output =
1
3
4
9
11
12
13
15
16
17
19
20
y2 from example.
y2 =
1 0 9 13 17
0 0 0 0 0
3 0 11 15 19
4 0 12 16 20

Find top n elements in matrix

I have a matrix which contains values and I wish to find the index of the top n minimum values.
I use the following code for finding the minimum most value:
[r,c]=find(Result==min(min(Result)));
I cant find any other questions on stack overflow which answer the question, please help
Maybe you could do something like this:
sorted = sort(Result(:));
topten = sorted(1:10);
[~,ia,~] = intersect(Result(:),topten(:)); % // Get the indices of the top ten values
[r,c]=ind2sub(size(Result),ia); % // Convert the indices to rows and columns
Or without Intersect in the other answer
[sorted,I] = sort(Result(:));
[r,c] = ind2sub(size(Result),I(1:10)); %//Change 10 to any other required value
Use prctile (Statistics Toolbox) to find the appropriate threshold, and then use indexing to select the elements above that threshold:
x = magic(4); %// example
n = 5; %// we want the top n elements
M = numel(x);
p = prctile(x(:), (M-n)/M*100);
indices = find(x>p); %// result in the form linear indices
[row, col] = find(x>p); %// result in the form of row and column indices
In this example:
>> x
x =
16 2 3 13
5 11 10 8
9 7 6 12
4 14 15 1
>> indices.'
ans =
1 8 12 13 15
>> row.'
ans =
1 4 4 1 3
>> col.'
ans =
1 2 3 4 4
>> x(indices).'
ans =
16 14 15 13 12
Example with repeated elements:
>> x = [1 1 2 5; 3 4 3 5];
>> n = 5;
gives
>> indices.'
ans =
2 4 6 7 8
>> row.'
ans =
2 2 2 1 2
>> col.'
ans =
1 2 3 4 4
>> x(indices).'
ans =
3 4 3 5 5

Creating Matrix with a loop in Matlab

I want to create a matrix of the following form
Y = [1 x x.^2 x.^3 x.^4 x.^5 ... x.^100]
Let x be a column vector.
or even some more variants such as
Y = [1 x1 x2 x3 (x1).^2 (x2).^2 (x3).^2 (x1.x2) (x2.x3) (x3.x1)]
Let x1,x2 and x3 be column vectors
Let us consider the first one. I tried using something like
Y = [1 : x : x.^100]
But this also didn't work because it means take Y = [1 x 2.*x 3.*x ... x.^100] ?
(ie all values between 1 to x.^100 with difference x)
So, this also cannot be used to generate such a matrix.
Please consider x = [1; 2; 3; 4];
and suggest a way to generate this matrix
Y = [1 1 1 1 1;
1 2 4 8 16;
1 3 9 27 81;
1 4 16 64 256];
without manually having to write
Y = [ones(size(x,1)) x x.^2 x.^3 x.^4]
Use this bsxfun technique -
N = 5; %// Number of columns needed in output
x = [1; 2; 3; 4]; %// or [1:4]'
Y = bsxfun(#power,x,[0:N-1])
Output -
Y =
1 1 1 1 1
1 2 4 8 16
1 3 9 27 81
1 4 16 64 256
If you have x = [1 2; 3 4; 5 6] and you want Y = [1 1 1 2 4; 1 3 9 4 16; 1 5 25 6 36] i.e. Y = [ 1 x1 x1.^2 x2 x2.^2 ] for column vectors x1, x2 ..., you can use this one-liner -
[ones(size(x,1),1) reshape(bsxfun(#power,permute(x,[1 3 2]),1:2),size(x,1),[])]
Using an adapted Version of the code found in Matlabs vander()-Function (which is also to be found in the polyfit-function) one can get a significant speedup compared to Divakars nice and short solution if you use something like this:
N = 5;
x = [1:4]';
V(:,n+1) = ones(length(x),1);
for j = n:-1:1
V(:,j) = x.*V(:,j+1);
end
V = V(:,end:-1:1);
It is about twice as fast for the example given and it gets about 20 times as fast if i set N=50 and x = [1:40]'. Although I state that is not easy to compare the times, just as an option if speed is an issue, you might have a look at this solution.
in octave, broadcasting allows to write
N=5;
x = [1; 2; 3; 4];
y = x.^(0:N-1)
output -
y =
1 1 1 1 1
1 2 4 8 16
1 3 9 27 81
1 4 16 64 256