diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h index ba5660d..05f68f8 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/Math/tracemath.h @@ -91,11 +91,11 @@ public: }; static TypeInfo getInfo(Type type); - Data getSample(unsigned int index); - Data getInterpolatedSample(double x); + virtual Data getSample(unsigned int index); + virtual Data getInterpolatedSample(double x); double getStepResponse(unsigned int index); double getInterpolatedStepResponse(double x); - unsigned int numSamples(); + virtual unsigned int numSamples(); static QString dataTypeToString(DataType type); static DataType dataTypeFromString(QString s); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp index 0de47df..b82b250 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp @@ -74,6 +74,7 @@ void Trace::clear(bool force) { return; } data.clear(); + clearDeembedding(); settings.valid = false; warning("No data"); emit cleared(this); @@ -149,6 +150,39 @@ void Trace::addData(const Trace::Data &d, const VirtualDevice::SASettings &s, in addData(d, domain, 50.0, index); } +void Trace::addDeembeddingData(const Trace::Data &d, int index) +{ + if(index >= 0) { + // index position specified + if(deembeddingData.size() <= (unsigned int) index) { + deembeddingData.resize(index + 1); + } + deembeddingData[index] = d; + } else { + // no index given, determine position by X-coordinate + + // add or replace data in vector while keeping it sorted with increasing frequency + auto lower = lower_bound(deembeddingData.begin(), deembeddingData.end(), d, [](const Data &lhs, const Data &rhs) -> bool { + return lhs.x < rhs.x; + }); + // calculate index now because inserting a sample into data might lead to reallocation -> arithmetic on lower not valid anymore + index = lower - deembeddingData.begin(); + if(lower == deembeddingData.end()) { + // highest frequency yet, add to vector + deembeddingData.push_back(d); + } else if(lower->x == d.x) { + *lower = d; + } else { + // insert at this position + deembeddingData.insert(lower, d); + } + } + if(deembeddingActive) { + emit outputSamplesChanged(index, index + 1); + } + emit deembeddingChanged(); +} + void Trace::setName(QString name) { _name = name; emit nameChanged(); @@ -265,11 +299,15 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter) return lastTraceName; } -void Trace::fillFromDatapoints(std::map traceSet, const std::vector &data) +void Trace::fillFromDatapoints(std::map traceSet, const std::vector &data, bool deembedded) { // remove all previous points for(auto m : traceSet) { - m.second->clear(); + if(!deembedded) { + m.second->clear(); + } else { + m.second->clearDeembedding(); + } } // add new points to traces for(auto d : data) { @@ -279,7 +317,11 @@ void Trace::fillFromDatapoints(std::map traceSet, const std::v td.y = m.second; QString measurement = m.first; if(traceSet.count(measurement)) { - traceSet[measurement]->addData(td, DataType::Frequency); + if(!deembedded) { + traceSet[measurement]->addData(td, DataType::Frequency); + } else { + traceSet[measurement]->addDeembeddingData(td); + } } } } @@ -743,6 +785,17 @@ nlohmann::json Trace::toJSON() j["data"] = jdata; } + j["deembeddingActive"] = deembeddingActive; + nlohmann::json jdedata; + for(const auto &d : deembeddingData) { + nlohmann::json jpoint; + jpoint["x"] = d.x; + jpoint["real"] = d.y.real(); + jpoint["imag"] = d.y.imag(); + jdedata.push_back(jpoint); + } + j["deembeddingData"] = jdedata; + nlohmann::json mathList; for(auto m : mathOps) { if(m.math->getType() == Type::Last) { @@ -759,6 +812,7 @@ nlohmann::json Trace::toJSON() j["math"] = mathList; j["math_enabled"] = mathEnabled(); + return j; } @@ -785,6 +839,18 @@ void Trace::fromJSON(nlohmann::json j) data.push_back(d); } } + if(j.contains("deembeddingData")) { + // Deembedded data is contained in the json, load now + clearDeembedding(); + for(auto jpoint : j["deembeddingData"]) { + Data d; + d.x = jpoint.value("x", 0.0); + d.y = complex(jpoint.value("real", 0.0), jpoint.value("imag", 0.0)); + deembeddingData.push_back(d); + } + } + deembeddingActive = j.value("deembeddingActive", false); + if(type == "Live") { if(j.contains("parameter")) { if(j["parameter"].type() == nlohmann::json::value_t::string) { @@ -1226,6 +1292,36 @@ unsigned int Trace::size() const return lastMath->numSamples(); } +bool Trace::isDeembeddingActive() +{ + return deembeddingActive; +} + +bool Trace::deembeddingAvailable() +{ + return deembeddingData.size() > 0; +} + +void Trace::setDeembeddingActive(bool active) +{ + deembeddingActive = active; + if(deembeddingAvailable()) { + if(active) { + emit outputSamplesChanged(0, deembeddingData.size()); + } else { + emit outputSamplesChanged(0, data.size()); + } + } + emit deembeddingChanged(); +} + +void Trace::clearDeembedding() +{ + setDeembeddingActive(false); + deembeddingData.clear(); + deembeddingChanged(); +} + double Trace::minX() { if(lastMath->numSamples() > 0) { @@ -1331,6 +1427,60 @@ Trace::Data Trace::sample(unsigned int index, bool getStepResponse) const return data; } +Trace::Data Trace::getSample(unsigned int index) +{ + if(deembeddingActive && deembeddingAvailable()) { + if(index < deembeddingData.size()) { + return deembeddingData[index]; + } else { + TraceMath::Data d; + d.x = 0; + d.y = 0; + return d; + } + } else { + return TraceMath::getSample(index); + } +} + +Trace::Data Trace::getInterpolatedSample(double x) +{ + if(deembeddingActive && deembeddingAvailable()) { + Data ret; + if(deembeddingData.size() == 0 || x < deembeddingData.front().x || x > deembeddingData.back().x) { + ret.y = std::numeric_limits>::quiet_NaN(); + ret.x = std::numeric_limits::quiet_NaN(); + } else { + auto it = lower_bound(deembeddingData.begin(), deembeddingData.end(), x, [](const Data &lhs, const double x) -> bool { + return lhs.x < x; + }); + if(it->x == x) { + ret = *it; + } else { + // no exact match, needs to interpolate + auto high = *it; + it--; + auto low = *it; + double alpha = (x - low.x) / (high.x - low.x); + ret.y = low.y * (1 - alpha) + high.y * alpha; + ret.x = x; + } + } + return ret; + } else { + return TraceMath::getInterpolatedSample(x); + } +} + +unsigned int Trace::numSamples() +{ + if(deembeddingActive && deembeddingAvailable()) { + return deembeddingData.size(); + } else { + return TraceMath::numSamples(); + } +} + double Trace::getUnwrappedPhase(unsigned int index) { if(index >= size()) { diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.h b/Software/PC_Application/LibreVNA-GUI/Traces/trace.h index 0bc3ae4..1b50b22 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.h @@ -45,11 +45,12 @@ public: void clear(bool force = false); void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1); void addData(const Data& d, const VirtualDevice::SASettings &s, int index = -1); + void addDeembeddingData(const Data& d, int index = -1); void setName(QString name); void setVelocityFactor(double v); void fillFromTouchstone(Touchstone &t, unsigned int parameter); QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data) - static void fillFromDatapoints(std::map traceSet, const std::vector &data); + static void fillFromDatapoints(std::map traceSet, const std::vector &data, bool deembedded = false); void fromLivedata(LivedataType type, QString param); void fromMath(); QString name() { return _name; } @@ -65,6 +66,12 @@ public: LivedataType liveType() { return _liveType; } TraceMath::DataType outputType() const { return lastMath->getDataType(); } unsigned int size() const; + + bool isDeembeddingActive(); + bool deembeddingAvailable(); + void setDeembeddingActive(bool active); + void clearDeembedding(); + double minX(); double maxX(); double findExtremum(bool max, double xmin = std::numeric_limits::lowest(), double xmax = std::numeric_limits::max()); @@ -82,6 +89,11 @@ public: }; Data sample(unsigned int index, bool getStepResponse = false) const; + + virtual Data getSample(unsigned int index) override; + virtual Data getInterpolatedSample(double x) override; + virtual unsigned int numSamples() override; + double getUnwrappedPhase(unsigned int index); // returns a (possibly interpolated sample) at a specified frequency/time/power Data interpolatedSample(double x); @@ -190,6 +202,7 @@ signals: void dataChanged(unsigned int begin, unsigned int end); void nameChanged(); void pauseChanged(); + void deembeddingChanged(); void colorChanged(Trace *t); void markerAdded(Marker *m); void markerRemoved(Marker *m); @@ -266,6 +279,10 @@ private: bool valid; } settings; + // de-embedding variables + std::vector deembeddingData; + bool deembeddingActive; + std::vector mathOps; TraceMath *lastMath; std::vector unwrappedPhase; diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.cpp index 891efdd..b2eacf4 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.cpp @@ -32,6 +32,9 @@ void TraceModel::addTrace(Trace *t) connect(t, &Trace::pauseChanged, [=](){ emit dataChanged(createIndex(0, 0), createIndex(traces.size() - 1, ColIndexLast - 1)); }); + connect(t, &Trace::deembeddingChanged, [=](){ + emit dataChanged(createIndex(0, 0), createIndex(traces.size() - 1, ColIndexLast - 1)); + }); traces.push_back(t); endInsertRows(); t->setModel(this); @@ -106,6 +109,18 @@ void TraceModel::toggleMath(unsigned int index) } } +void TraceModel::toggleDeembedding(unsigned int index) +{ + if (index >= traces.size()) { + return; + } + auto trace = traces[index]; + if(trace->deembeddingAvailable()) { + trace->setDeembeddingActive(!trace->isDeembeddingActive()); + emit dataChanged(createIndex(index, ColIndexDeembedding), createIndex(index, ColIndexDeembedding)); + } +} + int TraceModel::rowCount(const QModelIndex &) const { return traces.size(); @@ -133,6 +148,8 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const } else { return QIcon(":/icons/invisible.svg"); } + } else if(role == Qt::ToolTipRole){ + return "Toggle Visibility"; } else { return QVariant(); } @@ -144,6 +161,21 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const } else { return QIcon(":/icons/play.svg"); } + } else if(role == Qt::ToolTipRole && trace->canBePaused()){ + return "Toggle Play/Pause"; + } else { + return QVariant(); + } + break; + case ColIndexDeembedding: + if (role == Qt::DecorationRole && trace->deembeddingAvailable()) { + if(trace->isDeembeddingActive()) { + return QIcon(":icons/deembedding_enabled.svg"); + } else { + return QIcon(":icons/deembedding_disabled.svg"); + } + } else if(role == Qt::ToolTipRole && trace->deembeddingAvailable()){ + return "Toggle De-embedding"; } else { return QVariant(); } @@ -155,6 +187,10 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const } else { return QIcon(":icons/math_disabled"); } + } else if(role == Qt::ToolTipRole && trace->hasMathOperations()){ + return "Toggle Math Operations"; + } else { + return QVariant(); } break; case ColIndexName: @@ -175,6 +211,17 @@ std::vector TraceModel::getTraces() const return traces; } +std::vector TraceModel::getLiveTraces() const +{ + std::vector ret; + for(auto t : traces) { + if(t->getSource() == Trace::Source::Live) { + ret.push_back(t); + } + } + return ret; +} + bool TraceModel::PortExcitationRequired(int port) { port++; @@ -233,7 +280,7 @@ void TraceModel::clearLiveData() } } -void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype) +void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype, bool deembedded) { source = DataSource::VNA; lastReceivedData = QDateTime::currentDateTimeUtc(); @@ -263,7 +310,11 @@ void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::D // parameter not included in data, skip continue; } - t->addData(td, datatype, d.Z0, index); + if(!deembedded) { + t->addData(td, datatype, d.Z0, index); + } else { + t->addDeembeddingData(td, index); + } } } } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.h b/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.h index b93cc67..050d265 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracemodel.h @@ -20,8 +20,9 @@ public: enum { ColIndexVisible = 0, ColIndexPlayPause = 1, - ColIndexMath = 2, - ColIndexName = 3, + ColIndexDeembedding = 2, + ColIndexMath = 3, + ColIndexName = 4, ColIndexLast, }; @@ -39,12 +40,14 @@ public: void toggleVisibility(unsigned int index); void togglePause(unsigned int index); void toggleMath(unsigned int index); + void toggleDeembedding(unsigned int index); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; std::vector getTraces() const; + std::vector getLiveTraces() const; bool PortExcitationRequired(int port); @@ -68,7 +71,7 @@ signals: public slots: void clearLiveData(); - void addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype); + void addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype, bool deembedded); void addSAData(const VirtualDevice::SAMeasurement &d, const VirtualDevice::SASettings &settings); private: diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp index 998eb71..e5f680f 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp @@ -140,6 +140,8 @@ void TraceWidget::on_view_clicked(const QModelIndex &index) case TraceModel::ColIndexPlayPause: model.togglePause(index.row()); break; + case TraceModel::ColIndexDeembedding: + model.toggleDeembedding(index.row()); case TraceModel::ColIndexMath: model.toggleMath(index.row()); break; diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp index 12c7ffe..7ac3f2d 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp @@ -124,7 +124,10 @@ void Deembedding::Deembed(std::map traceSet) for(auto &p : points) { Deembed(p); } - Trace::fillFromDatapoints(traceSet, points); + Trace::fillFromDatapoints(traceSet, points, true); + for(auto t : traceSet) { + t.second->setDeembeddingActive(true); + } } } diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/manualdeembeddingdialog.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/manualdeembeddingdialog.cpp index 51996d8..de62d05 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/manualdeembeddingdialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/manualdeembeddingdialog.cpp @@ -2,6 +2,7 @@ #include "ui_manualdeembeddingdialog.h" #include "Traces/sparamtraceselector.h" +#include "CustomWidgets/informationbox.h" ManualDeembeddingDialog::ManualDeembeddingDialog(const TraceModel &model, Deembedding *deemb) : ui(new Ui::ManualDeembeddingDialog) @@ -12,6 +13,22 @@ ManualDeembeddingDialog::ManualDeembeddingDialog(const TraceModel &model, Deembe ui->buttonBox->setEnabled(false); connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=]() { + auto traces = traceSelector->getTraces(); + bool clearDeembedding = false; + for(auto t : traces) { + if(t.second->deembeddingAvailable()) { + clearDeembedding = InformationBox::AskQuestion("Clear previous de-embedding data?", "At least one of the selected traces " + "has already been de-embedded. Do you want to clear the old de-embedding data before applying the new de-embedding?", true); + break; + } + } + if(clearDeembedding) { + for(auto t : traces) { + if(t.second->deembeddingAvailable()) { + t.second->clearDeembedding(); + } + } + } deemb->Deembed(traceSelector->getTraces()); accept(); }); diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp index 7c1c992..8d9af79 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp @@ -850,10 +850,6 @@ void VNA::NewDatapoint(VirtualDevice::VNAMeasurement m) cal.correctMeasurement(m_avg); - if(deembedding_active) { - deembedding.Deembed(m_avg); - } - TraceMath::DataType type; if(settings.zerospan) { type = TraceMath::DataType::TimeZeroSpan; @@ -877,7 +873,11 @@ void VNA::NewDatapoint(VirtualDevice::VNAMeasurement m) } } - traceModel.addVNAData(m_avg, type); + traceModel.addVNAData(m_avg, type, false); + if(deembedding_active) { + deembedding.Deembed(m_avg); + traceModel.addVNAData(m_avg, type, true); + } emit dataChanged(); if(m_avg.pointNum == settings.npoints - 1) { UpdateAverageCount(); @@ -1579,6 +1579,14 @@ void VNA::EnableDeembedding(bool enable) enableDeembeddingAction->blockSignals(true); enableDeembeddingAction->setChecked(enable); enableDeembeddingAction->blockSignals(false); + for(auto t : traceModel.getLiveTraces()) { + if(enable) { + t->setDeembeddingActive(true); + } else { + t->clearDeembedding(); + } + } + } void VNA::setAveragingMode(Averaging::Mode mode) diff --git a/Software/PC_Application/LibreVNA-GUI/icons.qrc b/Software/PC_Application/LibreVNA-GUI/icons.qrc index 0c25332..1272367 100644 --- a/Software/PC_Application/LibreVNA-GUI/icons.qrc +++ b/Software/PC_Application/LibreVNA-GUI/icons.qrc @@ -71,5 +71,7 @@ icons/definedShunt.svg icons/definedThrough.png icons/definedThrough.svg + icons/deembedding_disabled.svg + icons/deembedding_enabled.svg diff --git a/Software/PC_Application/LibreVNA-GUI/icons/deembedding_disabled.svg b/Software/PC_Application/LibreVNA-GUI/icons/deembedding_disabled.svg new file mode 100644 index 0000000..368344f --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/icons/deembedding_disabled.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/LibreVNA-GUI/icons/deembedding_enabled.svg b/Software/PC_Application/LibreVNA-GUI/icons/deembedding_enabled.svg new file mode 100644 index 0000000..b35beb8 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/icons/deembedding_enabled.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + +