I have an input matrix X with dimensions N_rows x N_cols. I also have a sparse, tridiagonal matrix M which is square of size N_rows x N_rows. These are created as follows:
N_rows = 3;
N_cols = 6;
X = rand(N_rows,N_cols);
mm = 10*ones(N_cols,1); % Subdiagonal elements
dd = 20*ones(N_cols,1); % Main diagonal elements
pp = 30*ones(N_cols,1); % Superdiagonal elements
M = spdiags([mm dd pp],-1:1,N_cols,N_cols);
and look like the following:
>> X
X =
0.4018 0.1233 0.4173 0.9448 0.3377 0.1112
0.0760 0.1839 0.0497 0.4909 0.9001 0.7803
0.2399 0.2400 0.9027 0.4893 0.3692 0.3897
full(M)
ans =
2 3 0 0 0 0
1 2 3 0 0 0
0 1 2 3 0 0
0 0 1 2 3 0
0 0 0 1 2 3
0 0 0 0 1 2
I would like to take each row of X, and do a matrix multiplication with M, and piece the obtained rows back together to obtain an output Y. At the moment, I achieve this successfully with the following:
Y = (M*X.').';
The example above is for a 3x6 matrix for X, but in reality I need to do this for a matrix with dimensions 500 x 500, about 10000 times, and the profiler says that this operation in the bottleneck in my larger code. Is there a faster way to do this row-by-row matrix multiplication multiplication?
On my system, the following takes around 20 seconds to do this 10000 times:
N_rows = 500;
N_cols = 500;
X = rand(N_rows,N_cols);
mm = 10*ones(N_cols,1); % Subdiagonal elements
dd = 20*ones(N_cols,1); % Main diagonal elements
pp = 30*ones(N_cols,1); % Superdiagonal elements
M = spdiags([mm dd pp],-1:1,N_cols,N_cols);
tic
for k = 1:10000
Y = (M*X.').';
end
toc
Elapsed time is 18.632922 seconds.
You can use X*M.' instead of (M*X.').';. This saves around 35% of time on my computer.
This can be explained because transposing (or permuting dimensions) implies rearranging the elements in the internal (linear-order) representation of the matrix, which takes time.
Another option is using conv2:
Y = conv2(X, [30 20 10], 'same');
Explanation:
There is a tridiagonal matrix that all elements on each diagonal are identical to each other:
M =
2 3 0 0 0 0
1 2 3 0 0 0
0 1 2 3 0 0
0 0 1 2 3 0
0 0 0 1 2 3
0 0 0 0 1 2
Suppose you want to multiply the matrix by a vector:
V = [11 ;12 ;13 ;14 ;15 ;16];
R = M * V;
Each element of the vector R is computed by sum of products of each row of M by V:
R(1):
2 3 0 0 0 0
11 12 13 14 15 16
R(2):
1 2 3 0 0 0
11 12 13 14 15 16
R(3):
0 1 2 3 0 0
11 12 13 14 15 16
R(4):
0 0 1 2 3 0
11 12 13 14 15 16
R(5):
0 0 0 1 2 3
11 12 13 14 15 16
R(6):
0 0 0 0 1 2
11 12 13 14 15 16
It is the same as multiplying a sliding window of [1 2 3] by each row of M. Basically convolution applies a sliding window but first it reverses the direction of window so we need to provide the sliding window in the reversed order to get the correct result. Because of that I used Y = conv2(X, [30 20 10], 'same'); instead of Y = conv2(X, [10 20 30], 'same');.
I am setting up a bar3 plot and manipulated the X-Axis values since there is not a better way to do so. I went thorugh the code that Ander Biguri provided in his answer to this thread: How to set x and y values when using bar3 in Matlab?.
It turns out that the X-Axis values are fine but the bars that are not located at the borders are archetype shaped. Probably it has to do with the data manipulation.
Here is the corrisponding plot:
The data i used for this example:
klasse_sig_a=[70 82 94 106 118 130 142 154 166 178 190];
klasse_sig_m=[-120 -102 -84 -66 -48 -30 -12 6 24 42 60];
RFMatrix=
[2 0 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 1 0 0;
0 0 0 0 2 0 0 0 0 2;
0 0 0 0 1 0 0 0 0 0;
0 0 0 0 0 0 0 0 0 0;
0 0 0 0 2 0 0 0 0 0;]
My code:
b=bar3(klasse_sig_m(2:end),RFMatrix,1);
xlabel('\sigma_a [MPa]')
ylabel('\sigma_m [MPa]')
zlabel('N [-]')
axis tight
for k = 1:length(b)
zdata = b(k).ZData;
b(k).CData = zdata;
b(k).FaceColor = 'interp';
end
Xdat=get(b,'XData');
diff=klasse_sig_a(2)-klasse_sig_a(1);
ONEMAT=ones(size(Xdat{1},1),size(Xdat{1},2)/2);
for ii=1:length(Xdat)
MAT=(Xdat{ii}-0.5);
if ii==1
MAT=MAT+[ONEMAT*min(klasse_sig_a) ONEMAT*(min(klasse_sig_a)+diff)-ii];
MAT_VOR=MAT(:,3:4);
else
MAT(:,1:2)=MAT_VOR;
MAT(:,3:4)=MAT(:,3:4)+ONEMAT*(min(klasse_sig_a)+ii*diff)-ii;
MAT_VOR=MAT(:,3:4);
end
Xdat{ii}=MAT;
set(b(ii),'XData',Xdat{ii});
end
set(gca,'XTick', klasse_sig_a(1:2:end))
set(gca,'YTick', klasse_sig_m(1:2:end))
I noticed that the non manipulated data always has a difference of 1 between left and right side of the matrix for each xdata{ii}
... ... ... ...
NaN NaN NaN NaN
NaN 0.5000 1.5000 NaN
0.5000 0.5000 1.5000 1.5000
0.5000 0.5000 1.5000 1.5000
NaN 0.5000 1.5000 NaN
NaN 0.5000 1.5000 NaN
NaN NaN NaN NaN
When setting my own data the difference becomes much bigger and the bar plots become hollow
... ... ... ...
NaN 70 82 NaN
70 70 82 82
70 70 82 82
NaN 70 82 NaN
NaN 70 82 NaN
NaN NaN NaN NaN
How can I make the bars appear solid again? I guess the data manipulation is erroneous.
Thanks for any help!
Regards
Note that the answer to the related question is specifically designed to deal with the case when your x values are sequential integers (i.e. the bin width is 1). Your case is more general, with a bin width of 12. This requires slightly different logic. I was able to get your desired results with the following code:
b = bar3(klasse_sig_m(2:end), RFMatrix,1);
xlabel('\sigma_a [MPa]');
ylabel('\sigma_m [MPa]');
zlabel('N [-]');
axis tight;
for k = 1:length(b)
xData = b(k).XData;
zData = b(k).ZData;
set(b(k), 'XData', (xData-k).*diff(klasse_sig_a(k:(k+1)))+klasse_sig_a(k), ...
'CData', zData, 'FaceColor', 'interp');
end
set(gca, 'XTick', klasse_sig_a(1:2:end), 'YTick', klasse_sig_m(1:2:end));
And the plot:
Suppose I have two matrices:
A= [0 0 0 0 1;
0 0 0 1 0;
1 0 1 0 1;
0 0 0 0 0;
0 0 1 1 1]
B = [20 15 25 30 40;
12 15 25 38 24;
50 23 37 21 19;
7 20 89 31 41;
12 13 45 21 31]
How to make all the entries in a row of B nan the first time 1 appears in A. in this case I want the output to be:
B = [20 15 25 30 Nan;
12 15 25 Nan Nan;
Nan Nan Nan Nan Nan;
7 20 89 31 41;
12 13 Nan Nan Nan]
Thank you in advance
You can use cummax or cumsum and logical indexing to set the values to NaN:
B(logical(cumsum(A,2)))=NaN;
or
B(logical(cummax(A,2)))=NaN;
A simple solution can be using from a loop to consider each row:
for idx = 1 : size(B,1)
foundOne = find(A(idx,:) == 1);
B(idx, foundOne:end) = NaN;
end
You want to place NaN in B where there is 1 in the A. It can be simply achieved with one step.
A= [0 0 0 0 1;
0 0 0 1 0;
1 0 1 0 1;
0 0 0 0 0;
0 0 1 1 1] ;
B = [20 15 25 30 40;
12 15 25 38 24;
50 23 37 21 19;
7 20 89 31 41;
12 13 45 21 31] ;
B(A==1) = NaN ;
i want to patch points to obtain a square and do it for all other points on the axis, but i want to do it in a for loop.. Thereafter, i would apply some transformation properties to the patched points. This is what i have done so far. Any help will be well appreciated.
whitebg('g')
axis on
% first patch point on the axis
pts2 = [0 0 1 1 0;0 1 1 0 0];
% last patch point on the axis
pts3 = [29 29 30 30 29;29 30 30 29 29];
[n m] = size(pts2);
[o p] = size(pts3);
axis([0 30 0 30])
shg
theta=0;
dx=0;
dy=0;
d=patch(pts1(1,1:end),pts1(2,1:end),'b*-');
for pts1 = pts2:pts3
if (d==patch(pts1(1,1:end),pts1(2,1:end),'b*-'))
delete(d)
end
%function to process
rot = [cosd(theta) sind(theta);-sind(theta) cosd(theta)];
trans = [1 0 dx;0 1 dy; 0 0 1];
homogeneous_rot = eye(3);
homogeneous_rot(1:2,1:2) = rot;
homogeneous_pts1 = [pts1; ones(1,5)];
trans_pts1 = trans*homogeneous_rot*homogeneous_pts1;
hold off
f=patch(trans_pts1(1,1:end),trans_pts1(2,1:end),'r*-');
draw now
end
This is the following part of the below:
2) Additional question:
After getting the average of the non-zero neighbors, I also want to test if the neighbor elements are equal, lesser, or greater than the average of the nonzeros. If it is greater or equal then '1' or else '0'.
Note: if the neighbors are with in the radius of the two or more centres, take the smallest centre average to test.
0 12 9
4 **9** 15
11 19 0
The '9' in the middle is within the radius of 12, 15, and 19 centres, so take the minimum average of those min[9.000, 9.000, 8.000]=8.000
For example, when radius = 1 m or 1 element away.
new_x =
0 0 0 0 0
0 0 **9.0000** 9.0000 0
0 4.0000 9.0000 **9.0000** 0
0 **8.3333** **8.0000** 0 0
0 2.0000 4.0000 8.0000 0
0 4.0000 5.0000 8.0000 0
0 0 0 0 0
Test_x =
0 0 0 0 0
0 0 **9.0000** 1 0
0 0 1 **9.0000** 0
0 **8.3333** **8.0000** 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
=================================================================================
1) Say if I have a matrix, shown as below,
X =
0 0 0 0 0
0 0 12 9 0
0 4 9 15 0
0 11 19 0 0
0 2 4 8 0
0 4 5 8 0
0 0 0 0 0
and I want to find the average of the surrounding non-zero elements that is greater than 10. The rest of the elements still remain the same i.e. elements < 10.
So I want my solution to look something like,
new_x =
0 0 0 0 0
0 0 9.0000 9.0000 0
0 4.0000 9.0000 9.0000 0
0 8.3333 8.0000 0 0
0 2.0000 4.0000 8.0000 0
0 4.0000 5.0000 8.0000 0
0 0 0 0 0
Not: that I am NOT only looking at the neighbors of the element thats greather than some value (i.e. 10 in this case).
Lets say any elements thats are greater than 10 are the 'centre' and we want to find the avearge of the non-zeros with the radius of say 1 m. where 1 metre = 1 element away from the centre.
Note: It might not always be 1 meter away in radius i.e. can be 2 or more. In this case it wont be just top, bottom, left and right of the centre.
****Also Be aware of the matrix boundary. For example, when radius = 2 or more, some of the average of nonzero neighbors are out side the boundary.**
For example,
For radius =1 m = 1 element away,
new_x = average of [(i+1,j) , (i-1,j) , (i,j+1) and (i,j-1)] - top, bottom, right, and left of the centre.
For radius =2 m = 2 elements away,
new_x = average of [(i+1,j), (i+2,j) , (i-1,j) , (i-2,j), (i,j+1), (i,j+2), (i,j-1), (i,j-2), (i+1,j+1), (i+1,j-1), (i-1,j-1), and (i-1,j+1)].
==================================================================
I have tried a few things before, however I am not familiar with the functions.
So please help me to solve the problem.
Thank you in advance.
EDIT:
Note this requires functions from the Image Processing Toolbox, namely: COLFILT and STREL
r = 1; %# radius
t = 10; %# threshold value
mid = round((2*r+1)^2/2); %# mid point
nhood = getnhood(strel('diamond', r));
nhood(mid) = false;
fcn = #(M)sum(M(nhood(:),:),1)./(sum(M(nhood(:),:)~=0)+all(M(nhood(:),:)==0)).*(M(mid,:)>=t)+M(mid,:).*(M(mid,:)<t);
new_x = colfilt(x, 2*[r r]+1, 'sliding',fcn)
For r=1:
new_x =
0 0 0 0 0
0 0 9 9 0
0 4 9 9 0
0 8.3333 8 0 0
0 2 4 8 0
0 4 5 8 0
0 0 0 0 0
For r=2:
new_x =
0 0 0 0 0
0 0 11.2 9 0
0 4 9 10.167 0
0 7 7.7778 0 0
0 2 4 8 0
0 4 5 8 0
0 0 0 0 0
In fact, it should work for any radius >= 1
Notice how the diamond shape structuring element represents the neighborhood:
nhood =
0 1 0
1 0 1
0 1 0
nhood =
0 0 1 0 0
0 1 1 1 0
1 1 0 1 1
0 1 1 1 0
0 0 1 0 0
and so on..
Explanation:
We use the COLFILT function which traverse the matrix using a sliding neighborhood of NxN, and places each block as a column in a temporary matrix.
We process each column of this temp matrix (blocks) using the function fcn, and the result will be placed in the correct location once finished (COLFILT uses IM2COL and COL2IM underneath).
We check for two cases depending of the value of the center of the block:
If its less than 10, it returns that value unchanged: M(mid,:)
if its >=10, we compute the mean of the non-zero elements of its neighborhood
sum(M(nhood(:),:),1) ./ (sum(M(nhood(:),:)~=0) + all(M(nhood(:),:)==0)).
The last term in there is necessary to avoid dividing by zero
Notice how the result of 1 & 2 above are combined using R1.*(M(mid,:)<t) + R2.*(M(mid,:)>=t) to emulate an if/else choice.
Here is the algorithm I think you are describing in your question. For each pixel:
If the pixel value is less than 10, do nothing.
If the pixel value is greater than or equal to 10, replace the pixel value by the average of the non-zero 4-connected nearest neighbors.
If this is correct (as it appears to be from the sample matrices you gave), then you could use the function NLFILTER from the Image Processing Toolbox (if you have access to it) to perform this operation:
fcn = #(x) [x(5) sum(x(2:2:8))/max(sum(x(2:2:8) > 0),1)]*[x(5) < 10; x(5) >= 10];
new_x = nlfilter(X,[3 3],fcn);
EDIT: If you don't have access to the Image Processing Toolbox, you can also do this using the built-in CONV2 function, like so:
kernel = [0 1 0; ... %# Convolution kernel
1 0 1; ...
0 1 0];
sumX = conv2(X,kernel,'same'); %# Compute the sum of neighbors
%# for each pixel
nX = conv2(double(X > 0),kernel,'same'); %# Compute the number of non-zero
%# neighbors for each pixel
index = (X >= 10); %# Find logical index of pixels >= 10
new_x = X; %# Initialize new_x
new_x(index) = sumX(index)./max(nX(index),1); %# Replace the pixels in index
%# with the average of their
%# non-zero neighbors
The above handles your radius = 1 case. To address your radius = 2 case, you just have to change the convolution kernel to the following and rerun the above code:
kernel = [0 0 1 0 0; ...
0 1 1 1 0; ...
1 1 0 1 1; ...
0 1 1 1 0; ...
0 0 1 0 0];
You could do something like this: (tested in Octave, should work in matlab)
octave-3.2.3:17> toohigh = (x>=10)
toohigh =
0 0 0 0 0
0 0 1 0 0
0 0 0 1 0
0 1 1 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
octave-3.2.3:18> nbr_avg = filter2(ones(3,3)/9,x)
nbr_avg =
0.00000 1.33333 2.33333 2.33333 1.00000
0.44444 2.77778 5.44444 5.00000 2.66667
1.66667 6.11111 8.77778 7.11111 2.66667
1.88889 5.44444 8.00000 6.11111 2.55556
1.88889 5.00000 6.77778 4.88889 1.77778
0.66667 1.66667 3.44444 2.77778 1.77778
0.44444 1.00000 1.88889 1.44444 0.88889
octave-3.2.3:19> y=x; y(toohigh) = nbr_avg(toohigh)
y =
0.00000 0.00000 0.00000 0.00000 0.00000
0.00000 0.00000 5.44444 9.00000 0.00000
0.00000 4.00000 9.00000 7.11111 0.00000
0.00000 5.44444 8.00000 0.00000 0.00000
0.00000 2.00000 4.00000 8.00000 0.00000
0.00000 4.00000 5.00000 8.00000 0.00000
0.00000 0.00000 0.00000 0.00000 0.00000
The filter2 function allows you to filter on neighbors (not sure what function you want...), and if you use a boolean index matrix (toohigh in this case) to select those members of the original matrix that are too high, you can replace them with the ones you want.
More specifically, filter2 allows you to convolve with an arbitrary matrix. The matrix of all ones does a spatial low pass filter.
note: my math doesn't match yours. I'm not quite sure why you want to average only the nonzero neighbors (that gives higher weight to nonzero neighbors when there are zeros), but if you wanted to do that, you could do filter2(ones(3,3),x) ./ M where M = filter2(ones(3,3),(x ~= 0)) is the count of nonzero neighbors.
EDIT: Here is a solution that does not require the Image Processing Toolbox. It does, however, use conv2nan.m which is part of the free NaN toolbox.
This approach relies on doing two different filtering/convolution operations: one that gets the sum of surrounders for each element, and one that gets the count of nonzero surrounders. Then, you are ready to combine them to get the average of nonzero surrounders only. Like this:
% set up starting point matrix with some zeros
X = magic(4);
X(X < 5) = 0;
X(X == 0) = NaN; % convert zeros to NaNs to work with conv2nan
countmat = double(X > 0);
cmat = [0 1 0;
1 0 1;
0 1 0]; % consider surrounding elements only
[m1,c] = conv2nan(X,cmat); % sum of surrounding elements
[m2,c] = conv2nan(countmat,cmat); % number of surrounding elements > 0
x_new = m1./m2; % compute average we want
x_new = x_new(2:end-1,2:end-1); % trim edges created by conv2
x_new(~countmat) = 0; % restore zero elements
x_new(X < 10) = X(X < 10) % restore < 10 elements
It does some extra work in that the convolutions are done for all elements and not just those that are >= 10. But it's more general than the manual looping approach.