1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 01:39:05 -05:00

Spectrum: Use widget for measurements

This commit is contained in:
Jon Beniston 2022-09-28 16:59:35 +01:00
parent 8b1da4bfef
commit 2d43a5515e
18 changed files with 1205 additions and 165 deletions

View File

@ -23,6 +23,7 @@
#include "dsp/dspengine.h" #include "dsp/dspengine.h"
#include "dsp/dspcommands.h" #include "dsp/dspcommands.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/glspectrumtop.h"
#include "gui/glscope.h" #include "gui/glscope.h"
#include "gui/basicchannelsettingsdialog.h" #include "gui/basicchannelsettingsdialog.h"
#include "plugin/pluginapi.h" #include "plugin/pluginapi.h"
@ -192,17 +193,17 @@ void ChannelAnalyzerGUI::setSpectrumDisplay()
qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_sinkSampleRate: %d", sinkSampleRate); qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_sinkSampleRate: %d", sinkSampleRate);
if (m_settings.m_ssb) if (m_settings.m_ssb)
{ {
ui->glSpectrum->setCenterFrequency(sinkSampleRate/4); ui->glSpectrumTop->getSpectrum()->setCenterFrequency(sinkSampleRate/4);
ui->glSpectrum->setSampleRate(sinkSampleRate/2); ui->glSpectrumTop->getSpectrum()->setSampleRate(sinkSampleRate/2);
ui->glSpectrum->setSsbSpectrum(true); ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(true);
ui->glSpectrum->setLsbDisplay(ui->BW->value() < 0); ui->glSpectrumTop->getSpectrum()->setLsbDisplay(ui->BW->value() < 0);
} }
else else
{ {
ui->glSpectrum->setCenterFrequency(0); ui->glSpectrumTop->getSpectrum()->setCenterFrequency(0);
ui->glSpectrum->setSampleRate(sinkSampleRate); ui->glSpectrumTop->getSpectrum()->setSampleRate(sinkSampleRate);
ui->glSpectrum->setSsbSpectrum(false); ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(false);
ui->glSpectrum->setLsbDisplay(false); ui->glSpectrumTop->getSpectrum()->setLsbDisplay(false);
} }
} }
@ -542,7 +543,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device
m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate(); m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate();
qDebug("ChannelAnalyzerGUI::ChannelAnalyzerGUI: m_basebandSampleRate: %d", m_basebandSampleRate); qDebug("ChannelAnalyzerGUI::ChannelAnalyzerGUI: m_basebandSampleRate: %d", m_basebandSampleRate);
m_spectrumVis = m_channelAnalyzer->getSpectrumVis(); m_spectrumVis = m_channelAnalyzer->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum); m_spectrumVis->setGLSpectrum(ui->glSpectrumTop->getSpectrum());
m_scopeVis = m_channelAnalyzer->getScopeVis(); m_scopeVis = m_channelAnalyzer->getScopeVis();
m_scopeVis->setGLScope(ui->glScope); m_scopeVis->setGLScope(ui->glScope);
m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate(); m_basebandSampleRate = m_channelAnalyzer->getChannelSampleRate();
@ -556,12 +557,12 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device
ui->rationalDownSamplerRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->rationalDownSamplerRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->glSpectrum->setCenterFrequency(m_basebandSampleRate/2); ui->glSpectrumTop->getSpectrum()->setCenterFrequency(m_basebandSampleRate/2);
ui->glSpectrum->setSampleRate(m_basebandSampleRate); ui->glSpectrumTop->getSpectrum()->setSampleRate(m_basebandSampleRate);
ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrumTop->getSpectrum()->setDisplayWaterfall(true);
ui->glSpectrum->setDisplayMaxHold(true); ui->glSpectrumTop->getSpectrum()->setDisplayMaxHold(true);
ui->glSpectrum->setSsbSpectrum(false); ui->glSpectrumTop->getSpectrum()->setSsbSpectrum(false);
ui->glSpectrum->setLsbDisplay(false); ui->glSpectrumTop->getSpectrum()->setLsbDisplay(false);
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer()); ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
@ -578,7 +579,7 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device
m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addChannelMarker(&m_channelMarker);
ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum); ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrumTop->getSpectrum(), ui->glSpectrumTop);
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
m_settings.setChannelMarker(&m_channelMarker); m_settings.setChannelMarker(&m_channelMarker);
@ -694,7 +695,7 @@ void ChannelAnalyzerGUI::setFiltersUIBoundaries()
void ChannelAnalyzerGUI::blockApplySettings(bool block) void ChannelAnalyzerGUI::blockApplySettings(bool block)
{ {
ui->glScope->blockSignals(block); ui->glScope->blockSignals(block);
ui->glSpectrum->blockSignals(block); ui->glSpectrumTop->getSpectrum()->blockSignals(block);
m_doApplySettings = !block; m_doApplySettings = !block;
} }

View File

@ -899,7 +899,7 @@
<number>2</number> <number>2</number>
</property> </property>
<item> <item>
<widget class="GLSpectrum" name="glSpectrum" native="true"> <widget class="GLSpectrumTop" name="glSpectrumTop" native="true">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -1018,9 +1018,9 @@
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>GLSpectrum</class> <class>GLSpectrumTop</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>gui/glspectrum.h</header> <header>gui/glspectrumtop.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget> <customwidget>

View File

@ -68,12 +68,14 @@ void SpectrumSettings::resetToDefaults()
m_3DSpectrogramStyle = Outline; m_3DSpectrogramStyle = Outline;
m_colorMap = "Angel"; m_colorMap = "Angel";
m_spectrumStyle = Line; m_spectrumStyle = Line;
m_measurement = MeasurementNone; m_measure = false;
m_measurement = MeasurementPeaks;
m_measurementBandwidth = 10000; m_measurementBandwidth = 10000;
m_measurementChSpacing = 10000; m_measurementChSpacing = 10000;
m_measurementAdjChBandwidth = 10000; m_measurementAdjChBandwidth = 10000;
m_measurementHarmonics = 5; m_measurementHarmonics = 5;
m_measurementHighlight = true; m_measurementHighlight = true;
m_measurementPeaks = 5;
} }
QByteArray SpectrumSettings::serialize() const QByteArray SpectrumSettings::serialize() const
@ -120,6 +122,8 @@ QByteArray SpectrumSettings::serialize() const
s.writeS32(39, m_measurementHarmonics); s.writeS32(39, m_measurementHarmonics);
// 41, 42 used below // 41, 42 used below
s.writeBool(42, m_measurementHighlight); s.writeBool(42, m_measurementHighlight);
s.writeS32(43, m_measurementPeaks);
s.writeBool(44, m_measure);
s.writeS32(100, m_histogramMarkers.size()); s.writeS32(100, m_histogramMarkers.size());
for (int i = 0; i < m_histogramMarkers.size(); i++) { for (int i = 0; i < m_histogramMarkers.size(); i++) {
@ -221,12 +225,14 @@ bool SpectrumSettings::deserialize(const QByteArray& data)
d.readS32(32, (int*)&m_3DSpectrogramStyle, (int)Outline); d.readS32(32, (int*)&m_3DSpectrogramStyle, (int)Outline);
d.readString(33, &m_colorMap, "Angel"); d.readString(33, &m_colorMap, "Angel");
d.readS32(34, (int*)&m_spectrumStyle, (int)Line); d.readS32(34, (int*)&m_spectrumStyle, (int)Line);
d.readS32(35, (int*)&m_measurement, (int)MeasurementNone); d.readS32(35, (int*)&m_measurement, (int)MeasurementPeaks);
d.readS32(36, &m_measurementBandwidth, 10000); d.readS32(36, &m_measurementBandwidth, 10000);
d.readS32(37, &m_measurementChSpacing, 10000); d.readS32(37, &m_measurementChSpacing, 10000);
d.readS32(38, &m_measurementAdjChBandwidth, 10000); d.readS32(38, &m_measurementAdjChBandwidth, 10000);
d.readS32(39, &m_measurementHarmonics, 5); d.readS32(39, &m_measurementHarmonics, 5);
d.readBool(42, &m_measurementHighlight, true); d.readBool(42, &m_measurementHighlight, true);
d.readS32(43, &m_measurementPeaks, 5);
d.readBool(44, &m_measure, false);
int histogramMarkersSize; int histogramMarkersSize;
d.readS32(100, &histogramMarkersSize, 0); d.readS32(100, &histogramMarkersSize, 0);

View File

@ -72,16 +72,10 @@ public:
enum Measurement enum Measurement
{ {
MeasurementNone, MeasurementPeaks,
MeasurementPeak,
MeasurementChannelPower, MeasurementChannelPower,
MeasurementAdjacentChannelPower, MeasurementAdjacentChannelPower,
MeasurementSNR, MeasurementSNR
MeasurementSNFR,
MeasurementTHD,
MeasurementTHDPN,
MeasurementSINAD,
MeasurementSFDR
}; };
int m_fftSize; int m_fftSize;
@ -122,11 +116,13 @@ public:
SpectrogramStyle m_3DSpectrogramStyle; SpectrogramStyle m_3DSpectrogramStyle;
QString m_colorMap; QString m_colorMap;
SpectrumStyle m_spectrumStyle; SpectrumStyle m_spectrumStyle;
bool m_measure;
Measurement m_measurement; Measurement m_measurement;
int m_measurementBandwidth; int m_measurementBandwidth;
int m_measurementChSpacing; int m_measurementChSpacing;
int m_measurementAdjChBandwidth; int m_measurementAdjChBandwidth;
int m_measurementHarmonics; int m_measurementHarmonics;
int m_measurementPeaks;
bool m_measurementHighlight; bool m_measurementHighlight;
static const int m_log2FFTSizeMin = 6; // 64 static const int m_log2FFTSizeMin = 6; // 64
static const int m_log2FFTSizeMax = 15; // 32k static const int m_log2FFTSizeMax = 15; // 32k

View File

@ -51,6 +51,7 @@ set(sdrgui_SOURCES
gui/glshadertvarray.cpp gui/glshadertvarray.cpp
gui/glspectrum.cpp gui/glspectrum.cpp
gui/glspectrumgui.cpp gui/glspectrumgui.cpp
gui/glspectrumtop.cpp
gui/graphicsdialog.cpp gui/graphicsdialog.cpp
gui/graphicsviewzoom.cpp gui/graphicsviewzoom.cpp
gui/httpdownloadmanagergui.cpp gui/httpdownloadmanagergui.cpp
@ -69,6 +70,7 @@ set(sdrgui_SOURCES
gui/sdrangelsplash.cpp gui/sdrangelsplash.cpp
gui/spectrumcalibrationpointsdialog.cpp gui/spectrumcalibrationpointsdialog.cpp
gui/spectrummarkersdialog.cpp gui/spectrummarkersdialog.cpp
gui/spectrummeasurements.cpp
gui/tickedslider.cpp gui/tickedslider.cpp
gui/timedelegate.cpp gui/timedelegate.cpp
gui/transverterbutton.cpp gui/transverterbutton.cpp
@ -154,6 +156,7 @@ set(sdrgui_HEADERS
gui/glshadertextured.h gui/glshadertextured.h
gui/glspectrum.h gui/glspectrum.h
gui/glspectrumgui.h gui/glspectrumgui.h
gui/glspectrumtop.h
gui/graphicsdialog.h gui/graphicsdialog.h
gui/graphicsviewzoom.h gui/graphicsviewzoom.h
gui/httpdownloadmanagergui.h gui/httpdownloadmanagergui.h
@ -173,6 +176,7 @@ set(sdrgui_HEADERS
gui/sdrangelsplash.h gui/sdrangelsplash.h
gui/spectrumcalibrationpointsdialog.h gui/spectrumcalibrationpointsdialog.h
gui/spectrummarkersdialog.h gui/spectrummarkersdialog.h
gui/spectrummeasurements.h
gui/tickedslider.h gui/tickedslider.h
gui/timedelegate.h gui/timedelegate.h
gui/transverterbutton.h gui/transverterbutton.h

View File

@ -23,6 +23,7 @@
#include "dsp/dspdevicesourceengine.h" #include "dsp/dspdevicesourceengine.h"
#include "dsp/dspdevicesinkengine.h" #include "dsp/dspdevicesinkengine.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/glspectrumtop.h"
#include "gui/glspectrumgui.h" #include "gui/glspectrumgui.h"
// #include "gui/channelwindow.h" // #include "gui/channelwindow.h"
#include "gui/workspace.h" #include "gui/workspace.h"
@ -42,13 +43,14 @@
DeviceUISet::DeviceUISet(int deviceSetIndex, DeviceSet *deviceSet) DeviceUISet::DeviceUISet(int deviceSetIndex, DeviceSet *deviceSet)
{ {
m_spectrum = new GLSpectrum; m_spectrumTop = new GLSpectrumTop();
m_spectrum = m_spectrumTop->getSpectrum();
m_spectrum->setIsDeviceSpectrum(true); m_spectrum->setIsDeviceSpectrum(true);
m_spectrumVis = deviceSet->m_spectrumVis; m_spectrumVis = deviceSet->m_spectrumVis;
m_spectrumVis->setGLSpectrum(m_spectrum); m_spectrumVis->setGLSpectrum(m_spectrum);
m_spectrumGUI = new GLSpectrumGUI; m_spectrumGUI = new GLSpectrumGUI;
m_spectrumGUI->setBuddies(m_spectrumVis, m_spectrum); m_spectrumGUI->setBuddies(m_spectrumVis, m_spectrum, m_spectrumTop);
m_mainSpectrumGUI = new MainSpectrumGUI(m_spectrum, m_spectrumGUI); m_mainSpectrumGUI = new MainSpectrumGUI(m_spectrumTop, m_spectrum, m_spectrumGUI);
// m_channelWindow = new ChannelWindow; // m_channelWindow = new ChannelWindow;
m_deviceAPI = nullptr; m_deviceAPI = nullptr;
m_deviceGUI = nullptr; m_deviceGUI = nullptr;

View File

@ -26,6 +26,7 @@
class SpectrumVis; class SpectrumVis;
class GLSpectrum; class GLSpectrum;
class GLSpectrumTop;
class GLSpectrumGUI; class GLSpectrumGUI;
class MainSpectrumGUI; class MainSpectrumGUI;
// class ChannelWindow; // class ChannelWindow;
@ -53,6 +54,7 @@ class SDRGUI_API DeviceUISet : public QObject
Q_OBJECT Q_OBJECT
public: public:
SpectrumVis *m_spectrumVis; SpectrumVis *m_spectrumVis;
GLSpectrumTop *m_spectrumTop;
GLSpectrum *m_spectrum; GLSpectrum *m_spectrum;
GLSpectrumGUI *m_spectrumGUI; GLSpectrumGUI *m_spectrumGUI;
MainSpectrumGUI *m_mainSpectrumGUI; MainSpectrumGUI *m_mainSpectrumGUI;

View File

@ -27,6 +27,7 @@
#include "maincore.h" #include "maincore.h"
#include "dsp/spectrumvis.h" #include "dsp/spectrumvis.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/spectrummeasurements.h"
#include "settings/mainsettings.h" #include "settings/mainsettings.h"
#include "util/messagequeue.h" #include "util/messagequeue.h"
#include "util/db.h" #include "util/db.h"
@ -109,11 +110,14 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
m_messageQueueToGUI(nullptr), m_messageQueueToGUI(nullptr),
m_openGLLogger(nullptr), m_openGLLogger(nullptr),
m_isDeviceSpectrum(false), m_isDeviceSpectrum(false),
m_measurement(SpectrumSettings::MeasurementNone), m_measurements(nullptr),
m_measure(false),
m_measurement(SpectrumSettings::MeasurementPeaks),
m_measurementBandwidth(10000), m_measurementBandwidth(10000),
m_measurementChSpacing(10000), m_measurementChSpacing(10000),
m_measurementAdjChBandwidth(10000), m_measurementAdjChBandwidth(10000),
m_measurementHarmonics(5), m_measurementHarmonics(5),
m_measurementPeaks(5),
m_measurementHighlight(true) m_measurementHighlight(true)
{ {
// Enable multisampling anti-aliasing (MSAA) // Enable multisampling anti-aliasing (MSAA)
@ -491,18 +495,23 @@ void GLSpectrum::setUseCalibration(bool useCalibration)
update(); update();
} }
void GLSpectrum::setMeasurementParams(SpectrumSettings::Measurement measurement, void GLSpectrum::setMeasurementParams(bool measure, SpectrumSettings::Measurement measurement,
int bandwidth, int chSpacing, int adjChBandwidth, int bandwidth, int chSpacing, int adjChBandwidth,
int harmonics, bool highlight) int harmonics, int peaks, bool highlight)
{ {
m_mutex.lock(); m_mutex.lock();
m_measure = measure;
m_measurement = measurement; m_measurement = measurement;
m_measurementBandwidth = bandwidth; m_measurementBandwidth = bandwidth;
m_measurementChSpacing = chSpacing; m_measurementChSpacing = chSpacing;
m_measurementAdjChBandwidth = adjChBandwidth; m_measurementAdjChBandwidth = adjChBandwidth;
m_measurementHarmonics = harmonics; m_measurementHarmonics = harmonics;
m_measurementPeaks = peaks;
m_measurementHighlight = highlight; m_measurementHighlight = highlight;
m_changesPending = true; m_changesPending = true;
if (m_measurements) {
m_measurements->setMeasurementParams(measurement, peaks);
}
m_mutex.unlock(); m_mutex.unlock();
update(); update();
} }
@ -1672,12 +1681,12 @@ void GLSpectrum::paintGL()
m_glShaderInfo.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4); m_glShaderInfo.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
} }
if (m_currentSpectrum) if (m_currentSpectrum && m_measure)
{ {
switch (m_measurement) switch (m_measurement)
{ {
case SpectrumSettings::MeasurementPeak: case SpectrumSettings::MeasurementPeaks:
measurePeak(); measurePeaks();
break; break;
case SpectrumSettings::MeasurementChannelPower: case SpectrumSettings::MeasurementChannelPower:
measureChannelPower(); measureChannelPower();
@ -1686,13 +1695,7 @@ void GLSpectrum::paintGL()
measureAdjacentChannelPower(); measureAdjacentChannelPower();
break; break;
case SpectrumSettings::MeasurementSNR: case SpectrumSettings::MeasurementSNR:
case SpectrumSettings::MeasurementSNFR:
case SpectrumSettings::MeasurementTHD:
case SpectrumSettings::MeasurementTHDPN:
case SpectrumSettings::MeasurementSINAD:
measureSNR(); measureSNR();
break;
case SpectrumSettings::MeasurementSFDR:
measureSFDR(); measureSFDR();
break; break;
default: default:
@ -2069,6 +2072,60 @@ void GLSpectrum::measurePeak()
{m_peakPowerMaxStr, m_peakFrequencyMaxStr}, {m_peakPowerMaxStr, m_peakFrequencyMaxStr},
{m_peakPowerUnits, "Hz"} {m_peakPowerUnits, "Hz"}
); );
if (m_measurements) {
m_measurements->setPeak(0, frequency, power);
}
}
// Find and display peaks
void GLSpectrum::measurePeaks()
{
// Copy current spectrum so we can modify it
Real *spectrum = new Real[m_nbBins];
std::copy(m_currentSpectrum, m_currentSpectrum + m_nbBins, spectrum);
for (int i = 0; i < m_measurementPeaks; i++)
{
// Find peak
int peakBin = findPeakBin(spectrum);
int left, right;
peakWidth(spectrum, peakBin, left, right, 0, m_nbBins);
left++;
right--;
float power = m_linear ?
spectrum[peakBin] * (m_useCalibration ? m_calibrationGain : 1.0f) :
spectrum[peakBin] + (m_useCalibration ? m_calibrationShiftdB : 0.0f);
int64_t frequency = binToFrequency(peakBin);
// Add to table
if (m_measurements) {
m_measurements->setPeak(i, frequency, power);
}
if (m_measurementHighlight)
{
float x = peakBin / (float)m_nbBins;
float y = (m_powerScale.getRangeMax() - power) / m_powerScale.getRange();
QString text = QString::number(i + 1);
drawTextOverlayCentered(
text,
QColor(255, 255, 255),
m_textOverlayFont,
x * m_histogramRect.width(),
y * m_histogramRect.height(),
m_histogramRect);
}
// Remove peak from spectrum so not found on next pass
for (int j = left; j <= right; j++) {
spectrum[j] = -std::numeric_limits<float>::max();
}
}
delete spectrum;
} }
// Calculate and display channel power // Calculate and display channel power
@ -2077,7 +2134,9 @@ void GLSpectrum::measureChannelPower()
float power; float power;
power = calcChannelPower(m_centerFrequency, m_measurementBandwidth); power = calcChannelPower(m_centerFrequency, m_measurementBandwidth);
drawTextRight("Power: ", QString::number(power, 'f', 1), "-120.0", "dB"); if (m_measurements) {
m_measurements->setChannelPower(power);
}
if (m_measurementHighlight) { if (m_measurementHighlight) {
drawBandwidthMarkers(m_centerFrequency, m_measurementBandwidth, m_measurementLightMarkerColor); drawBandwidthMarkers(m_centerFrequency, m_measurementBandwidth, m_measurementLightMarkerColor);
} }
@ -2095,17 +2154,9 @@ void GLSpectrum::measureAdjacentChannelPower()
float leftDiff = powerLeft - power; float leftDiff = powerLeft - power;
float rightDiff = powerRight - power; float rightDiff = powerRight - power;
drawTextsRight( if (m_measurements) {
{"L: ", "", " C: ", " R: ", ""}, m_measurements->setAdjacentChannelPower(powerLeft, leftDiff, power, powerRight, rightDiff);
{ QString::number(powerLeft, 'f', 1), }
QString::number(leftDiff, 'f', 1),
QString::number(power, 'f', 1),
QString::number(powerRight, 'f', 1),
QString::number(rightDiff, 'f', 1)
},
{"-120.0", "-120.0", "-120.0", "-120.0", "-120.0"},
{"dB", "dBc", "dB", "dB", "dBc"}
);
if (m_measurementHighlight) if (m_measurementHighlight)
{ {
@ -2115,38 +2166,38 @@ void GLSpectrum::measureAdjacentChannelPower()
} }
} }
const QVector4D GLSpectrum::m_measurementLightMarkerColor = QVector4D(0.5f, 0.5f, 0.5f, 0.4f); const QVector4D GLSpectrum::m_measurementLightMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.2f);
const QVector4D GLSpectrum::m_measurementDarkMarkerColor = QVector4D(0.5f, 0.5f, 0.5f, 0.3f); const QVector4D GLSpectrum::m_measurementDarkMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.15f);
// Find the width of a peak, by seaching in either direction until // Find the width of a peak, by seaching in either direction until
// power is no longer falling // power is no longer falling
void GLSpectrum::peakWidth(int center, int &left, int &right, int maxLeft, int maxRight) const void GLSpectrum::peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const
{ {
float prevLeft = m_currentSpectrum[center]; float prevLeft = spectrum[center];
float prevRight = m_currentSpectrum[center]; float prevRight = spectrum[center];
left = center - 1; left = center - 1;
right = center + 1; right = center + 1;
while ((left > maxLeft) && (m_currentSpectrum[left] < prevLeft) && (right < maxRight) && (m_currentSpectrum[right] < prevRight)) while ((left > maxLeft) && (spectrum[left] < prevLeft) && (right < maxRight) && (spectrum[right] < prevRight))
{ {
prevLeft = m_currentSpectrum[left]; prevLeft = spectrum[left];
left--; left--;
prevRight = m_currentSpectrum[right]; prevRight = spectrum[right];
right++; right++;
} }
} }
int GLSpectrum::findPeakBin() const int GLSpectrum::findPeakBin(const Real *spectrum) const
{ {
int bin; int bin;
float power; float power;
bin = 0; bin = 0;
power = m_currentSpectrum[0]; power = spectrum[0];
for (int i = 1; i < m_nbBins; i++) for (int i = 1; i < m_nbBins; i++)
{ {
if (m_currentSpectrum[i] > power) if (spectrum[i] > power)
{ {
power = m_currentSpectrum[i]; power = spectrum[i];
bin = i; bin = i;
} }
} }
@ -2178,9 +2229,9 @@ int64_t GLSpectrum::binToFrequency(int bin) const
void GLSpectrum::measureSNR() void GLSpectrum::measureSNR()
{ {
// Find bin with max peak - that will be our signal // Find bin with max peak - that will be our signal
int sig = findPeakBin(); int sig = findPeakBin(m_currentSpectrum);
int sigLeft, sigRight; int sigLeft, sigRight;
peakWidth(sig, sigLeft, sigRight, 0, m_nbBins); peakWidth(m_currentSpectrum, sig, sigLeft, sigRight, 0, m_nbBins);
int sigBins = sigRight - sigLeft - 1; int sigBins = sigRight - sigLeft - 1;
int binsLeft = sig - sigLeft; int binsLeft = sig - sigLeft;
int binsRight = sigRight - sig; int binsRight = sigRight - sig;
@ -2209,7 +2260,7 @@ void GLSpectrum::measureSNR()
} }
hFreq = binToFrequency(hBin); hFreq = binToFrequency(hBin);
int hLeft, hRight; int hLeft, hRight;
peakWidth(hBin, hLeft, hRight, hBin - binsLeft, hBin + binsRight); peakWidth(m_currentSpectrum, hBin, hLeft, hRight, hBin - binsLeft, hBin + binsRight);
int hBins = hRight - hLeft - 1; int hBins = hRight - hLeft - 1;
if (m_measurementHighlight) { if (m_measurementHighlight) {
drawPeakMarkers(binToFrequency(hLeft+1), binToFrequency(hRight-1), m_measurementDarkMarkerColor); drawPeakMarkers(binToFrequency(hLeft+1), binToFrequency(hRight-1), m_measurementDarkMarkerColor);
@ -2279,55 +2330,34 @@ void GLSpectrum::measureSNR()
harmonicPower -= hNoise; harmonicPower -= hNoise;
} }
switch (m_measurement) if (m_measurements)
{
case SpectrumSettings::MeasurementSNR:
{ {
// Calculate SNR in dB over full bandwidth // Calculate SNR in dB over full bandwidth
float snr = CalcDb::dbPower(sigPower / noisePower); float snr = CalcDb::dbPower(sigPower / noisePower);
drawTextRight("SNR: ", QString::number(snr, 'f', 1), "100.0", "dB");
break;
}
case SpectrumSettings::MeasurementSNFR:
{
// Calculate SNR, where noise is median of noise summed over signal b/w // Calculate SNR, where noise is median of noise summed over signal b/w
float snfr = CalcDb::dbPower(sigPower / inBandNoise); float snfr = CalcDb::dbPower(sigPower / inBandNoise);
drawTextRight("SNFR: ", QString::number(snfr, 'f', 1), "100.0", "dB");
break;
}
case SpectrumSettings::MeasurementTHD:
{
// Calculate THD - Total harmonic distortion // Calculate THD - Total harmonic distortion
float thd = harmonicPower / sigPower; float thd = harmonicPower / sigPower;
float thdDB = CalcDb::dbPower(thd); float thdDB = CalcDb::dbPower(thd);
drawTextRight("THD: ", QString::number(thdDB, 'f', 1), "-120.0", "dB");
break;
}
case SpectrumSettings::MeasurementTHDPN:
{
// Calculate THD+N - Total harmonic distortion plus noise // Calculate THD+N - Total harmonic distortion plus noise
float thdpn = CalcDb::dbPower((harmonicPower + noisePower) / sigPower); float thdpn = CalcDb::dbPower((harmonicPower + noisePower) / sigPower);
drawTextRight("THD+N: ", QString::number(thdpn, 'f', 1), "-120.0", "dB");
break;
}
case SpectrumSettings::MeasurementSINAD:
{
// Calculate SINAD - Signal to noise and distotion ratio (Should be -THD+N) // Calculate SINAD - Signal to noise and distotion ratio (Should be -THD+N)
float sinad = CalcDb::dbPower((sigPower + harmonicPower + noisePower) / (harmonicPower + noisePower)); float sinad = CalcDb::dbPower((sigPower + harmonicPower + noisePower) / (harmonicPower + noisePower));
drawTextRight("SINAD: ", QString::number(sinad, 'f', 1), "120.0", "dB");
break; m_measurements->setSNR(snr, snfr, thdDB, thdpn, sinad);
}
default:
break;
} }
} }
void GLSpectrum::measureSFDR() void GLSpectrum::measureSFDR()
{ {
// Find first peak which is our signal // Find first peak which is our signal
int peakBin = findPeakBin(); int peakBin = findPeakBin(m_currentSpectrum);
int peakLeft, peakRight; int peakLeft, peakRight;
peakWidth(peakBin, peakLeft, peakRight, 0, m_nbBins); peakWidth(m_currentSpectrum, peakBin, peakLeft, peakRight, 0, m_nbBins);
// Find next largest peak, which is the spur // Find next largest peak, which is the spur
int nextPeakBin = -1; int nextPeakBin = -1;
@ -2353,13 +2383,15 @@ void GLSpectrum::measureSFDR()
float sfdr = peakPowerDB - nextPeakPowerDB; float sfdr = peakPowerDB - nextPeakPowerDB;
// Display // Display
drawTextRight("SFDR: ", QString::number(sfdr, 'f', 1), "100.0", "dB"); if (m_measurements) {
m_measurements->setSFDR(sfdr);
}
if (m_measurementHighlight) if (m_measurementHighlight)
{ {
if (m_linear) { if (m_linear) {
drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementLightMarkerColor); drawPowerBandMarkers(peakPower, nextPeakPower, m_measurementDarkMarkerColor);
} else { } else {
drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementLightMarkerColor); drawPowerBandMarkers(peakPowerDB, nextPeakPowerDB, m_measurementDarkMarkerColor);
} }
} }
} }
@ -4381,6 +4413,7 @@ int GLSpectrum::getPrecision(int value)
} }
} }
// Draw text right justified in top info bar - currently unused
void GLSpectrum::drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units) void GLSpectrum::drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units)
{ {
drawTextsRight({text}, {value}, {max}, {units}); drawTextsRight({text}, {value}, {max}, {units});
@ -4437,6 +4470,60 @@ void GLSpectrum::drawTextsRight(const QStringList &text, const QStringList &valu
m_glShaderTextOverlay.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4); m_glShaderTextOverlay.drawSurface(m_glInfoBoxMatrix, tex1, vtx1, 4);
} }
void GLSpectrum::drawTextOverlayCentered (
const QString &text,
const QColor &color,
const QFont& font,
float shiftX,
float shiftY,
const QRectF &glRect)
{
if (text.isEmpty()) {
return;
}
QFontMetricsF metrics(font);
QRectF textRect = metrics.boundingRect(text);
QRectF overlayRect(0, 0, textRect.width() * 1.05f + 4.0f, textRect.height());
QPixmap channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height());
channelOverlayPixmap.fill(Qt::transparent);
QPainter painter(&channelOverlayPixmap);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false);
painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80));
QColor textColor(color);
textColor.setAlpha(0xC0);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text);
painter.end();
m_glShaderTextOverlay.initTexture(channelOverlayPixmap.toImage());
{
GLfloat vtx1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0};
float rectX = glRect.x() + shiftX - ((overlayRect.width()/2)/width());
float rectY = glRect.y() + shiftY + (4.0f / height()) - ((overlayRect.height()+5)/height());
float rectW = overlayRect.width() / (float) width();
float rectH = overlayRect.height() / (float) height();
QMatrix4x4 mat;
mat.setToIdentity();
mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY);
mat.scale(2.0f * rectW, -2.0f * rectH);
m_glShaderTextOverlay.drawSurface(mat, tex1, vtx1, 4);
}
}
void GLSpectrum::drawTextOverlay( void GLSpectrum::drawTextOverlay(
const QString &text, const QString &text,
const QColor &color, const QColor &color,
@ -4516,9 +4603,6 @@ void GLSpectrum::formatTextInfo(QString& info)
getFrequencyZoom(centerFrequency, frequencySpan); getFrequencyZoom(centerFrequency, frequencySpan);
info.append(tr("CF:%1 ").arg(displayScaled(centerFrequency, 'f', getPrecision(centerFrequency/frequencySpan), true))); info.append(tr("CF:%1 ").arg(displayScaled(centerFrequency, 'f', getPrecision(centerFrequency/frequencySpan), true)));
info.append(tr("SP:%1 ").arg(displayScaled(frequencySpan, 'f', 3, true))); info.append(tr("SP:%1 ").arg(displayScaled(frequencySpan, 'f', 3, true)));
if (m_measurement != SpectrumSettings::MeasurementNone) {
info.append(tr("RBW:%1 ").arg(displayScaled(m_sampleRate / (float)m_fftSize, 'f', 3, true)));
}
} }
} }

View File

@ -48,6 +48,7 @@ class QOpenGLShaderProgram;
class MessageQueue; class MessageQueue;
class SpectrumVis; class SpectrumVis;
class QOpenGLDebugLogger; class QOpenGLDebugLogger;
class SpectrumMeasurements;
class SDRGUI_API GLSpectrum : public QOpenGLWidget, public GLSpectrumInterface { class SDRGUI_API GLSpectrum : public QOpenGLWidget, public GLSpectrumInterface {
Q_OBJECT Q_OBJECT
@ -161,9 +162,10 @@ public:
void setDisplayTraceIntensity(int intensity); void setDisplayTraceIntensity(int intensity);
void setLinear(bool linear); void setLinear(bool linear);
void setUseCalibration(bool useCalibration); void setUseCalibration(bool useCalibration);
void setMeasurementParams(SpectrumSettings::Measurement measurement, int bandwidth, void setMeasurements(SpectrumMeasurements *measurements) { m_measurements = measurements; }
int chSpacing, int adjChBandwidth, void setMeasurementParams(bool measure, SpectrumSettings::Measurement measurement,
int harmonics, bool highlight); int bandwidth, int chSpacing, int adjChBandwidth,
int harmonics, int peaks, bool highlight);
qint32 getSampleRate() const { return m_sampleRate; } qint32 getSampleRate() const { return m_sampleRate; }
void addChannelMarker(ChannelMarker* channelMarker); void addChannelMarker(ChannelMarker* channelMarker);
@ -375,11 +377,14 @@ private:
QOpenGLDebugLogger *m_openGLLogger; QOpenGLDebugLogger *m_openGLLogger;
bool m_isDeviceSpectrum; bool m_isDeviceSpectrum;
SpectrumMeasurements *m_measurements;
bool m_measure;
SpectrumSettings::Measurement m_measurement; SpectrumSettings::Measurement m_measurement;
int m_measurementBandwidth; int m_measurementBandwidth;
int m_measurementChSpacing; int m_measurementChSpacing;
int m_measurementAdjChBandwidth; int m_measurementAdjChBandwidth;
int m_measurementHarmonics; int m_measurementHarmonics;
int m_measurementPeaks;
bool m_measurementHighlight; bool m_measurementHighlight;
static const QVector4D m_measurementLightMarkerColor; static const QVector4D m_measurementLightMarkerColor;
static const QVector4D m_measurementDarkMarkerColor; static const QVector4D m_measurementDarkMarkerColor;
@ -398,15 +403,16 @@ private:
void drawAnnotationMarkers(); void drawAnnotationMarkers();
void measurePeak(); void measurePeak();
void measurePeaks();
void measureChannelPower(); void measureChannelPower();
void measureAdjacentChannelPower(); void measureAdjacentChannelPower();
void measureSNR(); void measureSNR();
void measureSFDR(); void measureSFDR();
float calcChannelPower(int64_t centerFrequency, int channelBandwidth) const; float calcChannelPower(int64_t centerFrequency, int channelBandwidth) const;
float calPower(float power) const; float calPower(float power) const;
int findPeakBin() const; int findPeakBin(const Real *spectrum) const;
void findPeak(float &power, float &frequency) const; void findPeak(float &power, float &frequency) const;
void peakWidth(int center, int &left, int &right, int maxLeft, int maxRight) const; void peakWidth(const Real *spectrum, int center, int &left, int &right, int maxLeft, int maxRight) const;
int frequencyToBin(int64_t frequency) const; int frequencyToBin(int64_t frequency) const;
int64_t binToFrequency(int bin) const; int64_t binToFrequency(int bin) const;
@ -441,6 +447,13 @@ private:
int getPrecision(int value); int getPrecision(int value);
void drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units); void drawTextRight(const QString &text, const QString &value, const QString &max, const QString &units);
void drawTextsRight(const QStringList &text, const QStringList &value, const QStringList &max, const QStringList &units); void drawTextsRight(const QStringList &text, const QStringList &value, const QStringList &max, const QStringList &units);
void drawTextOverlayCentered(
const QString& text,
const QColor& color,
const QFont& font,
float shiftX,
float shiftY,
const QRectF& glRect);
void drawTextOverlay( //!< Draws a text overlay void drawTextOverlay( //!< Draws a text overlay
const QString& text, const QString& text,
const QColor& color, const QColor& color,

View File

@ -28,10 +28,12 @@
#include "dsp/fftwindow.h" #include "dsp/fftwindow.h"
#include "dsp/spectrumvis.h" #include "dsp/spectrumvis.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/glspectrumtop.h"
#include "gui/crightclickenabler.h" #include "gui/crightclickenabler.h"
#include "gui/wsspectrumsettingsdialog.h" #include "gui/wsspectrumsettingsdialog.h"
#include "gui/spectrummarkersdialog.h" #include "gui/spectrummarkersdialog.h"
#include "gui/spectrumcalibrationpointsdialog.h" #include "gui/spectrumcalibrationpointsdialog.h"
#include "gui/spectrummeasurements.h"
#include "gui/flowlayout.h" #include "gui/flowlayout.h"
#include "util/colormap.h" #include "util/colormap.h"
#include "util/simpleserializer.h" #include "util/simpleserializer.h"
@ -45,6 +47,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
ui(new Ui::GLSpectrumGUI), ui(new Ui::GLSpectrumGUI),
m_spectrumVis(nullptr), m_spectrumVis(nullptr),
m_glSpectrum(nullptr), m_glSpectrum(nullptr),
m_glSpectrumTop(nullptr),
m_doApplySettings(true), m_doApplySettings(true),
m_calibrationShiftdB(0.0) m_calibrationShiftdB(0.0)
{ {
@ -69,7 +72,7 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
ui->verticalLayout->addItem(flowLayout); ui->verticalLayout->addItem(flowLayout);
on_linscale_toggled(false); on_linscale_toggled(false);
on_measurement_currentIndexChanged(0); displayMeasurementGUI();
QString levelStyle = QString( QString levelStyle = QString(
"QSpinBox {background-color: rgb(79, 79, 79);}" "QSpinBox {background-color: rgb(79, 79, 79);}"
@ -101,13 +104,14 @@ GLSpectrumGUI::~GLSpectrumGUI()
delete ui; delete ui;
} }
void GLSpectrumGUI::setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum) void GLSpectrumGUI::setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum, GLSpectrumTop *glSpectrumTop)
{ {
m_spectrumVis = spectrumVis; m_spectrumVis = spectrumVis;
m_glSpectrum = glSpectrum; m_glSpectrum = glSpectrum;
m_glSpectrum->setSpectrumVis(spectrumVis); m_glSpectrum->setSpectrumVis(spectrumVis);
m_glSpectrum->setMessageQueueToGUI(&m_messageQueue); m_glSpectrum->setMessageQueueToGUI(&m_messageQueue);
m_spectrumVis->setMessageQueueToGUI(&m_messageQueue); m_spectrumVis->setMessageQueueToGUI(&m_messageQueue);
m_glSpectrumTop = glSpectrumTop;
} }
void GLSpectrumGUI::resetToDefaults() void GLSpectrumGUI::resetToDefaults()
@ -229,12 +233,15 @@ void GLSpectrumGUI::displaySettings()
ui->calibration->setChecked(m_settings.m_useCalibration); ui->calibration->setChecked(m_settings.m_useCalibration);
displayGotoMarkers(); displayGotoMarkers();
ui->measure->setChecked(m_settings.m_measure);
ui->measurement->setCurrentIndex((int) m_settings.m_measurement); ui->measurement->setCurrentIndex((int) m_settings.m_measurement);
ui->highlight->setChecked(m_settings.m_measurementHighlight); ui->highlight->setChecked(m_settings.m_measurementHighlight);
ui->bandwidth->setValue(m_settings.m_measurementBandwidth); ui->bandwidth->setValue(m_settings.m_measurementBandwidth);
ui->chSpacing->setValue(m_settings.m_measurementChSpacing); ui->chSpacing->setValue(m_settings.m_measurementChSpacing);
ui->adjChBandwidth->setValue(m_settings.m_measurementAdjChBandwidth); ui->adjChBandwidth->setValue(m_settings.m_measurementAdjChBandwidth);
ui->harmonics->setValue(m_settings.m_measurementHarmonics); ui->harmonics->setValue(m_settings.m_measurementHarmonics);
ui->peaks->setValue(m_settings.m_measurementPeaks);
displayMeasurementGUI();
ui->fftWindow->blockSignals(false); ui->fftWindow->blockSignals(false);
ui->averaging->blockSignals(false); ui->averaging->blockSignals(false);
@ -344,11 +351,13 @@ void GLSpectrumGUI::applySpectrumSettings()
m_glSpectrum->setCalibrationInterpMode(m_settings.m_calibrationInterpMode); m_glSpectrum->setCalibrationInterpMode(m_settings.m_calibrationInterpMode);
m_glSpectrum->setMeasurementParams( m_glSpectrum->setMeasurementParams(
m_settings.m_measure,
m_settings.m_measurement, m_settings.m_measurement,
m_settings.m_measurementBandwidth, m_settings.m_measurementBandwidth,
m_settings.m_measurementChSpacing, m_settings.m_measurementChSpacing,
m_settings.m_measurementAdjChBandwidth, m_settings.m_measurementAdjChBandwidth,
m_settings.m_measurementHarmonics, m_settings.m_measurementHarmonics,
m_settings.m_measurementPeaks,
m_settings.m_measurementHighlight m_settings.m_measurementHighlight
); );
} }
@ -1027,29 +1036,51 @@ void GLSpectrumGUI::updateCalibrationPoints()
} }
} }
void GLSpectrumGUI::on_measurement_currentIndexChanged(int index) void GLSpectrumGUI::displayMeasurementGUI()
{ {
m_settings.m_measurement = (SpectrumSettings::Measurement)index; bool show = m_settings.m_measure;
bool highlight = (m_settings.m_measurement >= SpectrumSettings::MeasurementChannelPower); if (m_glSpectrumTop) {
ui->highlight->setVisible(highlight); m_glSpectrumTop->setMeasurementsVisible(show);
}
ui->measurement->setVisible(show);
ui->highlight->setVisible(show);
bool reset = (m_settings.m_measurement >= SpectrumSettings::MeasurementChannelPower);
ui->resetMeasurements->setVisible(reset && show);
bool bw = (m_settings.m_measurement == SpectrumSettings::MeasurementChannelPower) bool bw = (m_settings.m_measurement == SpectrumSettings::MeasurementChannelPower)
|| (m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower); || (m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower);
ui->bandwidthLabel->setVisible(bw); ui->bandwidthLabel->setVisible(bw && show);
ui->bandwidth->setVisible(bw); ui->bandwidth->setVisible(bw && show);
bool adj = m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower; bool adj = m_settings.m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower;
ui->chSpacingLabel->setVisible(adj); ui->chSpacingLabel->setVisible(adj && show);
ui->chSpacing->setVisible(adj); ui->chSpacing->setVisible(adj && show);
ui->adjChBandwidthLabel->setVisible(adj); ui->adjChBandwidthLabel->setVisible(adj && show);
ui->adjChBandwidth->setVisible(adj); ui->adjChBandwidth->setVisible(adj && show);
bool harmonics = (m_settings.m_measurement >= SpectrumSettings::MeasurementSNR) bool harmonics = (m_settings.m_measurement == SpectrumSettings::MeasurementSNR);
&& (m_settings.m_measurement <= SpectrumSettings::MeasurementSINAD); ui->harmonicsLabel->setVisible(harmonics && show);
ui->harmonicsLabel->setVisible(harmonics); ui->harmonics->setVisible(harmonics && show);
ui->harmonics->setVisible(harmonics);
bool peaks = (m_settings.m_measurement == SpectrumSettings::MeasurementPeaks);
ui->peaksLabel->setVisible(peaks && show);
ui->peaks->setVisible(peaks && show);
}
void GLSpectrumGUI::on_measure_clicked(bool checked)
{
m_settings.m_measure = checked;
displayMeasurementGUI();
applySettings();
}
void GLSpectrumGUI::on_measurement_currentIndexChanged(int index)
{
m_settings.m_measurement = (SpectrumSettings::Measurement)index;
displayMeasurementGUI();
applySettings(); applySettings();
} }
@ -1059,6 +1090,15 @@ void GLSpectrumGUI::on_highlight_toggled(bool checked)
applySettings(); applySettings();
} }
void GLSpectrumGUI::on_resetMeasurements_clicked(bool checked)
{
(void) checked;
if (m_glSpectrumTop) {
m_glSpectrumTop->getMeasurements()->reset();
}
}
void GLSpectrumGUI::on_bandwidth_valueChanged(int value) void GLSpectrumGUI::on_bandwidth_valueChanged(int value)
{ {
m_settings.m_measurementBandwidth = value; m_settings.m_measurementBandwidth = value;
@ -1082,3 +1122,9 @@ void GLSpectrumGUI::on_harmonics_valueChanged(int value)
m_settings.m_measurementHarmonics = value; m_settings.m_measurementHarmonics = value;
applySettings(); applySettings();
} }
void GLSpectrumGUI::on_peaks_valueChanged(int value)
{
m_settings.m_measurementPeaks = value;
applySettings();
}

View File

@ -36,6 +36,7 @@ namespace Ui {
class SpectrumVis; class SpectrumVis;
class GLSpectrum; class GLSpectrum;
class GLSpectrumTop;
class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable { class SDRGUI_API GLSpectrumGUI : public QWidget, public Serializable {
Q_OBJECT Q_OBJECT
@ -52,7 +53,7 @@ public:
explicit GLSpectrumGUI(QWidget* parent = NULL); explicit GLSpectrumGUI(QWidget* parent = NULL);
~GLSpectrumGUI(); ~GLSpectrumGUI();
void setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum); void setBuddies(SpectrumVis* spectrumVis, GLSpectrum* glSpectrum, GLSpectrumTop *glSpectrumTop = nullptr);
void setFFTSize(int log2FFTSize); void setFFTSize(int log2FFTSize);
void resetToDefaults(); void resetToDefaults();
@ -67,6 +68,7 @@ private:
SpectrumVis* m_spectrumVis; SpectrumVis* m_spectrumVis;
GLSpectrum* m_glSpectrum; GLSpectrum* m_glSpectrum;
GLSpectrumTop* m_glSpectrumTop;
MessageQueue m_messageQueue; MessageQueue m_messageQueue;
SpectrumSettings m_settings; SpectrumSettings m_settings;
bool m_doApplySettings; bool m_doApplySettings;
@ -86,6 +88,7 @@ private:
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void displayGotoMarkers(); void displayGotoMarkers();
QString displayScaled(int64_t value, char type, int precision, bool showMult); QString displayScaled(int64_t value, char type, int precision, bool showMult);
void displayMeasurementGUI();
private slots: private slots:
void on_fftWindow_currentIndexChanged(int index); void on_fftWindow_currentIndexChanged(int index);
@ -123,12 +126,15 @@ private slots:
void on_calibration_toggled(bool checked); void on_calibration_toggled(bool checked);
void on_gotoMarker_currentIndexChanged(int index); void on_gotoMarker_currentIndexChanged(int index);
void on_measure_clicked(bool checked);
void on_measurement_currentIndexChanged(int index); void on_measurement_currentIndexChanged(int index);
void on_highlight_toggled(bool checked); void on_highlight_toggled(bool checked);
void on_resetMeasurements_clicked(bool checked);
void on_bandwidth_valueChanged(int value); void on_bandwidth_valueChanged(int value);
void on_chSpacing_valueChanged(int value); void on_chSpacing_valueChanged(int value);
void on_adjChBandwidth_valueChanged(int value); void on_adjChBandwidth_valueChanged(int value);
void on_harmonics_valueChanged(int value); void on_harmonics_valueChanged(int value);
void on_peaks_valueChanged(int value);
void handleInputMessages(); void handleInputMessages();
void openWebsocketSpectrumSettingsDialog(const QPoint& p); void openWebsocketSpectrumSettingsDialog(const QPoint& p);

View File

@ -1073,6 +1073,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="ButtonSwitch" name="measure">
<property name="toolTip">
<string>Display measurements</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/ruler.png</normaloff>:/ruler.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="ButtonSwitch" name="calibration"> <widget class="ButtonSwitch" name="calibration">
<property name="toolTip"> <property name="toolTip">
@ -1139,14 +1153,12 @@
<property name="toolTip"> <property name="toolTip">
<string>Measurement</string> <string>Measurement</string>
</property> </property>
<item> <property name="currentIndex">
<property name="text"> <number>-1</number>
<string>None</string>
</property> </property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>Peak</string> <string>Peaks</string>
</property> </property>
</item> </item>
<item> <item>
@ -1164,31 +1176,6 @@
<string>SNR</string> <string>SNR</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>SNFR</string>
</property>
</item>
<item>
<property name="text">
<string>THD</string>
</property>
</item>
<item>
<property name="text">
<string>THD+N</string>
</property>
</item>
<item>
<property name="text">
<string>SINAD</string>
</property>
</item>
<item>
<property name="text">
<string>SFDR</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item> <item>
@ -1214,6 +1201,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="resetMeasurements">
<property name="toolTip">
<string>Reset measurements</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="bandwidthLabel"> <widget class="QLabel" name="bandwidthLabel">
<property name="text"> <property name="text">
@ -1309,6 +1310,26 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="peaksLabel">
<property name="text">
<string>Peaks</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="peaks">
<property name="toolTip">
<string>Number of peaks to display</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMainWindow>
#include <QDockWidget>
#include <QSplitter>
#include <QVBoxLayout>
#include <QLabel>
#include "gui/glspectrum.h"
#include "gui/glspectrumtop.h"
#include "gui/spectrummeasurements.h"
GLSpectrumTop::GLSpectrumTop(QWidget *parent) :
QWidget(parent)
{
m_mainWindow = new QMainWindow();
m_dock = new QDockWidget();
m_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
//m_dock->setTitleBarWidget(new QLabel("Measurements")); // Could add device or channel R:0 label and dock button?
m_dock->setVisible(false);
m_spectrum = new GLSpectrum();
m_measurements = new SpectrumMeasurements();
m_spectrum->setMeasurements(m_measurements);
m_dock->setWidget(m_measurements);
m_mainWindow->setCentralWidget(m_spectrum);
m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, m_dock);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_mainWindow);
setLayout(layout);
}
void GLSpectrumTop::setMeasurementsVisible(bool visible)
{
m_dock->setVisible(visible);
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRGUI_GLSPECTRUMTOP_H_
#define SDRGUI_GLSPECTRUMTOP_H_
#include <QWidget>
#include "export.h"
class QMainWindow;
class QDockWidget;
class GLSpectrum;
class SpectrumMeasurements;
// Combines GLSpectrum in a QMainWindow with SpectrumMeasurements in a QDockWidget
class SDRGUI_API GLSpectrumTop : public QWidget {
Q_OBJECT
public:
GLSpectrumTop(QWidget *parent = nullptr);
GLSpectrum *getSpectrum() const { return m_spectrum; }
SpectrumMeasurements *getMeasurements() const { return m_measurements; }
void setMeasurementsVisible(bool visible);
private:
QMainWindow *m_mainWindow;
QDockWidget *m_dock;
GLSpectrum *m_spectrum;
SpectrumMeasurements *m_measurements;
};
#endif // SDRGUI_GLSPECTRUMTOP_H_

View File

@ -0,0 +1,610 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QTableWidgetItem>
#include <QHeaderView>
#include <QVBoxLayout>
#include <QMenu>
#include <QAction>
#include <QClipboard>
#include <QGuiApplication>
#include <QDebug>
#include <QPainter>
#include "gui/spectrummeasurements.h"
#include <QStyledItemDelegate>
class SDRGUI_API UnitsDelegate : public QStyledItemDelegate {
public:
UnitsDelegate(QObject *parent = nullptr);
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QString s = text(index);
return QSize(width(s, option.fontMetrics) + 2, option.fontMetrics.height());
}
int width(const QString &s, const QFontMetrics &fm) const
{
int left = s.size() > 0 ? fm.leftBearing(s[0]) : 0;
int right = s.size() > 0 ? fm.rightBearing(s[s.size()-1]) : 0;
return fm.horizontalAdvance(s) + left + right;
}
QString text(const QModelIndex &index) const
{
QString units = index.data(UNITS_ROLE).toString();
QString s;
if (units == "Hz")
{
s = formatEngineering(index.data().toLongLong());
}
else
{
int precision = index.data(PRECISION_ROLE).toInt();
double d = index.data().toDouble();
s = QString::number(d, 'f', precision);
}
return s + units;
}
enum Roles {
UNITS_ROLE = Qt::UserRole,
PRECISION_ROLE,
SPEC_ROLE
};
protected:
QString formatEngineering(int64_t value) const;
};
UnitsDelegate::UnitsDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}
QString UnitsDelegate::formatEngineering(int64_t value) const
{
if (value == 0) {
return "0";
}
int64_t absValue = std::abs(value);
QString digits = QString::number(absValue);
int cnt = digits.size();
QString point = QLocale::system().decimalPoint();
QString group = QLocale::system().groupSeparator();
int i;
for (i = cnt - 3; i >= 4; i -= 3)
{
digits = digits.insert(i, group);
}
if (absValue >= 1000) {
digits = digits.insert(i, point);
}
if (cnt > 9) {
digits = digits.append("G");
} else if (cnt > 6) {
digits = digits.append("M");
} else if (cnt > 3) {
digits = digits.append("k");
}
if (value < 0) {
digits = digits.insert(0, "-");
}
return digits;
}
void UnitsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QFontMetrics fm = painter->fontMetrics();
QString s = text(index);
int sWidth = width(s, fm);
while ((sWidth > option.rect.width()) && !s.isEmpty())
{
s = s.mid(1);
sWidth = width(s, fm);
}
int y = option.rect.y() + (option.rect.height()) - ((option.rect.height() - fm.ascent()) / 2); // Align center vertically
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
painter->setPen(opt.palette.color(cg, QPalette::Text));
painter->drawText(option.rect.x() + option.rect.width() - 1 - sWidth, y, s);
}
const QStringList SpectrumMeasurements::m_measurementColumns = {
"Current",
"Mean",
"Min",
"Max",
"Range",
"Std Dev",
"Count",
"Spec",
"Fails",
""
};
const QStringList SpectrumMeasurements::m_tooltips = {
"Current value",
"Mean average of values",
"Minimum value",
"Maximum value",
"Range of values (Max-Min)",
"Standard deviation",
"Count of values",
"Specification for value.\n\nE.g. <-100.5, >34.5 or =10.2",
"Count of values that failed to meet specification",
""
};
SpectrumMeasurements::SpectrumMeasurements(QWidget *parent) :
QWidget(parent),
m_table(nullptr),
m_peakTable(nullptr)
{
m_textBrush.setColor(Qt::white); // Should get this from the style sheet?
m_redBrush.setColor(Qt::red);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
void SpectrumMeasurements::createMeasurementsTable(const QStringList &rows, const QStringList &units)
{
m_table = new QTableWidget();
m_table->horizontalHeader()->setSectionsMovable(true);
m_table->verticalHeader()->setSectionsMovable(true);
m_table->setColumnCount(m_measurementColumns.size());
for (int i = 0; i < m_measurementColumns.size(); i++)
{
QTableWidgetItem *item = new QTableWidgetItem(m_measurementColumns[i]);
item->setToolTip(m_tooltips[i]);
m_table->setHorizontalHeaderItem(i, item);
}
// Cell context menu
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_table, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::tableContextMenu);
m_table->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_table->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
// Fill up space at end of rows
m_table->horizontalHeader()->setSectionResizeMode(COL_EMPTY, QHeaderView::Stretch);
m_table->setRowCount(rows.size());
for (int i = 0; i < rows.size(); i++)
{
m_table->setVerticalHeaderItem(i, new QTableWidgetItem(rows[i]));
for (int j = 0; j < m_measurementColumns.size(); j++)
{
QTableWidgetItem *item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsEnabled);
item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
if (j < COL_COUNT)
{
item->setData(UnitsDelegate::UNITS_ROLE, units[i]);
item->setData(UnitsDelegate::PRECISION_ROLE, 1);
}
else if (j == COL_SPEC)
{
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
}
m_table->setItem(i, j, item);
}
Measurement m;
m.m_units = units[i];
m_measurements.append(m);
}
resizeMeasurementsTable();
for (int i = 0; i < COL_COUNT; i++) {
m_table->setItemDelegateForColumn(i, new UnitsDelegate());
}
createTableMenus();
}
void SpectrumMeasurements::createPeakTable(int peaks)
{
m_peakTable = new QTableWidget();
m_peakTable->horizontalHeader()->setSectionsMovable(true);
QStringList columns = QStringList{"Frequency", "Power", ""};
m_peakTable->setColumnCount(columns.size());
m_peakTable->setRowCount(peaks);
for (int i = 0; i < columns.size(); i++) {
m_peakTable->setHorizontalHeaderItem(i, new QTableWidgetItem(columns[i]));
}
for (int i = 0; i < peaks; i++)
{
for (int j = 0; j < 3; j++)
{
QTableWidgetItem *item = new QTableWidgetItem();
item->setFlags(Qt::ItemIsEnabled);
if (j == COL_FREQUENCY) {
item->setData(UnitsDelegate::UNITS_ROLE, "Hz");
} else if (j == COL_POWER) {
item->setData(UnitsDelegate::UNITS_ROLE, " dB");
item->setData(UnitsDelegate::PRECISION_ROLE, 1);
}
m_peakTable->setItem(i, j, item);
}
}
resizePeakTable();
m_peakTable->setItemDelegateForColumn(COL_FREQUENCY, new UnitsDelegate());
m_peakTable->setItemDelegateForColumn(COL_POWER, new UnitsDelegate());
// Fill up space at end of rows
m_peakTable->horizontalHeader()->setSectionResizeMode(COL_PEAK_EMPTY, QHeaderView::Stretch);
m_peakTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_peakTable->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
// Cell context menu
m_peakTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_peakTable, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::peakTableContextMenu);
}
void SpectrumMeasurements::createTableMenus()
{
// Add context menu to allow hiding/showing of columns
m_rowMenu = new QMenu(m_table);
for (int i = 0; i < m_table->verticalHeader()->count(); i++)
{
QString text = m_table->verticalHeaderItem(i)->text();
m_rowMenu->addAction(createCheckableItem(text, i, true, true));
}
m_table->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_table->verticalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::rowSelectMenu);
// Add context menu to allow hiding/showing of rows
m_columnMenu = new QMenu(m_table);
for (int i = 0; i < m_table->horizontalHeader()->count(); i++)
{
QString text = m_table->horizontalHeaderItem(i)->text();
m_columnMenu->addAction(createCheckableItem(text, i, true, false));
}
m_table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_table->horizontalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::columnSelectMenu);
}
void SpectrumMeasurements::createChannelPowerTable()
{
QStringList rows = {"Channel power"};
QStringList units = {" dB"};
createMeasurementsTable(rows, units);
}
void SpectrumMeasurements::createAdjacentChannelPowerTable()
{
QStringList rows = {"Left power", "Left ACPR", "Center power", "Right power", "Right ACPR"};
QStringList units = {" dB", " dBc", " dB", " dB", " dBc"};
createMeasurementsTable(rows, units);
}
void SpectrumMeasurements::createSNRTable()
{
QStringList rows = {"SNR", "SNFR", "THD", "THD+N", "SINAD", "SFDR",};
QStringList units = {" dB", " dB", " dB", " dB", " dB", " dBc"};
createMeasurementsTable(rows, units);
}
// Create column select menu item
QAction *SpectrumMeasurements::createCheckableItem(QString &text, int idx, bool checked, bool row)
{
QAction *action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
if (row) {
connect(action, &QAction::triggered, this, &SpectrumMeasurements::rowSelectMenuChecked);
} else {
connect(action, &QAction::triggered, this, &SpectrumMeasurements::columnSelectMenuChecked);
}
return action;
}
// Right click in table header - show row select menu
void SpectrumMeasurements::rowSelectMenu(QPoint pos)
{
m_rowMenu->popup(m_table->verticalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show row when menu selected
void SpectrumMeasurements::rowSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
m_table->setRowHidden(idx, !action->isChecked());
}
}
// Right click in table header - show column select menu
void SpectrumMeasurements::columnSelectMenu(QPoint pos)
{
m_columnMenu->popup(m_table->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void SpectrumMeasurements::columnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
m_table->setColumnHidden(idx, !action->isChecked());
}
}
void SpectrumMeasurements::tableContextMenu(QPoint pos)
{
QTableWidgetItem *item = m_table->itemAt(pos);
if (item)
{
int row = item->row();
QMenu* tableContextMenu = new QMenu(m_table);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
QAction* copyAction = new QAction("Copy", tableContextMenu);
const QString text = item->text();
connect(copyAction, &QAction::triggered, this, [text]()->void {
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
});
tableContextMenu->addAction(copyAction);
tableContextMenu->addSeparator();
tableContextMenu->popup(m_table->viewport()->mapToGlobal(pos));
}
}
void SpectrumMeasurements::peakTableContextMenu(QPoint pos)
{
QTableWidgetItem *item = m_peakTable->itemAt(pos);
if (item)
{
int row = item->row();
QMenu* tableContextMenu = new QMenu(m_peakTable);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
QAction* copyAction = new QAction("Copy", tableContextMenu);
const QString text = item->text();
connect(copyAction, &QAction::triggered, this, [text]()->void {
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
});
tableContextMenu->addAction(copyAction);
tableContextMenu->addSeparator();
tableContextMenu->popup(m_peakTable->viewport()->mapToGlobal(pos));
}
}
void SpectrumMeasurements::resizeMeasurementsTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = m_table->rowCount();
m_table->setRowCount(row + 1);
m_table->setItem(row, COL_CURRENT, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_MEAN, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_MIN, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_MAX, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_RANGE, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_STD_DEV, new QTableWidgetItem("-120.0 dBc"));
m_table->setItem(row, COL_COUNT, new QTableWidgetItem("100000"));
m_table->setItem(row, COL_SPEC, new QTableWidgetItem(">= -120.0"));
m_table->setItem(row, COL_FAILS, new QTableWidgetItem("100000"));
m_table->resizeColumnsToContents();
m_table->removeRow(row);
}
void SpectrumMeasurements::resizePeakTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = m_peakTable->rowCount();
m_peakTable->setRowCount(row + 1);
m_peakTable->setItem(row, COL_FREQUENCY, new QTableWidgetItem("6.000,000,000GHz"));
m_peakTable->setItem(row, COL_POWER, new QTableWidgetItem("-120.0 dB"));
m_peakTable->resizeColumnsToContents();
m_peakTable->removeRow(row);
}
void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks)
{
// Tried using setVisible(), but that would hang, so delete and recreate
delete m_peakTable;
m_peakTable = nullptr;
delete m_table;
m_table = nullptr;
switch (measurement)
{
case SpectrumSettings::MeasurementPeaks:
createPeakTable(peaks);
layout()->addWidget(m_peakTable);
break;
case SpectrumSettings::MeasurementChannelPower:
reset();
createChannelPowerTable();
layout()->addWidget(m_table);
break;
case SpectrumSettings::MeasurementAdjacentChannelPower:
reset();
createAdjacentChannelPowerTable();
layout()->addWidget(m_table);
break;
case SpectrumSettings::MeasurementSNR:
reset();
createSNRTable();
layout()->addWidget(m_table);
break;
default:
break;
}
}
void SpectrumMeasurements::reset()
{
for (int i = 0; i < m_measurements.size(); i++) {
m_measurements[i].reset();
}
if (m_table)
{
for (int i = 0; i < m_table->rowCount(); i++)
{
for (int j = 0; j < m_table->columnCount(); j++)
{
if (j != COL_SPEC) {
m_table->item(i, j)->setText("");
}
}
}
}
}
// Check the value meets the user-defined specification
bool SpectrumMeasurements::checkSpec(const QString &spec, double value) const
{
if (spec.isEmpty()) {
return true;
}
if (spec.startsWith("<="))
{
double limit = spec.mid(2).toDouble();
return value <= limit;
}
else if (spec[0] == '<')
{
double limit = spec.mid(1).toDouble();
return value < limit;
}
else if (spec.startsWith(">="))
{
double limit = spec.mid(2).toDouble();
return value >= limit;
}
else if (spec[0] == '>')
{
double limit = spec.mid(1).toDouble();
return value > limit;
}
else if (spec[0] == '=')
{
double limit = spec.mid(1).toDouble();
return value == limit;
}
return false;
}
void SpectrumMeasurements::updateMeasurement(int row, float value)
{
m_measurements[row].add(value);
double mean = m_measurements[row].mean();
m_table->item(row, COL_CURRENT)->setData(Qt::DisplayRole, value);
m_table->item(row, COL_MEAN)->setData(Qt::DisplayRole, mean);
m_table->item(row, COL_MIN)->setData(Qt::DisplayRole, m_measurements[row].m_min);
m_table->item(row, COL_MAX)->setData(Qt::DisplayRole, m_measurements[row].m_max);
m_table->item(row, COL_RANGE)->setData(Qt::DisplayRole, m_measurements[row].m_max - m_measurements[row].m_min);
m_table->item(row, COL_STD_DEV)->setData(Qt::DisplayRole, m_measurements[row].stdDev());
m_table->item(row, COL_COUNT)->setData(Qt::DisplayRole, m_measurements[row].m_values.size());
QString spec = m_table->item(row, COL_SPEC)->text();
bool valueOK = checkSpec(spec, value);
bool meanOK = checkSpec(spec, mean);
bool minOK = checkSpec(spec, m_measurements[row].m_min);
bool mmaxOK = checkSpec(spec, m_measurements[row].m_max);
if (!valueOK)
{
m_measurements[row].m_fails++;
m_table->item(row, 8)->setData(Qt::DisplayRole, m_measurements[row].m_fails);
}
// item->setForeground doesn't work, perhaps as we have style sheet applied?
m_table->item(row, COL_CURRENT)->setData(Qt::ForegroundRole, valueOK ? m_textBrush : m_redBrush);
m_table->item(row, COL_MEAN)->setData(Qt::ForegroundRole, meanOK ? m_textBrush : m_redBrush);
m_table->item(row, COL_MIN)->setData(Qt::ForegroundRole, minOK ? m_textBrush : m_redBrush);
m_table->item(row, COL_MAX)->setData(Qt::ForegroundRole, mmaxOK ? m_textBrush : m_redBrush);
}
void SpectrumMeasurements::setSNR(float snr, float snfr, float thd, float thdpn, float sinad)
{
updateMeasurement(0, snr);
updateMeasurement(1, snfr);
updateMeasurement(2, thd);
updateMeasurement(3, thdpn);
updateMeasurement(4, sinad);
}
void SpectrumMeasurements::setSFDR(float sfdr)
{
updateMeasurement(5, sfdr);
}
void SpectrumMeasurements::setChannelPower(float power)
{
updateMeasurement(0, power);
}
void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR)
{
updateMeasurement(0, left);
updateMeasurement(1, leftACPR);
updateMeasurement(2, center);
updateMeasurement(3, right);
updateMeasurement(4, rightACPR);
}
void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power)
{
m_peakTable->item(peak, COL_FREQUENCY)->setData(Qt::DisplayRole, frequency);
m_peakTable->item(peak, COL_POWER)->setData(Qt::DisplayRole, power);
}

View File

@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRGUI_SPECTRUMMEASUREMENTS_H_
#define SDRGUI_SPECTRUMMEASUREMENTS_H_
#include <QWidget>
#include <QMenu>
#include <QTableWidget>
#include "dsp/spectrumsettings.h"
#include "export.h"
// Displays spectrum measurements in a table
class SDRGUI_API SpectrumMeasurements : public QWidget {
Q_OBJECT
struct Measurement {
QList<float> m_values;
float m_min;
float m_max;
double m_sum;
int m_fails;
QString m_units;
Measurement()
{
reset();
}
void reset()
{
m_values.clear();
m_min = std::numeric_limits<float>::max();
m_max = -std::numeric_limits<float>::max();
m_sum = 0.0;
m_fails = 0;
}
void add(float value)
{
m_min = std::min(value, m_min);
m_max = std::max(value, m_max);
m_sum += value;
m_values.append(value);
}
double mean() const
{
return m_sum / m_values.size();
}
double stdDev() const
{
double u = mean();
double sum = 0.0;
for (int i = 0; i < m_values.size(); i++)
{
double d = m_values[i] - u;
sum += d * d;
}
if (m_values.size() < 2) {
return 0.0;
} else {
return sqrt(sum / (m_values.size() - 1));
}
}
};
public:
SpectrumMeasurements(QWidget *parent = nullptr);
void setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks);
void setSNR(float snr, float snfr, float thd, float thdpn, float sinad);
void setSFDR(float sfdr);
void setChannelPower(float power);
void setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR);
void setPeak(int peak, int64_t frequency, float power);
void reset();
private:
void createMeasurementsTable(const QStringList &rows, const QStringList &units);
void createPeakTable(int peaks);
void createTableMenus();
void createChannelPowerTable();
void createAdjacentChannelPowerTable();
void createSNRTable();
void tableContextMenu(QPoint pos);
void peakTableContextMenu(QPoint pos);
void rowSelectMenu(QPoint pos);
void rowSelectMenuChecked(bool checked);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked);
QAction *createCheckableItem(QString &text, int idx, bool checked, bool row);
void resizeMeasurementsTable();
void resizePeakTable();
void updateMeasurement(int row, float value);
bool checkSpec(const QString &spec, double value) const;
QTableWidget *m_table;
QMenu *m_rowMenu;
QMenu *m_columnMenu;
QList<Measurement> m_measurements;
QTableWidget *m_peakTable;
QBrush m_textBrush;
QBrush m_redBrush;
enum MeasurementsCol {
COL_CURRENT,
COL_MEAN,
COL_MIN,
COL_MAX,
COL_RANGE,
COL_STD_DEV,
COL_COUNT,
COL_SPEC,
COL_FAILS,
COL_EMPTY
};
enum PeakTableCol {
COL_FREQUENCY,
COL_POWER,
COL_PEAK_EMPTY
};
static const QStringList m_measurementColumns;
static const QStringList m_tooltips;
};
#endif // SDRGUI_SPECTRUMMEASUREMENTS_H_

View File

@ -26,13 +26,15 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/glspectrumtop.h"
#include "gui/glspectrumgui.h" #include "gui/glspectrumgui.h"
#include "gui/workspaceselectiondialog.h" #include "gui/workspaceselectiondialog.h"
#include "dsp/spectrumvis.h" #include "dsp/spectrumvis.h"
#include "mainspectrumgui.h" #include "mainspectrumgui.h"
MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent) : MainSpectrumGUI::MainSpectrumGUI(GLSpectrumTop *spectrumTop, GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent) :
QMdiSubWindow(parent), QMdiSubWindow(parent),
m_spectrumTop(spectrumTop),
m_spectrum(spectrum), m_spectrum(spectrum),
m_spectrumGUI(spectrumGUI), m_spectrumGUI(spectrumGUI),
m_deviceType(DeviceRx), m_deviceType(DeviceRx),
@ -113,7 +115,7 @@ MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGU
m_topLayout->addWidget(m_hideButton); m_topLayout->addWidget(m_hideButton);
m_spectrumLayout = new QHBoxLayout(); m_spectrumLayout = new QHBoxLayout();
m_spectrumLayout->addWidget(spectrum); m_spectrumLayout->addWidget(spectrumTop);
spectrum->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); spectrum->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_spectrumGUILayout = new QHBoxLayout(); m_spectrumGUILayout = new QHBoxLayout();
m_spectrumGUILayout->addWidget(spectrumGUI); m_spectrumGUILayout->addWidget(spectrumGUI);
@ -151,7 +153,7 @@ MainSpectrumGUI::MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGU
MainSpectrumGUI::~MainSpectrumGUI() MainSpectrumGUI::~MainSpectrumGUI()
{ {
qDebug("MainSpectrumGUI::~MainSpectrumGUI"); qDebug("MainSpectrumGUI::~MainSpectrumGUI");
m_spectrumLayout->removeWidget(m_spectrum); m_spectrumLayout->removeWidget(m_spectrumTop);
m_spectrumGUILayout->removeWidget(m_spectrumGUI); m_spectrumGUILayout->removeWidget(m_spectrumGUI);
delete m_sizeGripBottomRight; delete m_sizeGripBottomRight;
delete m_bottomLayout; delete m_bottomLayout;

View File

@ -26,6 +26,7 @@
#include "export.h" #include "export.h"
class GLSpectrum; class GLSpectrum;
class GLSpectrumTop;
class GLSpectrumGUI; class GLSpectrumGUI;
class QLabel; class QLabel;
class QPushButton; class QPushButton;
@ -44,7 +45,7 @@ public:
DeviceMIMO DeviceMIMO
}; };
MainSpectrumGUI(GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent = nullptr); MainSpectrumGUI(GLSpectrumTop *spectrumTop, GLSpectrum *spectrum, GLSpectrumGUI *spectrumGUI, QWidget *parent = nullptr);
virtual ~MainSpectrumGUI(); virtual ~MainSpectrumGUI();
void setDeviceType(DeviceType type); void setDeviceType(DeviceType type);
@ -60,6 +61,7 @@ public:
const QByteArray& getGeometryBytes() const { return m_geometryBytes; } const QByteArray& getGeometryBytes() const { return m_geometryBytes; }
private: private:
GLSpectrumTop *m_spectrumTop;
GLSpectrum *m_spectrum; GLSpectrum *m_spectrum;
GLSpectrumGUI *m_spectrumGUI; GLSpectrumGUI *m_spectrumGUI;
int m_workspaceIndex; int m_workspaceIndex;