diff --git a/doc/img/Spectrum_Markers_dialog.png b/doc/img/Spectrum_Markers_dialog.png index 8206bf399..219b61962 100644 Binary files a/doc/img/Spectrum_Markers_dialog.png and b/doc/img/Spectrum_Markers_dialog.png differ diff --git a/doc/img/Spectrum_Markers_dialog.xcf b/doc/img/Spectrum_Markers_dialog.xcf index bbd39f3ed..877a8dfc6 100644 Binary files a/doc/img/Spectrum_Markers_dialog.xcf and b/doc/img/Spectrum_Markers_dialog.xcf differ diff --git a/doc/img/Spectrum_Markers_dialog_hist.png b/doc/img/Spectrum_Markers_dialog_hist.png index bfc2ef8fa..f38bfa1a7 100644 Binary files a/doc/img/Spectrum_Markers_dialog_hist.png and b/doc/img/Spectrum_Markers_dialog_hist.png differ diff --git a/doc/img/Spectrum_Markers_dialog_hist.xcf b/doc/img/Spectrum_Markers_dialog_hist.xcf index 6794faf3d..4f0878175 100644 Binary files a/doc/img/Spectrum_Markers_dialog_hist.xcf and b/doc/img/Spectrum_Markers_dialog_hist.xcf differ diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 803e468c2..bb6f5f21b 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -192,6 +192,7 @@ set(sdrbase_SOURCES util/morse.cpp util/openaip.cpp util/osndb.cpp + util/peakfinder.cpp util/planespotters.cpp util/png.cpp util/prettyprint.cpp @@ -415,6 +416,7 @@ set(sdrbase_HEADERS util/movingaverage.h util/openaip.h util/osndb.h + util/peakfinder.h util/planespotters.h util/png.h util/prettyprint.h diff --git a/sdrbase/dsp/spectrumsettings.cpp b/sdrbase/dsp/spectrumsettings.cpp index 93564e925..3b9b34b8f 100644 --- a/sdrbase/dsp/spectrumsettings.cpp +++ b/sdrbase/dsp/spectrumsettings.cpp @@ -78,6 +78,7 @@ void SpectrumSettings::resetToDefaults() m_measurementHighlight = true; m_measurementsPosition = PositionBelow; m_measurementPrecision = 1; + m_findHistogramPeaks = false; } QByteArray SpectrumSettings::serialize() const @@ -128,6 +129,7 @@ QByteArray SpectrumSettings::serialize() const s.writeS32(44, (int)m_measurementsPosition); s.writeS32(45, m_measurementPrecision); s.writeS32(46, m_measurementCenterFrequencyOffset); + s.writeBool(47, m_findHistogramPeaks); s.writeS32(100, m_histogramMarkers.size()); for (int i = 0; i < m_histogramMarkers.size(); i++) { @@ -239,6 +241,7 @@ bool SpectrumSettings::deserialize(const QByteArray& data) d.readS32(44, (int*)&m_measurementsPosition, (int)PositionBelow); d.readS32(45, &m_measurementPrecision, 1); d.readS32(46, &m_measurementCenterFrequencyOffset, 0); + d.readBool(47, &m_findHistogramPeaks, false); int histogramMarkersSize; d.readS32(100, &histogramMarkersSize, 0); diff --git a/sdrbase/dsp/spectrumsettings.h b/sdrbase/dsp/spectrumsettings.h index 71bd6bfe5..3be5836f3 100644 --- a/sdrbase/dsp/spectrumsettings.h +++ b/sdrbase/dsp/spectrumsettings.h @@ -117,6 +117,7 @@ public: QList m_histogramMarkers; QList m_waterfallMarkers; QList m_annoationMarkers; + bool m_findHistogramPeaks; MarkersDisplay m_markersDisplay; QList m_calibrationPoints; bool m_useCalibration; @@ -149,6 +150,7 @@ public: QList& getHistogramMarkers() { return m_histogramMarkers; } QList& getWaterfallMarkers() { return m_waterfallMarkers; } + bool getHistogramFindPeaks() { return m_findHistogramPeaks; } static int getAveragingMaxScale(AveragingMode averagingMode); //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 static int getAveragingValue(int averagingIndex, AveragingMode averagingMode); diff --git a/sdrbase/util/peakfinder.cpp b/sdrbase/util/peakfinder.cpp new file mode 100644 index 000000000..a2c0f8a56 --- /dev/null +++ b/sdrbase/util/peakfinder.cpp @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 F4EXB // +// written by Edouard Griffiths // +// // +// Find peaks in a series of real values // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "peakfinder.h" + +PeakFinder::PeakFinder() : + m_prevValue(0.0), + m_index(0) +{} + +PeakFinder::~PeakFinder() +{} + +void PeakFinder::init(Real value) +{ + m_prevValue = value; + m_peaks.clear(); + m_index = 0; +} + +void PeakFinder::push(Real value, bool last) +{ + Real diff = value - m_prevValue; + + if (diff < 0) { + m_peaks.push_back({m_prevValue, m_index}); + } else if (last) { + m_peaks.push_back({value, m_index}); + } + + m_prevValue = value; + m_index++; +} + +void PeakFinder::sortPeaks() +{ + std::sort(m_peaks.rbegin(), m_peaks.rend()); // descending order of values +} diff --git a/sdrbase/util/peakfinder.h b/sdrbase/util/peakfinder.h new file mode 100644 index 000000000..64feeef40 --- /dev/null +++ b/sdrbase/util/peakfinder.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 F4EXB // +// written by Edouard Griffiths // +// // +// Find peaks in a series of real values // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_PEAKFINDER_H_ +#define SDRBASE_UTIL_PEAKFINDER_H_ + +#include +#include + +#include "dsp/dsptypes.h" +#include "export.h" + +class SDRBASE_API PeakFinder { +public: + PeakFinder(); + ~PeakFinder(); + + void init(Real value); + void push(Real value, bool last=false); + void sortPeaks(); + const std::vector>& getPeaks() { return m_peaks; } + +private: + Real m_prevValue; + int m_index; + std::vector> m_peaks; //!< index, value + +}; + + +#endif // SDRBASE_UTIL_PEAKFINDER_H_ diff --git a/sdrgui/gui/glspectrum.h b/sdrgui/gui/glspectrum.h index 2e784e5fe..9b50b5102 100644 --- a/sdrgui/gui/glspectrum.h +++ b/sdrgui/gui/glspectrum.h @@ -95,6 +95,8 @@ public: void updateMarkersDisplay() { m_spectrum->updateMarkersDisplay(); } void updateCalibrationPoints() { m_spectrum->updateCalibrationPoints(); } SpectrumSettings::MarkersDisplay& getMarkersDisplay() { return m_spectrum->getMarkersDisplay(); } + bool& getHistogramFindPeaks() { return m_spectrum->getHistogramFindPeaks(); } + void setHistogramFindPeaks(bool value) { m_spectrum->setHistogramFindPeaks(value); } void setMarkersDisplay(SpectrumSettings::MarkersDisplay markersDisplay) { m_spectrum->setMarkersDisplay(markersDisplay); } QList& getCalibrationPoints() { return m_spectrum->getCalibrationPoints(); } void setCalibrationPoints(const QList& calibrationPoints) { m_spectrum->setCalibrationPoints(calibrationPoints); } diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 53fbae5df..2ea4ba314 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -463,6 +463,7 @@ void GLSpectrumGUI::on_markers_clicked(bool checked) m_glSpectrum->getWaterfallMarkers(), m_glSpectrum->getAnnotationMarkers(), m_glSpectrum->getMarkersDisplay(), + m_glSpectrum->getHistogramFindPeaks(), m_calibrationShiftdB, this ); diff --git a/sdrgui/gui/glspectrumview.cpp b/sdrgui/gui/glspectrumview.cpp index ea37d0a9e..fd00b0a58 100644 --- a/sdrgui/gui/glspectrumview.cpp +++ b/sdrgui/gui/glspectrumview.cpp @@ -48,6 +48,7 @@ const float GLSpectrumView::m_annotationMarkerHeight = 20.0f; GLSpectrumView::GLSpectrumView(QWidget* parent) : QOpenGLWidget(parent), m_markersDisplay(SpectrumSettings::MarkersDisplaySpectrum), + m_histogramFindPeaks(false), m_cursorState(CSNormal), m_cursorChannel(0), m_spectrumVis(nullptr), @@ -1369,7 +1370,7 @@ void GLSpectrumView::paintGL() } // paint current spectrum line on top of histogram - if ((m_displayCurrent) && m_currentSpectrum) + if (m_displayCurrent && m_currentSpectrum) { Real bottom = -m_powerRange; GLfloat *q3; @@ -1403,6 +1404,10 @@ void GLSpectrumView::paintGL() } { + if (m_histogramFindPeaks) { + m_peakFinder.init(m_currentSpectrum[0]); + } + // Draw line q3 = m_q3FFT.m_array; for (int i = 0; i < m_nbBins; i++) @@ -1418,6 +1423,9 @@ void GLSpectrumView::paintGL() q3[2*i] = (Real) i; q3[2*i+1] = v; + if (m_histogramFindPeaks && (i > 0)) { + m_peakFinder.push(m_currentSpectrum[i], i == m_nbBins - 1); + } } QVector4D color; @@ -1427,12 +1435,22 @@ void GLSpectrumView::paintGL() color = QVector4D(1.0f, 1.0f, 0.25f, (float) m_displayTraceIntensity / 100.0f); } m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_nbBins); + + if (m_histogramFindPeaks) { + m_peakFinder.sortPeaks(); + } } } - if (m_markersDisplay & SpectrumSettings::MarkersDisplaySpectrum) { + if (m_displayCurrent && m_currentSpectrum && (m_markersDisplay & SpectrumSettings::MarkersDisplaySpectrum)) + { + if (m_histogramFindPeaks) { + updateHistogramPeaks(); + } + drawSpectrumMarkers(); } + if (m_markersDisplay & SpectrumSettings::MarkersDisplayAnnotations) { drawAnnotationMarkers(); } @@ -1724,7 +1742,7 @@ void GLSpectrumView::paintGL() } m_mutex.unlock(); -} +} // paintGL // Hightlight power band for SFDR void GLSpectrumView::drawPowerBandMarkers(float max, float min, const QVector4D &color) @@ -3289,7 +3307,7 @@ void GLSpectrumView::updateHistogramMarkers() if (i > 0) { int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency; - m_histogramMarkers.back().m_deltaFrequencyStr = displayScaled( + m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled( deltaFrequency, 'f', getPrecision(deltaFrequency/m_sampleRate), @@ -3297,7 +3315,7 @@ void GLSpectrumView::updateHistogramMarkers() float power0 = m_linear ? m_histogramMarkers.at(0).m_power * (m_useCalibration ? m_calibrationGain : 1.0f) : CalcDb::dbPower(m_histogramMarkers.at(0).m_power) + (m_useCalibration ? m_calibrationShiftdB : 0.0f); - m_histogramMarkers.back().m_deltaPowerStr = displayPower( + m_histogramMarkers[i].m_deltaPowerStr = displayPower( powerI - power0, m_linear ? 'e' : 'f', m_linear ? 3 : 1); @@ -3305,6 +3323,57 @@ void GLSpectrumView::updateHistogramMarkers() } } +void GLSpectrumView::updateHistogramPeaks() +{ + int j = 0; + for (int i = 0; i < m_histogramMarkers.size(); i++) + { + if (j >= (int) m_peakFinder.getPeaks().size()) { + break; + } + + int fftBin = m_peakFinder.getPeaks()[j].second; + Real power = m_peakFinder.getPeaks()[j].first; + // qDebug("GLSpectrumView::updateHistogramPeaks: %d %d %f", j, fftBin, power); + + if ((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) || + ((m_histogramMarkers.at(i).m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) && + (m_histogramMarkers.at(i).m_holdReset || (power > m_histogramMarkers.at(i).m_powerMax)))) + { + float binSize = m_frequencyScale.getRange() / m_nbBins; + m_histogramMarkers[i].m_fftBin = fftBin; + m_histogramMarkers[i].m_frequency = m_frequencyScale.getRangeMin() + binSize*fftBin; + m_histogramMarkers[i].m_point.rx() = binSize*fftBin / m_frequencyScale.getRange(); + + if (i == 0) + { + m_histogramMarkers[i].m_frequencyStr = displayScaled( + m_histogramMarkers[i].m_frequency, + 'f', + getPrecision((m_centerFrequency*1000)/m_sampleRate), + false + ); + } + else + { + int64_t deltaFrequency = m_histogramMarkers.at(i).m_frequency - m_histogramMarkers.at(0).m_frequency; + m_histogramMarkers[i].m_deltaFrequencyStr = displayScaled( + deltaFrequency, + 'f', + getPrecision(deltaFrequency/m_sampleRate), + true + ); + } + } + else + { + continue; + } + + j++; + } +} + void GLSpectrumView::updateWaterfallMarkers() { for (int i = 0; i < m_waterfallMarkers.size(); i++) diff --git a/sdrgui/gui/glspectrumview.h b/sdrgui/gui/glspectrumview.h index ddc7180e7..e5dcd64b5 100644 --- a/sdrgui/gui/glspectrumview.h +++ b/sdrgui/gui/glspectrumview.h @@ -43,6 +43,7 @@ #include "util/incrementalarray.h" #include "util/message.h" #include "util/colormap.h" +#include "util/peakfinder.h" class QOpenGLShaderProgram; class MessageQueue; @@ -214,11 +215,14 @@ public: QList& getAnnotationMarkers() { return m_annotationMarkers; } void setAnnotationMarkers(const QList& annotationMarkers); void updateHistogramMarkers(); + void updateHistogramPeaks(); void updateWaterfallMarkers(); void updateAnnotationMarkers(); void updateMarkersDisplay(); void updateCalibrationPoints(); - SpectrumSettings::MarkersDisplay& getMarkersDisplay() { return m_markersDisplay; } + SpectrumSettings::MarkersDisplay& getMarkersDisplay() { return m_markersDisplay; } + bool& getHistogramFindPeaks() { return m_histogramFindPeaks; } + void setHistogramFindPeaks(bool value) { m_histogramFindPeaks = value; } void setMarkersDisplay(SpectrumSettings::MarkersDisplay markersDisplay); QList& getCalibrationPoints() { return m_calibrationPoints; } void setCalibrationPoints(const QList& calibrationPoints); @@ -258,6 +262,8 @@ private: QList m_sortedAnnotationMarkers; QList m_visibleAnnotationMarkers; SpectrumSettings::MarkersDisplay m_markersDisplay; + bool m_histogramFindPeaks; + PeakFinder m_peakFinder; QList m_calibrationPoints; CursorState m_cursorState; diff --git a/sdrgui/gui/spectrummarkers.md b/sdrgui/gui/spectrummarkers.md index d845a2eb6..9f10c866b 100644 --- a/sdrgui/gui/spectrummarkers.md +++ b/sdrgui/gui/spectrummarkers.md @@ -72,6 +72,11 @@ This combo lets you select the type of marker: Use this slider to adjust the power position of the marker. The units are in dB irrespective of the linear or log set of the spectrum display. + +

11. Peak detection

+ +Activates or de-activates peak detection. With peak detection engaged markers with type "Cur" or "Max" will be automatically set to frequency (bin) of maximum power. The first marker in index order with "Cur" or "Max" will be set to the highest peak in magnitude then next marker to next peak in magnitude order etc,,, Markers of type "Cur" will track current peaks and markers of type "Max" will track peak maxima making it more suitable for transient signals. +

Waterfall markers tab

![Spectrum Markers waterfall dialog](../../doc/img/Spectrum_Markers_dialog_wat.png) diff --git a/sdrgui/gui/spectrummarkersdialog.cpp b/sdrgui/gui/spectrummarkersdialog.cpp index 28f6b9e7e..de1b54933 100644 --- a/sdrgui/gui/spectrummarkersdialog.cpp +++ b/sdrgui/gui/spectrummarkersdialog.cpp @@ -32,6 +32,7 @@ SpectrumMarkersDialog::SpectrumMarkersDialog( QList& waterfallMarkers, QList& annotationMarkers, SpectrumSettings::MarkersDisplay& markersDisplay, + bool& findPeaks, float calibrationShiftdB, QWidget* parent) : QDialog(parent), @@ -40,6 +41,7 @@ SpectrumMarkersDialog::SpectrumMarkersDialog( m_waterfallMarkers(waterfallMarkers), m_annotationMarkers(annotationMarkers), m_markersDisplay(markersDisplay), + m_findPeaks(findPeaks), m_calibrationShiftdB(calibrationShiftdB), m_histogramMarkerIndex(0), m_waterfallMarkerIndex(0), @@ -63,6 +65,7 @@ SpectrumMarkersDialog::SpectrumMarkersDialog( ui->fixedPower->setColorMapper(ColorMapper::GrayYellow); ui->fixedPower->setValueRange(false, 4, -2000, 400, 1); ui->showSelect->setCurrentIndex((int) m_markersDisplay); + ui->findPeaks->setChecked(m_findPeaks); displayHistogramMarker(); displayWaterfallMarker(); displayAnnotationMarker(); @@ -94,8 +97,12 @@ void SpectrumMarkersDialog::displayHistogramMarker() } else { + bool disableFreq = m_findPeaks && ( + (m_histogramMarkers[m_histogramMarkerIndex].m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePower) || + (m_histogramMarkers[m_histogramMarkerIndex].m_markerType == SpectrumHistogramMarker::SpectrumMarkerTypePowerMax) + ); ui->marker->setEnabled(true); - ui->markerFrequency->setEnabled(true); + ui->markerFrequency->setEnabled(!disableFreq); ui->powerMode->setEnabled(true); ui->fixedPower->setEnabled(true); ui->showMarker->setEnabled(true); @@ -403,6 +410,12 @@ void SpectrumMarkersDialog::on_powerHoldReset_clicked() m_histogramMarkers[m_histogramMarkerIndex].m_holdReset = true; } +void SpectrumMarkersDialog::on_findPeaks_toggled(bool checked) +{ + m_findPeaks = checked; + displayHistogramMarker(); +} + void SpectrumMarkersDialog::on_wMarkerFrequency_changed(qint64 value) { if (m_waterfallMarkers.size() == 0) { diff --git a/sdrgui/gui/spectrummarkersdialog.h b/sdrgui/gui/spectrummarkersdialog.h index 95e5d11ee..a732bb85d 100644 --- a/sdrgui/gui/spectrummarkersdialog.h +++ b/sdrgui/gui/spectrummarkersdialog.h @@ -39,6 +39,7 @@ public: QList& waterfallMarkers, QList& annotationMarkers, SpectrumSettings::MarkersDisplay& markersDisplay, + bool& findPeaks, float calibrationShiftdB, QWidget* parent = nullptr ); @@ -55,6 +56,7 @@ private: QList& m_waterfallMarkers; QList& m_annotationMarkers; SpectrumSettings::MarkersDisplay& m_markersDisplay; + bool &m_findPeaks; float m_calibrationShiftdB; int m_histogramMarkerIndex; int m_waterfallMarkerIndex; @@ -82,6 +84,7 @@ private slots: void on_markerDel_clicked(); void on_powerMode_currentIndexChanged(int index); void on_powerHoldReset_clicked(); + void on_findPeaks_toggled(bool checked); void on_wMarkerFrequency_changed(qint64 value); void on_timeCoarse_valueChanged(int value); diff --git a/sdrgui/gui/spectrummarkersdialog.ui b/sdrgui/gui/spectrummarkersdialog.ui index 33555e27b..f8b2cd874 100644 --- a/sdrgui/gui/spectrummarkersdialog.ui +++ b/sdrgui/gui/spectrummarkersdialog.ui @@ -498,6 +498,26 @@ Max - Marker will move according to the maximum power at the marker frequency + + + + + 24 + 24 + + + + Put markers in find peaks mode + + + + + + + :/dsb.png:/dsb.png + + + @@ -1799,6 +1819,11 @@ All - Show all markers QLabel
gui/clickablelabel.h
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
buttonBox