Assigning values in neighborhood of local maxima’s to the value of local maxima based on varying window width (non symmetric window width) - matlab

This question is an extension of my previous question with some new issues so I thought to make a new query. I hope it is ok.
https://stackoverflow.com/questions/46054811/changing-the-values-in-the-neighbourhood-of-local-maxima-to-the-local-maxima/46055833#46055833
Query:
Where I find local maxima. I want to make a window and assign the values depending upon window size to the neighbors the value of local maxima.
Problem: (I want my window size to change as in my original signal I have different behavior around local maxima’s.) For example in 8th window local maxima is at 34th location but I have assigned the values on the left of 34th location the value of local maxima.
In short I want to have varying and controllable window width.
Please have a look at the attached picture to get an idea of output what I want.
I hope it will give some good idea.
I want to have varying and non symmetric means not same at every local maxima window width .
I have also attached a code for nlfilter which is doing exactly what I want to do means it makes window and assign values to local maximas within that window width but I need to have flexible and changeable window width.
Is it possible to have varying window width or is there some other way possible to do that.
enter image description here
Code:
t = 1:35 ;
Y = [1 3 13 6 2 7 5 4 2 4 1 0 1 2 3 5 0 0 1 0 0 2 3 6 7 0 0 8 0 1 1 2 3 4 2];
n = 2;
[p l] = findpeaks(Y);
locations = zeros(size(Y));
locations(l) = true;
locations = conv(locations, ones(1, 2*n+1), 'same') > 0;
X = -inf(size(Y)); % create temporary
X(l) = Y(l); % copy the local maxima
X = nlfilter(X, [1 2*n+1 ], #(x) max(x)); %replace all values with it local maxima
X(l) = Y(l); % ensure local maxima are not changed
Y(locations) = X(locations); % copy filtered temporary to output
figure(1)
hold on
plot(t,Y,'r')
t = 1:35 ;
Y = [1 3 13 6 2 7 5 4 2 4 1 0 1 2 3 5 0 0 1 0 0 2 3 6 7 0 0 8 0 1 1 2 3 4 2];
plot(t,Y,'b')
hold off
I shall be grateful to you for your valuable replies.
Further Explanation:
Please have a look at the pictures attached.
2nd picture is a part of original signal with local maximas mentioned as green dots.
In 1st pictures the red lines shows the region which I want to assign the value of local maxima. Green Dot is local maxima . So you will see that if I apply window with fixed width it will not work because the points before local maxima are less than after local maxima.
The reason for placing 1 outside in example is same that there are few points before local maxima which I want to flat as compared to after local maxima.
The same is the case with other windows like last window 8 I have local maxima on 34th location but why I have chosen large values before it is only due to the values I want to assign the values of local maxima .

You can define a criterion that starting form a peak and going to the both sides we compute variance of neighbors of the peak and we increase the radius of the neighborhood until variance of neighboring elements becomes greater than a predefined threshold.
Here idx_peaks is position of peaks and peaks is value of peaks. After applying the threshold you can get number of elements before and after position of each peak n_before and n_after. Then you can find indices of neighborhood and assign values to them.
Y = [1 3 13 6 2 7 5 4 2 4 1 0 1 2 3 5 0 0 1 0 0 2 3 6 7 0 0 8 0 1 1 2 3 4 2];
idx_peaks = [3 6 10 16 19 25 28 34];
peaks = [13 7 4 5 1 7 8 4];
threshold = 2;
cumvar = #(a)cumsum(a(:).^2)./(1:numel(a)).'-(cumsum(a(:))./(1:numel(a)).').^2;
categ = zeros(numel(Y),1);
categ(idx_peaks)=1;
forward_categ = cumsum(categ(idx_peaks(1):end));
n_after = accumarray(forward_categ,Y(idx_peaks(1):end),[],#(x)sum(cumvar(x)<threshold)-1).';
backward_categ = cumsum(flipud(categ(1:idx_peaks(end))));
n_before = flipud(accumarray(backward_categ,fliplr(Y(1:idx_peaks(end))),[],#(x)sum(cumvar(x)<threshold)-1)).';
lo = idx_peaks-n_before;
up = idx_peaks+n_after;
val = repelem(peaks,up-lo+1);
index=cumsum(accumarray(cumsum([1;up(:)-lo(:)+1]),[lo(:);0]-[0;up(:)]-1)+1);
index= index(1:end-1);
Y(index) = val
Here is the result when setting the threshold to 2 :
Y=
[1 3 13 6 2 7 4 4 4 4 4 4 1 5 5 5 1 1 1 1 1 1 1 7 7 0 0 8 4 4 4 4 4 4 4]

Related

Put a numbers from a (n by n) matrix on each cell of a grid

I have implemented the A* algorithm to solve the 8-puzzle. However I want to be more fancy by displaying the result of each state change of my 3 by 3 matrix on a grid that animates the state.
My matrix has numbers from 0 to 8, so I want a grid with 3 rows and 3 columns with a number on each tile.
I really do not know where to start, all ideas are welcome.
Below the first matrix is where I have started, and I used A* to reach the last state which is the goal state. I would like to display these matrix on a grid, and show the transitions graphically. So each time the matrix changes, the grid also will change.
2 8 3
1 6 4
7 0 5
2 8 3
1 0 4
7 6 5
2 0 3
1 8 4
7 6 5
0 2 3
1 8 4
7 6 5
1 2 3
0 8 4
7 6 5
1 2 3
8 0 4
7 6 5
Have a look at this for example of a use of imagesc:
P = perms(0:8);
A = reshape(P(1:100,:).',3,[]);
A = reshape(A,3,3,[]);
for k = 1:size(A,3)
imagesc(A(:,:,k))
axis off
pause(0.1)
end
if you want to add the borders, you can either pad it with nans:
B = nan(5,5,size(A,3));
B(1:2:5,1:2:5,:) = A;
cmap = colormap;
cmap(1,:) = [0 0 0];
colormap(cmap)
for k = 1:size(B,3)
imagesc(B(:,:,k))
axis off
pause(0.1)
end
or use pcolor (with some padding with nans):
B = nan(4,4,size(A,3));
B(1:3,1:3,:) = A;
for k = 1:size(B,3)
pcolor(B(:,:,k))
axis off
pause(0.1)
end

Histogram of subblock matrix

Given some matrix, I want to divide it into blocks of size 2-by-2 and show a histogram for each of the blocks. The following is the code I wrote to solve the problem, but the sum of the histograms I'm generating is not the same as the histogram of the whole matrix. Actually the the sum of the blocks' histograms is double what I expected. What am I doing wrong?
im =[1 1 1 2 0 6 4 3; 1 1 0 4 2 9 1 2; 1 0 1 7 4 3 0 9; 2 3 4 7 8 1 1 4; 9 6 4 1 5 3 1 4; 1 3 5 7 9 0 2 5; 1 1 1 1 0 0 0 0; 1 1 2 2 3 3 4 4];
display(imhist(im));
[r c]=size(im);
bs = 2; % Block Size (8x8)
nob=[r c ]./ bs; % Total number of Blocks
% Dividing the image into 8x8 Blocks
kk=0;
for k=1:nob/2
for i=1:(r/bs)
for j=1:(c/bs)
Block(:,:,kk+j)=im((bs*(i-1)+1:bs*(i-1)+bs),(bs*(j-1)+1:bs*(j-1)+bs));
count(:,:,kk+j)=sum(sum(sum(hist(Block(:,:,kk+j)))));
p=sum(count(:,:,kk+j));
end
kk=kk+(r/bs);
end
end
The reason they aren't the same is because you use imhist for im and hist for the blocks. Hist separates data into 10 different bins based on your data range, imhist separates data based on the image type. Since your arrays are doubles, the imhist bins are from 0 to 1.0 Thats why your imhist has only values at 0, and 1. The hist produces bins based on your data range, so it will actually change slightly depending on what value you pass in. So you cant simply add bins together. Even though they are the same size vector 10x1 , the values in them can be very different. in one set bin(1) can be the range 1-5 but in another set of data bin(1) could be 1-500.
To fix all these issues I used imhist, and converted your data to uint8. At the very end I subtract the two histograms from one another and get zero, this shows that they are indeed the same
im =uint8([1 1 1 2 0 6 4 3 ;
1 1 0 4 2 9 1 2 ;
1 0 1 7 4 3 0 9 ;
2 3 4 7 8 1 1 4 ;
9 6 4 1 5 3 1 4 ;
1 3 5 7 9 0 2 5 ;
1 1 1 1 0 0 0 0 ;
1 1 2 2 3 3 4 4 ]);
orig_imhist = imhist(im);
%% next thing
[r c]=size(im);
bs=2; % Block Size (8x8)
nob=[r c ]./ bs; % Total number of Blocks
%creates arrays ahead of time
block = uint8(zeros(bs,bs,nob(1)*nob(2)));
%we use 256, because a uint8 has 256 values, or 256 'bins' for the
%histogram
block_imhist = zeros(256,nob(1)*nob(2));
sum_block_hist = zeros(256,1);
% Dividing the image into 2x2 Blocks
for i = 0:nob(1)-1
for j = 0:nob(2)-1
curr_block = i*nob(1)+(j+1);
%creates the 2x2 block
block(:,:,curr_block) = im(bs*i+1:bs*i+ bs,bs*j+1:bs*j+ bs);
%creates a histogram for the block
block_imhist(:,curr_block) = imhist(block(:,:,curr_block));
%adds the current histogram to the running sum
sum_block_hist = sum_block_hist + block_imhist(:,curr_block);
end
end
%shows that the two are the same
sum(sum(orig_imhist-sum_block_hist))
if my solution solves your problem please mark it as the answer

Matlab - use find in range of indices

I often need to do searches in various portions of a vector but I don't want the indices based on the portion but on the whole vector. Is there a more formal way than doing this:
find(y(5:10))+5-1
You can use find in conjunction with a mask that tells find which indices you want to skip. What you can do is declare a mask that is the same size as y, and place false in the locations that you don't want find to search for and true otherwise. In this way, you don't need to offset the indices of find when you're done. Once you declare this mask, search for the values you want with whatever Boolean condition you want to set up, and make sure you logical AND this result with your mask.
Something like this:
rng(123);
y = randi(10,20,1);
mask = false(20,1);
mask(5:10) = true;
ind = find((y == 10) & mask);
The above code sets a random generator seed to 123 so you can reproduce the results I am producing. Next, we generate 20 random integers between 1 and 10, and create a mask that is the same size where we only want to search within locations 5 to 10 of the random vector. I want to look for any instances within locations 5 to 10 that match the value of 10, so first search for y == 10, then logical AND this with your mask to only include those entries within the range you want. ind should give you those indices that are with respect to the entire vector.
To see what y and mask are, here's what they look like:
>> y
y =
7
3
3
6
8
5
10
7
5
4
4
8
5
1
4
8
2
2
6
6
>> mask
mask =
0
0
0
0
1
1
1
1
1
1
0
0
0
0
0
0
0
0
0
0
Note that between locations 5 and 10, this is what y gives us:
>> y(mask)
ans =
8
5
10
7
5
4
As such, the value of 8 is from location 5, the value of 5 is from location 6 and so on. I want to search for all values that are equal to 10, and so when we run the final statement with find, we get:
>> ind = find((y == 10) & mask);
ind =
7
As you can see, we found the value of 10 located at location 7, which is indeed correct with reference to the entire vector.

Find Value at a given Orientation in Matrix

In Matlab I've matrix where, in a previous stage of my code, an specific element was chosen. From this point of the matrix I would like to find a maximum, not just the maximum value between all its surounding neighbours for a given radius, but the maximum value at a given angle of orientation. Let me explain this with an example:
This is matrix A:
A =
0 1 1 1 0 0 9 1 0
0 2 2 4 3 2 8 1 0
0 2 2 3 3 2 2 1 0
0 1 1 3 2 2 2 1 0
0 8 2 3 3 2 7 2 1
0 1 1 2 3 2 3 2 1
The element chosen in the first stage is the 4 in A(2,4), and the next element should be the maximum value with, for example, a 315 degrees angle of orientation, that is the 7 in A(5,7).
What I've done is, depending on the angle, subdivide matrix A in different quadrants and make a new matrix (an A's submatrix) with only the values of that quadrant.
So, for this example, the submatrix will be A's 4th quadrant:
q_A =
4 3 2 8 1 0
3 3 2 2 1 0
3 2 2 2 1 0
3 3 2 7 2 1
2 3 2 3 2 1
And now, here is my question, how can I extract the 7?
The only thing I've been able to do (and it works) is to find all the values over a threshold value and then calculate how those points are orientated. Then, saving all the values that have a similar orientation to the given one (315 degrees in this example) and finally finding the maximum among them. It works but I guess there could be a much faster and "cleaner" solution.
This is my theory, but I don't have the image processing toolbox to test it. Maybe someone who does can comment?
%make (r,c) the center by padding with zeros
if r > size(A,1)/2
At = padarray(A, [size(A,1) - r], 0, 'pre');
else
At = padarray(A, [r-1], 0 'post');
if c > size(A,2)/2
At = padarray(At, [size(A,2) - c], 0, 'pre');
else
At = padarray(At, [c-1], 0 'post');
%rotate by your angle (maybe this should be -angle or else 360-angle or 2*pi-angle, I'm not sure
Ar = imrotate(At,angle, 'nearest', 'loose'); %though I think nearest and loose are defaults
%find the max
max(Ar(size(Ar,1)/2, size(Ar,2)/2:end); %Obviously you must adjust this to handle the case of odd dimension sizes.
Also depending on your array requirements, padding with -inf might be better than 0
The following is a relatively inexpensive solution to the problem, although I found wrapping my head around the matrix coordinate system a real pain, and there is probably room to tidy it up somewhat. It simply traces all matrix entries along a line around the starting point at the supplied angle (all coordinates and angles are of course based on matrix index units):
A = [ 0 1 1 1 0 0 9 1 0
0 2 2 4 3 2 8 1 0
0 2 2 3 3 2 2 1 0
0 1 1 3 2 2 2 1 0
0 8 2 3 3 2 7 2 1
0 1 1 2 3 2 3 2 1 ];
alph = 315;
r = 2;
c = 4;
% generate a line through point (r,c) with angle alph
[nr nc] = size(A);
x = [1:0.1:nc]; % overkill
m = tan(alph);
b = r-m*c;
y = m*x + b;
crd = unique(round([y(:) x(:)]),'rows');
iok = find((crd(:,1)>0) & (crd(:,1)<=nr) & (crd(:,2)>0) & (crd(:,2)<=nc));
crd = crd(iok,:);
indx=sub2ind([nr,nc],crd(:,1),crd(:,2));
% find max and position of max
[val iv]=max(A(indx)); % <-- val is the value of the max
crd(iv,:) % <-- matrix coordinates (row, column) of max value
Result:
val =
7
iv =
8
ans =
5 7

How can I find local maxima in an image in MATLAB?

I have an image in MATLAB:
y = rgb2gray(imread('some_image_file.jpg'));
and I want to do some processing on it:
pic = some_processing(y);
and find the local maxima of the output. That is, all the points in y that are greater than all of their neighbors.
I can't seem to find a MATLAB function to do that nicely. The best I can come up with is:
[dim_y,dim_x]=size(pic);
enlarged_pic=[zeros(1,dim_x+2);
zeros(dim_y,1),pic,zeros(dim_y,1);
zeros(1,dim_x+2)];
% now build a 3D array
% each plane will be the enlarged picture
% moved up,down,left or right,
% to all the diagonals, or not at all
[en_dim_y,en_dim_x]=size(enlarged_pic);
three_d(:,:,1)=enlarged_pic;
three_d(:,:,2)=[enlarged_pic(2:end,:);zeros(1,en_dim_x)];
three_d(:,:,3)=[zeros(1,en_dim_x);enlarged_pic(1:end-1,:)];
three_d(:,:,4)=[zeros(en_dim_y,1),enlarged_pic(:,1:end-1)];
three_d(:,:,5)=[enlarged_pic(:,2:end),zeros(en_dim_y,1)];
three_d(:,:,6)=[pic,zeros(dim_y,2);zeros(2,en_dim_x)];
three_d(:,:,7)=[zeros(2,en_dim_x);pic,zeros(dim_y,2)];
three_d(:,:,8)=[zeros(dim_y,2),pic;zeros(2,en_dim_x)];
three_d(:,:,9)=[zeros(2,en_dim_x);zeros(dim_y,2),pic];
And then see if the maximum along the 3rd dimension appears in the 1st layer (that is: three_d(:,:,1)):
(max_val, max_i) = max(three_d, 3);
result = find(max_i == 1);
Is there any more elegant way to do this? This seems like a bit of a kludge.
bw = pic > imdilate(pic, [1 1 1; 1 0 1; 1 1 1]);
If you have the Image Processing Toolbox, you could use the IMREGIONALMAX function:
BW = imregionalmax(y);
The variable BW will be a logical matrix the same size as y with ones indicating the local maxima and zeroes otherwise.
NOTE: As you point out, IMREGIONALMAX will find maxima that are greater than or equal to their neighbors. If you want to exclude neighboring maxima with the same value (i.e. find maxima that are single pixels), you could use the BWCONNCOMP function. The following should remove points in BW that have any neighbors, leaving only single pixels:
CC = bwconncomp(BW);
for i = 1:CC.NumObjects,
index = CC.PixelIdxList{i};
if (numel(index) > 1),
BW(index) = false;
end
end
Alternatively, you can use nlfilter and supply your own function to be applied to each neighborhood.
This "find strict max" function would simply check if the center of the neighborhood is strictly greater than all the other elements in that neighborhood, which is always 3x3 for this purpose. Therefore:
I = imread('tire.tif');
BW = nlfilter(I, [3 3], #(x) all(x(5) > x([1:4 6:9])) );
imshow(BW)
In addition to imdilate, which is in the Image Processing Toolbox, you can also use ordfilt2.
ordfilt2 sorts values in local neighborhoods and picks the n-th value. (The MathWorks example demonstrates how to implemented a max filter.) You can also implement a 3x3 peak finder with ordfilt2 with the following logic:
Define a 3x3 domain that does not include the center pixel (8 pixels).
>> mask = ones(3); mask(5) = 0 % 3x3 max
mask =
1 1 1
1 0 1
1 1 1
Select the largest (8th) value with ordfilt2.
>> B = ordfilt2(A,8,mask)
B =
3 3 3 3 3 4 4 4
3 5 5 5 4 4 4 4
3 5 3 5 4 4 4 4
3 5 5 5 4 6 6 6
3 3 3 3 4 6 4 6
1 1 1 1 4 6 6 6
Compare this output to the center value of each neighborhood (just A):
>> peaks = A > B
peaks =
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 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0
or, just use the excellent: extrema2.m