ngscopeclient v0.1
PeakDetectionFilter.h
Go to the documentation of this file.
1/***********************************************************************************************************************
2* *
3* libscopehal *
4* *
5* Copyright (c) 2012-2025 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 int64_t m_x;
51 float m_y;
52 float m_fwhm;
53};
54
56{
57public:
59 virtual ~PeakDetector();
60
61 const std::vector<Peak>& GetPeaks()
62 { return m_peaks; }
63
64 template<class T>
65 __attribute__((noinline))
66 void FindPeaks(
67 T* cap,
68 int64_t max_peaks,
69 float search_hz,
70 bool yUnitIsDB,
71 [[maybe_unused]] vk::raii::CommandBuffer& cmdBuf,
72 [[maybe_unused]] std::shared_ptr<QueueHandle> queue)
73 {
74 //input must be analog
75 AssertTypeIsAnalogWaveform(cap);
76
77 //double start = GetTime();
78
79 size_t nouts = cap->size();
80 if( (max_peaks == 0) || (nouts < 2) )
81 m_peaks.clear();
82 else
83 {
84 //TODO: use the GPU
85 cap->PrepareForCpuAccess();
86
87 std::vector<Peak> peaks;
88
89 //Get peak search width in bins
90 //(assume bins are equal size, this should get us close)
91 int64_t binsize = GetOffsetScaled(cap, 1) - GetOffsetScaled(cap, 0);
92 int64_t search_bins = ceil(search_hz / binsize);
93 int64_t search_rad = search_bins/2;
94 search_rad = std::max(search_rad, (int64_t)1);
95
96 float baseline = Filter::GetMinVoltage(cap);
97
98 //Find peaks (TODO: can we vectorize/multithread this?)
99 ssize_t nend = nouts-1;
100 size_t minpeak = 10; //Skip this many bins at left to avoid false positives on the DC peak
101 //(TODO: this only makes sense for FFT)
102 for(ssize_t i=minpeak; i<(ssize_t)nouts; i++)
103 {
104 //Locate the peak
105 ssize_t left = std::max((ssize_t)minpeak, (ssize_t)(i - search_rad));
106 ssize_t right = std::min((ssize_t)(i + search_rad), (ssize_t)nend);
107
108 float target = cap->m_samples[i];
109 bool is_peak = true;
110 for(ssize_t j=left; j<=right; j++)
111 {
112 if(i == j)
113 continue;
114 if(cap->m_samples[j] >= target)
115 {
116 //Something higher is to our right.
117 //It's higher than anything from left to j. This makes it a candidate peak.
118 //Restart our search from there.
119 if(j > i)
120 i = j-1;
121
122 is_peak = false;
123 break;
124 }
125 }
126 if(!is_peak)
127 continue;
128
129 //Do a weighted average of our immediate neighbors to fine tune our position
130 ssize_t fine_rad = 10;
131 left = std::max((ssize_t)1, i - fine_rad);
132 right = std::min(i + fine_rad, (ssize_t)nouts-1);
133 //LogDebug("peak range: %zu, %zu\n", left, right);
134 double total = 0;
135 double count = 0;
136 for(ssize_t j=left; j<=right; j++)
137 {
138 total += GetSampleTimesIndex(cap, j);
139 count += cap->m_samples[j];
140 }
141 ssize_t peak_location = round(total / count);
142 //LogDebug("Moved peak from %zu to %zd\n", (size_t)cap->m_offsets[i], peak_location);
143
144 //Move left and right from the peak until we get half magnitude
145 //If Y axis is dB, we want to be half *magnitude* not half dB
146 float hmtarget;
147 if(yUnitIsDB)
148 hmtarget = target - 3;
149 else
150 hmtarget = (target - baseline)/2 + baseline;
151 ssize_t hmleft = target;
152 ssize_t hmright = target;
153 for(ssize_t j=i; j >= 0; j--)
154 {
155 //TODO: interpolate
156 if(cap->m_samples[j] <= hmtarget)
157 {
158 hmleft = j;
159 break;
160 }
161 }
162 for(ssize_t j=i; j < (ssize_t)nouts; j++)
163 {
164 //TODO: interpolate
165 if(cap->m_samples[j] <= hmtarget)
166 {
167 hmright = j;
168 break;
169 }
170 }
171 float fwhm = GetOffsetScaled(cap, hmright) - GetOffsetScaled(cap, hmleft);
172
173 peaks.push_back(Peak(peak_location, target, fwhm));
174
175 //We know we're the highest point until at least i+search_rad.
176 //Don't bother searching those points.
177 i += (search_rad-1);
178 }
179
180 //Sort the peak table and pluck out the requested count
181 std::sort(peaks.rbegin(), peaks.rend(), std::less<Peak>());
182 m_peaks.clear();
183 for(size_t i=0; i<(size_t)max_peaks && i<peaks.size(); i++)
184 {
185 //TODO: Find FWHM of only the target peaks
186 m_peaks.push_back(peaks[i]);
187 }
188 }
189
190 //double dt = GetTime() - start;
191 //LogDebug("delta = %.3f ms\n", dt * 1000);
192 }
193
194protected:
195 std::vector<Peak> m_peaks;
196
197 AcceleratorBuffer<float> m_filteredInput;
198 AcceleratorBuffer<float> m_peakCoefficients;
199
200 ComputePipeline m_peakFirComputePipeline;
201};
202
207 : public Filter
208 , public PeakDetector
209{
210public:
211 PeakDetectionFilter(const std::string& color, Category cat);
212 virtual ~PeakDetectionFilter();
213
214protected:
215
216 template<class T>
217 void FindPeaks(T* cap, vk::raii::CommandBuffer& cmdBuf, std::shared_ptr<QueueHandle> queue)
218 {
219 PeakDetector::FindPeaks(
220 cap,
221 m_parameters[m_numpeaksname].GetIntVal(),
222 m_parameters[m_peakwindowname].GetFloatVal(),
223 GetYAxisUnits(0).IsLogarithmic(),
224 cmdBuf,
225 queue);
226 }
227
228 std::string m_numpeaksname;
229 std::string m_peakwindowname;
230};
231
232#endif
233
Encapsulates a Vulkan compute pipeline and all necessary resources to use it.
Definition: ComputePipeline.h:55
Abstract base class for all filter graph blocks which are not physical instrument channels.
Definition: Filter.h:95
static float GetMinVoltage(SparseAnalogWaveform *s, UniformAnalogWaveform *u)
Gets the lowest voltage of a waveform.
Definition: Filter.h:480
Category
Category the filter should be displayed under in the GUI.
Definition: Filter.h:108
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:209
Definition: PeakDetectionFilter.h:56
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:741