ngscopeclient v0.2.1
Loading...
Searching...
No Matches
PeakDetectionFilter.h
Go to the documentation of this file.
1/***********************************************************************************************************************
2* *
3* libscopehal *
4* *
5* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors *
6* All rights reserved. *
7* *
8* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
9* following conditions are met: *
10* *
11* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
12* following disclaimer. *
13* *
14* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
15* following disclaimer in the documentation and/or other materials provided with the distribution. *
16* *
17* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
18* derived from this software without specific prior written permission. *
19* *
20* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
21* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
22* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
23* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
24* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
25* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
26* POSSIBILITY OF SUCH DAMAGE. *
27* *
28***********************************************************************************************************************/
29
35#ifndef PeakDetectionFilter_h
36#define PeakDetectionFilter_h
37
38class Peak
39{
40public:
41 Peak(int64_t x, float y, float fwhm)
42 : m_x(x)
43 , m_y(y)
44 , m_fwhm(fwhm)
45 {}
46
47 bool operator<(const Peak& rhs) const
48 { return (m_y < rhs.m_y); }
49
50 //X coordinate is in base units, not scaled by timebase, since we do sub-sample interpolation
51 int64_t m_x;
52
53 float m_y;
54 float m_fwhm;
55};
56
58{
59public:
61 virtual ~PeakDetector();
62
63 const std::vector<Peak>& GetPeaks()
64 { return m_peaks; }
65
66 template<class T>
67 __attribute__((noinline))
68 void FindPeaks(
69 T* cap,
70 int64_t max_peaks,
71 float search_hz,
72 bool yUnitIsDB,
73 [[maybe_unused]] vk::raii::CommandBuffer& cmdBuf,
74 [[maybe_unused]] std::shared_ptr<QueueHandle> queue)
75 {
76 //input must be analog
77 AssertTypeIsAnalogWaveform(cap);
78
79 //double start = GetTime();
80
81 size_t nouts = cap->size();
82 if( (max_peaks == 0) || (nouts < 2) )
83 m_peaks.clear();
84 else
85 {
86 //TODO: use the GPU
87 cap->PrepareForCpuAccess();
88
89 std::vector<Peak> peaks;
90
91 //Get peak search width in bins
92 //(assume bins are equal size, this should get us close)
93 int64_t binsize = GetOffsetScaled(cap, 1) - GetOffsetScaled(cap, 0);
94 int64_t search_bins = ceil(search_hz / binsize);
95 int64_t search_rad = search_bins/2;
96 search_rad = std::max(search_rad, (int64_t)1);
97
98 float baseline = Filter::GetMinVoltage(cap);
99
100 //Find peaks (TODO: can we vectorize/multithread this?)
101 ssize_t nend = nouts-1;
102 size_t minpeak = 10; //Skip this many bins at left to avoid false positives on the DC peak
103 //(TODO: this only makes sense for FFT)
104 for(ssize_t i=minpeak; i<(ssize_t)nouts; i++)
105 {
106 //Locate the peak
107 ssize_t left = std::max((ssize_t)minpeak, (ssize_t)(i - search_rad));
108 ssize_t right = std::min((ssize_t)(i + search_rad), (ssize_t)nend);
109
110 float target = cap->m_samples[i];
111 bool is_peak = true;
112 for(ssize_t j=left; j<=right; j++)
113 {
114 if(i == j)
115 continue;
116 if(cap->m_samples[j] >= target)
117 {
118 //Something higher is to our right.
119 //It's higher than anything from left to j. This makes it a candidate peak.
120 //Restart our search from there.
121 if(j > i)
122 i = j-1;
123
124 is_peak = false;
125 break;
126 }
127 }
128 if(!is_peak)
129 continue;
130
131 //Quadratic interpolate peak position
132 //https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
133 float alpha = cap->m_samples[i-1];
134 float beta = cap->m_samples[i];
135 float gamma = cap->m_samples[i+1];
136 float p = 0.5 * (alpha - gamma) / (alpha - 2*beta + gamma);
138 (i * cap->m_timescale) +
139 static_cast<int64_t>(round(p * cap->m_timescale)) +
140 cap->m_triggerPhase;
141
142 //Interpolate peak magnitude
143 float lerpMag = beta - 0.25 * (alpha - gamma) * p;
144
145 //Move left and right from the peak until we get half magnitude
146 //If Y axis is dB, we want to be half *magnitude* not half dB
147 float hmtarget;
148 if(yUnitIsDB)
149 hmtarget = lerpMag - 3;
150 else
151 hmtarget = (lerpMag - baseline)/2 + baseline;
152 ssize_t hmleft = i;
153 ssize_t hmright = i;
154 for(ssize_t j=i; j >= 0; j--)
155 {
156 //TODO: interpolate
157 if(cap->m_samples[j] <= hmtarget)
158 {
159 hmleft = j;
160 break;
161 }
162 }
163 for(ssize_t j=i; j < (ssize_t)nouts; j++)
164 {
165 //TODO: interpolate
166 if(cap->m_samples[j] <= hmtarget)
167 {
168 hmright = j;
169 break;
170 }
171 }
173
174 peaks.push_back(Peak(peak_location, lerpMag, fwhm));
175
176 //We know we're the highest point until at least i+search_rad.
177 //Don't bother searching those points.
178 i += (search_rad-1);
179 }
180
181 //Sort the peak table and pluck out the requested count
182 std::sort(peaks.rbegin(), peaks.rend(), std::less<Peak>());
183 m_peaks.clear();
184 for(size_t i=0; i<(size_t)max_peaks && i<peaks.size(); i++)
185 {
186 //TODO: Find FWHM of only the target peaks
187 m_peaks.push_back(peaks[i]);
188 }
189 }
190
191 //double dt = GetTime() - start;
192 //LogDebug("delta = %.3f ms\n", dt * 1000);
193 }
194
195protected:
196 std::vector<Peak> m_peaks;
197
198 AcceleratorBuffer<float> m_filteredInput;
199 AcceleratorBuffer<float> m_peakCoefficients;
200
201 ComputePipeline m_peakFirComputePipeline;
202};
203
208 : public Filter
209 , public PeakDetector
210{
211public:
212 PeakDetectionFilter(const std::string& color, Category cat);
213 virtual ~PeakDetectionFilter();
214
215protected:
216
217 template<class T>
218 void FindPeaks(T* cap, vk::raii::CommandBuffer& cmdBuf, std::shared_ptr<QueueHandle> queue)
219 {
220 PeakDetector::FindPeaks(
221 cap,
222 m_numpeaks.GetIntVal(),
223 m_peakwindow.GetFloatVal(),
224 GetYAxisUnits(0).IsLogarithmic(),
225 cmdBuf,
226 queue);
227 }
228
229 FilterParameter& m_numpeaks;
230 FilterParameter& m_peakwindow;
231};
232
233#endif
234
Definition AcceleratorBuffer.h:204
Encapsulates a Vulkan compute pipeline and all necessary resources to use it.
Definition ComputePipeline.h:55
A parameter to a filter.
Definition FilterParameter.h:86
int64_t GetIntVal() const
Returns the value of the parameter interpreted as an integer.
Definition FilterParameter.h:119
float GetFloatVal() const
Returns the value of the parameter interpreted as a floating point number.
Definition FilterParameter.h:144
Abstract base class for all filter graph blocks which are not physical instrument channels.
Definition Filter.h:105
static float GetMinVoltage(SparseAnalogWaveform *s, UniformAnalogWaveform *u)
Gets the lowest voltage of a waveform.
Definition Filter.h:530
Category
Category the filter should be displayed under in the GUI.
Definition Filter.h:118
virtual Unit GetYAxisUnits(size_t stream)
Returns the Y axis unit for a specified stream.
Definition InstrumentChannel.h:140
A filter that does peak detection.
Definition PeakDetectionFilter.h:210
Definition PeakDetectionFilter.h:58
Definition PeakDetectionFilter.h:39
int64_t GetOffsetScaled(T *wfm, size_t i)
Returns the offset of a sample from the start of the waveform, in X axis units.
Definition Waveform.h:841