mirror of
https://github.com/jankae/LibreVNA.git
synced 2026-04-04 14:07:30 +00:00
Save/load trace and graph setup
This commit is contained in:
parent
b91f431473
commit
9ad8def2ea
33 changed files with 605 additions and 28 deletions
|
|
@ -55,6 +55,20 @@ QWidget *MedianFilter::createExplanationWidget()
|
|||
return w;
|
||||
}
|
||||
|
||||
nlohmann::json MedianFilter::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["kernel"] = kernelSize;
|
||||
j["order"] = order;
|
||||
return j;
|
||||
}
|
||||
|
||||
void MedianFilter::fromJSON(nlohmann::json j)
|
||||
{
|
||||
kernelSize = j.value("kernel", 3);
|
||||
order = j.value("order", Order::AbsoluteValue);
|
||||
}
|
||||
|
||||
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
|
||||
if(data.size() != input->rData().size()) {
|
||||
data.resize(input->rData().size());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ public:
|
|||
|
||||
static QWidget *createExplanationWidget();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::MedianFilter;};
|
||||
|
||||
public slots:
|
||||
// a single value of the input data has changed, index determines which sample has changed
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
|
|
|||
|
|
@ -111,6 +111,44 @@ QWidget *TDR::createExplanationWidget()
|
|||
return new QLabel("Test");
|
||||
}
|
||||
|
||||
nlohmann::json TDR::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["bandpass_mode"] = mode == Mode::Bandpass;
|
||||
j["window"] = window.toJSON();
|
||||
if(mode == Mode::Lowpass) {
|
||||
j["step_response"] = stepResponse;
|
||||
if(stepResponse) {
|
||||
j["automatic_DC"] = automaticDC;
|
||||
if(!automaticDC) {
|
||||
j["manual_DC_real"] = manualDC.real();
|
||||
j["manual_DC_imag"] = manualDC.imag();
|
||||
}
|
||||
}
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void TDR::fromJSON(nlohmann::json j)
|
||||
{
|
||||
if(j.value("bandpass_mode", true)) {
|
||||
mode = Mode::Bandpass;
|
||||
} else {
|
||||
mode = Mode::Lowpass;
|
||||
if(j.value("step_response", true)) {
|
||||
stepResponse = true;
|
||||
if(j.value("automatic_DC", true)) {
|
||||
automaticDC = true;
|
||||
} else {
|
||||
automaticDC = false;
|
||||
manualDC = complex<double>(j.value("manual_DC_real", 1.0), j.value("manual_DC_imag", 0.0));
|
||||
}
|
||||
} else {
|
||||
stepResponse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
{
|
||||
Q_UNUSED(begin);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ public:
|
|||
|
||||
static QWidget* createExplanationWidget();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::TDR;};
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <QObject>
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
#include "savable.h"
|
||||
|
||||
/*
|
||||
* How to implement a new type of math operation:
|
||||
|
|
@ -34,6 +35,8 @@
|
|||
* Provide a hint by passing a short description string
|
||||
* error(): something went wrong (called with wrong type of data, mathematical error, ...).
|
||||
* Provide a hint by passing a short description string
|
||||
* e. getType(): return the type of the operation
|
||||
* f. toJSON() and fromJSON(). Save/load all internal parameters
|
||||
* 3. Add a new type to the Type enum for your operation
|
||||
* 4. Extend the createMath(Type type) factory function to create an instance of your operation
|
||||
* 5. Add a static function "createExplanationWidget" which returns a QWidget explaining what your operation does.
|
||||
|
|
@ -43,7 +46,7 @@
|
|||
|
||||
class Trace;
|
||||
|
||||
class TraceMath : public QObject {
|
||||
class TraceMath : public QObject, public Savable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceMath();
|
||||
|
|
@ -98,6 +101,7 @@ public:
|
|||
std::vector<Data>& rData() { return data;};
|
||||
Status getStatus() const;
|
||||
QString getStatusDescription() const;
|
||||
virtual Type getType() = 0;
|
||||
|
||||
// returns the trace this math operation is attached to
|
||||
Trace* root();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <QLabel>
|
||||
#include <QFormLayout>
|
||||
#include "CustomWidgets/siunitedit.h"
|
||||
#include <QDebug>
|
||||
|
||||
QString WindowFunction::typeToName(WindowFunction::Type type)
|
||||
{
|
||||
|
|
@ -108,6 +109,45 @@ QString WindowFunction::getDescription()
|
|||
return ret;
|
||||
}
|
||||
|
||||
nlohmann::json WindowFunction::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["type"] = typeToName(type).toStdString();
|
||||
// add additional parameter if type has one
|
||||
switch(type) {
|
||||
case Type::Gaussian:
|
||||
j["sigma"] = gaussian_sigma;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void WindowFunction::fromJSON(nlohmann::json j)
|
||||
{
|
||||
qDebug() << "Setting window function from json";
|
||||
QString typeName = QString::fromStdString(j["type"]);
|
||||
unsigned int i=0;
|
||||
for(;i<(int) Type::Last;i++) {
|
||||
if(typeToName((Type) i) == typeName) {
|
||||
type = Type(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i>=(int) Type::Last) {
|
||||
qWarning() << "Invalid window type specified, defaulting to hamming";
|
||||
type = Type::Hamming;
|
||||
}
|
||||
switch(type) {
|
||||
case Type::Gaussian:
|
||||
gaussian_sigma = j.value("sigma", 0.4);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double WindowFunction::getFactor(unsigned int n, unsigned int N)
|
||||
{
|
||||
// all formulas from https://en.wikipedia.org/wiki/Window_function
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
#include <QWidget>
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include "savable.h"
|
||||
|
||||
class WindowFunction : public QObject
|
||||
class WindowFunction : public QObject, public Savable
|
||||
{
|
||||
Q_OBJECT;
|
||||
public:
|
||||
|
|
@ -31,6 +32,9 @@ public:
|
|||
Type getType() const;
|
||||
QString getDescription();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include "trace.h"
|
||||
#include <math.h>
|
||||
#include "fftcomplex.h"
|
||||
#include <QDebug>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -193,6 +195,112 @@ double Trace::distanceToTime(double distance)
|
|||
return time;
|
||||
}
|
||||
|
||||
nlohmann::json Trace::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
if(isCalibration()) {
|
||||
// calibration traces can't be saved
|
||||
return j;
|
||||
}
|
||||
j["name"] = _name.toStdString();
|
||||
j["color"] = _color.name().toStdString();
|
||||
j["visible"] = visible;
|
||||
if(isLive()) {
|
||||
j["type"] = "Live";
|
||||
j["parameter"] = _liveParam;
|
||||
j["livetype"] = _liveType;
|
||||
j["paused"] = paused;
|
||||
} else if(isTouchstone()) {
|
||||
j["type"] = "Touchstone";
|
||||
j["filename"] = touchstoneFilename.toStdString();
|
||||
j["parameter"] = touchstoneParameter;
|
||||
}
|
||||
j["reflection"] = reflection;
|
||||
// TODO how to save assigned markers?
|
||||
nlohmann::json mathList;
|
||||
for(auto m : mathOps) {
|
||||
if(m.math->getType() == Type::Last) {
|
||||
// this is an invalid type reserved for the trace itself, skip
|
||||
continue;
|
||||
}
|
||||
nlohmann::json jm;
|
||||
auto info = TraceMath::getInfo(m.math->getType());
|
||||
jm["operation"] = info.name.toStdString();
|
||||
jm["enabled"] = m.enabled;
|
||||
jm["settings"] = m.math->toJSON();
|
||||
mathList.push_back(jm);
|
||||
}
|
||||
j["math"] = mathList;
|
||||
j["math_enabled"] = mathEnabled();
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void Trace::fromJSON(nlohmann::json j)
|
||||
{
|
||||
touchstone = false;
|
||||
calibration = false;
|
||||
_name = QString::fromStdString(j.value("name", "Missing name"));
|
||||
_color = QColor(QString::fromStdString(j.value("color", "yellow")));
|
||||
visible = j.value("visible", true);
|
||||
auto type = QString::fromStdString(j.value("type", "Live"));
|
||||
if(type == "Live") {
|
||||
_liveParam = j.value("parameter", LiveParameter::S11);
|
||||
_liveType = j.value("livetype", LivedataType::Overwrite);
|
||||
paused = j.value("paused", false);
|
||||
} else if(type == "Touchstone") {
|
||||
std::string filename = j.value("filename", "");
|
||||
touchstoneParameter = j.value("parameter", 0);
|
||||
try {
|
||||
Touchstone t = Touchstone::fromFile(filename);
|
||||
fillFromTouchstone(t, touchstoneParameter, QString::fromStdString(filename));
|
||||
} catch (const exception &e) {
|
||||
std::string what = e.what();
|
||||
throw runtime_error("Failed to create touchstone:" + what);
|
||||
}
|
||||
}
|
||||
reflection = j.value("reflection", false);
|
||||
for(auto jm : j["math"]) {
|
||||
QString operation = QString::fromStdString(jm.value("operation", ""));
|
||||
if(operation.isEmpty()) {
|
||||
qWarning() << "Skipping empty math operation";
|
||||
continue;
|
||||
}
|
||||
// attempt to find the type of operation
|
||||
TraceMath::Type type = Type::Last;
|
||||
for(unsigned int i=0;i<(int) Type::Last;i++) {
|
||||
auto info = TraceMath::getInfo((Type) i);
|
||||
if(info.name == operation) {
|
||||
// found the correct operation
|
||||
type = (Type) i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(type == Type::Last) {
|
||||
// unable to find this operation
|
||||
qWarning() << "Unable to create math operation:" << operation;
|
||||
continue;
|
||||
}
|
||||
qDebug() << "Creating math operation of type:" << operation;
|
||||
auto op = TraceMath::createMath(type);
|
||||
MathInfo info;
|
||||
info.enabled = jm.value("enabled", true);
|
||||
info.math = op;
|
||||
op->assignInput(lastMath);
|
||||
mathOps.push_back(info);
|
||||
updateLastMath(mathOps.rbegin());
|
||||
}
|
||||
enableMath(j.value("math_enabled", true));
|
||||
}
|
||||
|
||||
unsigned int Trace::toHash()
|
||||
{
|
||||
// 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
|
||||
std::string json_string = toJSON().dump();
|
||||
return hash<std::string>{}(json_string);
|
||||
}
|
||||
|
||||
void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
|
||||
{
|
||||
TraceMath *newLast = nullptr;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,17 @@ public:
|
|||
double timeToDistance(double time);
|
||||
double distanceToTime(double distance);
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
Type getType() override {return Type::Last;}; // can return invalid type, this will never be called
|
||||
|
||||
// Traces are referenced by pointers throughout this project (e.g. when added to a graph)
|
||||
// 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();
|
||||
|
||||
public slots:
|
||||
void setTouchstoneParameter(int value);
|
||||
void setTouchstoneFilename(const QString &value);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "tracemodel.h"
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -164,6 +165,32 @@ bool TraceModel::PortExcitationRequired(int port)
|
|||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json TraceModel::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
for(auto t : traces) {
|
||||
j.push_back(t->toJSON());
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void TraceModel::fromJSON(nlohmann::json j)
|
||||
{
|
||||
// clear old traces
|
||||
while(traces.size()) {
|
||||
removeTrace(0);
|
||||
}
|
||||
for(auto jt : j) {
|
||||
auto trace = new Trace();
|
||||
try {
|
||||
trace->fromJSON(jt);
|
||||
addTrace(trace);
|
||||
} catch (const exception &e) {
|
||||
qWarning() << "Failed to create trace:" << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceModel::clearVNAData()
|
||||
{
|
||||
for(auto t : traces) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
#include "trace.h"
|
||||
#include <vector>
|
||||
#include "Device/device.h"
|
||||
#include "savable.h"
|
||||
|
||||
class TraceModel : public QAbstractTableModel
|
||||
class TraceModel : public QAbstractTableModel, public Savable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
|
@ -36,6 +37,9 @@ public:
|
|||
|
||||
bool PortExcitationRequired(int port);
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
signals:
|
||||
void SpanChanged(double fmin, double fmax);
|
||||
void traceAdded(Trace *t);
|
||||
|
|
|
|||
|
|
@ -7,17 +7,24 @@
|
|||
#include <QContextMenuEvent>
|
||||
#include <QTime>
|
||||
#include <QLabel>
|
||||
#include "savable.h"
|
||||
|
||||
class TracePlot : public QWidget
|
||||
class TracePlot : public QWidget, public Savable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type {
|
||||
SmithChart,
|
||||
XYPlot,
|
||||
};
|
||||
|
||||
TracePlot(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TracePlot();
|
||||
|
||||
virtual void enableTrace(Trace *t, bool enabled);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
virtual void updateSpan(double min, double max);
|
||||
virtual Type getType() = 0;
|
||||
|
||||
static std::set<TracePlot *> getPlots();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,15 +13,43 @@ using namespace std;
|
|||
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
|
||||
: TracePlot(model, parent)
|
||||
{
|
||||
chartLinesPen = QPen(palette().windowText(), 0.75);
|
||||
thinPen = QPen(palette().windowText(), 0.25);
|
||||
textPen = QPen(palette().windowText(), 0.25);
|
||||
pointDataPen = QPen(QColor("red"), 4.0, Qt::SolidLine, Qt::RoundCap);
|
||||
lineDataPen = QPen(QColor("blue"), 1.0);
|
||||
limitToSpan = true;
|
||||
initializeTraceInfo();
|
||||
}
|
||||
|
||||
nlohmann::json TraceSmithChart::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["limit_to_span"] = limitToSpan;
|
||||
nlohmann::json jtraces;
|
||||
for(auto t : traces) {
|
||||
if(t.second) {
|
||||
jtraces.push_back(t.first->toHash());
|
||||
}
|
||||
}
|
||||
j["traces"] = jtraces;
|
||||
return j;
|
||||
}
|
||||
|
||||
void TraceSmithChart::fromJSON(nlohmann::json j)
|
||||
{
|
||||
limitToSpan = j.value("limit_to_span", true);
|
||||
for(unsigned int hash : j["traces"]) {
|
||||
// attempt to find the traces with this hash
|
||||
bool found = false;
|
||||
for(auto t : model.getTraces()) {
|
||||
if(t->toHash() == hash) {
|
||||
enableTrace(t, true);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
qWarning() << "Unable to find trace with hash" << hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceSmithChart::axisSetupDialog()
|
||||
{
|
||||
auto dialog = new QDialog();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ class TraceSmithChart : public TracePlot
|
|||
Q_OBJECT
|
||||
public:
|
||||
TraceSmithChart(TraceModel &model, QWidget *parent = 0);
|
||||
|
||||
virtual Type getType() override { return Type::SmithChart;};
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
public slots:
|
||||
void axisSetupDialog();
|
||||
protected:
|
||||
|
|
@ -29,18 +34,7 @@ protected:
|
|||
virtual void draw(QPainter& painter) override;
|
||||
virtual void traceDropped(Trace *t, QPoint position) override;
|
||||
QString mouseText(QPoint pos) override;
|
||||
QPen textPen;
|
||||
QPen chartLinesPen;
|
||||
QPen thinPen;
|
||||
QPen pointDataPen;
|
||||
QPen lineDataPen;
|
||||
bool limitToSpan;
|
||||
|
||||
/// Path for the thin arcs
|
||||
QPainterPath thinArcsPath;
|
||||
/// Path for the thick arcs
|
||||
QPainterPath thickArcsPath;
|
||||
|
||||
QTransform transform;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,76 @@ void TraceXYPlot::replot()
|
|||
TracePlot::replot();
|
||||
}
|
||||
|
||||
nlohmann::json TraceXYPlot::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
nlohmann::json jX;
|
||||
jX["type"] = XAxis.type;
|
||||
jX["mode"] = XAxis.mode;
|
||||
jX["log"] = XAxis.log;
|
||||
jX["min"] = XAxis.rangeMin;
|
||||
jX["max"] = XAxis.rangeMax;
|
||||
jX["div"] = XAxis.rangeDiv;
|
||||
j["XAxis"] = jX;
|
||||
for(unsigned int i=0;i<2;i++) {
|
||||
nlohmann::json jY;
|
||||
jY["type"] = YAxis[i].type;
|
||||
jY["log"] = YAxis[i].log;
|
||||
jY["autorange"] = YAxis[i].autorange;
|
||||
jY["min"] = YAxis[i].rangeMin;
|
||||
jY["max"] = YAxis[i].rangeMax;
|
||||
jY["div"] = YAxis[i].rangeDiv;
|
||||
nlohmann::json jtraces;
|
||||
for(auto t : tracesAxis[i]) {
|
||||
jtraces.push_back(t->toHash());
|
||||
}
|
||||
jY["traces"] = jtraces;
|
||||
|
||||
if(i==0) {
|
||||
j["YPrimary"] = jY;
|
||||
} else {
|
||||
j["YSecondary"] = jY;
|
||||
}
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void TraceXYPlot::fromJSON(nlohmann::json j)
|
||||
{
|
||||
auto jX = j["XAxis"];
|
||||
auto xtype = jX.value("type", XAxisType::Frequency);
|
||||
auto xmode = jX.value("mode", XAxisMode::UseSpan);
|
||||
// auto xlog = jX.value("log", false);
|
||||
auto xmin = jX.value("min", 0);
|
||||
auto xmax = jX.value("max", 6000000000);
|
||||
auto xdiv = jX.value("div", 600000000);
|
||||
setXAxis(xtype, xmode, xmin, xmax, xdiv);
|
||||
nlohmann::json jY[2] = {j["YPrimary"], j["YSecondary"]};
|
||||
for(unsigned int i=0;i<2;i++) {
|
||||
auto ytype = jY[i].value("type", YAxisType::Disabled);
|
||||
auto yauto = jY[i].value("autorange", true);
|
||||
auto ylog = jY[i].value("log", false);
|
||||
auto ymin = jY[i].value("min", -120);
|
||||
auto ymax = jY[i].value("max", 20);
|
||||
auto ydiv = jY[i].value("div", 10);
|
||||
setYAxis(i, ytype, ylog, yauto, ymin, ymax, ydiv);
|
||||
for(unsigned int hash : jY[i]["traces"]) {
|
||||
// attempt to find the traces with this hash
|
||||
bool found = false;
|
||||
for(auto t : model.getTraces()) {
|
||||
if(t->toHash() == hash) {
|
||||
enableTraceAxis(t, i, true);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
qWarning() << "Unable to find trace with hash" << hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceXYPlot::isTDRtype(TraceXYPlot::YAxisType type)
|
||||
{
|
||||
switch(type) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ public:
|
|||
void updateSpan(double min, double max) override;
|
||||
void replot() override;
|
||||
|
||||
virtual Type getType() override { return Type::XYPlot;};
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
|
||||
bool isTDRtype(YAxisType type);
|
||||
|
||||
public slots:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue