diff --git a/Software/PC_Application/Application.pro b/Software/PC_Application/Application.pro index d7e10f6..f88eae3 100644 --- a/Software/PC_Application/Application.pro +++ b/Software/PC_Application/Application.pro @@ -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 \ diff --git a/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.cpp b/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.cpp index 4da1239..a1c29d1 100644 --- a/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.cpp @@ -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(); +} diff --git a/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.h b/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.h index 2455324..d2d1eb2 100644 --- a/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.h +++ b/Software/PC_Application/SpectrumAnalyzer/tracewidgetsa.h @@ -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: diff --git a/Software/PC_Application/Traces/Math/dft.cpp b/Software/PC_Application/Traces/Math/dft.cpp new file mode 100644 index 0000000..dbc4ac2 --- /dev/null +++ b/Software/PC_Application/Traces/Math/dft.cpp @@ -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(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> timeDomain(samples); + for(unsigned int i=0;irData()[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) irData().size()); + } +} diff --git a/Software/PC_Application/Traces/Math/dft.h b/Software/PC_Application/Traces/Math/dft.h new file mode 100644 index 0000000..64cbe2c --- /dev/null +++ b/Software/PC_Application/Traces/Math/dft.h @@ -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 diff --git a/Software/PC_Application/Traces/Math/dftdialog.ui b/Software/PC_Application/Traces/Math/dftdialog.ui new file mode 100644 index 0000000..61531f1 --- /dev/null +++ b/Software/PC_Application/Traces/Math/dftdialog.ui @@ -0,0 +1,104 @@ + + + DFTDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 268 + 421 + + + + DFT + + + true + + + + + + DC frequency (center) + + + + + + true + + + Automatic + + + buttonGroup + + + + + + + true + + + Specify manually: + + + buttonGroup + + + + + + + + + Frequency: + + + + + + + true + + + + + + + + + + + + Window + + + + + + + QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+
+ + + + + +
diff --git a/Software/PC_Application/Traces/Math/dftexplanationwidget.ui b/Software/PC_Application/Traces/Math/dftexplanationwidget.ui new file mode 100644 index 0000000..acaf6c8 --- /dev/null +++ b/Software/PC_Application/Traces/Math/dftexplanationwidget.ui @@ -0,0 +1,44 @@ + + + DFTExplanationWidget + + + + 0 + 0 + 364 + 412 + + + + Form + + + + + + <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> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Software/PC_Application/Traces/Math/tdr.cpp b/Software/PC_Application/Traces/Math/tdr.cpp index d0dae10..ec60792 100644 --- a/Software/PC_Application/Traces/Math/tdr.cpp +++ b/Software/PC_Application/Traces/Math/tdr.cpp @@ -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> 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; +} diff --git a/Software/PC_Application/Traces/Math/tdr.h b/Software/PC_Application/Traces/Math/tdr.h index 2b19715..4d223fb 100644 --- a/Software/PC_Application/Traces/Math/tdr.h +++ b/Software/PC_Application/Traces/Math/tdr.h @@ -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; diff --git a/Software/PC_Application/Traces/Math/tracemath.cpp b/Software/PC_Application/Traces/Math/tracemath.cpp index ab09646..696d3ed 100644 --- a/Software/PC_Application/Traces/Math/tracemath.cpp +++ b/Software/PC_Application/Traces/Math/tracemath.cpp @@ -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; diff --git a/Software/PC_Application/Traces/Math/tracemath.h b/Software/PC_Application/Traces/Math/tracemath.h index a5cb613..b05ddb3 100644 --- a/Software/PC_Application/Traces/Math/tracemath.h +++ b/Software/PC_Application/Traces/Math/tracemath.h @@ -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)}; diff --git a/Software/PC_Application/Traces/Math/windowfunction.cpp b/Software/PC_Application/Traces/Math/windowfunction.cpp index 8ae0444..5790043 100644 --- a/Software/PC_Application/Traces/Math/windowfunction.cpp +++ b/Software/PC_Application/Traces/Math/windowfunction.cpp @@ -30,7 +30,7 @@ WindowFunction::WindowFunction(WindowFunction::Type type) chebyshev_alpha = 5; } -void WindowFunction::apply(std::vector > &data) +void WindowFunction::apply(std::vector > &data) const { unsigned int N = data.size(); for(unsigned int n = 0;n > &data) } } +void WindowFunction::reverse(std::vector > &data) const +{ + unsigned int N = data.size(); + for(unsigned int n = 0;n>& data); + void apply(std::vector>& data) const; + void reverse(std::vector>& 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 diff --git a/Software/PC_Application/Traces/fftcomplex.cpp b/Software/PC_Application/Traces/fftcomplex.cpp index cdfa177..9846004 100644 --- a/Software/PC_Application/Traces/fftcomplex.cpp +++ b/Software/PC_Application/Traces/fftcomplex.cpp @@ -155,7 +155,7 @@ static size_t reverseBits(size_t val, int width) { void Fft::shift(std::vector > &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++; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 3478eb8..8996458 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -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(); } diff --git a/Software/PC_Application/Traces/tracecsvexport.cpp b/Software/PC_Application/Traces/tracecsvexport.cpp new file mode 100644 index 0000000..996d740 --- /dev/null +++ b/Software/PC_Application/Traces/tracecsvexport.cpp @@ -0,0 +1,184 @@ +#include "tracecsvexport.h" +#include "ui_tracecsvexport.h" +#include +#include +#include +#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 X; + QString Xname = traces[0]->outputType() == Trace::DataType::Frequency ? "Frequency" : "Time"; + auto samples = traces[0]->numSamples(); + for(unsigned int i=0;isample(i).x); + } + csv.addColumn(Xname, X); + // add the trace data + for(auto t : traces) { + vector real; + vector imag; + auto samples = t->numSamples(); + for(unsigned int i=0;isample(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 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 TraceCSVModel::tracesToExport() +{ + vector ret; + for(unsigned int i=0;inumSamples(); + 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;inumSamples() == 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); +} diff --git a/Software/PC_Application/Traces/tracecsvexport.h b/Software/PC_Application/Traces/tracecsvexport.h new file mode 100644 index 0000000..2325eda --- /dev/null +++ b/Software/PC_Application/Traces/tracecsvexport.h @@ -0,0 +1,53 @@ +#ifndef TRACECSVEXPORT_H +#define TRACECSVEXPORT_H + +#include +#include "tracemodel.h" + +namespace Ui { +class TraceCSVExport; +} + +class TraceCSVModel : public QAbstractListModel +{ + Q_OBJECT +public: + TraceCSVModel(std::vector 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 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 traces; + std::vector enabled; + std::vector 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 diff --git a/Software/PC_Application/Traces/tracecsvexport.ui b/Software/PC_Application/Traces/tracecsvexport.ui new file mode 100644 index 0000000..b793847 --- /dev/null +++ b/Software/PC_Application/Traces/tracecsvexport.ui @@ -0,0 +1,74 @@ + + + TraceCSVExport + + + Qt::ApplicationModal + + + + 0 + 0 + 286 + 322 + + + + CSV Export + + + true + + + + + + QAbstractItemView::SelectRows + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + TraceCSVExport + accept() + + + 298 + 300 + + + 298 + 160 + + + + + buttonBox + rejected() + TraceCSVExport + reject() + + + 298 + 300 + + + 298 + 160 + + + + + diff --git a/Software/PC_Application/Traces/tracemarkermodel.cpp b/Software/PC_Application/Traces/tracemarkermodel.cpp index bb5bf32..ccc9983 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.cpp +++ b/Software/PC_Application/Traces/tracemarkermodel.cpp @@ -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; diff --git a/Software/PC_Application/Traces/traceexportdialog.cpp b/Software/PC_Application/Traces/tracetouchstoneexport.cpp similarity index 90% rename from Software/PC_Application/Traces/traceexportdialog.cpp rename to Software/PC_Application/Traces/tracetouchstoneexport.cpp index 1735190..f114ff1 100644 --- a/Software/PC_Application/Traces/traceexportdialog.cpp +++ b/Software/PC_Application/Traces/tracetouchstoneexport.cpp @@ -1,13 +1,13 @@ -#include "traceexportdialog.h" +#include "tracetouchstoneexport.h" #include "ui_traceexportdialog.h" #include #include #include "touchstone.h" #include -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(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 diff --git a/Software/PC_Application/Traces/traceexportdialog.h b/Software/PC_Application/Traces/tracetouchstoneexport.h similarity index 65% rename from Software/PC_Application/Traces/traceexportdialog.h rename to Software/PC_Application/Traces/tracetouchstoneexport.h index a92f66c..8ef2ada 100644 --- a/Software/PC_Application/Traces/traceexportdialog.h +++ b/Software/PC_Application/Traces/tracetouchstoneexport.h @@ -1,5 +1,5 @@ -#ifndef TRACEEXPORTDIALOG_H -#define TRACEEXPORTDIALOG_H +#ifndef TRACETOUCHSTONEEXPORT_H +#define TRACETOUCHSTONEEXPORT_H #include #include @@ -7,16 +7,16 @@ #include 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> cTraces; diff --git a/Software/PC_Application/Traces/traceexportdialog.ui b/Software/PC_Application/Traces/tracetouchstoneexport.ui similarity index 97% rename from Software/PC_Application/Traces/traceexportdialog.ui rename to Software/PC_Application/Traces/tracetouchstoneexport.ui index c64a5a3..543664c 100644 --- a/Software/PC_Application/Traces/traceexportdialog.ui +++ b/Software/PC_Application/Traces/tracetouchstoneexport.ui @@ -1,7 +1,7 @@ - TraceExportDialog - + TraceTouchstoneExport + 0 @@ -11,7 +11,7 @@ - Dialog + Touchstone Export @@ -226,7 +226,7 @@ buttonBox rejected() - TraceExportDialog + TraceTouchstoneExport reject() diff --git a/Software/PC_Application/Traces/tracewidget.cpp b/Software/PC_Application/Traces/tracewidget.cpp index 0ef1212..8252fe2 100644 --- a/Software/PC_Application/Traces/tracewidget.cpp +++ b/Software/PC_Application/Traces/tracewidget.cpp @@ -4,7 +4,7 @@ #include #include "traceeditdialog.h" #include "traceimportdialog.h" -#include "traceexportdialog.h" +#include "tracetouchstoneexport.h" #include #include #include diff --git a/Software/PC_Application/VNA/tracewidgetvna.cpp b/Software/PC_Application/VNA/tracewidgetvna.cpp index 7664909..902af58 100644 --- a/Software/PC_Application/VNA/tracewidgetvna.cpp +++ b/Software/PC_Application/VNA/tracewidgetvna.cpp @@ -2,28 +2,42 @@ #include #include "Traces/traceimportdialog.h" -#include "Traces/traceexportdialog.h" +#include "Traces/tracetouchstoneexport.h" +#include "Traces/tracecsvexport.h" +#include "ui_tracewidget.h" + +#include 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); - // 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); - auto traces = model.getTraces(); - for(unsigned int i=0;i<4;i++) { - if(i >= traces.size()) { - break; + 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); + auto traces = model.getTraces(); + for(unsigned int i=0;i<4;i++) { + if(i >= traces.size()) { + 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() diff --git a/Software/PC_Application/VNA/tracewidgetvna.h b/Software/PC_Application/VNA/tracewidgetvna.h index 85ee90c..2398a29 100644 --- a/Software/PC_Application/VNA/tracewidgetvna.h +++ b/Software/PC_Application/VNA/tracewidgetvna.h @@ -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: diff --git a/Software/PC_Application/csv.cpp b/Software/PC_Application/csv.cpp new file mode 100644 index 0000000..0fa62a6 --- /dev/null +++ b/Software/PC_Application/csv.cpp @@ -0,0 +1,95 @@ +#include "csv.h" +#include +#include +#include +#include + +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 CSV::getColumn(QString header) +{ + for(auto c : _columns) { + if(c.header == header) { + return c.data; + } + } + throw runtime_error("Header name not found"); +} + +std::vector CSV::getColumn(unsigned int index) +{ + return _columns.at(index).data; +} + +void CSV::addColumn(QString name, const std::vector &data) +{ + Column c; + c.header = name; + c.data = data; + _columns.push_back(c); +} diff --git a/Software/PC_Application/csv.h b/Software/PC_Application/csv.h new file mode 100644 index 0000000..54e8b88 --- /dev/null +++ b/Software/PC_Application/csv.h @@ -0,0 +1,30 @@ +#ifndef CSV_H +#define CSV_H + +#include +#include + +class CSV +{ +public: + CSV(); + + static CSV fromFile(QString filename, char sep = ','); + + void toFile(QString filename, char sep = ','); + std::vector getColumn(QString header); + std::vector getColumn(unsigned int index); + unsigned int columns() { return _columns.size();} + + void addColumn(QString name, const std::vector &data); + +private: + class Column { + public: + QString header; + std::vector data; + }; + std::vector _columns; +}; + +#endif // CSV_H