CSV export + DFT math operation
This commit is contained in:
parent
79a990af47
commit
6e55eb02dd
@ -24,6 +24,7 @@ HEADERS += \
|
||||
SpectrumAnalyzer/tracewidgetsa.h \
|
||||
Tools/eseries.h \
|
||||
Tools/impedancematchdialog.h \
|
||||
Traces/Math/dft.h \
|
||||
Traces/Math/medianfilter.h \
|
||||
Traces/Math/tdr.h \
|
||||
Traces/Math/tracemath.h \
|
||||
@ -32,14 +33,15 @@ HEADERS += \
|
||||
Traces/fftcomplex.h \
|
||||
Traces/markerwidget.h \
|
||||
Traces/trace.h \
|
||||
Traces/tracecsvexport.h \
|
||||
Traces/traceeditdialog.h \
|
||||
Traces/traceexportdialog.h \
|
||||
Traces/traceimportdialog.h \
|
||||
Traces/tracemarker.h \
|
||||
Traces/tracemarkermodel.h \
|
||||
Traces/tracemodel.h \
|
||||
Traces/traceplot.h \
|
||||
Traces/tracesmithchart.h \
|
||||
Traces/tracetouchstoneexport.h \
|
||||
Traces/tracewidget.h \
|
||||
Traces/tracexyplot.h \
|
||||
Traces/xyplotaxisdialog.h \
|
||||
@ -50,6 +52,7 @@ HEADERS += \
|
||||
VNA/vna.h \
|
||||
appwindow.h \
|
||||
averaging.h \
|
||||
csv.h \
|
||||
json.hpp \
|
||||
mode.h \
|
||||
preferences.h \
|
||||
@ -83,6 +86,7 @@ SOURCES += \
|
||||
SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||
Tools/eseries.cpp \
|
||||
Tools/impedancematchdialog.cpp \
|
||||
Traces/Math/dft.cpp \
|
||||
Traces/Math/medianfilter.cpp \
|
||||
Traces/Math/tdr.cpp \
|
||||
Traces/Math/tracemath.cpp \
|
||||
@ -91,14 +95,15 @@ SOURCES += \
|
||||
Traces/fftcomplex.cpp \
|
||||
Traces/markerwidget.cpp \
|
||||
Traces/trace.cpp \
|
||||
Traces/tracecsvexport.cpp \
|
||||
Traces/traceeditdialog.cpp \
|
||||
Traces/traceexportdialog.cpp \
|
||||
Traces/traceimportdialog.cpp \
|
||||
Traces/tracemarker.cpp \
|
||||
Traces/tracemarkermodel.cpp \
|
||||
Traces/tracemodel.cpp \
|
||||
Traces/traceplot.cpp \
|
||||
Traces/tracesmithchart.cpp \
|
||||
Traces/tracetouchstoneexport.cpp \
|
||||
Traces/tracewidget.cpp \
|
||||
Traces/tracexyplot.cpp \
|
||||
Traces/xyplotaxisdialog.cpp \
|
||||
@ -107,6 +112,7 @@ SOURCES += \
|
||||
VNA/vna.cpp \
|
||||
appwindow.cpp \
|
||||
averaging.cpp \
|
||||
csv.cpp \
|
||||
main.cpp \
|
||||
mode.cpp \
|
||||
preferences.cpp \
|
||||
@ -132,6 +138,8 @@ FORMS += \
|
||||
Device/manualcontroldialog.ui \
|
||||
Generator/signalgenwidget.ui \
|
||||
Tools/impedancematchdialog.ui \
|
||||
Traces/Math/dftdialog.ui \
|
||||
Traces/Math/dftexplanationwidget.ui \
|
||||
Traces/Math/medianexplanationwidget.ui \
|
||||
Traces/Math/medianfilterdialog.ui \
|
||||
Traces/Math/newtracemathdialog.ui \
|
||||
@ -140,9 +148,10 @@ FORMS += \
|
||||
Traces/Math/tracematheditdialog.ui \
|
||||
Traces/markerwidget.ui \
|
||||
Traces/smithchartdialog.ui \
|
||||
Traces/tracecsvexport.ui \
|
||||
Traces/traceeditdialog.ui \
|
||||
Traces/traceexportdialog.ui \
|
||||
Traces/traceimportdialog.ui \
|
||||
Traces/tracetouchstoneexport.ui \
|
||||
Traces/tracewidget.ui \
|
||||
Traces/xyplotaxisdialog.ui \
|
||||
VNA/portextensioneditdialog.ui \
|
||||
|
@ -1,7 +1,15 @@
|
||||
#include "tracewidgetsa.h"
|
||||
|
||||
#include "Traces/tracecsvexport.h"
|
||||
|
||||
TraceWidgetSA::TraceWidgetSA(TraceModel &model, QWidget *parent)
|
||||
: TraceWidget(model, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TraceWidgetSA::exportDialog()
|
||||
{
|
||||
auto csv = new TraceCSVExport(model);
|
||||
csv->show();
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class TraceWidgetSA : public TraceWidget
|
||||
public:
|
||||
TraceWidgetSA(TraceModel &model, QWidget *parent = nullptr);
|
||||
protected slots:
|
||||
virtual void exportDialog() override {};
|
||||
virtual void exportDialog() override;
|
||||
virtual void importDialog() override {};
|
||||
|
||||
protected:
|
||||
|
183
Software/PC_Application/Traces/Math/dft.cpp
Normal file
183
Software/PC_Application/Traces/Math/dft.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#include "dft.h"
|
||||
#include "tdr.h"
|
||||
#include "Traces/fftcomplex.h"
|
||||
#include "unit.h"
|
||||
#include "ui_dftdialog.h"
|
||||
#include "ui_dftexplanationwidget.h"
|
||||
using namespace std;
|
||||
|
||||
Math::DFT::DFT()
|
||||
{
|
||||
automaticDC = true;
|
||||
DCfreq = 1000000000.0;
|
||||
|
||||
connect(&window, &WindowFunction::changed, this, &DFT::updateDFT);
|
||||
}
|
||||
|
||||
TraceMath::DataType Math::DFT::outputType(TraceMath::DataType inputType)
|
||||
{
|
||||
if(inputType == DataType::Time) {
|
||||
return DataType::Frequency;
|
||||
} else {
|
||||
return DataType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QString Math::DFT::description()
|
||||
{
|
||||
QString ret = "DFT (";
|
||||
if(automaticDC) {
|
||||
ret += "automatic DC)";
|
||||
} else {
|
||||
ret += "DC:" + Unit::ToString(DCfreq, "Hz", " kMG", 6) + ")";
|
||||
}
|
||||
ret += ", window: " + window.getDescription();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Math::DFT::edit()
|
||||
{
|
||||
auto d = new QDialog();
|
||||
auto ui = new Ui::DFTDialog;
|
||||
ui->setupUi(d);
|
||||
ui->windowBox->setLayout(new QVBoxLayout);
|
||||
ui->windowBox->layout()->addWidget(window.createEditor());
|
||||
|
||||
connect(ui->DCautomatic, &QRadioButton::toggled, [=](bool automatic){
|
||||
automaticDC = automatic;
|
||||
ui->freq->setEnabled(!automatic);
|
||||
updateDFT();
|
||||
});
|
||||
|
||||
if(automaticDC) {
|
||||
ui->DCautomatic->setChecked(true);
|
||||
} else {
|
||||
ui->DCmanual->setChecked(true);
|
||||
}
|
||||
|
||||
ui->freq->setUnit("Hz");
|
||||
ui->freq->setPrecision(6);
|
||||
ui->freq->setPrefixes(" kMG");
|
||||
ui->freq->setValue(DCfreq);
|
||||
|
||||
connect(ui->freq, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
DCfreq = newval;
|
||||
updateDFT();
|
||||
});
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, d, &QDialog::accept);
|
||||
d->show();
|
||||
}
|
||||
|
||||
QWidget *Math::DFT::createExplanationWidget()
|
||||
{
|
||||
auto w = new QWidget();
|
||||
auto ui = new Ui::DFTExplanationWidget;
|
||||
ui->setupUi(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
nlohmann::json Math::DFT::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["automatic_DC"] = automaticDC;
|
||||
j["window"] = window.toJSON();
|
||||
if(!automaticDC) {
|
||||
j["DC"] = DCfreq;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void Math::DFT::fromJSON(nlohmann::json j)
|
||||
{
|
||||
automaticDC = j.value("automatic_DC", true);
|
||||
DCfreq = j.value("DC", 1000000000.0);
|
||||
if(j.contains("window")) {
|
||||
window.fromJSON(j["window"]);
|
||||
}
|
||||
}
|
||||
|
||||
void Math::DFT::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
{
|
||||
Q_UNUSED(end);
|
||||
if(input->rData().size() < 2) {
|
||||
// not enough input data
|
||||
data.clear();
|
||||
emit outputSamplesChanged(0, 0);
|
||||
warning("Not enough input samples");
|
||||
return;
|
||||
}
|
||||
// DFT is computationally expensive, only update at the end of sweep -> check if this is the first changed data in the next sweep
|
||||
if(begin != 0) {
|
||||
// not the end, do nothing
|
||||
return;
|
||||
}
|
||||
double DC = DCfreq;
|
||||
TDR *tdr = nullptr;
|
||||
if(automaticDC) {
|
||||
// find the last operation that transformed from the frequency domain to the time domain
|
||||
auto in = input;
|
||||
while(in->getInput()->getDataType() != DataType::Frequency) {
|
||||
in = input->getInput();
|
||||
}
|
||||
switch(in->getType()) {
|
||||
case Type::TDR: {
|
||||
tdr = static_cast<TDR*>(in);
|
||||
if(tdr->getMode() == TDR::Mode::Lowpass) {
|
||||
DC = 0;
|
||||
} else {
|
||||
// bandpass mode, assume DC is in the middle of the frequency data
|
||||
DC = tdr->getInput()->getSample(tdr->getInput()->numSamples()/2).x;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// unknown, assume DC is in the middle of the frequency data
|
||||
DC = in->getInput()->getSample(in->getInput()->numSamples()/2).x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto samples = input->rData().size();
|
||||
auto timeSpacing = input->rData()[1].x - input->rData()[0].x;
|
||||
vector<complex<double>> timeDomain(samples);
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
timeDomain.at(i) = input->rData()[i].y;
|
||||
}
|
||||
|
||||
Fft::shift(timeDomain, false);
|
||||
window.apply(timeDomain);
|
||||
Fft::shift(timeDomain, true);
|
||||
Fft::transform(timeDomain, false);
|
||||
// shift DC bin into the middle
|
||||
Fft::shift(timeDomain, false);
|
||||
|
||||
double binSpacing = 1.0 / (timeSpacing * timeDomain.size());
|
||||
data.clear();
|
||||
int DCbin = timeDomain.size() / 2, startBin = 0;
|
||||
if(DC > 0) {
|
||||
data.resize(timeDomain.size());
|
||||
} else {
|
||||
startBin = (timeDomain.size()+1) / 2;
|
||||
data.resize(timeDomain.size()/2);
|
||||
}
|
||||
|
||||
// reverse effect of frequency domain window function from TDR (if available)
|
||||
if(tdr) {
|
||||
tdr->getWindow().reverse(timeDomain);
|
||||
}
|
||||
|
||||
for(int i = startBin;(unsigned int) i<timeDomain.size();i++) {
|
||||
auto freq = (i - DCbin) * binSpacing + DC;
|
||||
data[i - startBin].x = round(freq);
|
||||
data[i - startBin].y = timeDomain.at(i);
|
||||
}
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
success();
|
||||
}
|
||||
|
||||
void Math::DFT::updateDFT()
|
||||
{
|
||||
if(dataType != DataType::Invalid) {
|
||||
inputSamplesChanged(0, input->rData().size());
|
||||
}
|
||||
}
|
36
Software/PC_Application/Traces/Math/dft.h
Normal file
36
Software/PC_Application/Traces/Math/dft.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef DFT_H
|
||||
#define DFT_H
|
||||
|
||||
#include "tracemath.h"
|
||||
#include "windowfunction.h"
|
||||
|
||||
namespace Math {
|
||||
|
||||
class DFT : public TraceMath
|
||||
{
|
||||
public:
|
||||
DFT();
|
||||
|
||||
DataType outputType(DataType inputType) override;
|
||||
QString description() override;
|
||||
void edit() override;
|
||||
|
||||
static QWidget* createExplanationWidget();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::DFT;};
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
||||
private:
|
||||
void updateDFT();
|
||||
bool automaticDC;
|
||||
double DCfreq;
|
||||
WindowFunction window;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // DFT_H
|
104
Software/PC_Application/Traces/Math/dftdialog.ui
Normal file
104
Software/PC_Application/Traces/Math/dftdialog.ui
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DFTDialog</class>
|
||||
<widget class="QDialog" name="DFTDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>268</width>
|
||||
<height>421</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>DFT</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>DC frequency (center)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="DCautomatic">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="DCmanual">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Specify manually:</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frequency:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="freq">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="windowBox">
|
||||
<property name="title">
|
||||
<string>Window</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SIUnitEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>CustomWidgets/siunitedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
44
Software/PC_Application/Traces/Math/dftexplanationwidget.ui
Normal file
44
Software/PC_Application/Traces/Math/dftexplanationwidget.ui
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DFTExplanationWidget</class>
|
||||
<widget class="QWidget" name="DFTExplanationWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>364</width>
|
||||
<height>412</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="widget">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">DFT</span></p><p>Performs a DFT on the time domain data, transforming it back into the frequency domain.</p><p>There are two possible choices for the DC bin frequency:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Automatic: Check frequency span of the original data that was used to create the time domain values and use the same frequency values for the DFT.</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Manual: User selectable frequency which is used for the DC bin.</li></ul><p><br/></p><p><br/></p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -138,6 +138,9 @@ nlohmann::json TDR::toJSON()
|
||||
|
||||
void TDR::fromJSON(nlohmann::json j)
|
||||
{
|
||||
if(j.contains("window")) {
|
||||
window.fromJSON(j["window"]);
|
||||
}
|
||||
if(j.value("bandpass_mode", true)) {
|
||||
mode = Mode::Bandpass;
|
||||
} else {
|
||||
@ -166,7 +169,7 @@ void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
return;
|
||||
}
|
||||
vector<complex<double>> frequencyDomain;
|
||||
auto stepSize = input->rData()[1].x - input->rData()[0].x;
|
||||
auto stepSize = (input->rData().back().x - input->rData().front().x) / (input->rData().size() - 1);
|
||||
if(mode == Mode::Lowpass) {
|
||||
if(stepResponse) {
|
||||
auto steps = input->rData().size();
|
||||
@ -180,6 +183,7 @@ void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
if(firstStep * steps != input->rData().back().x) {
|
||||
// data is not available with correct frequency spacing, calculate required steps
|
||||
steps = input->rData().back().x / firstStep;
|
||||
stepSize = firstStep;
|
||||
}
|
||||
frequencyDomain.resize(2 * steps + 1);
|
||||
// copy frequencies, use the flipped conjugate for negative part
|
||||
@ -259,3 +263,13 @@ void TDR::updateTDR()
|
||||
inputSamplesChanged(0, input->rData().size());
|
||||
}
|
||||
}
|
||||
|
||||
const WindowFunction& TDR::getWindow() const
|
||||
{
|
||||
return window;
|
||||
}
|
||||
|
||||
TDR::Mode TDR::getMode() const
|
||||
{
|
||||
return mode;
|
||||
}
|
||||
|
@ -21,15 +21,18 @@ public:
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::TDR;};
|
||||
|
||||
enum class Mode {
|
||||
Lowpass,
|
||||
Bandpass,
|
||||
};
|
||||
Mode getMode() const;
|
||||
const WindowFunction& getWindow() const;
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
||||
private:
|
||||
void updateTDR();
|
||||
enum class Mode {
|
||||
Lowpass,
|
||||
Bandpass,
|
||||
};
|
||||
Mode mode;
|
||||
WindowFunction window;
|
||||
bool stepResponse;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "medianfilter.h"
|
||||
#include "tdr.h"
|
||||
#include "dft.h"
|
||||
#include "Traces/trace.h"
|
||||
|
||||
TraceMath::TraceMath()
|
||||
@ -18,6 +19,8 @@ TraceMath *TraceMath::createMath(TraceMath::Type type)
|
||||
return new Math::MedianFilter();
|
||||
case Type::TDR:
|
||||
return new Math::TDR();
|
||||
case Type::DFT:
|
||||
return new Math::DFT();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@ -35,6 +38,10 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
|
||||
ret.name = "TDR";
|
||||
ret.explanationWidget = Math::TDR::createExplanationWidget();
|
||||
break;
|
||||
case Type::DFT:
|
||||
ret.name = "DFT";
|
||||
ret.explanationWidget = Math::DFT::createExplanationWidget();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -161,6 +168,11 @@ void TraceMath::updateStepResponse(bool valid)
|
||||
}
|
||||
}
|
||||
|
||||
TraceMath *TraceMath::getInput() const
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
QString TraceMath::getStatusDescription() const
|
||||
{
|
||||
return statusString;
|
||||
|
@ -72,6 +72,7 @@ public:
|
||||
enum class Type {
|
||||
MedianFilter,
|
||||
TDR,
|
||||
DFT,
|
||||
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
|
||||
Last,
|
||||
};
|
||||
@ -106,6 +107,8 @@ public:
|
||||
// returns the trace this math operation is attached to
|
||||
Trace* root();
|
||||
|
||||
TraceMath *getInput() const;
|
||||
|
||||
public slots:
|
||||
// some values of the input data have changed, begin/end determine which sample(s) has changed
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end){Q_UNUSED(begin) Q_UNUSED(end)};
|
||||
|
@ -30,7 +30,7 @@ WindowFunction::WindowFunction(WindowFunction::Type type)
|
||||
chebyshev_alpha = 5;
|
||||
}
|
||||
|
||||
void WindowFunction::apply(std::vector<std::complex<double> > &data)
|
||||
void WindowFunction::apply(std::vector<std::complex<double> > &data) const
|
||||
{
|
||||
unsigned int N = data.size();
|
||||
for(unsigned int n = 0;n<N;n++) {
|
||||
@ -38,6 +38,14 @@ void WindowFunction::apply(std::vector<std::complex<double> > &data)
|
||||
}
|
||||
}
|
||||
|
||||
void WindowFunction::reverse(std::vector<std::complex<double> > &data) const
|
||||
{
|
||||
unsigned int N = data.size();
|
||||
for(unsigned int n = 0;n<N;n++) {
|
||||
data[n] /= getFactor(n, N);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *WindowFunction::createEditor()
|
||||
{
|
||||
auto top = new QWidget();
|
||||
@ -148,7 +156,7 @@ void WindowFunction::fromJSON(nlohmann::json j)
|
||||
}
|
||||
}
|
||||
|
||||
double WindowFunction::getFactor(unsigned int n, unsigned int N)
|
||||
double WindowFunction::getFactor(unsigned int n, unsigned int N) const
|
||||
{
|
||||
// all formulas from https://en.wikipedia.org/wiki/Window_function
|
||||
switch(type) {
|
||||
|
@ -25,7 +25,8 @@ public:
|
||||
|
||||
WindowFunction(Type type = Type::Hamming);
|
||||
|
||||
void apply(std::vector<std::complex<double>>& data);
|
||||
void apply(std::vector<std::complex<double>>& data) const;
|
||||
void reverse(std::vector<std::complex<double>>& data) const;
|
||||
|
||||
QWidget *createEditor();
|
||||
|
||||
@ -39,7 +40,7 @@ signals:
|
||||
void changed();
|
||||
|
||||
private:
|
||||
double getFactor(unsigned int n, unsigned int N);
|
||||
double getFactor(unsigned int n, unsigned int N) const;
|
||||
Type type;
|
||||
// parameters for the different types. Not all windows use one and most only one.
|
||||
// But keeping all parameters for all windows allows switching between window types
|
||||
|
@ -155,7 +155,7 @@ static size_t reverseBits(size_t val, int width) {
|
||||
void Fft::shift(std::vector<std::complex<double> > &vec, bool inverse)
|
||||
{
|
||||
int rotate_len = vec.size() / 2;
|
||||
if(vec.size() % 0x01 != 0) {
|
||||
if((vec.size() & 0x01) != 0) {
|
||||
// odd size, behavior depends on whether this is an inverse shift
|
||||
if(!inverse) {
|
||||
rotate_len++;
|
||||
|
@ -627,6 +627,10 @@ int Trace::index(double x)
|
||||
auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), x, [](const Data &lhs, const double x) -> bool {
|
||||
return lhs.x < x;
|
||||
});
|
||||
if(lower == lastMath->rData().end()) {
|
||||
// actually beyond the last sample, return the index of the last anyway to avoid access past data
|
||||
return lastMath->rData().size() - 1;
|
||||
}
|
||||
return lower - lastMath->rData().begin();
|
||||
}
|
||||
|
||||
|
184
Software/PC_Application/Traces/tracecsvexport.cpp
Normal file
184
Software/PC_Application/Traces/tracecsvexport.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include "tracecsvexport.h"
|
||||
#include "ui_tracecsvexport.h"
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include "csv.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TraceCSVExport::TraceCSVExport(TraceModel &traceModel, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::TraceCSVExport),
|
||||
model(traceModel.getTraces())
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->listView->setModel(&model);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
connect(&model, &TraceCSVModel::selectionChanged, ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::setEnabled);
|
||||
}
|
||||
|
||||
TraceCSVExport::~TraceCSVExport()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void TraceCSVExport::on_buttonBox_accepted()
|
||||
{
|
||||
auto traces = model.tracesToExport();
|
||||
if(traces.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", "", "CSV files (*.csv)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.isEmpty()) {
|
||||
// aborted selection
|
||||
return;
|
||||
}
|
||||
if(!filename.endsWith(".csv")) {
|
||||
filename.append(".csv");
|
||||
}
|
||||
|
||||
CSV csv;
|
||||
// create the first column (X data)
|
||||
vector<double> X;
|
||||
QString Xname = traces[0]->outputType() == Trace::DataType::Frequency ? "Frequency" : "Time";
|
||||
auto samples = traces[0]->numSamples();
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
X.push_back(traces[0]->sample(i).x);
|
||||
}
|
||||
csv.addColumn(Xname, X);
|
||||
// add the trace data
|
||||
for(auto t : traces) {
|
||||
vector<double> real;
|
||||
vector<double> imag;
|
||||
auto samples = t->numSamples();
|
||||
for(unsigned int i=0;i<samples;i++) {
|
||||
real.push_back(t->sample(i).y.real());
|
||||
imag.push_back(t->sample(i).y.imag());
|
||||
}
|
||||
// check if this is a real or complex trace
|
||||
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);
|
||||
}
|
||||
|
||||
TraceCSVModel::TraceCSVModel(std::vector<Trace *> traces, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
this->traces = traces;
|
||||
save.resize(traces.size(), false);
|
||||
enabled.resize(traces.size(), true);
|
||||
points = 0;
|
||||
updateEnabledTraces();
|
||||
}
|
||||
|
||||
int TraceCSVModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return traces.size();
|
||||
}
|
||||
|
||||
QVariant TraceCSVModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if(!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
switch(role) {
|
||||
case Qt::CheckStateRole:
|
||||
if(save[index.row()]) {
|
||||
return Qt::Checked;
|
||||
} else {
|
||||
return Qt::Unchecked;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return traces[index.row()]->name();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceCSVModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if(!index.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if(role==Qt::CheckStateRole) {
|
||||
save[index.row()] = !save[index.row()];
|
||||
updateEnabledTraces();
|
||||
return true;
|
||||
} else {
|
||||
return QAbstractItemModel::setData(index, value, role);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags TraceCSVModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
unsigned int flags = Qt::ItemIsUserCheckable;
|
||||
if(index.isValid()) {
|
||||
if(enabled[index.row()]) {
|
||||
flags |= Qt::ItemIsEnabled;
|
||||
}
|
||||
}
|
||||
return (Qt::ItemFlags) flags;
|
||||
}
|
||||
|
||||
std::vector<Trace *> TraceCSVModel::tracesToExport()
|
||||
{
|
||||
vector<Trace*> ret;
|
||||
for(unsigned int i=0;i<traces.size();i++) {
|
||||
if(save[i]) {
|
||||
ret.push_back(traces[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TraceCSVModel::updateEnabledTraces()
|
||||
{
|
||||
beginResetModel();
|
||||
// find first selected trace and use its settings
|
||||
points = 0;
|
||||
type = Trace::DataType::Invalid;
|
||||
for(unsigned int i=0;i<traces.size();i++) {
|
||||
if(save[i]) {
|
||||
points = traces[i]->numSamples();
|
||||
minX = traces[i]->minX();
|
||||
maxX = traces[i]->maxX();
|
||||
type = traces[i]->outputType();
|
||||
}
|
||||
}
|
||||
// second pass: compare to the settings and only enable identical traces
|
||||
for(unsigned int i=0;i<traces.size();i++) {
|
||||
auto enableTrace = true;
|
||||
if(traces[i]->numSamples() == 0 || traces[i]->outputType() == Trace::DataType::Invalid) {
|
||||
// trace has no valid data, unable to export
|
||||
enableTrace = false;
|
||||
}
|
||||
if(points > 0 && type != Trace::DataType::Invalid) {
|
||||
// check if this trace matches the already selected one
|
||||
if((traces[i]->numSamples() != points)
|
||||
|| (traces[i]->minX() != minX)
|
||||
|| (traces[i]->maxX() != maxX)
|
||||
|| (traces[i]->outputType() != type)) {
|
||||
// different settings, not possible to export in this combination
|
||||
enableTrace = false;
|
||||
}
|
||||
}
|
||||
enabled[i] = enableTrace;
|
||||
if(!enableTrace) {
|
||||
save[i] = false;
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
emit selectionChanged(points > 0);
|
||||
}
|
53
Software/PC_Application/Traces/tracecsvexport.h
Normal file
53
Software/PC_Application/Traces/tracecsvexport.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef TRACECSVEXPORT_H
|
||||
#define TRACECSVEXPORT_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "tracemodel.h"
|
||||
|
||||
namespace Ui {
|
||||
class TraceCSVExport;
|
||||
}
|
||||
|
||||
class TraceCSVModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TraceCSVModel(std::vector<Trace*> traces, QObject *parent = 0);
|
||||
~TraceCSVModel(){};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
std::vector<Trace*> tracesToExport();
|
||||
signals:
|
||||
void selectionChanged(bool anySelected);
|
||||
private:
|
||||
// check which traces can be exported (only allow traces on the same domain with identical span/time)
|
||||
void updateEnabledTraces();
|
||||
unsigned int points;
|
||||
double minX, maxX;
|
||||
Trace::DataType type;
|
||||
std::vector<Trace*> traces;
|
||||
std::vector<bool> enabled;
|
||||
std::vector<bool> save;
|
||||
};
|
||||
|
||||
class TraceCSVExport : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceCSVExport(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceCSVExport();
|
||||
|
||||
private slots:
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
private:
|
||||
Ui::TraceCSVExport *ui;
|
||||
TraceCSVModel model;
|
||||
};
|
||||
|
||||
#endif // TRACECSVEXPORT_H
|
74
Software/PC_Application/Traces/tracecsvexport.ui
Normal file
74
Software/PC_Application/Traces/tracecsvexport.ui
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceCSVExport</class>
|
||||
<widget class="QDialog" name="TraceCSVExport">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>286</width>
|
||||
<height>322</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>CSV Export</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="listView">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>TraceCSVExport</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>298</x>
|
||||
<y>300</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>298</x>
|
||||
<y>160</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TraceCSVExport</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>298</x>
|
||||
<y>300</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>298</x>
|
||||
<y>160</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -220,7 +220,7 @@ bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
|
||||
Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
int flags = Qt::NoItemFlags;
|
||||
int flags = Qt::ItemIsSelectable;
|
||||
switch(index.column()) {
|
||||
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||
case ColIndexTrace: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||
|
@ -1,13 +1,13 @@
|
||||
#include "traceexportdialog.h"
|
||||
#include "tracetouchstoneexport.h"
|
||||
#include "ui_traceexportdialog.h"
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include "touchstone.h"
|
||||
#include <QPushButton>
|
||||
|
||||
TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
|
||||
TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::TraceExportDialog),
|
||||
ui(new Ui::TraceTouchstoneExport),
|
||||
model(model),
|
||||
freqsSet(false)
|
||||
{
|
||||
@ -17,12 +17,12 @@ TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
|
||||
on_sbPorts_valueChanged(ui->sbPorts->value());
|
||||
}
|
||||
|
||||
TraceExportDialog::~TraceExportDialog()
|
||||
TraceTouchstoneExport::~TraceTouchstoneExport()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool TraceExportDialog::setTrace(int portFrom, int portTo, Trace *t)
|
||||
bool TraceTouchstoneExport::setTrace(int portFrom, int portTo, Trace *t)
|
||||
{
|
||||
if(portFrom < 0 || portFrom >= ui->sbPorts->value() || portTo < 0 || portTo >= ui->sbPorts->value()) {
|
||||
// invalid port selection
|
||||
@ -46,7 +46,7 @@ bool TraceExportDialog::setTrace(int portFrom, int portTo, Trace *t)
|
||||
}
|
||||
}
|
||||
|
||||
bool TraceExportDialog::setPortNum(int ports)
|
||||
bool TraceTouchstoneExport::setPortNum(int ports)
|
||||
{
|
||||
if(ports < 1 || ports > 4) {
|
||||
return false;
|
||||
@ -55,7 +55,7 @@ bool TraceExportDialog::setPortNum(int ports)
|
||||
return true;
|
||||
}
|
||||
|
||||
void TraceExportDialog::on_buttonBox_accepted()
|
||||
void TraceTouchstoneExport::on_buttonBox_accepted()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||
if(filename.length() > 0) {
|
||||
@ -99,7 +99,7 @@ void TraceExportDialog::on_buttonBox_accepted()
|
||||
}
|
||||
}
|
||||
|
||||
void TraceExportDialog::on_sbPorts_valueChanged(int ports)
|
||||
void TraceTouchstoneExport::on_sbPorts_valueChanged(int ports)
|
||||
{
|
||||
// remove the previous widgets
|
||||
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
|
||||
@ -118,6 +118,10 @@ void TraceExportDialog::on_sbPorts_valueChanged(int ports)
|
||||
// create possible trace selections
|
||||
c->addItem("None");
|
||||
for(auto t : availableTraces) {
|
||||
if(t->getDataType() != Trace::DataType::Frequency) {
|
||||
// can only add frequency traces
|
||||
continue;
|
||||
}
|
||||
if(i == j && !t->isReflection()) {
|
||||
// can not add through measurement at reflection port
|
||||
continue;
|
||||
@ -137,7 +141,7 @@ void TraceExportDialog::on_sbPorts_valueChanged(int ports)
|
||||
}
|
||||
}
|
||||
|
||||
void TraceExportDialog::selectionChanged(QComboBox *w)
|
||||
void TraceTouchstoneExport::selectionChanged(QComboBox *w)
|
||||
{
|
||||
if(w->currentIndex() != 0 && !freqsSet) {
|
||||
// the first trace has been selected, extract frequency info
|
@ -1,5 +1,5 @@
|
||||
#ifndef TRACEEXPORTDIALOG_H
|
||||
#define TRACEEXPORTDIALOG_H
|
||||
#ifndef TRACETOUCHSTONEEXPORT_H
|
||||
#define TRACETOUCHSTONEEXPORT_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
@ -7,16 +7,16 @@
|
||||
#include <QSignalMapper>
|
||||
|
||||
namespace Ui {
|
||||
class TraceExportDialog;
|
||||
class TraceTouchstoneExport;
|
||||
}
|
||||
|
||||
class TraceExportDialog : public QDialog
|
||||
class TraceTouchstoneExport : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TraceExportDialog(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceExportDialog();
|
||||
explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr);
|
||||
~TraceTouchstoneExport();
|
||||
bool setTrace(int portFrom, int portTo, Trace *t);
|
||||
bool setPortNum(int ports);
|
||||
|
||||
@ -26,7 +26,7 @@ private slots:
|
||||
void selectionChanged(QComboBox *w);
|
||||
|
||||
private:
|
||||
Ui::TraceExportDialog *ui;
|
||||
Ui::TraceTouchstoneExport *ui;
|
||||
TraceModel &model;
|
||||
std::vector<std::vector<QComboBox*>> cTraces;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceExportDialog</class>
|
||||
<widget class="QDialog" name="TraceExportDialog">
|
||||
<class>TraceTouchstoneExport</class>
|
||||
<widget class="QDialog" name="TraceTouchstoneExport">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
<string>Touchstone Export</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
|
||||
<item>
|
||||
@ -226,7 +226,7 @@
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TraceExportDialog</receiver>
|
||||
<receiver>TraceTouchstoneExport</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
@ -4,7 +4,7 @@
|
||||
#include <QKeyEvent>
|
||||
#include "traceeditdialog.h"
|
||||
#include "traceimportdialog.h"
|
||||
#include "traceexportdialog.h"
|
||||
#include "tracetouchstoneexport.h"
|
||||
#include <QFileDialog>
|
||||
#include <QDrag>
|
||||
#include <QMimeData>
|
||||
|
@ -2,17 +2,25 @@
|
||||
|
||||
#include <QFileDialog>
|
||||
#include "Traces/traceimportdialog.h"
|
||||
#include "Traces/traceexportdialog.h"
|
||||
#include "Traces/tracetouchstoneexport.h"
|
||||
#include "Traces/tracecsvexport.h"
|
||||
#include "ui_tracewidget.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
TraceWidgetVNA::TraceWidgetVNA(TraceModel &model, QWidget *parent)
|
||||
: TraceWidget(model, parent)
|
||||
{
|
||||
auto exportMenu = new QMenu();
|
||||
auto exportTouchstone = new QAction("Touchstone");
|
||||
auto exportCSV = new QAction("CSV");
|
||||
exportMenu->addAction(exportTouchstone);
|
||||
exportMenu->addAction(exportCSV);
|
||||
|
||||
}
|
||||
ui->bExport->setMenu(exportMenu);
|
||||
|
||||
void TraceWidgetVNA::exportDialog()
|
||||
{
|
||||
auto e = new TraceExportDialog(model);
|
||||
connect(exportTouchstone, &QAction::triggered, [&]() {
|
||||
auto e = new TraceTouchstoneExport(model);
|
||||
// Attempt to set default traces (this will result in correctly populated
|
||||
// 2 port export if the initial 4 traces have not been modified)
|
||||
e->setPortNum(2);
|
||||
@ -24,6 +32,12 @@ void TraceWidgetVNA::exportDialog()
|
||||
e->setTrace(i%2, i/2, traces[i]);
|
||||
}
|
||||
e->show();
|
||||
});
|
||||
|
||||
connect(exportCSV, &QAction::triggered, [&]() {
|
||||
auto e = new TraceCSVExport(model);
|
||||
e->show();
|
||||
});
|
||||
}
|
||||
|
||||
void TraceWidgetVNA::importDialog()
|
||||
|
@ -8,7 +8,7 @@ class TraceWidgetVNA : public TraceWidget
|
||||
public:
|
||||
TraceWidgetVNA(TraceModel &model, QWidget *parent = nullptr);
|
||||
protected slots:
|
||||
virtual void exportDialog() override;
|
||||
virtual void exportDialog() override {};
|
||||
virtual void importDialog() override;
|
||||
|
||||
protected:
|
||||
|
95
Software/PC_Application/csv.cpp
Normal file
95
Software/PC_Application/csv.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include "csv.h"
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <QStringList>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
CSV::CSV()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CSV CSV::fromFile(QString filename, char sep)
|
||||
{
|
||||
CSV csv;
|
||||
ifstream file;
|
||||
file.open(filename.toStdString());
|
||||
if(!file.is_open()) {
|
||||
throw runtime_error("Unable to open file:"+filename.toStdString());
|
||||
}
|
||||
string line;
|
||||
bool firstLine = true;
|
||||
while(getline(file, line)) {
|
||||
auto qline = QString::fromStdString(line);
|
||||
auto stringList = qline.split(sep);
|
||||
if(firstLine) {
|
||||
// create columns and set headers
|
||||
for(auto l : stringList) {
|
||||
Column c;
|
||||
c.header = l;
|
||||
csv._columns.push_back(c);
|
||||
}
|
||||
firstLine = false;
|
||||
} else {
|
||||
// not the header, attempt to parse data
|
||||
for(unsigned int i=0;i < csv._columns.size();i++) {
|
||||
double value = 0.0;
|
||||
if(i < (unsigned int) stringList.size()) {
|
||||
value = stringList[i].toDouble();
|
||||
}
|
||||
csv._columns[i].data.push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return csv;
|
||||
}
|
||||
|
||||
void CSV::toFile(QString filename, char sep)
|
||||
{
|
||||
ofstream file;
|
||||
file.open(filename.toStdString());
|
||||
file << setprecision(10);
|
||||
unsigned maxlen = 0;
|
||||
for(auto c : _columns) {
|
||||
file << c.header.toStdString();
|
||||
file << sep;
|
||||
if(c.data.size() > maxlen) {
|
||||
maxlen = c.data.size();
|
||||
}
|
||||
}
|
||||
file << endl;
|
||||
for(unsigned int i=0;i<maxlen;i++) {
|
||||
for(auto c : _columns) {
|
||||
if(i < c.data.size()) {
|
||||
file << c.data[i] << sep;
|
||||
}
|
||||
}
|
||||
file << endl;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
std::vector<double> CSV::getColumn(QString header)
|
||||
{
|
||||
for(auto c : _columns) {
|
||||
if(c.header == header) {
|
||||
return c.data;
|
||||
}
|
||||
}
|
||||
throw runtime_error("Header name not found");
|
||||
}
|
||||
|
||||
std::vector<double> CSV::getColumn(unsigned int index)
|
||||
{
|
||||
return _columns.at(index).data;
|
||||
}
|
||||
|
||||
void CSV::addColumn(QString name, const std::vector<double> &data)
|
||||
{
|
||||
Column c;
|
||||
c.header = name;
|
||||
c.data = data;
|
||||
_columns.push_back(c);
|
||||
}
|
30
Software/PC_Application/csv.h
Normal file
30
Software/PC_Application/csv.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef CSV_H
|
||||
#define CSV_H
|
||||
|
||||
#include <QString>
|
||||
#include <vector>
|
||||
|
||||
class CSV
|
||||
{
|
||||
public:
|
||||
CSV();
|
||||
|
||||
static CSV fromFile(QString filename, char sep = ',');
|
||||
|
||||
void toFile(QString filename, char sep = ',');
|
||||
std::vector<double> getColumn(QString header);
|
||||
std::vector<double> getColumn(unsigned int index);
|
||||
unsigned int columns() { return _columns.size();}
|
||||
|
||||
void addColumn(QString name, const std::vector<double> &data);
|
||||
|
||||
private:
|
||||
class Column {
|
||||
public:
|
||||
QString header;
|
||||
std::vector<double> data;
|
||||
};
|
||||
std::vector<Column> _columns;
|
||||
};
|
||||
|
||||
#endif // CSV_H
|
Loading…
Reference in New Issue
Block a user