Marker extended for use on power domain traces

This commit is contained in:
Jan Käberich 2021-07-10 12:16:28 +02:00
parent 67489084e9
commit b45645f04e
8 changed files with 213 additions and 74 deletions

View File

@ -123,6 +123,7 @@ QString Marker::formatToString(Marker::Format f)
case Format::Cutoff: return "Cutoff frequency";
case Format::CenterBandwidth: return "Center + Bandwidth";
case Format::InsertionLoss: return "Insertion loss";
case Format::P1dB: return "P1dB";
case Format::Last: return "";
}
return "";
@ -150,23 +151,26 @@ std::vector<Marker::Format> Marker::formats()
std::vector<Marker::Format> Marker::applicableFormats()
{
std::vector<Format> ret;
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
switch(type) {
case Type::Manual:
case Type::Delta:
ret.push_back(Format::dB);
ret.push_back(Format::RealImag);
if(parentTrace) {
auto step = parentTrace->sample(parentTrace->index(position), Trace::SampleType::TimeStep).y.real();
// check if step response data is available
auto step = parentTrace->sample(parentTrace->index(position), true).y.real();
if(!isnan(step)) {
ret.push_back(Format::Impedance);
}
}
break;
default:
return {};
break;
}
} else {
break;
case Trace::DataType::Frequency:
switch(type) {
case Type::Manual:
case Type::Delta:
@ -208,9 +212,39 @@ std::vector<Marker::Format> Marker::applicableFormats()
ret.push_back(Format::AvgTone);
ret.push_back(Format::AvgModulationProduct);
break;
case Type::Last:
default:
break;
}
break;
case Trace::DataType::Power:
switch(type) {
case Type::P1dB:
ret.push_back(Format::P1dB);
[[fallthrough]];
case Type::Manual:
case Type::Delta:
case Type::Maximum:
case Type::Minimum:
ret.push_back(Format::dB);
ret.push_back(Format::dBAngle);
ret.push_back(Format::RealImag);
if(parentTrace) {
if(parentTrace->isReflection()) {
ret.push_back(Format::Impedance);
ret.push_back(Format::VSWR);
ret.push_back(Format::SeriesR);
ret.push_back(Format::Capacitance);
ret.push_back(Format::Inductance);
ret.push_back(Format::QualityFactor);
}
}
break;
default:
break;
}
break;
case Trace::DataType::Invalid:
break;
}
return ret;
}
@ -229,7 +263,8 @@ QString Marker::readableData(Format f)
f = formatTable;
}
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
if(type != Type::Delta) {
switch(f) {
case Format::dB:
@ -237,7 +272,7 @@ QString Marker::readableData(Format f)
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 step = parentTrace->sample(parentTrace->index(position), true).y.real();
auto impedance = Util::SparamToImpedance(step).real();
return Unit::ToString(impedance, "Ω", "m kM", 3);
}
@ -246,7 +281,7 @@ QString Marker::readableData(Format f)
return "Invalid";
}
} else {
if(!delta || !delta->isTimeDomain()) {
if(!delta || delta->getDomain() != Trace::DataType::Time) {
return "Invalid";
}
switch(f) {
@ -255,8 +290,8 @@ QString Marker::readableData(Format f)
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 step = parentTrace->sample(parentTrace->index(position), true).y.real();
auto stepDelta = delta->parentTrace->sample(delta->parentTrace->index(delta->position), true).y.real();
auto impedance = Util::SparamToImpedance(step).real();
auto impedanceDelta = Util::SparamToImpedance(stepDelta).real();
return "Δ:"+Unit::ToString(impedance - impedanceDelta, "Ω", "m kM", 3);
@ -266,7 +301,9 @@ QString Marker::readableData(Format f)
return "Invalid";
}
}
} else {
break;
case Trace::DataType::Frequency:
case Trace::DataType::Power:
switch(type) {
case Type::PeakTable:
return "Found " + QString::number(helperMarkers.size()) + " peaks";
@ -381,10 +418,20 @@ QString Marker::readableData(Format f)
return ret;
}
break;
case Format::P1dB:
if(position == parentTrace->maxX()) {
// compression point higher than measurable with current power setting
return "Input P1dB:>"+Unit::ToString(position, "dBm", " ", 4);
} else {
return "Input P1dB:"+Unit::ToString(position, "dBm", " ", 4);
}
case Format::Last:
return "Invalid";
}
}
break;
case Trace::DataType::Invalid:
break;
}
return "Invalid";
}
@ -397,17 +444,26 @@ QString Marker::readablePosition()
pos -= delta->position;
ret += "Δ:";
}
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
ret += Unit::ToString(pos, "s", "pnum ", 6);
} else {
break;
case Trace::DataType::Frequency:
ret += Unit::ToString(pos, "Hz", " kMG", 6);
break;
case Trace::DataType::Power:
ret += Unit::ToString(pos, "dBm", " ", 4);
break;
case Trace::DataType::Invalid:
ret += "Invalid";
}
return ret;
}
QString Marker::readableSettings()
{
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
switch(type) {
case Type::Manual:
case Type::Delta:
@ -415,7 +471,8 @@ QString Marker::readableSettings()
default:
return "Unhandled case";
}
} else {
break;
case Trace::DataType::Frequency:
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -433,22 +490,41 @@ QString Marker::readableSettings()
case Type::PhaseNoise:
return Unit::ToString(offset, "Hz", " kM", 4);
default:
return "Unhandled case";
break;
}
break;
case Trace::DataType::Power:
switch(type) {
case Type::Manual:
case Type::Maximum:
case Type::Minimum:
case Type::Delta:
return Unit::ToString(position, "dBm", " ", 4);
case Type::P1dB:
return "none";
default:
break;
}
break;
case Trace::DataType::Invalid:
break;
}
return "Unhandled case";
}
QString Marker::tooltipSettings()
{
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
switch(type) {
case Type::Manual:
case Type::Delta:
return "Time/Distance";
default:
return QString();
break;
}
} else {
break;
case Trace::DataType::Frequency:
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -463,10 +539,25 @@ QString Marker::tooltipSettings()
return "Peak threshold";
case Type::PhaseNoise:
return "Frequency offset";
default:
break;
}
break;
case Trace::DataType::Power:
switch(type) {
case Type::Manual:
case Type::Maximum:
case Type::Minimum:
case Type::Delta:
return "Input power position";
default:
return QString();
}
break;
case Trace::DataType::Invalid:
break;
}
return QString();
}
QString Marker::readableType()
@ -505,8 +596,7 @@ void Marker::traceDataChanged()
newdata = numeric_limits<complex<double>>::quiet_NaN();
} else {
// some data of the parent trace changed, check if marker data also changed
auto sampleType = isTimeDomain() ? Trace::SampleType::TimeImpulse : Trace::SampleType::Frequency;
newdata = parentTrace->sample(parentTrace->index(position), sampleType).y;
newdata = parentTrace->sample(parentTrace->index(position)).y;
}
}
if (newdata != data) {
@ -545,7 +635,7 @@ void Marker::checkDeltaMarker()
return;
}
// Check if type of delta marker is still okay
if(!delta || delta->isTimeDomain() != isTimeDomain()) {
if(!delta || delta->getDomain() != getDomain()) {
// not the same domain anymore, adjust delta
assignDeltaMarker(bestDeltaCandidate());
}
@ -689,11 +779,13 @@ std::set<Marker::Type> Marker::getSupportedTypes()
{
set<Marker::Type> supported;
if(parentTrace) {
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
// only basic markers in time domain
supported.insert(Type::Manual);
supported.insert(Type::Delta);
} else {
break;
case Trace::DataType::Frequency:
// all traces support some basic markers
supported.insert(Type::Manual);
supported.insert(Type::Maximum);
@ -722,6 +814,16 @@ std::set<Marker::Type> Marker::getSupportedTypes()
default: break;
}
}
break;
case Trace::DataType::Power:
supported.insert(Type::Manual);
supported.insert(Type::Maximum);
supported.insert(Type::Minimum);
supported.insert(Type::Delta);
supported.insert(Type::P1dB);
break;
case Trace::DataType::Invalid:
break;
}
}
return supported;
@ -765,7 +867,7 @@ Marker *Marker::bestDeltaCandidate()
// invalid delta marker assigned, attempt to find a matching marker
for(int pass = 0;pass < 3;pass++) {
for(auto m : model->getMarkers()) {
if(m->isTimeDomain() != isTimeDomain()) {
if(m->getDomain() != getDomain()) {
// markers are not on the same domain
continue;
}
@ -881,6 +983,7 @@ bool Marker::isVisible()
case Type::Maximum:
case Type::Minimum:
case Type::PhaseNoise:
case Type::P1dB:
return true;
default:
return false;
@ -1152,7 +1255,8 @@ void Marker::updateTypeFromEditor(QWidget *w)
SIUnitEdit *Marker::getSettingsEditor()
{
SIUnitEdit *ret = nullptr;
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
switch(type) {
case Type::Manual:
case Type::Delta:
@ -1162,7 +1266,8 @@ SIUnitEdit *Marker::getSettingsEditor()
default:
return nullptr;
}
} else {
break;
case Trace::DataType::Frequency:
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -1185,17 +1290,33 @@ SIUnitEdit *Marker::getSettingsEditor()
ret = new SIUnitEdit("db", " ", 3);
ret->setValue(peakThreshold);
break;
case Type::TOI:
case Type::Last:
default:
return nullptr;
}
break;
case Trace::DataType::Power:
switch(type) {
case Type::Manual:
case Type::Maximum:
case Type::Minimum:
case Type::Delta:
ret = new SIUnitEdit("dBm", " ", 4);
ret->setValue(position);
break;
default:
return nullptr;
}
break;
case Trace::DataType::Invalid:
return nullptr;
}
return ret;
}
void Marker::adjustSettings(double value)
{
if(isTimeDomain()) {
switch(getDomain()) {
case Trace::DataType::Time:
switch(type) {
case Type::Manual:
case Type::Delta: {
@ -1211,7 +1332,8 @@ void Marker::adjustSettings(double value)
default:
break;
}
} else {
break;
case Trace::DataType::Frequency:
switch(type) {
case Type::Manual:
case Type::Maximum:
@ -1236,6 +1358,21 @@ void Marker::adjustSettings(double value)
default:
break;
}
break;
case Trace::DataType::Power:
switch(type) {
case Type::Manual:
case Type::Maximum:
case Type::Minimum:
case Type::Delta:
setPosition(value);
break;
default:
break;
}
break;
case Trace::DataType::Invalid:
break;
}
update();
}
@ -1260,10 +1397,10 @@ void Marker::update()
// nothing to do
break;
case Type::Maximum:
setPosition(parentTrace->findExtremumFreq(true));
setPosition(parentTrace->findExtremum(true));
break;
case Type::Minimum:
setPosition(parentTrace->findExtremumFreq(false));
setPosition(parentTrace->findExtremum(false));
break;
case Type::PeakTable: {
deleteHelperMarkers();
@ -1289,7 +1426,7 @@ void Marker::update()
break;
} else {
// find the maximum
auto peakFreq = parentTrace->findExtremumFreq(true);
auto peakFreq = parentTrace->findExtremum(true);
// this marker shows the insertion loss
setPosition(peakFreq);
// find the cutoff frequency
@ -1319,7 +1456,7 @@ void Marker::update()
break;
} else {
// find the maximum
auto peakFreq = parentTrace->findExtremumFreq(true);
auto peakFreq = parentTrace->findExtremum(true);
// this marker shows the insertion loss
setPosition(peakFreq);
// find the cutoff frequencies
@ -1375,9 +1512,26 @@ void Marker::update()
}
break;
case Type::PhaseNoise:
setPosition(parentTrace->findExtremumFreq(true));
setPosition(parentTrace->findExtremum(true));
helperMarkers[0]->setPosition(position + offset);
break;
case Type::P1dB: {
// find maximum
auto maxpos = parentTrace->findExtremum(true);
// starting at the maximum point, traverse trace data towards higher power levels until amplitude dropped by 1dB
auto maxindex = parentTrace->index(maxpos);
auto maxpower = abs(parentTrace->sample(maxindex).y);
double p1db = parentTrace->maxX();
for(unsigned int i = maxindex; i < parentTrace->size(); i++) {
auto sample = parentTrace->sample(i);
if(Util::SparamTodB(maxpower) - Util::SparamTodB(sample.y) >= 1.0) {
p1db = sample.x;
break;
}
}
setPosition(p1db);
}
break;
case Type::Last:
break;
}
@ -1424,13 +1578,11 @@ double Marker::getPosition() const
return position;
}
bool Marker::isTimeDomain()
Trace::DataType Marker::getDomain()
{
if(parentTrace) {
if(parentTrace->outputType() == Trace::DataType::Time) {
return true;
}
return parentTrace->outputType();
}
return false;
return Trace::DataType::Invalid;
}

View File

@ -42,6 +42,8 @@ public:
TOI, // third order intercept point
AvgTone, // average level of tone
AvgModulationProduct, // average level of modulation products
// compression parameters
P1dB, // power level at 1dB compression
// keep last at end
Last,
};
@ -60,7 +62,7 @@ public:
double getPosition() const;
std::complex<double> getData() const;
bool isMovable();
bool isTimeDomain();
Trace::DataType getDomain();
QPixmap& getSymbol();
@ -81,6 +83,7 @@ public:
Bandpass,
TOI,
PhaseNoise,
P1dB,
// keep last at end
Last,
};
@ -154,6 +157,7 @@ private:
case Type::Bandpass: return "Bandpass";
case Type::TOI: return "TOI/IP3";
case Type::PhaseNoise: return "Phase noise";
case Type::P1dB: return "1dB compression";
default: return QString();
}
}

View File

@ -68,10 +68,10 @@ bool MarkerGroup::applicable(Marker *m)
}
if(markers.size() == 0) {
// first marker in group
isTimeDomain = m->isTimeDomain();
domain = m->getDomain();
} else {
// check domain
if(isTimeDomain != m->isTimeDomain()) {
if(domain != m->getDomain()) {
// only markers of the same domain are allowed in a group
return false;
}

View File

@ -11,7 +11,7 @@ class MarkerGroup : public QObject
public:
MarkerGroup(unsigned int number)
: adjustingMarkers(false),
isTimeDomain(false),
domain(Trace::DataType::Invalid),
markers(),
number(number){};
~MarkerGroup();
@ -31,7 +31,7 @@ private:
// Check if marker still applicable for group, remove if necessary
void checkMarker(Marker *m);
bool adjustingMarkers;
bool isTimeDomain;
Trace::DataType domain;
std::set<Marker*> markers;
unsigned int number;
};

View File

@ -838,12 +838,8 @@ double Trace::maxX()
}
}
double Trace::findExtremumFreq(bool max)
double Trace::findExtremum(bool max)
{
if(lastMath->getDataType() != DataType::Frequency) {
// not in frequency domain
return numeric_limits<double>::quiet_NaN();
}
double compare = max ? numeric_limits<double>::min() : numeric_limits<double>::max();
double freq = 0.0;
for(auto sample : lastMath->rData()) {
@ -913,10 +909,10 @@ std::vector<double> Trace::findPeakFrequencies(unsigned int maxPeaks, double min
return frequencies;
}
Trace::Data Trace::sample(unsigned int index, SampleType type) const
Trace::Data Trace::sample(unsigned int index, bool getStepResponse) const
{
auto data = lastMath->getSample(index);
if(type == SampleType::TimeStep) {
if(outputType() == Trace::DataType::Time && getStepResponse) {
// exchange impulse data with step data
data.y = lastMath->getStepResponse(index);
}

View File

@ -66,7 +66,7 @@ public:
unsigned int size() const;
double minX();
double maxX();
double findExtremumFreq(bool max);
double findExtremum(bool max);
/* Searches for peaks in the trace data and returns the peak frequencies in ascending order.
* Up to maxPeaks will be returned, with higher level peaks taking priority over lower level peaks.
* Only peaks with at least minLevel will be considered.
@ -79,8 +79,8 @@ public:
TimeStep,
};
Data sample(unsigned int index, SampleType type = SampleType::Frequency) const;
// returns a (possibly interpolated sample) at a specified frequency/time
Data sample(unsigned int index, bool getStepResponse = false) const;
// returns a (possibly interpolated sample) at a specified frequency/time/power
Data interpolatedSample(double x);
QString getFilename() const;
unsigned int getFileParameter() const;

View File

@ -239,7 +239,7 @@ void TraceWidget::SetupSCPI()
if(!t) {
return "ERROR";
}
auto d = t->interpolatedSample(t->findExtremumFreq(true));
auto d = t->interpolatedSample(t->findExtremum(true));
return QString::number(d.x)+","+createStringFromData(t, d);
}));
add(new SCPICommand("MINAmplitude", nullptr, [=](QStringList params) -> QString {
@ -247,7 +247,7 @@ void TraceWidget::SetupSCPI()
if(!t) {
return "ERROR";
}
auto d = t->interpolatedSample(t->findExtremumFreq(false));
auto d = t->interpolatedSample(t->findExtremum(false));
return QString::number(d.x)+","+createStringFromData(t, d);
}));
add(new SCPICommand("NEW", [=](QStringList params) -> QString {

View File

@ -498,20 +498,7 @@ void TraceXYPlot::draw(QPainter &p)
// only draw markers on primary YAxis and if the trace has at least one point
auto markers = t->getMarkers();
for(auto m : markers) {
// if(m->isTimeDomain() != (XAxis.type != XAxisType::Frequency)) {
// // wrong domain, skip this marker
// continue;
// }
double xPosition;
// if(m->isTimeDomain()) {
// if(XAxis.type == XAxisType::Distance) {
// xPosition = m->getTimeData().distance;
// } else {
// xPosition = m->getTimeData().time;
// }
// } else {
xPosition = m->getPosition();
// }
double xPosition = m->getPosition();
if (xPosition < XAxis.rangeMin || xPosition > XAxis.rangeMax) {
// marker not in graph range
continue;
@ -905,10 +892,10 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo
ret.setY(Util::SparamTodB(data.y));
break;
case YAxisType::Step:
ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real());
ret.setY(t->sample(sample, true).y.real());
break;
case YAxisType::Impedance: {
double step = t->sample(sample, Trace::SampleType::TimeStep).y.real();
double step = t->sample(sample, true).y.real();
if(abs(step) < 1.0) {
ret.setY(Util::SparamToImpedance(step).real());
}