Finding specific peaks and valleys

I have a periodic signal with 6 peaks and 6 valleys. I am able to find all 6 peaks and all 6 valleys using the code below:
% plot periodic signal
plot(angle,signal)
hold on;
% fit to avoid noisy oscillations and find 6 global peaks
f = fit(angle,signal,'sin9');
yfitted = feval(f,angle);
plot(angle,yfitted,'r')
% find peaks and corresponding angles
[ypk0,idx0] = findpeaks(yfitted);
peaks = signal(idx0);
max_angles = angle(idx0);
% invert fitted signal to find 6 global valleys and corresponding angles
yfittedinv = max(yfitted) - yfitted;
[ypk,idx] = findpeaks(yfittedinv);
valleys = signal(idx);
min_angles = angle(idx);
Furthermore, I am able to find the global minimum (i.e., the lowest valley) using this line:
% find the global minimum
minangle = min_angles(valleys == min(valleys));
Both the arrays "max_angles" and "min_angles" have 6 elements. The question is how to reduce the number of elements in the array "min_angles" from 6 to 3, where I will have only the global minimum and the 2 non-neighbor valleys. The attached pic should help. I am able to find the green circle, but I also need to find the 2 red circles. Note that the global valley (green circle) is not always in the middle. It can be the first element of the array "min_angles", or it can be the last one. In the picture below, it's number 4.

2 Comments

Make it easy for us to help you, not hard. Attach your variables "angle" and "signal" in a .mat or text file. That way we'll have something to work with and won't have to try to create data on our own.
save('answers.mat', 'angle', 'signal');
If you have any more questions, then attach your data and code to read it in with the paperclip icon after you read this:
Please see attached. Thank you!

Sign in to comment.

 Accepted Answer

To find specific valleys, one option might be to use the MinPeakProminence value. The prominences are the tthird output returned by findpeaks, so you can look at those to see iif that option would work.
Another option is to get the peak values and use the mink (or maxk) function to return thee three lowest values (since that appears to be what the green circle and the red circles represent). Return both outputs from mink beecausee the second will be the index values of the minima (or maxima, depending on you you have the results returned), and you can use those indices with the returned values for the peaks and their locations to isolate the ones you want.
This is based on the figure you posted.
.
EDIT — (30 Jan 2025 at 21:33)
Example —
a = linspace(0,360).';
s = sin(2*pi*a/360*6)-sin(2*pi*a/720);
[vlys,vlocs] = findpeaks(-s)
vlys = 6×1
1.3727 1.7946 1.9796 1.9212 1.6080 1.1255
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
vlocs = 6×1
14 30 46 63 79 96
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
[minpks, minpksidx] = mink(-vlys,3)
minpks = 3×1
-1.9796 -1.9212 -1.7946
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
minpksidx = 3×1
3 4 2
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
figure
plot(a, s)
hold on
plot(a(vlocs(minpksidx)), -vlys(minpksidx), 'rs')
hold off
grid
ylim([-2.5 1])
.

8 Comments

Aram Zeytunyan
Aram Zeytunyan on 31 Jan 2025
Edited: Aram Zeytunyan on 31 Jan 2025
This is extemely helpful, thank you for the help. Let me try to apply it in my data analysis and get back to you. But I am suspecting that it will work for some cases and won't work for the others. For example, in the case below it won't allow to find the three green circles (which is my goal). One of the valleys found this way will probably be the red circle.
My pleasure!
The findpeaks function has several options, among which is MinPeakDistance and.MiinPeakHeight
What are your criteria for identifying the peaks? Understanding that would help significantly.
The goal is to find the lowest valley, skip the ones that are neighboring the lowest valley and find the other two. Again, the mink function would be ideal if these curves were always "perfect", meaning that the main (lower) valleys are followed by secondary (higher) valleys, but unfortunately that's not the case with the experimental data. I should be able to use MinPeakDistance somehow, but at this point I'm not sure how to do that.
If you want to find and isolate the three lowest valleys, mink should be able to do that. The peaks in my earlier eexample were vicinal, however that is not required.
Example —
a = linspace(0,360).';
angle_divisor = randi([90 360])
angle_divisor = 140
s = sin(2*pi*a/360*6)-sin(2*pi*a/angle_divisor);
[vlys,vlocs] = findpeaks(-s)
vlys = 6×1
1.9156 -0.0009 1.9123 0.4391 1.2924 1.3040
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
vlocs = 6×1
13 30 47 62 80 95
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
[minpks, minpksidx] = mink(-vlys,3)
minpks = 3×1
-1.9156 -1.9123 -1.3040
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
minpksidx = 3×1
1 3 6
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
figure
plot(a, s)
hold on
plot(a(vlocs(minpksidx)), -vlys(minpksidx), 'rs')
hold off
grid
ylim([-2.5 max(ylim)])
.
I came up with the solution in the attached file. I know it's grossly non-optimized, but works for now. Thanks again for your help. The attached data file is a good example why mink won't do the right job every time.
Considering the information in your original question, mink seems an appropriate approach.
I still don’t understand what your criteria are for identifying the peaks and valleys of interest, and your code doesn’t illuminate that.
clear
close all
% [fn,pn] = uigetfile('*.csv;*.mat;*.xlsx*','Select saved data file','MultiSelect', 'on');
% [fn,pn] = uigetfile('*.csv;*.mat;*.xlsx*','Select saved data file');
% cd(pn);
files = dir('*.csv')
files = struct with fields:
name: 'testdata1.csv' folder: '/users/mss.system.p2qdd' date: '31-Jan-2025 23:37:57' bytes: 25305 isdir: 0 datenum: 7.3965e+05
k = 1;
% for k = 1:numel(cellstr(fn))
% if numel(cellstr(fn))>=2
% loaddata = importdata([pn,char(fn(k))]);
% signal(:,k) = loaddata(:,2);
% else
% loaddata = importdata([pn,char(fn)]);
% signal = loaddata(:,2);
% end
% end
% if numel(cellstr(fn))>=2
% for k = 1:numel(cellstr(fn))
% loaddata = importdata([pn,char(fn(k))]);
loaddata = readmatrix(files.name)
loaddata = 834×2
0.3530 133.2857 0.7844 134.3363 1.2157 136.3152 1.6471 137.6488 2.0785 139.0422 2.5099 141.3151 2.9412 142.2948 3.3726 144.7676 3.8040 146.6495 4.2354 147.9877
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
signal(:,k) = loaddata(:,2);
angle(:,k) = loaddata(:,1);
plot(angle(:,k),signal(:,k),'LineWidth',1);
hold on;
% end
% else
% loaddata = importdata([pn,char(fn)]);
% angle = loaddata(:,1);
% signal = loaddata(:,2);
% plot(angle,signal)
% hold on;
% end
% angle = loaddata(:,1);
% app.angle = angle;
% app.signal = signal;
%
% signal_test = app.signal;
% size(signal_test,2);
% numel(cellstr(fn));
% plot(angle,signal)
% hold on
% [globalmin,globalminidx] = min(signal)
% globalminangle = angle(signal == globalmin)
% globalminangle1 = angle(globalminidx)
% plot(angle,signal)
f = fit(angle,signal,'sin9');
yfitted = feval(f,angle);
plot(angle,yfitted,'r')
xlabel('angle (deg)')
ylabel('signal (mV)')
xlim([0 360])
hold on;
[ypk0,idx0] = findpeaks(yfitted);
peaks = signal(idx0);
max_angles = angle(idx0);
yfittedinv = max(yfitted) - yfitted;
% plot(app.UIAxes_2,angle,yfitted,'r','LineWidth',1);
[ypk,idx] = findpeaks(yfittedinv);
% [ypk1,idx1] = mink(ypk,3);
% valleys = yfitted(idx);
valleys = signal(idx);
min_angles = angle(idx);
minangle = min_angles(valleys == min(valleys));
minvalley = valleys(valleys == min(valleys));
idx2 = idx(valleys == min(valleys));
idx3 = find(idx == idx2);
if rem(idx3, 2) == 0 % global min index is even
idxv = [2 4 6];
if yfitted(2)-yfitted(1)>0 % curve is ascending
idxp = [2 3 4 5 6 1];
else % curve is descending
idxp = [1 2 3 4 5 6];
end
else % global min index is odd
idxv = [1 3 5];
if yfitted(2)-yfitted(1)>0 % curve is ascending
idxp = [1 2 3 4 5 6];
else % curve is descending
idxp = [6 1 2 3 4 5];
end
end
% plot(max_angles(idxp),peaks(idxp),'o','MarkerSize',12)
% hold on;
min3_angles = min_angles(idxv);
valleys3 = valleys(idxv);
plot(min3_angles,valleys3,'x','MarkerSize',12)
hold on
% plot(minangle,minvalley,'.r','MarkerSize',30)
% hold on
newpeaks = peaks(idxp);
newmax_angles = max_angles(idxp);
plot(newmax_angles(1:2),newpeaks(1:2),'.r','MarkerSize',15)
hold on;
plot(newmax_angles(3:4),newpeaks(3:4),'.g','MarkerSize',15)
hold on;
plot(newmax_angles(5:6),newpeaks(5:6),'.b','MarkerSize',15)
hold on;
newpeaks1 = (newpeaks(1:2:end) + newpeaks(2:2:end))/2;
delta = newpeaks1-valleys(idxv);
idx4 = find(delta == min(delta));
plot(minangle,minvalley,'.r','MarkerSize',30)
hold on
plot(min3_angles(idx4),valleys3(idx4),'.g','MarkerSize',30)
hold on
xline(min3_angles(idx4),'-g', LineWidth = 1.5);
hold on
yline(valleys3(idx4),'-g', LineWidth = 1.5);
.
If you describe the problem you want to solve, it would likely be straightforward to devise a more efficient solution.
Lackng that description, I’ll delete my Answer in a few days.
.
I need to find 3x valleys out of 6. The starting/reference point is the lowest valley (the large red dot in the plot, which is easy to find). The other two cannot be the neighboring valleys as that's forbidden by physics. mink will find valleys labeled 2, 4, and 5. I need to find valleys 2,4, and 6.
I am still not certain what you want, since I am still uncertain of the criteria.
See if this works —
loaddata = readmatrix('testdata.csv');
angle = loaddata(:,1);
signal = loaddata(:,2);
[pks,plocs,pprm] = findpeaks(signal, MinPeakProminence=10);
[vys,vlocs,vprm] = findpeaks(-signal, MinPeakProminence=10);
[minvly,minidx] = min(-vys); % Minimum Valley & Associated Valley Index
idxrng = [max(1,(minidx-2)) minidx min((minidx+2),numel(vlocs))] % Valley Indices ±2 Positions From Minimum
idxrng = 1×3
2 4 6
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Results = table(angle(vlocs(idxrng)), signal(vlocs(idxrng)), VariableNames=["Angle","Value"])
Results = 3x2 table
Angle Value ______ ______ 102.16 83.776 226.39 77.624 343.3 81.332
figure
plot(angle, signal, DisplayName='Signal')
hold on
plot(angle(plocs), pks, 'vg', DisplayName='Peaks')
plot(angle(vlocs), -vys, '^r', DisplayName='Valleys')
plot(angle(vlocs(idxrng)), signal(vlocs(idxrng)), 'ms', MarkerSize=12, DisplayName='Selected')
hold off
grid
xlabel('Angle (°)')
ylabel('Signal (mV)')
legend(Location='best')
The ‘idxrng’ calculation contains a ‘fail safe’ to prevent finding indices outside the allotted range.
.

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2024b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!