diff --git a/Software/PC_Application/Calibration/amplitudecaldialog.cpp b/Software/PC_Application/Calibration/amplitudecaldialog.cpp index 26ece2a..7069f6b 100644 --- a/Software/PC_Application/Calibration/amplitudecaldialog.cpp +++ b/Software/PC_Application/Calibration/amplitudecaldialog.cpp @@ -416,7 +416,7 @@ void AmplitudeCalDialog::ReceivedMeasurement(Protocol::SpectrumAnalyzerResult re if(measured.size() >= averages) { measured.pop_front(); } - MeasurementResult m = {.port1 = 20*log10(res.port1), .port2 = 20*log10(res.port2)}; + MeasurementResult m = {.port1 = Unit::dB(res.port1), .port2 = Unit::dB(res.port2)}; measured.push_back(m); } } diff --git a/Software/PC_Application/Device/manualcontroldialog.cpp b/Software/PC_Application/Device/manualcontroldialog.cpp index bdfd749..2c09cc0 100644 --- a/Software/PC_Application/Device/manualcontroldialog.cpp +++ b/Software/PC_Application/Device/manualcontroldialog.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "unit.h" using namespace std; @@ -206,8 +207,8 @@ void ManualControlDialog::NewStatus(Protocol::ManualStatus status) auto port1referenced = port1 / ref; auto port2referenced = port2 / ref; - auto port1db = 20*log10(abs(port1referenced)); - auto port2db = 20*log10(abs(port2referenced)); + auto port1db = Unit::dB(port1referenced); + auto port2db = Unit::dB(port2referenced); ui->port1referenced->setText(QString::number(port1db, 'f', 1) + "db@" + QString::number(arg(port1referenced)*180/M_PI, 'f', 0) + "°"); ui->port2referenced->setText(QString::number(port2db, 'f', 1) + "db@" + QString::number(arg(port2referenced)*180/M_PI, 'f', 0) + "°"); diff --git a/Software/PC_Application/Tools/impedancematchdialog.cpp b/Software/PC_Application/Tools/impedancematchdialog.cpp index 6ed92b5..9ae154a 100644 --- a/Software/PC_Application/Tools/impedancematchdialog.cpp +++ b/Software/PC_Application/Tools/impedancematchdialog.cpp @@ -1,6 +1,7 @@ #include "impedancematchdialog.h" #include "ui_impedancematchdialog.h" #include "Tools/eseries.h" +#include "unit.h" using namespace std; @@ -239,7 +240,7 @@ void ImpedanceMatchDialog::calculateMatch() ui->mReal->setValue(Zmatched.real()); ui->mImag->setValue(Zmatched.imag()); double reflection = abs((Zmatched-Z0)/(Zmatched+Z0)); - auto loss = 20.0*log10(reflection); + auto loss = Unit::dB(reflection); ui->mLoss->setValue(loss); // set correct image diff --git a/Software/PC_Application/Traces/Math/tdr.cpp b/Software/PC_Application/Traces/Math/tdr.cpp index 9c05983..02da726 100644 --- a/Software/PC_Application/Traces/Math/tdr.cpp +++ b/Software/PC_Application/Traces/Math/tdr.cpp @@ -6,6 +6,8 @@ #include #include #include "ui_tdrexplanationwidget.h" +#include "unit.h" + using namespace Math; using namespace std; @@ -93,7 +95,7 @@ void TDR::edit() ui->manualMag->setUnit("dBm"); ui->manualMag->setPrecision(3); - ui->manualMag->setValue(20*log10(abs(manualDC))); + ui->manualMag->setValue(Unit::dB(manualDC)); ui->manualPhase->setUnit("°"); ui->manualPhase->setPrecision(4); ui->manualPhase->setValue(180.0/M_PI * arg(manualDC)); diff --git a/Software/PC_Application/Traces/Math/timegate.cpp b/Software/PC_Application/Traces/Math/timegate.cpp index ff36b34..50b2a0f 100644 --- a/Software/PC_Application/Traces/Math/timegate.cpp +++ b/Software/PC_Application/Traces/Math/timegate.cpp @@ -345,8 +345,8 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event) auto last = input[i-1]; auto now = input[i]; - auto y_last = 20*log10(abs(last.y)); - auto y_now = 20*log10(abs(now.y)); + auto y_last = Unit::dB(last.y); + auto y_now = Unit::dB(now.y); if(std::isnan(y_last) || std::isnan(y_now) || std::isinf(y_last) || std::isinf(y_now)) { continue; @@ -366,8 +366,8 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event) auto x_last = input[i-1].x; auto x_now = input[i].x; - auto f_last = 20*log10(filter[i-1]); - auto f_now = 20*log10(filter[i]); + auto f_last = Unit::dB(filter[i-1]); + auto f_now = Unit::dB(filter[i]); if(std::isnan(f_last) || std::isnan(f_now) || std::isinf(f_last) || std::isinf(f_now)) { continue; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 751bba0..848e9de 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "unit.h" using namespace std; @@ -867,7 +868,7 @@ std::vector Trace::findPeakFrequencies(unsigned int maxPeaks, double min double max_dbm = -200.0; double min_dbm = 200.0; for(auto d : lastMath->rData()) { - double dbm = 20*log10(abs(d.y)); + double dbm = Unit::dB(d.y); if((dbm >= max_dbm) && (min_dbm <= dbm - minValley)) { // potential peak frequency frequency = d.x; @@ -941,7 +942,7 @@ double Trace::getNoise(double frequency) return std::numeric_limits::quiet_NaN(); } // convert to dbm - auto dbm = 20*log10(abs(lastMath->getInterpolatedSample(frequency).y)); + auto dbm = Unit::dB(lastMath->getInterpolatedSample(frequency).y); // convert to 1Hz bandwidth dbm -= 10*log10(settings.SA.RBW); return dbm; diff --git a/Software/PC_Application/Traces/tracemarker.cpp b/Software/PC_Application/Traces/tracemarker.cpp index 39fbbac..bc9a5d3 100644 --- a/Software/PC_Application/Traces/tracemarker.cpp +++ b/Software/PC_Application/Traces/tracemarker.cpp @@ -1,4 +1,4 @@ -#include "tracemarker.h" +#include "tracemarker.h" #include #include "CustomWidgets/siunitedit.h" #include @@ -9,6 +9,7 @@ #include "unit.h" #include #include +#include using namespace std; @@ -26,7 +27,8 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *paren parent(parent), cutoffAmplitude(-3.0), peakThreshold(-40.0), - offset(10000) + offset(10000), + formatTable(Format::dBAngle) { connect(this, &TraceMarker::traceChanged, this, &TraceMarker::updateContextmenu); connect(this, &TraceMarker::typeChanged, this, &TraceMarker::updateContextmenu); @@ -71,6 +73,7 @@ void TraceMarker::assignTrace(Trace *t) for(auto m : helperMarkers) { m->assignTrace(t); } + constrainFormat(); update(); emit traceChanged(this); } @@ -80,7 +83,110 @@ Trace *TraceMarker::trace() return parentTrace; } -QString TraceMarker::readableData() +QString TraceMarker::formatToString(TraceMarker::Format f) +{ + switch(f) { + case Format::dB: return "dB"; + case Format::dBAngle: return "dB + angle"; + case Format::RealImag: return "real + imag"; + case Format::Impedance: return "Impedance"; + case Format::TOI: return "Third order intercept"; + case Format::AvgTone: return "Average Tone Level"; + case Format::AvgModulationProduct: return "Average Modulation Product Level"; + case Format::Noise: return "Noise level"; + case Format::PhaseNoise: return "Phase noise"; + case Format::Cutoff: return "Cutoff frequency"; + case Format::CenterBandwidth: return "Center + Bandwidth"; + case Format::InsertionLoss: return "Insertion loss"; + case Format::Last: return ""; + } + return ""; +} + +TraceMarker::Format TraceMarker::formatFromString(QString s) +{ + for(int i=0;i<(int) Format::Last;i++) { + if(s.compare(formatToString((Format) i)) == 0) { + return (Format) i; + } + } + return Format::Last; +} + +std::vector TraceMarker::formats() +{ + std::vector ret; + for(int i=0;i<(int) Format::Last;i++) { + ret.push_back((Format) i); + } + return ret; +} + +std::set TraceMarker::applicableFormats() +{ + std::set ret; + if(isTimeDomain()) { + switch(type) { + case Type::Manual: + case Type::Delta: + ret.insert(Format::dB); + ret.insert(Format::RealImag); + if(parentTrace) { + auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); + if(!isnan(step)) { + ret.insert(Format::Impedance); + } + } + break; + default: + return {}; + } + } else { + switch(type) { + case Type::Manual: + case Type::Delta: + case Type::Maximum: + case Type::Minimum: + case Type::PeakTable: + ret.insert(Format::dB); + ret.insert(Format::dBAngle); + ret.insert(Format::RealImag); + if(parentTrace) { + if(parentTrace->isReflection()) { + ret.insert(Format::Impedance); + } + if(!isnan(parentTrace->getNoise(parentTrace->minX()))) { + ret.insert(Format::Noise); + } + } + + break; + case Type::Bandpass: + ret.insert(Format::CenterBandwidth); + ret.insert(Format::InsertionLoss); + break; + case Type::Lowpass: + case Type::Highpass: + ret.insert(Format::Cutoff); + ret.insert(Format::InsertionLoss); + break; + case Type::PhaseNoise: + ret.insert(Format::PhaseNoise); + ret.insert(Format::dB); + break; + case Type::TOI: + ret.insert(Format::TOI); + ret.insert(Format::AvgTone); + ret.insert(Format::AvgModulationProduct); + break; + case Type::Last: + break; + } + } + return ret; +} + +QString TraceMarker::readableData(Format f) { if(!parentTrace) { return ""; @@ -88,128 +194,128 @@ QString TraceMarker::readableData() if(position < parentTrace->minX() || position > parentTrace->maxX()) { return ""; } + + if(f == Format::Last) { + // format not explicitly specified, use setting for table + f = formatTable; + } + if(isTimeDomain()) { - switch(type) { - case Type::Manual: { - QString ret; - auto impulse = data.real(); - auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); - ret += "Impulse:"+Unit::ToString(impulse, "", "m ", 3); - if(!isnan(step)) { - ret += " Step:"+Unit::ToString(step, "", "m ", 3); - if(abs(step) < 1.0) { - auto impedance = 50.0 * (1.0 + step) / (1.0 - step); - ret += " Impedance:"+Unit::ToString(impedance, "Ω", "m kM", 3); - } + if(type != Type::Delta) { + switch(f) { + case Format::dB: + return Unit::ToString(Unit::dB(data), "dB", " ", 3); + case Format::RealImag: + return Unit::ToString(data.real(), "", " ", 5) + "+"+Unit::ToString(data.imag(), "", " ", 5)+"j"; + case Format::Impedance: { + auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); + auto impedance = 50.0 * (1.0 + step) / (1.0 - step); + return Unit::ToString(impedance, "Ω", "m kM", 3); } - return ret; - } - case Type::Delta: { + break; + default: + return "Invalid"; + } + } else { if(!delta || !delta->isTimeDomain()) { - return "Invalid delta marker"; + return "Invalid"; } - // calculate difference between markers - auto impulse = data.real() - delta->data.real(); - QString ret; - auto timeDiff = position - delta->position; - auto distanceDiff = parentTrace->timeToDistance(position) - delta->parentTrace->timeToDistance(delta->position); - ret += "Δ:"+Unit::ToString(timeDiff, "s", "fpnum ", 4) + "/" + Unit::ToString(distanceDiff, "m", "m k", 4); - ret += " ΔImpulse:"+Unit::ToString(impulse, "", "m ", 3); - auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); - auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), Trace::SampleType::TimeStep).y.real(); - if(!isnan(step) && !isnan(stepDelta)) { - auto stepDiff = step - stepDelta; - ret += " ΔStep:"+Unit::ToString(stepDiff, "", "m ", 3); - if(abs(step) < 1.0 && abs(stepDelta) < 1.0) { - auto impedance = 50.0 * (1.0 + step) / (1.0 - step); - auto impedanceDelta = 50.0 * (1.0 + stepDelta) / (1.0 - stepDelta); - auto impedanceDiff = impedance - impedanceDelta; - ret += " ΔImpedance:"+Unit::ToString(impedanceDiff, "Ω", "m kM", 3); - } + switch(f) { + case Format::dB: + return "Δ:"+Unit::ToString(Unit::dB(data) - Unit::dB(delta->data), "dB", " ", 3); + case Format::RealImag: + return "Δ:"+Unit::ToString(data.real() - delta->data.real(), "", " ", 5) + "+"+Unit::ToString(data.imag() - delta->data.real(), "", " ", 5)+"j"; + case Format::Impedance: { + auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real(); + auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), Trace::SampleType::TimeStep).y.real(); + auto impedance = 50.0 * (1.0 + step) / (1.0 - step); + auto impedanceDelta = 50.0 * (1.0 + stepDelta) / (1.0 - stepDelta); + return "Δ:"+Unit::ToString(impedance - impedanceDelta, "Ω", "m kM", 3); + } + break; + default: + return "Invalid"; } - return ret; - } - default: - return "Invalid type"; } } else { switch(type) { - case Type::Manual: - case Type::Maximum: - case Type::Minimum: { - auto phase = arg(data); - return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); - } - case Type::Delta: - if(!delta || delta->isTimeDomain()) { - return "Invalid delta marker"; - } else { - // calculate difference between markers - auto freqDiff = position - delta->position; - auto valueDiff = data / delta->data; - auto phase = arg(valueDiff); - auto db = 20*log10(abs(valueDiff)); - return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); - } - break; - case Type::Noise: - return Unit::ToString(parentTrace->getNoise(position), "dbm/Hz", " ", 3); case Type::PeakTable: return "Found " + QString::number(helperMarkers.size()) + " peaks"; - case Type::Lowpass: - case Type::Highpass: - if(parentTrace->isReflection()) { - return "Calculation not possible with reflection measurement"; - } else { - auto insertionLoss = toDecibel(); - auto cutoff = helperMarkers[0]->toDecibel(); - QString ret = "fc: "; - if(cutoff > insertionLoss + cutoffAmplitude) { - // the trace never dipped below the specified cutoffAmplitude, exact cutoff frequency unknown - ret += type == Type::Lowpass ? ">" : "<"; - } - ret += Unit::ToString(helperMarkers[0]->position, "Hz", " kMG", 4); - ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; - return ret; - } - break; - case Type::Bandpass: - if(parentTrace->isReflection()) { - return "Calculation not possible with reflection measurement"; - } else { - auto insertionLoss = toDecibel(); - auto cutoffL = helperMarkers[0]->toDecibel(); - auto cutoffH = helperMarkers[1]->toDecibel(); - auto bandwidth = helperMarkers[1]->position - helperMarkers[0]->position; - auto center = helperMarkers[2]->position; - QString ret = "fc: "; - if(cutoffL > insertionLoss + cutoffAmplitude || cutoffH > insertionLoss + cutoffAmplitude) { - // the trace never dipped below the specified cutoffAmplitude, center and exact bandwidth unknown - ret += "?, BW: >"; - } else { - ret += Unit::ToString(center, "Hz", " kMG", 5)+ ", BW: "; - } - ret += Unit::ToString(bandwidth, "Hz", " kMG", 4); - ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; - return ret; - } - break; - case Type::TOI: { - auto avgFundamental = (helperMarkers[0]->toDecibel() + helperMarkers[1]->toDecibel()) / 2; - auto avgDistortion = (helperMarkers[2]->toDecibel() + helperMarkers[3]->toDecibel()) / 2; - auto TOI = (3 * avgFundamental - avgDistortion) / 2; - return "Fundamental: " + Unit::ToString(avgFundamental, "dbm", " ", 3) + ", distortion: " + Unit::ToString(avgDistortion, "dbm", " ", 3) + ", TOI: "+Unit::ToString(TOI, "dbm", " ", 3); - } - break; - case Type::PhaseNoise: { - auto carrier = toDecibel(); - auto phasenoise = parentTrace->getNoise(helperMarkers[0]->position) - carrier; - return Unit::ToString(phasenoise, "dbc/Hz", " ", 3) +"@" + Unit::ToString(offset, "Hz", " kM", 4) + " offset (" + Unit::ToString(position, "Hz", " kMG", 6) + " carrier)"; - } + case Type::Delta: + // TODO + return "TODO"; default: - return "Unknown marker type"; + switch(f) { + case Format::dB: return Unit::ToString(Unit::dB(data), "dB", " ", 3); + case Format::dBAngle: return Unit::ToString(Unit::dB(data), "dB", " ", 3) + "/"+Unit::ToString(arg(data)*180/M_PI, "°", " ", 3); + case Format::RealImag: return Unit::ToString(data.real(), "", " ", 5) + "+"+Unit::ToString(data.imag(), "", " ", 5)+"j"; + case Format::Noise: return Unit::ToString(parentTrace->getNoise(position), "dbm/Hz", " ", 3); + case Format::TOI: { + auto avgFundamental = (helperMarkers[0]->toDecibel() + helperMarkers[1]->toDecibel()) / 2; + auto avgDistortion = (helperMarkers[2]->toDecibel() + helperMarkers[3]->toDecibel()) / 2; + auto TOI = (3 * avgFundamental - avgDistortion) / 2; + return "TOI: "+Unit::ToString(TOI, "dbm", " ", 3); + } + break; + case Format::AvgTone: { + auto avgFundamental = (helperMarkers[0]->toDecibel() + helperMarkers[1]->toDecibel()) / 2; + return "Avg. Tone: " + Unit::ToString(avgFundamental, "dbm", " ", 3); + } + break; + case Format::AvgModulationProduct: { + auto avgDistortion = (helperMarkers[2]->toDecibel() + helperMarkers[3]->toDecibel()) / 2; + return "Distortion: " + Unit::ToString(avgDistortion, "dbm", " ", 3); + } + break; + case Format::Cutoff: + if(parentTrace->isReflection()) { + return "Calculation not possible with reflection measurement"; + } else { + return "Cutoff:" + Unit::ToString(helperMarkers[0]->position, "Hz", " kMG", 4); + } + case Format::InsertionLoss: + if(parentTrace->isReflection()) { + return "Calculation not possible with reflection measurement"; + } else { + return "Ins. Loss:"+Unit::ToString(Unit::dB(data), "dB", " ", 3); + } + case Format::PhaseNoise: { + auto carrier = toDecibel(); + auto phasenoise = parentTrace->getNoise(helperMarkers[0]->position) - carrier; + return Unit::ToString(phasenoise, "dbc/Hz", " ", 3) +"@" + Unit::ToString(offset, "Hz", " kM", 4) + " offset"; + } + break; + case Format::Impedance: { + auto impedance = 50.0 * (1.0 + data) / (1.0 - data); + return Unit::ToString(impedance.real(), "Ω", "m k", 5) + "+"+Unit::ToString(impedance.imag(), "Ω", "m k", 5)+"j"; + } + case Format::CenterBandwidth: + if(parentTrace->isReflection()) { + return "Calculation not possible with reflection measurement"; + } else { + auto insertionLoss = toDecibel(); + auto cutoffL = helperMarkers[0]->toDecibel(); + auto cutoffH = helperMarkers[1]->toDecibel(); + auto bandwidth = helperMarkers[1]->position - helperMarkers[0]->position; + auto center = helperMarkers[2]->position; + QString ret = "fc: "; + if(cutoffL > insertionLoss + cutoffAmplitude || cutoffH > insertionLoss + cutoffAmplitude) { + // the trace never dipped below the specified cutoffAmplitude, center and exact bandwidth unknown + ret += "?, BW: >"; + } else { + ret += Unit::ToString(center, "Hz", " kMG", 5)+ ", BW: "; + } + ret += Unit::ToString(bandwidth, "Hz", " kMG", 4); + ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; + return ret; + } + break; + case Format::Last: + return "Invalid"; + } } } + return "Invalid"; } QString TraceMarker::readableSettings() @@ -228,7 +334,6 @@ QString TraceMarker::readableSettings() case Type::Maximum: case Type::Minimum: case Type::Delta: - case Type::Noise: return Unit::ToString(position, "Hz", " kMG", 6); case Type::Lowpass: case Type::Highpass: @@ -262,7 +367,6 @@ QString TraceMarker::tooltipSettings() case Type::Maximum: case Type::Minimum: case Type::Delta: - case Type::Noise: return "Marker frequency"; case Type::Lowpass: case Type::Highpass: @@ -370,25 +474,70 @@ void TraceMarker::deltaDeleted() void TraceMarker::updateContextmenu() { - if(contextmenu) { - delete contextmenu; - } - contextmenu = new QMenu(); - auto typemenu = new QMenu("Type"); - auto typegroup = new QActionGroup(contextmenu); - for(auto t : getSupportedTypes()) { - auto setTypeAction = new QAction(typeToString(t)); - setTypeAction->setCheckable(true); - if(t == type) { - setTypeAction->setChecked(true); + if(parent) { + parent->updateContextmenu(); + contextmenu = parent->contextmenu; + } else { + if(contextmenu) { + // check if the contextmenu or one of its submenus is currently open + auto *activeWidget = QApplication::activePopupWidget(); + while (activeWidget) { + if(activeWidget == contextmenu) { + // contextmenu currently open, do not update + return; + } + activeWidget = activeWidget->parentWidget(); + } + delete contextmenu; + } + contextmenu = new QMenu(); + auto typemenu = contextmenu->addMenu("Type"); + auto typegroup = new QActionGroup(contextmenu); + for(auto t : getSupportedTypes()) { + auto setTypeAction = new QAction(typeToString(t)); + setTypeAction->setCheckable(true); + if(t == type) { + setTypeAction->setChecked(true); + } + connect(setTypeAction, &QAction::triggered, [=](){ + setType(t); + }); + typegroup->addAction(setTypeAction); + typemenu->addAction(setTypeAction); + } + + auto table = contextmenu->addMenu("Data Format in Table"); + auto tablegroup = new QActionGroup(contextmenu); + for(auto f : applicableFormats()) { + auto setFormatAction = new QAction(formatToString(f)); + setFormatAction->setCheckable(true); + if(f == formatTable) { + setFormatAction->setChecked(true); + } + connect(setFormatAction, &QAction::triggered, [=](){ + setTableFormat(f); + }); + tablegroup->addAction(setFormatAction); + table->addAction(setFormatAction); + } + + auto graph = contextmenu->addMenu("Show on Graph"); + for(auto f : applicableFormats()) { + auto setFormatAction = new QAction(formatToString(f)); + setFormatAction->setCheckable(true); + if(formatGraph.count(f)) { + setFormatAction->setChecked(true); + } + connect(setFormatAction, &QAction::triggered, [=](bool checked){ + if(checked) { + formatGraph.insert(f); + } else { + formatGraph.erase(f); + } + }); + graph->addAction(setFormatAction); } - connect(setTypeAction, &QAction::triggered, [=](){ - setType(t); - }); - typegroup->addAction(setTypeAction); - typemenu->addAction(setTypeAction); } - contextmenu->addMenu(typemenu); } std::set TraceMarker::getSupportedTypes() @@ -420,7 +569,6 @@ std::set TraceMarker::getSupportedTypes() case Trace::LiveParameter::Port1: case Trace::LiveParameter::Port2: // special SA marker types - supported.insert(Type::Noise); supported.insert(Type::TOI); supported.insert(Type::PhaseNoise); break; @@ -446,6 +594,19 @@ void TraceMarker::constrainPosition() } } +void TraceMarker::constrainFormat() +{ + // check format + if(!applicableFormats().count(formatTable)) { + setTableFormat(*applicableFormats().begin()); + } + for(auto f : formatGraph) { + if(!applicableFormats().count(f)) { + formatGraph.erase(f); + } + } +} + TraceMarker *TraceMarker::bestDeltaCandidate() { TraceMarker *match = nullptr; @@ -535,7 +696,7 @@ void TraceMarker::setType(TraceMarker::Type t) required_helpers = {{"p", "first peak", Type::Manual}, {"p", "second peak", Type::Manual}, {"l", "left intermodulation", Type::Manual}, {"r", "right intermodulation", Type::Manual}}; break; case Type::PhaseNoise: - required_helpers = {{"o", "Offset", Type::Noise}}; + required_helpers = {{"o", "Offset", Type::Manual}}; break; default: break; @@ -548,6 +709,8 @@ void TraceMarker::setType(TraceMarker::Type t) helper->setType(h.type); helperMarkers.push_back(helper); } + constrainFormat(); + updateSymbol(); emit typeChanged(this); update(); @@ -555,7 +718,7 @@ void TraceMarker::setType(TraceMarker::Type t) double TraceMarker::toDecibel() { - return 20*log10(abs(data)); + return Unit::dB(data); } bool TraceMarker::isVisible() @@ -565,7 +728,6 @@ bool TraceMarker::isVisible() case Type::Delta: case Type::Maximum: case Type::Minimum: - case Type::Noise: case Type::PhaseNoise: return true; default: @@ -573,6 +735,24 @@ bool TraceMarker::isVisible() } } +void TraceMarker::setTableFormat(TraceMarker::Format f) +{ + if(formatTable == f) { + // already correct format, nothing to do + return; + } + + if(!applicableFormats().count(f)) { + // can't use requested format for this type of marker + qWarning() << "Requested marker format" << formatToString(f) << "(not applicable for type" << typeToString(type) <<")"; + return; + } + + formatTable = f; + updateContextmenu(); + emit dataChanged(this); +} + TraceMarker::Type TraceMarker::getType() const { return type; @@ -609,6 +789,12 @@ nlohmann::json TraceMarker::toJSON() // other types have no settings break; } + j["formatTable"] = formatToString(formatTable).toStdString(); + nlohmann::json jformatGraph; + for(auto f : formatGraph) { + jformatGraph.push_back(formatToString(f).toStdString()); + } + j["formatGraph"] = jformatGraph; return j; } @@ -660,6 +846,19 @@ void TraceMarker::fromJSON(nlohmann::json j) // other types have no settings break; } + formatTable = formatFromString(QString::fromStdString(j.value("formatTable", formatToString(Format::dBAngle).toStdString()))); + if(formatTable == Format::Last) { + // invalid string, use default + formatTable = Format::dBAngle; + } + formatGraph.clear(); + for(std::string s : j["formatGraph"]) { + auto f = formatFromString(QString::fromStdString(s)); + if(f != Format::Last) { + formatGraph.insert(f); + } + } + updateContextmenu(); update(); } @@ -790,7 +989,6 @@ SIUnitEdit *TraceMarker::getSettingsEditor() case Type::Maximum: case Type::Minimum: case Type::Delta: - case Type::Noise: case Type::PhaseNoise: default: return new SIUnitEdit("Hz", " kMG", 6); @@ -829,7 +1027,6 @@ void TraceMarker::adjustSettings(double value) case Type::Maximum: case Type::Minimum: case Type::Delta: - case Type::Noise: setPosition(value); break; case Type::Lowpass: @@ -862,7 +1059,6 @@ void TraceMarker::update() switch(type) { case Type::Manual: case Type::Delta: - case Type::Noise: // nothing to do break; case Type::Maximum: @@ -880,6 +1076,9 @@ void TraceMarker::update() helper->suffix = suffix; helper->assignTrace(parentTrace); helper->setPosition(p); + helper->formatTable = formatTable; + helper->formatGraph = formatGraph; + helper->updateContextmenu(); suffix++; helperMarkers.push_back(helper); } @@ -897,11 +1096,11 @@ void TraceMarker::update() setPosition(peakFreq); // find the cutoff frequency auto index = parentTrace->index(peakFreq); - auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).y)); + auto peakAmplitude = Unit::dB(parentTrace->sample(index).y); auto cutoff = peakAmplitude + cutoffAmplitude; int inc = type == Type::Lowpass ? 1 : -1; while(index >= 0 && index < (int) parentTrace->size()) { - auto amplitude = 20*log10(abs(parentTrace->sample(index).y)); + auto amplitude = Unit::dB(parentTrace->sample(index).y); if(amplitude <= cutoff) { break; } @@ -927,12 +1126,12 @@ void TraceMarker::update() setPosition(peakFreq); // find the cutoff frequencies auto index = parentTrace->index(peakFreq); - auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).y)); + auto peakAmplitude = Unit::dB(parentTrace->sample(index).y); auto cutoff = peakAmplitude + cutoffAmplitude; auto low_index = index; while(low_index >= 0) { - auto amplitude = 20*log10(abs(parentTrace->sample(low_index).y)); + auto amplitude = Unit::dB(parentTrace->sample(low_index).y); if(amplitude <= cutoff) { break; } @@ -946,7 +1145,7 @@ void TraceMarker::update() auto high_index = index; while(high_index < (int) parentTrace->size()) { - auto amplitude = 20*log10(abs(parentTrace->sample(high_index).y)); + auto amplitude = Unit::dB(parentTrace->sample(high_index).y); if(amplitude <= cutoff) { break; } @@ -1011,7 +1210,6 @@ bool TraceMarker::isMovable() switch(type) { case Type::Manual: case Type::Delta: - case Type::Noise: return true; default: return false; diff --git a/Software/PC_Application/Traces/tracemarker.h b/Software/PC_Application/Traces/tracemarker.h index 8f2fbe0..a6fd670 100644 --- a/Software/PC_Application/Traces/tracemarker.h +++ b/Software/PC_Application/Traces/tracemarker.h @@ -18,7 +18,33 @@ public: ~TraceMarker(); void assignTrace(Trace *t); Trace* trace(); - QString readableData(); + + enum class Format { + dB, + dBAngle, + RealImag, + Impedance, + // Noise marker parameters + Noise, + PhaseNoise, + // Filter parameters + CenterBandwidth, + Cutoff, + InsertionLoss, + // TOI parameters + TOI, // third order intercept point + AvgTone, // average level of tone + AvgModulationProduct, // average level of modulation products + // keep last at end + Last, + }; + + static QString formatToString(Format f); + static Format formatFromString(QString s); + static std::vector formats(); + std::set applicableFormats(); + + QString readableData(Format format = Format::Last); QString readableSettings(); QString tooltipSettings(); QString readableType(); @@ -41,7 +67,6 @@ public: Maximum, Minimum, Delta, - Noise, PeakTable, Lowpass, Highpass, @@ -107,7 +132,6 @@ private: case Type::Maximum: return "Maximum"; case Type::Minimum: return "Minimum"; case Type::Delta: return "Delta"; - case Type::Noise: return "Noise"; case Type::PeakTable: return "Peak Table"; case Type::Lowpass: return "Lowpass"; case Type::Highpass: return "Highpass"; @@ -118,12 +142,15 @@ private: } } void constrainPosition(); + void constrainFormat(); TraceMarker *bestDeltaCandidate(); void deleteHelperMarkers(); void setType(Type t); double toDecibel(); bool isVisible(); + void setTableFormat(Format f); + TraceMarkerModel *model; Trace *parentTrace; double position; @@ -145,6 +172,9 @@ private: double cutoffAmplitude; double peakThreshold; double offset; + + Format formatTable; + std::set formatGraph; }; #endif // TRACEMARKER_H diff --git a/Software/PC_Application/Traces/tracetouchstoneexport.cpp b/Software/PC_Application/Traces/tracetouchstoneexport.cpp index 7d120a1..f426487 100644 --- a/Software/PC_Application/Traces/tracetouchstoneexport.cpp +++ b/Software/PC_Application/Traces/tracetouchstoneexport.cpp @@ -85,12 +85,12 @@ void TraceTouchstoneExport::on_buttonBox_accepted() } t.AddDatapoint(tData); } - Touchstone::Unit unit = Touchstone::Unit::GHz; + Touchstone::Scale unit = Touchstone::Scale::GHz; switch(ui->cUnit->currentIndex()) { - case 0: unit = Touchstone::Unit::Hz; break; - case 1: unit = Touchstone::Unit::kHz; break; - case 2: unit = Touchstone::Unit::MHz; break; - case 3: unit = Touchstone::Unit::GHz; break; + case 0: unit = Touchstone::Scale::Hz; break; + case 1: unit = Touchstone::Scale::kHz; break; + case 2: unit = Touchstone::Scale::MHz; break; + case 3: unit = Touchstone::Scale::GHz; break; } Touchstone::Format format = Touchstone::Format::RealImaginary; switch(ui->cFormat->currentIndex()) { diff --git a/Software/PC_Application/Traces/tracewidget.cpp b/Software/PC_Application/Traces/tracewidget.cpp index 49e8676..5821239 100644 --- a/Software/PC_Application/Traces/tracewidget.cpp +++ b/Software/PC_Application/Traces/tracewidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "unit.h" TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) : QWidget(parent), @@ -171,7 +172,7 @@ void TraceWidget::SetupSCPI() if(std::isnan(d.x)) { return "NaN"; } - return QString::number(20*log10(d.y.real())); + return QString::number(Unit::dB(d.y.real())); } else { if(std::isnan(d.x)) { return "NaN,NaN"; diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index b23f0d9..5c08435 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -845,7 +845,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo } switch(type) { case YAxisType::Magnitude: - ret.setY(20*log10(abs(data.y))); + ret.setY(Unit::dB(data.y)); break; case YAxisType::Phase: ret.setY(arg(data.y) * 180.0 / M_PI); @@ -859,7 +859,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo ret.setY(real(data.y)); break; case YAxisType::ImpulseMag: - ret.setY(20*log10(abs(data.y))); + ret.setY(Unit::dB(data.y)); break; case YAxisType::Step: ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real()); diff --git a/Software/PC_Application/VNA/Deembedding/portextension.cpp b/Software/PC_Application/VNA/Deembedding/portextension.cpp index 666fe8f..2f5c4fc 100644 --- a/Software/PC_Application/VNA/Deembedding/portextension.cpp +++ b/Software/PC_Application/VNA/Deembedding/portextension.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "unit.h" using namespace std; @@ -243,7 +244,7 @@ void PortExtension::measurementCompleted(std::vector m) } double x = sqrt(p.frequency / m.back().frequency); - double y = 20*log10(abs(reflection)); + double y = Unit::dB(reflection); att_x.push_back(x); att_y.push_back(y); avg_x += x; diff --git a/Software/PC_Application/touchstone.cpp b/Software/PC_Application/touchstone.cpp index 68a1e2d..5e74ed2 100644 --- a/Software/PC_Application/touchstone.cpp +++ b/Software/PC_Application/touchstone.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "unit.h" using namespace std; @@ -33,7 +34,7 @@ void Touchstone::AddDatapoint(Touchstone::Datapoint p) } } -void Touchstone::toFile(string filename, Unit unit, Format format) +void Touchstone::toFile(string filename, Scale unit, Format format) { // strip any potential file name extension and apply snp convention if(filename.find_last_of('.') != string::npos) { @@ -49,10 +50,10 @@ void Touchstone::toFile(string filename, Unit unit, Format format) // write option line file << "# "; switch(unit) { - case Unit::Hz: file << "HZ "; break; - case Unit::kHz: file << "KHZ "; break; - case Unit::MHz: file << "MHZ "; break; - case Unit::GHz: file << "GHZ "; break; + case Scale::Hz: file << "HZ "; break; + case Scale::kHz: file << "KHZ "; break; + case Scale::MHz: file << "MHZ "; break; + case Scale::GHz: file << "GHZ "; break; } // only S parameters supported so far file << "S "; @@ -73,17 +74,17 @@ void Touchstone::toFile(string filename, Unit unit, Format format) out << abs(c) << " " << arg(c) / M_PI * 180.0; break; case Format::DBAngle: - out << 20*log10(abs(c)) << " " << arg(c) / M_PI * 180.0; + out << Unit::dB(c) << " " << arg(c) / M_PI * 180.0; break; } }; for(auto p : m_datapoints) { switch(unit) { - case Unit::Hz: file << p.frequency; break; - case Unit::kHz: file << p.frequency / 1e3; break; - case Unit::MHz: file << p.frequency / 1e6; break; - case Unit::GHz: file << p.frequency / 1e9; break; + case Scale::Hz: file << p.frequency; break; + case Scale::kHz: file << p.frequency / 1e3; break; + case Scale::MHz: file << p.frequency / 1e6; break; + case Scale::GHz: file << p.frequency / 1e9; break; } file << " "; // special cases for 1 and 2 port @@ -141,7 +142,7 @@ Touchstone Touchstone::fromFile(string filename) unsigned int ports = filename[index_extension + 2] - '0'; auto ret = Touchstone(ports); - Unit unit = Unit::GHz; + Scale unit = Scale::GHz; Format format = Format::RealImaginary; bool option_line_found = false; @@ -187,13 +188,13 @@ Touchstone Touchstone::fromFile(string filename) break; } if (!s.compare("HZ")) { - unit = Unit::Hz; + unit = Scale::Hz; } else if (!s.compare("KHZ")) { - unit = Unit::kHz; + unit = Scale::kHz; } else if (!s.compare("MHZ")) { - unit = Unit::MHz; + unit = Scale::MHz; } else if (!s.compare("GHZ")) { - unit = Unit::GHz; + unit = Scale::GHz; } else if (!s.compare("S")) { // S parameter, nothing to do } else if (!s.compare("Y")) { @@ -245,10 +246,10 @@ Touchstone Touchstone::fromFile(string filename) iss >> point.frequency; point.S.clear(); switch(unit) { - case Unit::Hz: break; - case Unit::kHz: point.frequency *= 1e3; break; - case Unit::MHz: point.frequency *= 1e6; break; - case Unit::GHz: point.frequency *= 1e9; break; + case Scale::Hz: break; + case Scale::kHz: point.frequency *= 1e3; break; + case Scale::MHz: point.frequency *= 1e6; break; + case Scale::GHz: point.frequency *= 1e9; break; } } unsigned int parameters_per_line; diff --git a/Software/PC_Application/touchstone.h b/Software/PC_Application/touchstone.h index 4ec7ac8..9e638a1 100644 --- a/Software/PC_Application/touchstone.h +++ b/Software/PC_Application/touchstone.h @@ -9,7 +9,7 @@ class Touchstone { public: - enum class Unit { + enum class Scale { Hz, kHz, MHz, @@ -30,7 +30,7 @@ public: Touchstone(unsigned int m_ports); void AddDatapoint(Datapoint p); - void toFile(std::string filename, Unit unit = Unit::GHz, Format format = Format::RealImaginary); + void toFile(std::string filename, Scale unit = Scale::GHz, Format format = Format::RealImaginary); static Touchstone fromFile(std::string filename); double minFreq(); double maxFreq(); diff --git a/Software/PC_Application/unit.cpp b/Software/PC_Application/unit.cpp index bb957ed..b82fa22 100644 --- a/Software/PC_Application/unit.cpp +++ b/Software/PC_Application/unit.cpp @@ -87,3 +87,13 @@ double Unit::SIPrefixToFactor(char prefix) default: return 1e0; break; } } + +double Unit::dB(double d) +{ + return 20*log10(d); +} + +double Unit::dB(std::complex d) +{ + return dB(abs(d)); +} diff --git a/Software/PC_Application/unit.h b/Software/PC_Application/unit.h index db8522e..2871a1a 100644 --- a/Software/PC_Application/unit.h +++ b/Software/PC_Application/unit.h @@ -2,6 +2,7 @@ #define UNIT_H #include +#include namespace Unit { @@ -9,6 +10,8 @@ namespace Unit // prefixed need to be in ascending order (e.g. "m kMG" is okay, whjle "MkG" does not work) QString ToString(double value, QString unit = QString(), QString prefixes = " ", int precision = 6); double SIPrefixToFactor(char prefix); + double dB(std::complex d); + double dB(double d); }; #endif // UNIT_H