Basic working support for math operations between traces
This commit is contained in:
parent
97fceb4ff4
commit
cf31fd7908
@ -672,7 +672,7 @@ std::vector<Trace *> Calibration::getErrorTermTraces()
|
||||
constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false};
|
||||
for(int i=0;i<12;i++) {
|
||||
auto t = new Trace(traceNames[i], Qt::red);
|
||||
t->setCalibration(true);
|
||||
t->setCalibration();
|
||||
t->setReflection(reflection[i]);
|
||||
traces.push_back(t);
|
||||
}
|
||||
@ -728,7 +728,7 @@ std::vector<Trace *> Calibration::getMeasurementTraces()
|
||||
}
|
||||
for(auto prefix : usedPrefixes) {
|
||||
auto t = new Trace(prefix + " " + info.name);
|
||||
t->setCalibration(true);
|
||||
t->setCalibration();
|
||||
t->setReflection(prefix == "S11" || prefix == "S22");
|
||||
for(auto p : m.second.datapoints) {
|
||||
Trace::Data d;
|
||||
|
@ -870,7 +870,7 @@ std::set<Marker::Type> Marker::getSupportedTypes()
|
||||
supported.insert(Type::Highpass);
|
||||
supported.insert(Type::Bandpass);
|
||||
}
|
||||
if(parentTrace->isLive()) {
|
||||
if(parentTrace->getSource() == Trace::Source::Live) {
|
||||
switch(parentTrace->liveParameter()) {
|
||||
case Trace::LiveParameter::S11:
|
||||
case Trace::LiveParameter::S12:
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "Util/util.h"
|
||||
#include "Marker/marker.h"
|
||||
#include "traceaxis.h"
|
||||
#include "tracemodel.h"
|
||||
#include "Math/parser/mpParser.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <QDebug>
|
||||
@ -12,19 +14,23 @@
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace mup;
|
||||
|
||||
Trace::Trace(QString name, QColor color, LiveParameter live)
|
||||
: _name(name),
|
||||
: model(nullptr),
|
||||
_name(name),
|
||||
_color(color),
|
||||
source(Source::Live),
|
||||
hash(0),
|
||||
hashSet(false),
|
||||
JSONskipHash(false),
|
||||
_liveType(LivedataType::Overwrite),
|
||||
_liveParam(live),
|
||||
vFactor(0.66),
|
||||
reflection(true),
|
||||
reference_impedance(50.0),
|
||||
visible(true),
|
||||
paused(false),
|
||||
createdFromFile(false),
|
||||
calibration(false),
|
||||
reference_impedance(50.0),
|
||||
domain(DataType::Frequency),
|
||||
lastMath(nullptr)
|
||||
{
|
||||
@ -32,6 +38,10 @@ Trace::Trace(QString name, QColor color, LiveParameter live)
|
||||
mathOps.push_back(self);
|
||||
updateLastMath(mathOps.rbegin());
|
||||
|
||||
lastMathUpdate = QTime::currentTime();
|
||||
mathCalcTimer.setSingleShot(true);
|
||||
connect(&mathCalcTimer, &QTimer::timeout, this, &Trace::calculateMath);
|
||||
|
||||
self.enabled = false;
|
||||
dataType = DataType::Frequency;
|
||||
connect(this, &Trace::typeChanged, [=](){
|
||||
@ -52,8 +62,8 @@ Trace::~Trace()
|
||||
emit deleted(this);
|
||||
}
|
||||
|
||||
void Trace::clear() {
|
||||
if(paused) {
|
||||
void Trace::clear(bool force) {
|
||||
if(paused && !force) {
|
||||
return;
|
||||
}
|
||||
data.clear();
|
||||
@ -166,7 +176,8 @@ void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter)
|
||||
break;
|
||||
}
|
||||
}
|
||||
createdFromFile = true;
|
||||
clearMathSources();
|
||||
source = Source::File;
|
||||
reference_impedance = t.getReferenceImpedance();
|
||||
emit typeChanged(this);
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
@ -241,7 +252,8 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter)
|
||||
addData(d, domain);
|
||||
}
|
||||
reflection = false;
|
||||
createdFromFile = true;
|
||||
clearMathSources();
|
||||
source = Source::File;
|
||||
emit typeChanged(this);
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
return lastTraceName;
|
||||
@ -269,7 +281,8 @@ void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, c
|
||||
|
||||
void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
|
||||
{
|
||||
createdFromFile = false;
|
||||
clearMathSources();
|
||||
source = Source::Live;
|
||||
_liveType = type;
|
||||
_liveParam = param;
|
||||
if(param == LiveParameter::S11 || param == LiveParameter::S22) {
|
||||
@ -280,6 +293,15 @@ void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
|
||||
emit typeChanged(this);
|
||||
}
|
||||
|
||||
void Trace::fromMath()
|
||||
{
|
||||
source = Source::Math;
|
||||
clear();
|
||||
updateMathTracePoints();
|
||||
scheduleMathCalculation(0, data.size());
|
||||
emit typeChanged(this);
|
||||
}
|
||||
|
||||
void Trace::setColor(QColor color) {
|
||||
if(_color != color) {
|
||||
_color = color;
|
||||
@ -309,6 +331,281 @@ void Trace::markerVisibilityChanged(Marker *m)
|
||||
emit visibilityChanged(this);
|
||||
}
|
||||
|
||||
const QString &Trace::getMathFormula() const
|
||||
{
|
||||
return mathFormula;
|
||||
}
|
||||
|
||||
void Trace::setMathFormula(const QString &newMathFormula)
|
||||
{
|
||||
mathFormula = newMathFormula;
|
||||
}
|
||||
|
||||
bool Trace::mathFormularValid() const
|
||||
{
|
||||
if(mathFormula.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX);
|
||||
parser.SetExpr(mathFormula.toStdString());
|
||||
auto vars = parser.GetExprVar();
|
||||
for(auto var : vars) {
|
||||
auto varName = QString::fromStdString(var.first);
|
||||
// try to find variable name
|
||||
bool found = false;
|
||||
for(auto ms : mathSourceTraces) {
|
||||
if(ms.second == varName) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (const ParserError &e) {
|
||||
// parser error occurred
|
||||
return false;
|
||||
}
|
||||
// all variables used in the expression are set as math sources
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Trace::resolveMathSourceHashes()
|
||||
{
|
||||
bool success = true;
|
||||
for(auto unresolved : mathSourceUnresolvedHashes) {
|
||||
if(!addMathSource(unresolved.first, unresolved.second)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if(success) {
|
||||
mathSourceUnresolvedHashes.clear();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Trace::updateMathTracePoints()
|
||||
{
|
||||
if(!mathSourceTraces.size()) {
|
||||
return false;
|
||||
}
|
||||
double startX = std::numeric_limits<double>::lowest();
|
||||
double stopX = std::numeric_limits<double>::max();
|
||||
double stepSize = std::numeric_limits<double>::max();
|
||||
for(auto t : mathSourceTraces) {
|
||||
if(t.first->minX() > startX) {
|
||||
startX = t.first->minX();
|
||||
}
|
||||
if(t.first->maxX() < stopX) {
|
||||
stopX = t.first->maxX();
|
||||
}
|
||||
double traceStepSize = std::numeric_limits<double>::max();
|
||||
if(t.first->numSamples() > 1) {
|
||||
traceStepSize = (t.first->maxX() - t.first->minX()) / (t.first->numSamples() - 1);
|
||||
}
|
||||
if(traceStepSize < stepSize) {
|
||||
stepSize = traceStepSize;
|
||||
}
|
||||
}
|
||||
unsigned int samples = round((stopX - startX) / stepSize + 1);
|
||||
bool fullUpdate = false;
|
||||
if(samples != data.size()) {
|
||||
data.resize(samples);
|
||||
fullUpdate = true;
|
||||
}
|
||||
if(samples > 0 && (startX != data.front().x || stopX != data.back().x)) {
|
||||
fullUpdate = true;
|
||||
}
|
||||
if(fullUpdate) {
|
||||
// steps changed, complete update required
|
||||
mathUpdateBegin = 0;
|
||||
mathUpdateEnd = samples;
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
data[i].x = startX + i * stepSize;
|
||||
data[i].y = numeric_limits<complex<double>>::quiet_NaN();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Trace::mathSourceTraceDeleted(Trace *t)
|
||||
{
|
||||
if (mathSourceTraces.count(t)) {
|
||||
removeMathSource(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Trace::scheduleMathCalculation(unsigned int begin, unsigned int end)
|
||||
{
|
||||
if(source != Source::Math) {
|
||||
return;
|
||||
}
|
||||
if(begin < mathUpdateBegin) {
|
||||
mathUpdateBegin = begin;
|
||||
}
|
||||
if(end > mathUpdateEnd) {
|
||||
mathUpdateEnd = end;
|
||||
}
|
||||
auto now = QTime::currentTime();
|
||||
if (lastMathUpdate.msecsTo(now) >= MinMathUpdateInterval) {
|
||||
calculateMath();
|
||||
} else {
|
||||
mathCalcTimer.start(MinMathUpdateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void Trace::calculateMath()
|
||||
{
|
||||
lastMathUpdate = QTime::currentTime();
|
||||
if(mathUpdateBegin >= data.size() || mathUpdateEnd >= data.size() + 1) {
|
||||
return;
|
||||
}
|
||||
if(mathFormula.isEmpty()) {
|
||||
error("Expression is empty");
|
||||
return;
|
||||
}
|
||||
if(!isPaused()) {
|
||||
try {
|
||||
ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX);
|
||||
parser.SetExpr(mathFormula.toStdString());
|
||||
map<Trace*,Value> values;
|
||||
Value x;
|
||||
parser.DefineVar("x", Variable(&x));
|
||||
for(const auto &ts : mathSourceTraces) {
|
||||
values[ts.first] = Value();
|
||||
parser.DefineVar(ts.second.toStdString(), Variable(&values[ts.first]));
|
||||
}
|
||||
for(unsigned int i=mathUpdateBegin;i<mathUpdateEnd;i++) {
|
||||
x = data[i].x;
|
||||
for(auto &val : values) {
|
||||
val.second = val.first->interpolatedSample(data[i].x).y;
|
||||
}
|
||||
Value res = parser.Eval();
|
||||
data[i].y = res.GetComplex();
|
||||
}
|
||||
} catch (const ParserError &e) {
|
||||
error(QString::fromStdString(e.GetMsg()));
|
||||
// parser error occurred
|
||||
for(unsigned int i=mathUpdateBegin;i<mathUpdateEnd;i++) {
|
||||
data[i].y = numeric_limits<complex<double>>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
success();
|
||||
emit outputSamplesChanged(mathUpdateBegin, mathUpdateEnd + 1);
|
||||
}
|
||||
mathUpdateBegin = data.size();
|
||||
mathUpdateEnd = 0;
|
||||
}
|
||||
|
||||
void Trace::clearMathSources()
|
||||
{
|
||||
while(mathSourceTraces.size() > 0) {
|
||||
removeMathSource(mathSourceTraces.begin()->first);
|
||||
}
|
||||
}
|
||||
|
||||
bool Trace::addMathSource(unsigned int hash, QString variableName)
|
||||
{
|
||||
if(!model) {
|
||||
return false;
|
||||
}
|
||||
for(auto t : model->getTraces()) {
|
||||
if(t->toHash() == hash) {
|
||||
return addMathSource(t, variableName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Trace::mathDependsOn(Trace *t, bool onlyDirectDependency)
|
||||
{
|
||||
if(mathSourceTraces.count(t)) {
|
||||
return true;
|
||||
}
|
||||
if(onlyDirectDependency) {
|
||||
return false;
|
||||
} else {
|
||||
// also check math source traces recursively
|
||||
for(auto m : mathSourceTraces) {
|
||||
if(m.first->mathDependsOn(t)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Trace::canAddAsMathSource(Trace *t)
|
||||
{
|
||||
if(t == this) {
|
||||
// can't add itself
|
||||
return false;
|
||||
}
|
||||
// check if we would create a loop of math traces depending on each other
|
||||
if(t->mathDependsOn(this)) {
|
||||
return false;
|
||||
}
|
||||
if(mathSourceTraces.size() == 0) {
|
||||
// no traces used as source yet, can add anything
|
||||
return true;
|
||||
} else {
|
||||
// can only add traces of the same domain
|
||||
if(mathSourceTraces.begin()->first->outputType() == t->outputType()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Trace::addMathSource(Trace *t, QString variableName)
|
||||
{
|
||||
// qDebug() << "Adding trace" << t << "as a math source to" << this << "as variable" << variableName;
|
||||
if(!canAddAsMathSource(t)) {
|
||||
return false;
|
||||
}
|
||||
mathSourceTraces[t] = variableName;
|
||||
connect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted, Qt::UniqueConnection);
|
||||
connect(t, &Trace::dataChanged, this, [=](unsigned int begin, unsigned int end){
|
||||
updateMathTracePoints();
|
||||
auto startX = t->sample(begin).x;
|
||||
auto stopX = t->sample(end).x;
|
||||
scheduleMathCalculation(index(startX), index(stopX));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void Trace::removeMathSource(Trace *t)
|
||||
{
|
||||
// qDebug() << "Removing trace" << t << "as a math source from" << this;
|
||||
mathSourceTraces.erase(t);
|
||||
disconnect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted);
|
||||
disconnect(t, &Trace::dataChanged, this, nullptr);
|
||||
}
|
||||
|
||||
QString Trace::getSourceVariableName(Trace *t)
|
||||
{
|
||||
if(mathSourceTraces.count(t)) {
|
||||
return mathSourceTraces[t];
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
TraceModel *Trace::getModel() const
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
void Trace::setModel(TraceModel *model)
|
||||
{
|
||||
this->model = model;
|
||||
}
|
||||
|
||||
double Trace::getReferenceImpedance() const
|
||||
{
|
||||
return reference_impedance;
|
||||
@ -347,22 +644,41 @@ double Trace::distanceToTime(double distance)
|
||||
nlohmann::json Trace::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
if(isCalibration()) {
|
||||
if(!JSONskipHash) {
|
||||
j["hash"] = toHash(true);
|
||||
}
|
||||
if(source == Source::Calibration) {
|
||||
// calibration traces can't be saved
|
||||
return j;
|
||||
}
|
||||
j["name"] = _name.toStdString();
|
||||
j["color"] = _color.name().toStdString();
|
||||
j["visible"] = visible;
|
||||
if(isLive()) {
|
||||
switch(source) {
|
||||
case Source::Live:
|
||||
j["type"] = "Live";
|
||||
j["parameter"] = _liveParam;
|
||||
j["livetype"] = _liveType;
|
||||
j["paused"] = paused;
|
||||
} else if(isFromFile()) {
|
||||
break;
|
||||
case Source::File:
|
||||
j["type"] = "File";
|
||||
j["filename"] = filename.toStdString();
|
||||
j["parameter"] = fileParameter;
|
||||
break;
|
||||
case Source::Math: {
|
||||
j["type"] = "Math";
|
||||
j["expression"] = mathFormula.toStdString();
|
||||
nlohmann::json jsources;
|
||||
for(auto ms : mathSourceTraces) {
|
||||
nlohmann::json jsource;
|
||||
jsource["trace"] = ms.first->toHash();
|
||||
jsource["variable"] = ms.second.toStdString();
|
||||
jsources.push_back(jsource);
|
||||
}
|
||||
j["sources"] = jsources;
|
||||
}
|
||||
break;
|
||||
}
|
||||
j["velocityFactor"] = vFactor;
|
||||
j["reflection"] = reflection;
|
||||
@ -388,8 +704,13 @@ nlohmann::json Trace::toJSON()
|
||||
|
||||
void Trace::fromJSON(nlohmann::json j)
|
||||
{
|
||||
createdFromFile = false;
|
||||
calibration = false;
|
||||
source = Source::Live;
|
||||
if(j.contains("hash")) {
|
||||
hash = j["hash"];
|
||||
hashSet = true;
|
||||
} else {
|
||||
hashSet = false;
|
||||
}
|
||||
_name = QString::fromStdString(j.value("name", "Missing name"));
|
||||
_color = QColor(QString::fromStdString(j.value("color", "yellow")));
|
||||
visible = j.value("visible", true);
|
||||
@ -414,6 +735,19 @@ void Trace::fromJSON(nlohmann::json j)
|
||||
std::string what = e.what();
|
||||
throw runtime_error("Failed to create from file:" + what);
|
||||
}
|
||||
} else if(type == "Math") {
|
||||
mathFormula = QString::fromStdString(j.value("expression", ""));
|
||||
if(j.contains("sources")) {
|
||||
for(auto js : j["sources"]) {
|
||||
auto hash = js.value("trace", 0);
|
||||
QString varName = QString::fromStdString(js.value("variable", ""));
|
||||
if(!addMathSource(hash, varName)) {
|
||||
qWarning() << "Unable to find requested math source trace ( hash:"<<hash<<"), probably not loaded yet";
|
||||
mathSourceUnresolvedHashes[hash] = varName;
|
||||
}
|
||||
}
|
||||
}
|
||||
fromMath();
|
||||
}
|
||||
vFactor = j.value("velocityFactor", 0.66);
|
||||
reflection = j.value("reflection", false);
|
||||
@ -453,12 +787,18 @@ void Trace::fromJSON(nlohmann::json j)
|
||||
enableMath(j.value("math_enabled", true));
|
||||
}
|
||||
|
||||
unsigned int Trace::toHash()
|
||||
unsigned int Trace::toHash(bool forceUpdate)
|
||||
{
|
||||
if(!hashSet || forceUpdate) {
|
||||
// taking the easy way: create the json string and hash it (already contains all necessary information)
|
||||
// This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem
|
||||
JSONskipHash = true;
|
||||
std::string json_string = toJSON().dump();
|
||||
return hash<std::string>{}(json_string);
|
||||
JSONskipHash = false;
|
||||
hash = std::hash<std::string>{}(json_string);
|
||||
hashSet = true;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::vector<Trace *> Trace::createFromTouchstone(Touchstone &t)
|
||||
@ -687,9 +1027,9 @@ QString Trace::description()
|
||||
return name() + ": measured data";
|
||||
}
|
||||
|
||||
void Trace::setCalibration(bool value)
|
||||
void Trace::setCalibration()
|
||||
{
|
||||
calibration = value;
|
||||
source = Source::Calibration;
|
||||
}
|
||||
|
||||
std::set<Marker *> Trace::getMarkers() const
|
||||
@ -731,21 +1071,6 @@ bool Trace::isPaused()
|
||||
return paused;
|
||||
}
|
||||
|
||||
bool Trace::isFromFile()
|
||||
{
|
||||
return createdFromFile;
|
||||
}
|
||||
|
||||
bool Trace::isCalibration()
|
||||
{
|
||||
return calibration;
|
||||
}
|
||||
|
||||
bool Trace::isLive()
|
||||
{
|
||||
return !isCalibration() && !isFromFile();
|
||||
}
|
||||
|
||||
bool Trace::isReflection()
|
||||
{
|
||||
return reflection;
|
||||
@ -997,7 +1322,7 @@ unsigned int Trace::getFileParameter() const
|
||||
|
||||
double Trace::getNoise(double frequency)
|
||||
{
|
||||
if(!isLive() || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) {
|
||||
if(source != Trace::Source::Live || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) {
|
||||
// data not suitable for noise calculation
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "Device/device.h"
|
||||
#include "Math/tracemath.h"
|
||||
#include "Tools/parameters.h"
|
||||
#include "tracemodel.h"
|
||||
#include "VNA/vnadata.h"
|
||||
|
||||
#include <QObject>
|
||||
@ -14,8 +13,10 @@
|
||||
#include <map>
|
||||
#include <QColor>
|
||||
#include <set>
|
||||
#include <QTime>
|
||||
|
||||
class Marker;
|
||||
class TraceModel;
|
||||
|
||||
class Trace : public TraceMath
|
||||
{
|
||||
@ -24,6 +25,14 @@ public:
|
||||
|
||||
using Data = TraceMath::Data;
|
||||
|
||||
enum class Source {
|
||||
Live,
|
||||
File,
|
||||
Math,
|
||||
Calibration,
|
||||
Last,
|
||||
};
|
||||
|
||||
enum class LiveParameter {
|
||||
S11,
|
||||
S12,
|
||||
@ -44,7 +53,7 @@ public:
|
||||
Invalid,
|
||||
};
|
||||
|
||||
void clear();
|
||||
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 Protocol::SpectrumAnalyzerSettings& s, int index = -1);
|
||||
void setName(QString name);
|
||||
@ -53,15 +62,14 @@ public:
|
||||
QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data)
|
||||
static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector<VNAData> &data);
|
||||
void fromLivedata(LivedataType type, LiveParameter param);
|
||||
void fromMath();
|
||||
QString name() { return _name; }
|
||||
QColor color() { return _color; }
|
||||
bool isVisible();
|
||||
void pause();
|
||||
void resume();
|
||||
bool isPaused();
|
||||
bool isFromFile();
|
||||
bool isCalibration();
|
||||
bool isLive();
|
||||
Source getSource() {return source;}
|
||||
bool isReflection();
|
||||
LiveParameter liveParameter() { return _liveParam; }
|
||||
LivedataType liveType() { return _liveType; }
|
||||
@ -92,7 +100,7 @@ public:
|
||||
double getNoise(double frequency);
|
||||
int index(double x);
|
||||
std::set<Marker *> getMarkers() const;
|
||||
void setCalibration(bool value);
|
||||
void setCalibration();
|
||||
void setReflection(bool value);
|
||||
|
||||
DataType outputType(DataType inputType) override;
|
||||
@ -129,7 +137,7 @@ public:
|
||||
// When saving the current graph configuration, the pointer is not useful. Instead a trace
|
||||
// hash is saved to identify the correct trace. The hash should be influenced by every setting
|
||||
// the trace can have (and its math function). It should not depend on the acquired trace samples
|
||||
unsigned int toHash();
|
||||
unsigned int toHash(bool forceUpdate = false);
|
||||
|
||||
static std::vector<Trace*> createFromTouchstone(Touchstone &t);
|
||||
static std::vector<Trace*> createFromCSV(CSV &csv);
|
||||
@ -148,12 +156,28 @@ public:
|
||||
|
||||
double getReferenceImpedance() const;
|
||||
|
||||
void setModel(TraceModel *newModel);
|
||||
TraceModel *getModel() const;
|
||||
|
||||
const QString &getMathFormula() const;
|
||||
void setMathFormula(const QString &newMathFormula);
|
||||
bool mathFormularValid() const;
|
||||
|
||||
bool resolveMathSourceHashes();
|
||||
|
||||
public slots:
|
||||
void setVisible(bool visible);
|
||||
void setColor(QColor color);
|
||||
void addMarker(Marker *m);
|
||||
void removeMarker(Marker *m);
|
||||
|
||||
// functions for handling source == Source::Math
|
||||
bool mathDependsOn(Trace *t, bool onlyDirectDependency = false);
|
||||
bool canAddAsMathSource(Trace *t);
|
||||
bool addMathSource(Trace *t, QString variableName);
|
||||
void removeMathSource(Trace *t);
|
||||
QString getSourceVariableName(Trace *t);
|
||||
|
||||
signals:
|
||||
void cleared(Trace *t);
|
||||
void typeChanged(Trace *t);
|
||||
@ -170,21 +194,50 @@ signals:
|
||||
private slots:
|
||||
void markerVisibilityChanged(Marker *m);
|
||||
|
||||
// functions for handling source == Source::Math
|
||||
bool updateMathTracePoints();
|
||||
void mathSourceTraceDeleted(Trace *t);
|
||||
void scheduleMathCalculation(unsigned int begin, unsigned int end);
|
||||
void calculateMath();
|
||||
void clearMathSources();
|
||||
|
||||
bool addMathSource(unsigned int hash, QString variableName);
|
||||
|
||||
private:
|
||||
TraceModel *model; // model which this trace will be part of
|
||||
QString _name;
|
||||
QColor _color;
|
||||
Source source;
|
||||
|
||||
unsigned int hash;
|
||||
bool hashSet;
|
||||
bool JSONskipHash;
|
||||
|
||||
// Members for when source == Source::Live
|
||||
LivedataType _liveType;
|
||||
LiveParameter _liveParam;
|
||||
|
||||
// Members for when source == Source::File
|
||||
QString filename;
|
||||
unsigned int fileParameter;
|
||||
|
||||
// Members for when source == Source::Math
|
||||
std::map<Trace*,QString> mathSourceTraces;
|
||||
std::map<unsigned int,QString> mathSourceUnresolvedHashes;
|
||||
QString mathFormula;
|
||||
static constexpr int MinMathUpdateInterval = 100;
|
||||
QTime lastMathUpdate;
|
||||
QTimer mathCalcTimer;
|
||||
unsigned int mathUpdateBegin;
|
||||
unsigned int mathUpdateEnd;
|
||||
|
||||
double vFactor;
|
||||
bool reflection;
|
||||
bool visible;
|
||||
bool paused;
|
||||
bool createdFromFile;
|
||||
bool calibration;
|
||||
double reference_impedance;
|
||||
DataType domain;
|
||||
QString filename;
|
||||
unsigned int fileParameter;
|
||||
|
||||
std::set<Marker*> markers;
|
||||
struct {
|
||||
union {
|
||||
|
@ -26,6 +26,11 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
ui->impedance->setPrecision(3);
|
||||
ui->impedance->setValue(t.getReferenceImpedance());
|
||||
|
||||
if(!t.getModel()) {
|
||||
// without information about the other traces in the model, math is not available as a source
|
||||
ui->bMath->setEnabled(false);
|
||||
}
|
||||
|
||||
connect(ui->bLive, &QPushButton::clicked, [=](bool live) {
|
||||
if(live) {
|
||||
ui->stack->setCurrentIndex(0);
|
||||
@ -45,6 +50,12 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(ui->bMath, &QPushButton::clicked, [&](bool math){
|
||||
if(math) {
|
||||
ui->stack->setCurrentIndex(3);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->color, &ColorPickerButton::colorChanged, [=](const QColor& color){
|
||||
trace.setColor(color);
|
||||
@ -52,8 +63,9 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
|
||||
ui->GSource->setId(ui->bLive, 0);
|
||||
ui->GSource->setId(ui->bFile, 1);
|
||||
ui->GSource->setId(ui->bFile, 2);
|
||||
|
||||
if(t.isCalibration()) {
|
||||
if(t.getSource() == Trace::Source::Calibration) {
|
||||
// prevent editing imported calibration traces (and csv files for now)
|
||||
ui->bLive->setEnabled(false);
|
||||
ui->bFile->setEnabled(false);
|
||||
@ -136,8 +148,83 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateTouchstoneFileStatus);
|
||||
connect(ui->csvImport, &CSVImport::filenameChanged, updateCSVFileStatus);
|
||||
|
||||
if(t.isFromFile()) {
|
||||
ui->bFile->click();
|
||||
// Math source configuration
|
||||
if(t.getModel()) {
|
||||
ui->lMathFormula->setText(t.getMathFormula());
|
||||
connect(ui->lMathFormula, &QLineEdit::editingFinished, [&](){
|
||||
t.setMathFormula(ui->lMathFormula->text());
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
|
||||
});
|
||||
|
||||
ui->mathTraceTable->setColumnCount(2);
|
||||
ui->mathTraceTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Trace Name"));
|
||||
ui->mathTraceTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Variable Name"));
|
||||
auto traces = t.getModel()->getTraces();
|
||||
ui->mathTraceTable->setRowCount(traces.size());
|
||||
for(unsigned int i=0;i<traces.size();i++) {
|
||||
auto ts = traces[i];
|
||||
auto traceItem = new QTableWidgetItem(ts->name());
|
||||
auto flags = traceItem->flags() | Qt::ItemIsUserCheckable;
|
||||
flags &= ~(Qt::ItemIsEditable | Qt::ItemIsEnabled);
|
||||
if(t.canAddAsMathSource(ts)) {
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
}
|
||||
traceItem->setFlags(flags);
|
||||
auto variableItem = new QTableWidgetItem(t.getSourceVariableName(ts));
|
||||
variableItem->setFlags(variableItem->flags() & ~Qt::ItemIsEditable);
|
||||
if(t.mathDependsOn(ts, true)) {
|
||||
traceItem->setCheckState(Qt::Checked);
|
||||
variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
||||
} else {
|
||||
traceItem->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
ui->mathTraceTable->setItem(i, 0, traceItem);
|
||||
ui->mathTraceTable->setItem(i, 1, variableItem);
|
||||
}
|
||||
connect(ui->mathTraceTable, &QTableWidget::itemChanged, [&](QTableWidgetItem *item){
|
||||
auto row = ui->mathTraceTable->row(item);
|
||||
auto column = ui->mathTraceTable->column(item);
|
||||
qDebug() << "Item changed at row"<<row<<"column"<<column;
|
||||
ui->mathTraceTable->blockSignals(true);
|
||||
auto trace = t.getModel()->trace(row);
|
||||
if(column == 0) {
|
||||
auto variableItem = ui->mathTraceTable->item(row, 1);
|
||||
// checked state changed
|
||||
if(item->checkState() == Qt::Checked) {
|
||||
// add this trace to the math sources, enable editing of variable name
|
||||
t.addMathSource(trace, trace->name());
|
||||
variableItem->setText(trace->name());
|
||||
variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
||||
} else {
|
||||
// trace disabled, remove from math sources
|
||||
t.removeMathSource(trace);
|
||||
variableItem->setText("");
|
||||
variableItem->setFlags(variableItem->flags() & ~(Qt::ItemIsEnabled | Qt::ItemIsEditable));
|
||||
}
|
||||
// available trace selections may have changed, disable/enable other rows
|
||||
for(unsigned int i=0;i<t.getModel()->getTraces().size();i++) {
|
||||
auto traceItem = ui->mathTraceTable->item(i, 0);
|
||||
auto flags = traceItem->flags();
|
||||
if(t.canAddAsMathSource(t.getModel()->trace(i))) {
|
||||
traceItem->setFlags(flags | Qt::ItemIsEnabled);
|
||||
} else {
|
||||
traceItem->setFlags(flags & ~Qt::ItemIsEnabled);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// changed the variable name text
|
||||
t.addMathSource(trace, item->text());
|
||||
}
|
||||
ui->mathTraceTable->blockSignals(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
|
||||
});
|
||||
}
|
||||
|
||||
switch(t.getSource()) {
|
||||
case Trace::Source::Live: ui->bLive->click(); break;
|
||||
case Trace::Source::File: ui->bFile->click(); break;
|
||||
case Trace::Source::Math: ui->bMath->click(); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// setup math part of the GUI
|
||||
@ -254,7 +341,7 @@ void TraceEditDialog::on_buttonBox_accepted()
|
||||
{
|
||||
trace.setName(ui->name->text());
|
||||
trace.setVelocityFactor(ui->vFactor->value());
|
||||
if(!trace.isCalibration()) {
|
||||
if(trace.getSource() != Trace::Source::Calibration) {
|
||||
// only apply changes if it is not a calibration trace
|
||||
if (ui->bFile->isChecked()) {
|
||||
if(ui->stack->currentIndex() == 1) {
|
||||
@ -265,7 +352,7 @@ void TraceEditDialog::on_buttonBox_accepted()
|
||||
// CSV page active
|
||||
ui->csvImport->fillTrace(trace);
|
||||
}
|
||||
} else {
|
||||
} else if(ui->bLive->isChecked()) {
|
||||
Trace::LivedataType type = Trace::LivedataType::Overwrite;
|
||||
Trace::LiveParameter param = Trace::LiveParameter::S11;
|
||||
switch(ui->CLiveType->currentIndex()) {
|
||||
@ -287,6 +374,9 @@ void TraceEditDialog::on_buttonBox_accepted()
|
||||
}
|
||||
}
|
||||
trace.fromLivedata(type, param);
|
||||
} else {
|
||||
// math operation trace
|
||||
trace.fromMath();
|
||||
}
|
||||
}
|
||||
delete this;
|
||||
|
@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>931</width>
|
||||
<height>392</height>
|
||||
<width>979</width>
|
||||
<height>487</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -110,6 +110,16 @@
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="bMath">
|
||||
<property name="text">
|
||||
<string>From Math</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">GSource</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@ -133,7 +143,7 @@
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="LivePage">
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
@ -203,6 +213,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="MathPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="mathTraceTable">
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Formula:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lMathFormula"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -32,6 +32,7 @@ void TraceModel::addTrace(Trace *t)
|
||||
});
|
||||
traces.push_back(t);
|
||||
endInsertRows();
|
||||
t->setModel(this);
|
||||
emit traceAdded(t);
|
||||
}
|
||||
|
||||
@ -117,7 +118,7 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
break;
|
||||
case ColIndexPlayPause:
|
||||
if (role == Qt::DecorationRole && trace->isLive()) {
|
||||
if (role == Qt::DecorationRole && trace->getSource() == Trace::Source::Live) { // TODO trace needs function to check if it may change due to live data
|
||||
if (trace->isPaused()) {
|
||||
return QIcon(":/icons/pause.svg");
|
||||
} else {
|
||||
@ -157,7 +158,7 @@ std::vector<Trace *> TraceModel::getTraces() const
|
||||
bool TraceModel::PortExcitationRequired(int port)
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if(t->isLive() && !t->isPaused()) {
|
||||
if(t->getSource() == Trace::Source::Live && !t->isPaused()) {
|
||||
// this trace needs measurements from VNA, check if port has to be excited for its measurement
|
||||
auto param = t->liveParameter();
|
||||
if(port == 1 && (param == Trace::LiveParameter::S11 || param == Trace::LiveParameter::S21)) {
|
||||
@ -188,6 +189,7 @@ void TraceModel::fromJSON(nlohmann::json j)
|
||||
}
|
||||
for(auto jt : j) {
|
||||
auto trace = new Trace();
|
||||
trace->setModel(this);
|
||||
try {
|
||||
trace->fromJSON(jt);
|
||||
addTrace(trace);
|
||||
@ -195,12 +197,17 @@ void TraceModel::fromJSON(nlohmann::json j)
|
||||
qWarning() << "Failed to create trace:" << e.what();
|
||||
}
|
||||
}
|
||||
for(auto t : traces) {
|
||||
if(!t->resolveMathSourceHashes()) {
|
||||
qWarning() << "Failed to resolve all math source hashes for"<<t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceModel::clearLiveData()
|
||||
{
|
||||
for(auto t : traces) {
|
||||
if (t->isLive()) {
|
||||
if (t->getSource() == Trace::Source::Live) {
|
||||
// this trace is fed from live data
|
||||
t->clear();
|
||||
}
|
||||
@ -211,7 +218,7 @@ void TraceModel::addVNAData(const VNAData& d, TraceMath::DataType datatype)
|
||||
{
|
||||
source = DataSource::VNA;
|
||||
for(auto t : traces) {
|
||||
if (t->isLive() && !t->isPaused()) {
|
||||
if (t->getSource() == Trace::Source::Live && !t->isPaused()) {
|
||||
int index = -1;
|
||||
Trace::Data td;
|
||||
switch(datatype) {
|
||||
@ -247,7 +254,7 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot
|
||||
{
|
||||
source = DataSource::SA;
|
||||
for(auto t : traces) {
|
||||
if (t->isLive() && !t->isPaused()) {
|
||||
if (t->getSource() == Trace::Source::Live && !t->isPaused()) {
|
||||
int index = -1;
|
||||
Trace::Data td;
|
||||
if(settings.f_start == settings.f_stop) {
|
||||
|
@ -426,6 +426,8 @@ void TraceWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||
auto duplicate = new Trace();
|
||||
duplicate->fromJSON(json);
|
||||
duplicate->setName(duplicate->name() + " - Duplicate");
|
||||
// force update of hash
|
||||
duplicate->toHash(true);
|
||||
model.addTrace(duplicate);
|
||||
});
|
||||
ctxmenu->addAction(action_duplicate);
|
||||
|
Loading…
Reference in New Issue
Block a user