CSV export + DFT math operation
This commit is contained in:
parent
79a990af47
commit
6e55eb02dd
@ -24,6 +24,7 @@ HEADERS += \
|
|||||||
SpectrumAnalyzer/tracewidgetsa.h \
|
SpectrumAnalyzer/tracewidgetsa.h \
|
||||||
Tools/eseries.h \
|
Tools/eseries.h \
|
||||||
Tools/impedancematchdialog.h \
|
Tools/impedancematchdialog.h \
|
||||||
|
Traces/Math/dft.h \
|
||||||
Traces/Math/medianfilter.h \
|
Traces/Math/medianfilter.h \
|
||||||
Traces/Math/tdr.h \
|
Traces/Math/tdr.h \
|
||||||
Traces/Math/tracemath.h \
|
Traces/Math/tracemath.h \
|
||||||
@ -32,14 +33,15 @@ HEADERS += \
|
|||||||
Traces/fftcomplex.h \
|
Traces/fftcomplex.h \
|
||||||
Traces/markerwidget.h \
|
Traces/markerwidget.h \
|
||||||
Traces/trace.h \
|
Traces/trace.h \
|
||||||
|
Traces/tracecsvexport.h \
|
||||||
Traces/traceeditdialog.h \
|
Traces/traceeditdialog.h \
|
||||||
Traces/traceexportdialog.h \
|
|
||||||
Traces/traceimportdialog.h \
|
Traces/traceimportdialog.h \
|
||||||
Traces/tracemarker.h \
|
Traces/tracemarker.h \
|
||||||
Traces/tracemarkermodel.h \
|
Traces/tracemarkermodel.h \
|
||||||
Traces/tracemodel.h \
|
Traces/tracemodel.h \
|
||||||
Traces/traceplot.h \
|
Traces/traceplot.h \
|
||||||
Traces/tracesmithchart.h \
|
Traces/tracesmithchart.h \
|
||||||
|
Traces/tracetouchstoneexport.h \
|
||||||
Traces/tracewidget.h \
|
Traces/tracewidget.h \
|
||||||
Traces/tracexyplot.h \
|
Traces/tracexyplot.h \
|
||||||
Traces/xyplotaxisdialog.h \
|
Traces/xyplotaxisdialog.h \
|
||||||
@ -50,6 +52,7 @@ HEADERS += \
|
|||||||
VNA/vna.h \
|
VNA/vna.h \
|
||||||
appwindow.h \
|
appwindow.h \
|
||||||
averaging.h \
|
averaging.h \
|
||||||
|
csv.h \
|
||||||
json.hpp \
|
json.hpp \
|
||||||
mode.h \
|
mode.h \
|
||||||
preferences.h \
|
preferences.h \
|
||||||
@ -83,6 +86,7 @@ SOURCES += \
|
|||||||
SpectrumAnalyzer/tracewidgetsa.cpp \
|
SpectrumAnalyzer/tracewidgetsa.cpp \
|
||||||
Tools/eseries.cpp \
|
Tools/eseries.cpp \
|
||||||
Tools/impedancematchdialog.cpp \
|
Tools/impedancematchdialog.cpp \
|
||||||
|
Traces/Math/dft.cpp \
|
||||||
Traces/Math/medianfilter.cpp \
|
Traces/Math/medianfilter.cpp \
|
||||||
Traces/Math/tdr.cpp \
|
Traces/Math/tdr.cpp \
|
||||||
Traces/Math/tracemath.cpp \
|
Traces/Math/tracemath.cpp \
|
||||||
@ -91,14 +95,15 @@ SOURCES += \
|
|||||||
Traces/fftcomplex.cpp \
|
Traces/fftcomplex.cpp \
|
||||||
Traces/markerwidget.cpp \
|
Traces/markerwidget.cpp \
|
||||||
Traces/trace.cpp \
|
Traces/trace.cpp \
|
||||||
|
Traces/tracecsvexport.cpp \
|
||||||
Traces/traceeditdialog.cpp \
|
Traces/traceeditdialog.cpp \
|
||||||
Traces/traceexportdialog.cpp \
|
|
||||||
Traces/traceimportdialog.cpp \
|
Traces/traceimportdialog.cpp \
|
||||||
Traces/tracemarker.cpp \
|
Traces/tracemarker.cpp \
|
||||||
Traces/tracemarkermodel.cpp \
|
Traces/tracemarkermodel.cpp \
|
||||||
Traces/tracemodel.cpp \
|
Traces/tracemodel.cpp \
|
||||||
Traces/traceplot.cpp \
|
Traces/traceplot.cpp \
|
||||||
Traces/tracesmithchart.cpp \
|
Traces/tracesmithchart.cpp \
|
||||||
|
Traces/tracetouchstoneexport.cpp \
|
||||||
Traces/tracewidget.cpp \
|
Traces/tracewidget.cpp \
|
||||||
Traces/tracexyplot.cpp \
|
Traces/tracexyplot.cpp \
|
||||||
Traces/xyplotaxisdialog.cpp \
|
Traces/xyplotaxisdialog.cpp \
|
||||||
@ -107,6 +112,7 @@ SOURCES += \
|
|||||||
VNA/vna.cpp \
|
VNA/vna.cpp \
|
||||||
appwindow.cpp \
|
appwindow.cpp \
|
||||||
averaging.cpp \
|
averaging.cpp \
|
||||||
|
csv.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
mode.cpp \
|
mode.cpp \
|
||||||
preferences.cpp \
|
preferences.cpp \
|
||||||
@ -132,6 +138,8 @@ FORMS += \
|
|||||||
Device/manualcontroldialog.ui \
|
Device/manualcontroldialog.ui \
|
||||||
Generator/signalgenwidget.ui \
|
Generator/signalgenwidget.ui \
|
||||||
Tools/impedancematchdialog.ui \
|
Tools/impedancematchdialog.ui \
|
||||||
|
Traces/Math/dftdialog.ui \
|
||||||
|
Traces/Math/dftexplanationwidget.ui \
|
||||||
Traces/Math/medianexplanationwidget.ui \
|
Traces/Math/medianexplanationwidget.ui \
|
||||||
Traces/Math/medianfilterdialog.ui \
|
Traces/Math/medianfilterdialog.ui \
|
||||||
Traces/Math/newtracemathdialog.ui \
|
Traces/Math/newtracemathdialog.ui \
|
||||||
@ -140,9 +148,10 @@ FORMS += \
|
|||||||
Traces/Math/tracematheditdialog.ui \
|
Traces/Math/tracematheditdialog.ui \
|
||||||
Traces/markerwidget.ui \
|
Traces/markerwidget.ui \
|
||||||
Traces/smithchartdialog.ui \
|
Traces/smithchartdialog.ui \
|
||||||
|
Traces/tracecsvexport.ui \
|
||||||
Traces/traceeditdialog.ui \
|
Traces/traceeditdialog.ui \
|
||||||
Traces/traceexportdialog.ui \
|
|
||||||
Traces/traceimportdialog.ui \
|
Traces/traceimportdialog.ui \
|
||||||
|
Traces/tracetouchstoneexport.ui \
|
||||||
Traces/tracewidget.ui \
|
Traces/tracewidget.ui \
|
||||||
Traces/xyplotaxisdialog.ui \
|
Traces/xyplotaxisdialog.ui \
|
||||||
VNA/portextensioneditdialog.ui \
|
VNA/portextensioneditdialog.ui \
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
#include "tracewidgetsa.h"
|
#include "tracewidgetsa.h"
|
||||||
|
|
||||||
|
#include "Traces/tracecsvexport.h"
|
||||||
|
|
||||||
TraceWidgetSA::TraceWidgetSA(TraceModel &model, QWidget *parent)
|
TraceWidgetSA::TraceWidgetSA(TraceModel &model, QWidget *parent)
|
||||||
: TraceWidget(model, parent)
|
: TraceWidget(model, parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TraceWidgetSA::exportDialog()
|
||||||
|
{
|
||||||
|
auto csv = new TraceCSVExport(model);
|
||||||
|
csv->show();
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ class TraceWidgetSA : public TraceWidget
|
|||||||
public:
|
public:
|
||||||
TraceWidgetSA(TraceModel &model, QWidget *parent = nullptr);
|
TraceWidgetSA(TraceModel &model, QWidget *parent = nullptr);
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void exportDialog() override {};
|
virtual void exportDialog() override;
|
||||||
virtual void importDialog() override {};
|
virtual void importDialog() override {};
|
||||||
|
|
||||||
protected:
|
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)
|
void TDR::fromJSON(nlohmann::json j)
|
||||||
{
|
{
|
||||||
|
if(j.contains("window")) {
|
||||||
|
window.fromJSON(j["window"]);
|
||||||
|
}
|
||||||
if(j.value("bandpass_mode", true)) {
|
if(j.value("bandpass_mode", true)) {
|
||||||
mode = Mode::Bandpass;
|
mode = Mode::Bandpass;
|
||||||
} else {
|
} else {
|
||||||
@ -166,7 +169,7 @@ void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vector<complex<double>> frequencyDomain;
|
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(mode == Mode::Lowpass) {
|
||||||
if(stepResponse) {
|
if(stepResponse) {
|
||||||
auto steps = input->rData().size();
|
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) {
|
if(firstStep * steps != input->rData().back().x) {
|
||||||
// data is not available with correct frequency spacing, calculate required steps
|
// data is not available with correct frequency spacing, calculate required steps
|
||||||
steps = input->rData().back().x / firstStep;
|
steps = input->rData().back().x / firstStep;
|
||||||
|
stepSize = firstStep;
|
||||||
}
|
}
|
||||||
frequencyDomain.resize(2 * steps + 1);
|
frequencyDomain.resize(2 * steps + 1);
|
||||||
// copy frequencies, use the flipped conjugate for negative part
|
// copy frequencies, use the flipped conjugate for negative part
|
||||||
@ -259,3 +263,13 @@ void TDR::updateTDR()
|
|||||||
inputSamplesChanged(0, input->rData().size());
|
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;
|
virtual void fromJSON(nlohmann::json j) override;
|
||||||
Type getType() override {return Type::TDR;};
|
Type getType() override {return Type::TDR;};
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
Lowpass,
|
||||||
|
Bandpass,
|
||||||
|
};
|
||||||
|
Mode getMode() const;
|
||||||
|
const WindowFunction& getWindow() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateTDR();
|
void updateTDR();
|
||||||
enum class Mode {
|
|
||||||
Lowpass,
|
|
||||||
Bandpass,
|
|
||||||
};
|
|
||||||
Mode mode;
|
Mode mode;
|
||||||
WindowFunction window;
|
WindowFunction window;
|
||||||
bool stepResponse;
|
bool stepResponse;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "medianfilter.h"
|
#include "medianfilter.h"
|
||||||
#include "tdr.h"
|
#include "tdr.h"
|
||||||
|
#include "dft.h"
|
||||||
#include "Traces/trace.h"
|
#include "Traces/trace.h"
|
||||||
|
|
||||||
TraceMath::TraceMath()
|
TraceMath::TraceMath()
|
||||||
@ -18,6 +19,8 @@ TraceMath *TraceMath::createMath(TraceMath::Type type)
|
|||||||
return new Math::MedianFilter();
|
return new Math::MedianFilter();
|
||||||
case Type::TDR:
|
case Type::TDR:
|
||||||
return new Math::TDR();
|
return new Math::TDR();
|
||||||
|
case Type::DFT:
|
||||||
|
return new Math::DFT();
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -35,6 +38,10 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
|
|||||||
ret.name = "TDR";
|
ret.name = "TDR";
|
||||||
ret.explanationWidget = Math::TDR::createExplanationWidget();
|
ret.explanationWidget = Math::TDR::createExplanationWidget();
|
||||||
break;
|
break;
|
||||||
|
case Type::DFT:
|
||||||
|
ret.name = "DFT";
|
||||||
|
ret.explanationWidget = Math::DFT::createExplanationWidget();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -161,6 +168,11 @@ void TraceMath::updateStepResponse(bool valid)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TraceMath *TraceMath::getInput() const
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
QString TraceMath::getStatusDescription() const
|
QString TraceMath::getStatusDescription() const
|
||||||
{
|
{
|
||||||
return statusString;
|
return statusString;
|
||||||
|
@ -72,6 +72,7 @@ public:
|
|||||||
enum class Type {
|
enum class Type {
|
||||||
MedianFilter,
|
MedianFilter,
|
||||||
TDR,
|
TDR,
|
||||||
|
DFT,
|
||||||
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
|
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
|
||||||
Last,
|
Last,
|
||||||
};
|
};
|
||||||
@ -106,6 +107,8 @@ public:
|
|||||||
// returns the trace this math operation is attached to
|
// returns the trace this math operation is attached to
|
||||||
Trace* root();
|
Trace* root();
|
||||||
|
|
||||||
|
TraceMath *getInput() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// some values of the input data have changed, begin/end determine which sample(s) has changed
|
// 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)};
|
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;
|
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();
|
unsigned int N = data.size();
|
||||||
for(unsigned int n = 0;n<N;n++) {
|
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()
|
QWidget *WindowFunction::createEditor()
|
||||||
{
|
{
|
||||||
auto top = new QWidget();
|
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
|
// all formulas from https://en.wikipedia.org/wiki/Window_function
|
||||||
switch(type) {
|
switch(type) {
|
||||||
|
@ -25,7 +25,8 @@ public:
|
|||||||
|
|
||||||
WindowFunction(Type type = Type::Hamming);
|
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();
|
QWidget *createEditor();
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ signals:
|
|||||||
void changed();
|
void changed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double getFactor(unsigned int n, unsigned int N);
|
double getFactor(unsigned int n, unsigned int N) const;
|
||||||
Type type;
|
Type type;
|
||||||
// parameters for the different types. Not all windows use one and most only one.
|
// 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
|
// 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)
|
void Fft::shift(std::vector<std::complex<double> > &vec, bool inverse)
|
||||||
{
|
{
|
||||||
int rotate_len = vec.size() / 2;
|
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
|
// odd size, behavior depends on whether this is an inverse shift
|
||||||
if(!inverse) {
|
if(!inverse) {
|
||||||
rotate_len++;
|
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 {
|
auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), x, [](const Data &lhs, const double x) -> bool {
|
||||||
return lhs.x < x;
|
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();
|
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
|
Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
int flags = Qt::NoItemFlags;
|
int flags = Qt::ItemIsSelectable;
|
||||||
switch(index.column()) {
|
switch(index.column()) {
|
||||||
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
|
||||||
case ColIndexTrace: 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 "ui_traceexportdialog.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include "touchstone.h"
|
#include "touchstone.h"
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
|
TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
ui(new Ui::TraceExportDialog),
|
ui(new Ui::TraceTouchstoneExport),
|
||||||
model(model),
|
model(model),
|
||||||
freqsSet(false)
|
freqsSet(false)
|
||||||
{
|
{
|
||||||
@ -17,12 +17,12 @@ TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
|
|||||||
on_sbPorts_valueChanged(ui->sbPorts->value());
|
on_sbPorts_valueChanged(ui->sbPorts->value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceExportDialog::~TraceExportDialog()
|
TraceTouchstoneExport::~TraceTouchstoneExport()
|
||||||
{
|
{
|
||||||
delete ui;
|
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()) {
|
if(portFrom < 0 || portFrom >= ui->sbPorts->value() || portTo < 0 || portTo >= ui->sbPorts->value()) {
|
||||||
// invalid port selection
|
// 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) {
|
if(ports < 1 || ports > 4) {
|
||||||
return false;
|
return false;
|
||||||
@ -55,7 +55,7 @@ bool TraceExportDialog::setPortNum(int ports)
|
|||||||
return true;
|
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);
|
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
|
||||||
if(filename.length() > 0) {
|
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
|
// remove the previous widgets
|
||||||
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
|
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
|
||||||
@ -118,6 +118,10 @@ void TraceExportDialog::on_sbPorts_valueChanged(int ports)
|
|||||||
// create possible trace selections
|
// create possible trace selections
|
||||||
c->addItem("None");
|
c->addItem("None");
|
||||||
for(auto t : availableTraces) {
|
for(auto t : availableTraces) {
|
||||||
|
if(t->getDataType() != Trace::DataType::Frequency) {
|
||||||
|
// can only add frequency traces
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(i == j && !t->isReflection()) {
|
if(i == j && !t->isReflection()) {
|
||||||
// can not add through measurement at reflection port
|
// can not add through measurement at reflection port
|
||||||
continue;
|
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) {
|
if(w->currentIndex() != 0 && !freqsSet) {
|
||||||
// the first trace has been selected, extract frequency info
|
// the first trace has been selected, extract frequency info
|
@ -1,5 +1,5 @@
|
|||||||
#ifndef TRACEEXPORTDIALOG_H
|
#ifndef TRACETOUCHSTONEEXPORT_H
|
||||||
#define TRACEEXPORTDIALOG_H
|
#define TRACETOUCHSTONEEXPORT_H
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
@ -7,16 +7,16 @@
|
|||||||
#include <QSignalMapper>
|
#include <QSignalMapper>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class TraceExportDialog;
|
class TraceTouchstoneExport;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TraceExportDialog : public QDialog
|
class TraceTouchstoneExport : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TraceExportDialog(TraceModel &model, QWidget *parent = nullptr);
|
explicit TraceTouchstoneExport(TraceModel &model, QWidget *parent = nullptr);
|
||||||
~TraceExportDialog();
|
~TraceTouchstoneExport();
|
||||||
bool setTrace(int portFrom, int portTo, Trace *t);
|
bool setTrace(int portFrom, int portTo, Trace *t);
|
||||||
bool setPortNum(int ports);
|
bool setPortNum(int ports);
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ private slots:
|
|||||||
void selectionChanged(QComboBox *w);
|
void selectionChanged(QComboBox *w);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::TraceExportDialog *ui;
|
Ui::TraceTouchstoneExport *ui;
|
||||||
TraceModel &model;
|
TraceModel &model;
|
||||||
std::vector<std::vector<QComboBox*>> cTraces;
|
std::vector<std::vector<QComboBox*>> cTraces;
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>TraceExportDialog</class>
|
<class>TraceTouchstoneExport</class>
|
||||||
<widget class="QDialog" name="TraceExportDialog">
|
<widget class="QDialog" name="TraceTouchstoneExport">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Dialog</string>
|
<string>Touchstone Export</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
|
||||||
<item>
|
<item>
|
||||||
@ -226,7 +226,7 @@
|
|||||||
<connection>
|
<connection>
|
||||||
<sender>buttonBox</sender>
|
<sender>buttonBox</sender>
|
||||||
<signal>rejected()</signal>
|
<signal>rejected()</signal>
|
||||||
<receiver>TraceExportDialog</receiver>
|
<receiver>TraceTouchstoneExport</receiver>
|
||||||
<slot>reject()</slot>
|
<slot>reject()</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel">
|
<hint type="sourcelabel">
|
@ -4,7 +4,7 @@
|
|||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include "traceeditdialog.h"
|
#include "traceeditdialog.h"
|
||||||
#include "traceimportdialog.h"
|
#include "traceimportdialog.h"
|
||||||
#include "traceexportdialog.h"
|
#include "tracetouchstoneexport.h"
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QDrag>
|
#include <QDrag>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
@ -2,28 +2,42 @@
|
|||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include "Traces/traceimportdialog.h"
|
#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)
|
TraceWidgetVNA::TraceWidgetVNA(TraceModel &model, QWidget *parent)
|
||||||
: TraceWidget(model, 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()
|
connect(exportTouchstone, &QAction::triggered, [&]() {
|
||||||
{
|
auto e = new TraceTouchstoneExport(model);
|
||||||
auto e = new TraceExportDialog(model);
|
// Attempt to set default traces (this will result in correctly populated
|
||||||
// Attempt to set default traces (this will result in correctly populated
|
// 2 port export if the initial 4 traces have not been modified)
|
||||||
// 2 port export if the initial 4 traces have not been modified)
|
e->setPortNum(2);
|
||||||
e->setPortNum(2);
|
auto traces = model.getTraces();
|
||||||
auto traces = model.getTraces();
|
for(unsigned int i=0;i<4;i++) {
|
||||||
for(unsigned int i=0;i<4;i++) {
|
if(i >= traces.size()) {
|
||||||
if(i >= traces.size()) {
|
break;
|
||||||
break;
|
}
|
||||||
|
e->setTrace(i%2, i/2, traces[i]);
|
||||||
}
|
}
|
||||||
e->setTrace(i%2, i/2, traces[i]);
|
e->show();
|
||||||
}
|
});
|
||||||
e->show();
|
|
||||||
|
connect(exportCSV, &QAction::triggered, [&]() {
|
||||||
|
auto e = new TraceCSVExport(model);
|
||||||
|
e->show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TraceWidgetVNA::importDialog()
|
void TraceWidgetVNA::importDialog()
|
||||||
|
@ -8,7 +8,7 @@ class TraceWidgetVNA : public TraceWidget
|
|||||||
public:
|
public:
|
||||||
TraceWidgetVNA(TraceModel &model, QWidget *parent = nullptr);
|
TraceWidgetVNA(TraceModel &model, QWidget *parent = nullptr);
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void exportDialog() override;
|
virtual void exportDialog() override {};
|
||||||
virtual void importDialog() override;
|
virtual void importDialog() override;
|
||||||
|
|
||||||
protected:
|
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