CSV export options extended

This commit is contained in:
Jan Käberich 2022-04-08 21:55:58 +02:00
parent 000ad0098d
commit 2cf4c5a311
9 changed files with 266 additions and 140 deletions

View File

@ -3,6 +3,7 @@
#include "fftcomplex.h" #include "fftcomplex.h"
#include "Util/util.h" #include "Util/util.h"
#include "Marker/marker.h" #include "Marker/marker.h"
#include "traceaxis.h"
#include <math.h> #include <math.h>
#include <QDebug> #include <QDebug>
@ -167,54 +168,48 @@ void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter)
QString Trace::fillFromCSV(CSV &csv, unsigned int parameter) QString Trace::fillFromCSV(CSV &csv, unsigned int parameter)
{ {
// find correct column // find correct column
unsigned int traceNum = 0; int traceNum = -1;
vector<double> real;
vector<double> imag;
unsigned int i=1; unsigned int i=1;
QString traceName; QString lastTraceName = "";
bool hasImagValues; bool hasImagValues;
std::map<YAxis::Type, int> columnMapping;
for(;i<csv.columns();i++) { for(;i<csv.columns();i++) {
traceName = QString(); auto header = csv.getHeader(i);
hasImagValues = false; auto splitIndex = header.lastIndexOf("_");
// check column names if(splitIndex == -1) {
if(i < csv.columns() - 1) { // no "_", not following naming format of CSV export, skip
// not the last column, check if this and next header implies real/imag values continue;
auto name_real = csv.getHeader(i);
auto name_imag = csv.getHeader(i + 1);
if(name_real.endsWith("_real") && name_imag.endsWith("_imag")) {
// check if headers have the same beginning
name_real.chop(5);
name_imag.chop(5);
if(name_real == name_imag) {
hasImagValues = true;
traceName = name_real;
} }
auto traceName = header.left(splitIndex);
auto yaxistype = header.right(header.size() - splitIndex - 1);
if(traceName != lastTraceName) {
traceNum++;
if(traceNum > parameter) {
// got all columns for the trace we are interested in
break;
} }
} lastTraceName = traceName;
if(!hasImagValues) {
traceName = csv.getHeader(i);
} }
if(traceNum == parameter) { if(traceNum == parameter) {
// this is the desired trace // this is the trace we are looking for, get axistype and add to mapping
break;
} else { // handle legacy column naming, translate to new naming
traceNum++; if(yaxistype == "real") {
yaxistype = YAxis::TypeToName(YAxis::Type::Real);
} else if(yaxistype == "imag") {
yaxistype = YAxis::TypeToName(YAxis::Type::Imaginary);
} }
if(hasImagValues) {
// next column already used by this trace, skip columnMapping[YAxis::TypeFromName(yaxistype)] = i;
i++;
} }
} }
if(i >= csv.columns()) { if(traceNum < parameter) {
throw runtime_error("Not enough traces in CSV file"); throw runtime_error("Not enough traces in CSV file");
} }
real = csv.getColumn(i); if(columnMapping.size() == 0) {
if(hasImagValues) { throw runtime_error("No data for trace in CSV file");
imag = csv.getColumn(i + 1);
} else {
imag.resize(real.size());
fill(imag.begin(), imag.end(), 0.0);
} }
clear(); clear();
fileParameter = parameter; fileParameter = parameter;
filename = csv.getFilename(); filename = csv.getFilename();
@ -227,16 +222,20 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter)
domain = DataType::Frequency; domain = DataType::Frequency;
} }
for(unsigned int i=0;i<xColumn.size();i++) { for(unsigned int i=0;i<xColumn.size();i++) {
std::map<YAxis::Type, double> data;
for(auto map : columnMapping) {
data[map.first] = csv.getColumn(map.second)[i];
}
Data d; Data d;
d.x = xColumn[i]; d.x = xColumn[i];
d.y = complex<double>(real[i], imag[i]); d.y = YAxis::reconstructValueFromYAxisType(data);
addData(d, domain); addData(d, domain);
} }
reflection = false; reflection = false;
createdFromFile = true; createdFromFile = true;
emit typeChanged(this); emit typeChanged(this);
emit outputSamplesChanged(0, data.size()); emit outputSamplesChanged(0, data.size());
return traceName; return lastTraceName;
} }
void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector<VNAData> &data) void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector<VNAData> &data)

View File

@ -317,6 +317,95 @@ YAxis::Type YAxis::getType() const
return type; return type;
} }
std::set<YAxis::Type> YAxis::getSupported(XAxis::Type type, TraceModel::DataSource source)
{
std::set<YAxis::Type> ret = {YAxis::Type::Disabled};
if(source == TraceModel::DataSource::VNA) {
switch(type) {
case XAxis::Type::Frequency:
case XAxis::Type::Power:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudeLinear);
ret.insert(YAxis::Type::Phase);
ret.insert(YAxis::Type::UnwrappedPhase);
ret.insert(YAxis::Type::VSWR);
ret.insert(YAxis::Type::Real);
ret.insert(YAxis::Type::Imaginary);
ret.insert(YAxis::Type::SeriesR);
ret.insert(YAxis::Type::Reactance);
ret.insert(YAxis::Type::Capacitance);
ret.insert(YAxis::Type::Inductance);
ret.insert(YAxis::Type::QualityFactor);
ret.insert(YAxis::Type::GroupDelay);
break;
case XAxis::Type::Time:
case XAxis::Type::Distance:
ret.insert(YAxis::Type::ImpulseReal);
ret.insert(YAxis::Type::ImpulseMag);
ret.insert(YAxis::Type::Step);
ret.insert(YAxis::Type::Impedance);
break;
default:
break;
}
} else if(source == TraceModel::DataSource::SA) {
switch(type) {
case XAxis::Type::Frequency:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudedBuV);
break;
default:
break;
}
}
return ret;
}
std::complex<double> YAxis::reconstructValueFromYAxisType(std::map<YAxis::Type, double> yaxistypes)
{
std::complex<double> ret = std::numeric_limits<std::complex<double>>::quiet_NaN();
if(yaxistypes.count(Type::Real)) {
ret.real(yaxistypes[Type::Real]);
if(yaxistypes.count(Type::Imaginary)) {
ret.imag(yaxistypes[Type::Imaginary]);
} else {
ret.imag(0.0);
}
} else if(yaxistypes.count(Type::Magnitude) || yaxistypes.count(Type::MagnitudedBuV) || yaxistypes.count(Type::MagnitudeLinear)) {
double maglin, phase;
if(yaxistypes.count(Type::MagnitudeLinear)) {
maglin = yaxistypes[Type::MagnitudeLinear];
} else if(yaxistypes.count(Type::Magnitude)) {
maglin = Util::dBToMagnitude(yaxistypes[Type::Magnitude]);
} else {
auto dBm = Util::dBuVTodBm(yaxistypes[Type::MagnitudedBuV]);
maglin = Util::dBToMagnitude(dBm);
}
if(yaxistypes.count(Type::Phase)) {
phase = yaxistypes[Type::Phase];
} else {
phase = 0.0;
}
ret = polar<double>(maglin, phase / 180.0 * M_PI);
}
return ret;
}
bool XAxis::isSupported(XAxis::Type type, TraceModel::DataSource source)
{
if(source == TraceModel::DataSource::VNA) {
// all X axis types are supported
return true;
} else if(source == TraceModel::DataSource::SA) {
if (type == XAxis::Type::Frequency) {
return true;
} else {
return false;
}
}
return false;
}
void Axis::updateTicks() void Axis::updateTicks()
{ {
if(log) { if(log) {

View File

@ -29,6 +29,32 @@ protected:
std::vector<double> ticks; std::vector<double> ticks;
}; };
class XAxis : public Axis {
public:
enum class Type {
Frequency,
Time,
Distance,
Power,
Last,
};
XAxis();
double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override;
void set(Type type, bool log, bool autorange, double min, double max, double div);
static QString TypeToName(Type type);
static Type TypeFromName(QString name);
static QString Unit(Type type);
QString TypeToName();
QString Unit();
Type getType() const;
static bool isSupported(XAxis::Type type, TraceModel::DataSource source);
private:
Type type;
};
class YAxis : public Axis { class YAxis : public Axis {
public: public:
enum class Type { enum class Type {
@ -58,6 +84,7 @@ public:
}; };
YAxis(); YAxis();
double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override; double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override;
void set(Type type, bool log, bool autorange, double min, double max, double div); void set(Type type, bool log, bool autorange, double min, double max, double div);
static QString TypeToName(Type type); static QString TypeToName(Type type);
static Type TypeFromName(QString name); static Type TypeFromName(QString name);
@ -69,29 +96,8 @@ public:
Type getType() const; Type getType() const;
private: static std::set<YAxis::Type> getSupported(XAxis::Type type, TraceModel::DataSource source);
Type type; static std::complex<double> reconstructValueFromYAxisType(std::map<Type, double> yaxistypes);
};
class XAxis : public Axis {
public:
enum class Type {
Frequency,
Time,
Distance,
Power,
Last,
};
XAxis();
double sampleToCoordinate(Trace::Data data, Trace *t = nullptr, unsigned int sample = 0) override;
void set(Type type, bool log, bool autorange, double min, double max, double div);
static QString TypeToName(Type type);
static Type TypeFromName(QString name);
static QString Unit(Type type);
QString TypeToName();
QString Unit();
Type getType() const;
private: private:
Type type; Type type;

View File

@ -3,6 +3,8 @@
#include "ui_tracecsvexport.h" #include "ui_tracecsvexport.h"
#include "csv.h" #include "csv.h"
#include "traceaxis.h"
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QPushButton> #include <QPushButton>
#include <QFileDialog> #include <QFileDialog>
@ -19,6 +21,35 @@ TraceCSVExport::TraceCSVExport(TraceModel &traceModel, QWidget *parent) :
ui->listView->setModel(&model); ui->listView->setModel(&model);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(&model, &TraceCSVModel::selectionChanged, ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::setEnabled); connect(&model, &TraceCSVModel::selectionChanged, ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::setEnabled);
connect(&model, &TraceCSVModel::selectionChanged, [&](){
auto traces = model.tracesToExport();
if(traces.size() == 0) {
ui->listColumns->clear();
} else if(ui->listColumns->count() == 0) {
// first trace has bee selected, fill column selection
auto t = traces.front();
auto domain = t->outputType();
auto Xaxis = XAxis::Type::Last;
switch(domain) {
case Trace::DataType::Frequency: Xaxis = XAxis::Type::Frequency; break;
case Trace::DataType::Power: Xaxis = XAxis::Type::Power; break;
case Trace::DataType::Time: Xaxis = XAxis::Type::Time; break;
}
if(Xaxis == XAxis::Type::Last) {
// invalid axis selection
return;
}
for(auto ytype : YAxis::getSupported(Xaxis, traceModel.getSource())) {
if(ytype != YAxis::Type::Disabled) {
auto item = new QListWidgetItem(YAxis::TypeToName(ytype), ui->listColumns);
item->setCheckState(Qt::Unchecked);
}
}
// first fill of selection, nothing selected yet, disable OK button
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
});
connect(ui->listColumns, &QListWidget::itemChanged, this, &TraceCSVExport::columnSelectionChanged);
} }
TraceCSVExport::~TraceCSVExport() TraceCSVExport::~TraceCSVExport()
@ -26,6 +57,12 @@ TraceCSVExport::~TraceCSVExport()
delete ui; delete ui;
} }
void TraceCSVExport::columnSelectionChanged()
{
auto types = getSelectedYAxisTypes();
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(types.size() > 0);
}
void TraceCSVExport::on_buttonBox_accepted() void TraceCSVExport::on_buttonBox_accepted()
{ {
auto traces = model.tracesToExport(); auto traces = model.tracesToExport();
@ -52,29 +89,35 @@ void TraceCSVExport::on_buttonBox_accepted()
} }
csv.addColumn(Xname, X); csv.addColumn(Xname, X);
// add the trace data // add the trace data
for(auto t : traces) { for(auto trace : traces) {
vector<double> real; for(auto ytype : getSelectedYAxisTypes()) {
vector<double> imag; auto axis = YAxis();
auto samples = t->numSamples(); axis.set(ytype, false, false, 0, 1, 1);
auto samples = trace->numSamples();
vector<double> values;
for(unsigned int i=0;i<samples;i++) { for(unsigned int i=0;i<samples;i++) {
real.push_back(t->sample(i).y.real()); values.push_back(axis.sampleToCoordinate(trace->sample(i), trace, i));
imag.push_back(t->sample(i).y.imag());
} }
// check if this is a real or complex trace csv.addColumn(trace->name()+"_"+axis.TypeToName(), values);
bool allZeros = std::all_of(imag.begin(), imag.end(), [](double i) { return i==0.0; });
if(allZeros) {
// only real values, one column is enough
csv.addColumn(t->name(), real);
} else {
// complex values, need two columns
csv.addColumn(t->name()+"_real", real);
csv.addColumn(t->name()+"_imag", imag);
} }
} }
csv.toFile(filename); csv.toFile(filename);
} }
std::vector<YAxis::Type> TraceCSVExport::getSelectedYAxisTypes()
{
std::vector<YAxis::Type> ret;
for(unsigned int i=0;i<ui->listColumns->count();i++) {
auto item = ui->listColumns->item(i);
if(item->checkState() == Qt::Checked) {
auto type = YAxis::TypeFromName(item->text());
ret.push_back(type);
}
}
return ret;
}
TraceCSVModel::TraceCSVModel(std::vector<Trace *> traces, QObject *parent) TraceCSVModel::TraceCSVModel(std::vector<Trace *> traces, QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {

View File

@ -2,6 +2,7 @@
#define TRACECSVEXPORT_H #define TRACECSVEXPORT_H
#include "tracemodel.h" #include "tracemodel.h"
#include "traceaxis.h"
#include <QDialog> #include <QDialog>
@ -44,9 +45,11 @@ public:
~TraceCSVExport(); ~TraceCSVExport();
private slots: private slots:
void columnSelectionChanged();
void on_buttonBox_accepted(); void on_buttonBox_accepted();
private: private:
std::vector<YAxis::Type> getSelectedYAxisTypes();
Ui::TraceCSVExport *ui; Ui::TraceCSVExport *ui;
TraceCSVModel model; TraceCSVModel model;
}; };

View File

@ -9,8 +9,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>286</width> <width>469</width>
<height>322</height> <height>330</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -19,6 +19,14 @@
<property name="modal"> <property name="modal">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Traces</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QListView" name="listView"> <widget class="QListView" name="listView">
@ -27,6 +35,23 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Columns</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="listColumns"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">

View File

@ -1,6 +1,7 @@
#include "xyplotaxisdialog.h" #include "xyplotaxisdialog.h"
#include "ui_xyplotaxisdialog.h" #include "ui_xyplotaxisdialog.h"
#include "traceaxis.h"
#include <QStandardItemModel> #include <QStandardItemModel>
@ -223,61 +224,10 @@ void XYplotAxisDialog::XAxisTypeChanged(int XAxisIndex)
std::set<YAxis::Type> XYplotAxisDialog::supportedYAxis(XAxis::Type type) std::set<YAxis::Type> XYplotAxisDialog::supportedYAxis(XAxis::Type type)
{ {
set<YAxis::Type> ret = {YAxis::Type::Disabled}; return YAxis::getSupported(type, plot->getModel().getSource());
auto source = plot->getModel().getSource();
if(source == TraceModel::DataSource::VNA) {
switch(type) {
case XAxis::Type::Frequency:
case XAxis::Type::Power:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudeLinear);
ret.insert(YAxis::Type::Phase);
ret.insert(YAxis::Type::UnwrappedPhase);
ret.insert(YAxis::Type::VSWR);
ret.insert(YAxis::Type::Real);
ret.insert(YAxis::Type::Imaginary);
ret.insert(YAxis::Type::SeriesR);
ret.insert(YAxis::Type::Reactance);
ret.insert(YAxis::Type::Capacitance);
ret.insert(YAxis::Type::Inductance);
ret.insert(YAxis::Type::QualityFactor);
ret.insert(YAxis::Type::GroupDelay);
break;
case XAxis::Type::Time:
case XAxis::Type::Distance:
ret.insert(YAxis::Type::ImpulseReal);
ret.insert(YAxis::Type::ImpulseMag);
ret.insert(YAxis::Type::Step);
ret.insert(YAxis::Type::Impedance);
break;
default:
break;
}
} else if(source == TraceModel::DataSource::SA) {
switch(type) {
case XAxis::Type::Frequency:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudedBuV);
break;
default:
break;
}
}
return ret;
} }
bool XYplotAxisDialog::isSupported(XAxis::Type type) bool XYplotAxisDialog::isSupported(XAxis::Type type)
{ {
auto source = plot->getModel().getSource(); return XAxis::isSupported(type, plot->getModel().getSource());
if(source == TraceModel::DataSource::VNA) {
// all X axis types are supported
return true;
} else if(source == TraceModel::DataSource::SA) {
if (type == XAxis::Type::Frequency) {
return true;
} else {
return false;
}
}
return false;
} }

View File

@ -72,3 +72,10 @@ double Util::dBmTodBuV(double dBm)
double dBdiff = 10*log10(uVpower*1000); double dBdiff = 10*log10(uVpower*1000);
return dBm - dBdiff; return dBm - dBdiff;
} }
double Util::dBuVTodBm(double dBuV)
{
double uVpower = 0.000001*0.000001/50.0;
double dBdiff = 10*log10(uVpower*1000);
return dBuV + dBdiff;
}

View File

@ -31,7 +31,11 @@ namespace Util {
static inline double SparamTodB(std::complex<double> d) { static inline double SparamTodB(std::complex<double> d) {
return SparamTodB(abs(d)); return SparamTodB(abs(d));
} }
static inline double dBToMagnitude(double dB) {
return pow(10, dB / 20);
}
double dBmTodBuV(double dBm); double dBmTodBuV(double dBm);
double dBuVTodBm(double dBuV);
static inline double SparamToDegree(std::complex<double> d) { static inline double SparamToDegree(std::complex<double> d) {
return (arg(d) * 180.0 / M_PI); return (arg(d) * 180.0 / M_PI);
} }