Merge branch 'master' of https://www.github.com/jankae/VNA1
This commit is contained in:
commit
36398f626e
@ -933,10 +933,10 @@
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="TRL_Rtype"/>
|
||||
<buttongroup name="LoadType"/>
|
||||
<buttongroup name="ThroughType"/>
|
||||
<buttongroup name="TRL_Rtype"/>
|
||||
<buttongroup name="ShortType"/>
|
||||
<buttongroup name="OpenType"/>
|
||||
<buttongroup name="ThroughType"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -79,6 +79,7 @@ HEADERS += \
|
||||
Traces/Math/parser/suStringTokens.h \
|
||||
Traces/Math/parser/utGeneric.h \
|
||||
Traces/Math/tdr.h \
|
||||
Traces/Math/timegate.h \
|
||||
Traces/Math/tracemath.h \
|
||||
Traces/Math/windowfunction.h \
|
||||
Traces/fftcomplex.h \
|
||||
@ -190,6 +191,7 @@ SOURCES += \
|
||||
Traces/Math/parser/mpValueCache.cpp \
|
||||
Traces/Math/parser/mpVariable.cpp \
|
||||
Traces/Math/tdr.cpp \
|
||||
Traces/Math/timegate.cpp \
|
||||
Traces/Math/tracemath.cpp \
|
||||
Traces/Math/windowfunction.cpp \
|
||||
Traces/fftcomplex.cpp \
|
||||
@ -260,6 +262,9 @@ FORMS += \
|
||||
Traces/Math/newtracemathdialog.ui \
|
||||
Traces/Math/tdrdialog.ui \
|
||||
Traces/Math/tdrexplanationwidget.ui \
|
||||
Traces/Math/timedomaingatingexplanationwidget.ui \
|
||||
Traces/Math/timegatedialog.ui \
|
||||
Traces/Math/timegateexplanationwidget.ui \
|
||||
Traces/markerwidget.ui \
|
||||
Traces/smithchartdialog.ui \
|
||||
Traces/tracecsvexport.ui \
|
||||
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TimeDomainGatingExplanationWidget</class>
|
||||
<widget class="QWidget" name="TimeDomainGatingExplanationWidget">
|
||||
<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;">Time Domain Gating</span></p><p>This is not a dedicated math operation but rather a convenience option to automatically setup a TDR with a time gate and followed by a DFT. The combination of these three operations performs time domain gating<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>
|
404
Software/PC_Application/Traces/Math/timegate.cpp
Normal file
404
Software/PC_Application/Traces/Math/timegate.cpp
Normal file
@ -0,0 +1,404 @@
|
||||
#include "timegate.h"
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
#include "ui_timegatedialog.h"
|
||||
#include "ui_timegateexplanationwidget.h"
|
||||
#include "preferences.h"
|
||||
#include <QPainter>
|
||||
#include "Util/util.h"
|
||||
#include "Traces/fftcomplex.h"
|
||||
#include "unit.h"
|
||||
#include <QMouseEvent>
|
||||
|
||||
Math::TimeGate::TimeGate()
|
||||
{
|
||||
bandpass = true;
|
||||
// if(input->rData().size()) {
|
||||
// center = input->rData().back().x/2;
|
||||
// span = center / 2;
|
||||
// } else {
|
||||
center = 10e-9;
|
||||
span = 2e-9;
|
||||
// }
|
||||
connect(&window, &WindowFunction::changed, this, &TimeGate::updateFilter);
|
||||
}
|
||||
|
||||
TraceMath::DataType Math::TimeGate::outputType(TraceMath::DataType inputType)
|
||||
{
|
||||
if(inputType == DataType::Time) {
|
||||
return DataType::Time;
|
||||
} else {
|
||||
return DataType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QString Math::TimeGate::description()
|
||||
{
|
||||
QString d = "Time gate (";
|
||||
if(bandpass) {
|
||||
d.append("bandpass");
|
||||
} else {
|
||||
d.append("notch");
|
||||
}
|
||||
d.append(") center: "+Unit::ToString(center, "s", "pnum ", 3)+", span: "+Unit::ToString(span, "s", "pnum ", 3));
|
||||
d += ", window: " + window.getDescription();
|
||||
return d;
|
||||
}
|
||||
|
||||
void Math::TimeGate::edit()
|
||||
{
|
||||
if(dataType == DataType::Invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto d = new QDialog();
|
||||
auto ui = new Ui::TimeGateDialog();
|
||||
ui->setupUi(d);
|
||||
ui->graph->setGate(this);
|
||||
ui->windowBox->setLayout(new QVBoxLayout);
|
||||
ui->windowBox->layout()->addWidget(window.createEditor());
|
||||
|
||||
ui->start->setUnit("s");
|
||||
ui->start->setPrefixes("pnum ");
|
||||
ui->start->setValue(center - span / 2);
|
||||
|
||||
ui->stop->setUnit("s");
|
||||
ui->stop->setPrefixes("pnum ");
|
||||
ui->stop->setValue(center + span / 2);
|
||||
|
||||
ui->center->setUnit("s");
|
||||
ui->center->setPrefixes("pnum ");
|
||||
ui->center->setValue(center);
|
||||
|
||||
ui->span->setUnit("s");
|
||||
ui->span->setPrefixes("pnum ");
|
||||
ui->span->setValue(center);
|
||||
|
||||
if(bandpass) {
|
||||
ui->type->setCurrentIndex(0);
|
||||
} else {
|
||||
ui->type->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
ui->graph->setFocus();
|
||||
|
||||
connect(ui->span, &SIUnitEdit::valueChanged, this, &TimeGate::setSpan);
|
||||
connect(this, &TimeGate::spanChanged, ui->span, &SIUnitEdit::setValueQuiet);
|
||||
connect(ui->center, &SIUnitEdit::valueChanged, this, &TimeGate::setCenter);
|
||||
connect(this, &TimeGate::centerChanged, ui->center, &SIUnitEdit::setValueQuiet);
|
||||
connect(ui->start, &SIUnitEdit::valueChanged, this, &TimeGate::setStart);
|
||||
connect(this, &TimeGate::startChanged, ui->start, &SIUnitEdit::setValueQuiet);
|
||||
connect(ui->stop, &SIUnitEdit::valueChanged, this, &TimeGate::setStop);
|
||||
connect(this, &TimeGate::stopChanged, ui->stop, &SIUnitEdit::setValueQuiet);
|
||||
|
||||
connect(ui->type, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index) {
|
||||
bandpass = index == 0;
|
||||
updateFilter();
|
||||
});
|
||||
|
||||
connect(this, &TimeGate::outputSamplesChanged, ui->graph, qOverload<>(&QWidget::update));
|
||||
connect(this, &TimeGate::filterUpdated, ui->graph, qOverload<>(&QWidget::update));
|
||||
|
||||
updateFilter();
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
QWidget *Math::TimeGate::createExplanationWidget()
|
||||
{
|
||||
auto w = new QWidget();
|
||||
auto ui = new Ui::TimeGateExplanationWidget;
|
||||
ui->setupUi(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
nlohmann::json Math::TimeGate::toJSON()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["center"] = center;
|
||||
j["span"] = span;
|
||||
j["bandpass"] = bandpass;
|
||||
j["window"] = window.toJSON();
|
||||
return j;
|
||||
}
|
||||
|
||||
void Math::TimeGate::fromJSON(nlohmann::json j)
|
||||
{
|
||||
if(j.contains("window")) {
|
||||
window.fromJSON(j["window"]);
|
||||
}
|
||||
bandpass = j.value("bandpass", true);
|
||||
center = j.value("center", center);
|
||||
span = j.value("span", span);
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
void Math::TimeGate::setStart(double start)
|
||||
{
|
||||
double stop = center + span / 2;
|
||||
if(start < stop) {
|
||||
span = stop - start;
|
||||
center = (stop + start) / 2;
|
||||
updateFilter();
|
||||
}
|
||||
emit centerChanged(center);
|
||||
emit startChanged(center - span / 2);
|
||||
emit spanChanged(span);
|
||||
}
|
||||
|
||||
void Math::TimeGate::setStop(double stop)
|
||||
{
|
||||
double start = center - span / 2;
|
||||
if(stop > start) {
|
||||
span = stop - start;
|
||||
center = (start + stop) / 2;
|
||||
updateFilter();
|
||||
}
|
||||
emit centerChanged(center);
|
||||
emit stopChanged(center + span / 2);
|
||||
emit spanChanged(span);
|
||||
}
|
||||
|
||||
void Math::TimeGate::setCenter(double center)
|
||||
{
|
||||
this->center = center;
|
||||
updateFilter();
|
||||
emit centerChanged(center);
|
||||
emit startChanged(center - span / 2);
|
||||
emit stopChanged(center + span / 2);
|
||||
}
|
||||
|
||||
void Math::TimeGate::setSpan(double span)
|
||||
{
|
||||
this->span = span;
|
||||
updateFilter();
|
||||
emit spanChanged(span);
|
||||
emit startChanged(center - span / 2);
|
||||
emit stopChanged(center + span / 2);
|
||||
}
|
||||
|
||||
double Math::TimeGate::getStart()
|
||||
{
|
||||
return center - span / 2;
|
||||
}
|
||||
|
||||
double Math::TimeGate::getStop()
|
||||
{
|
||||
return center + span / 2;
|
||||
}
|
||||
|
||||
void Math::TimeGate::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
{
|
||||
if(data.size() != input->rData().size()) {
|
||||
data.resize(input->rData().size());
|
||||
updateFilter();
|
||||
}
|
||||
for(auto i = begin;i<end;i++) {
|
||||
data[i] = input->rData()[i];
|
||||
data[i].y *= filter[i];
|
||||
}
|
||||
emit outputSamplesChanged(begin, end);
|
||||
success();
|
||||
}
|
||||
|
||||
void Math::TimeGate::updateFilter()
|
||||
{
|
||||
if(!input) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::complex<double>> buf;
|
||||
filter.clear();
|
||||
buf.resize(input->rData().size() * 2);
|
||||
if(!buf.size()) {
|
||||
return;
|
||||
}
|
||||
auto maxX = input->rData().back().x;
|
||||
auto minX = input->rData().front().x;
|
||||
if(center - span / 2 < minX) {
|
||||
span = (center - minX) * 2;
|
||||
}
|
||||
if(center + span / 2 > maxX) {
|
||||
span = (maxX - center) * 2;
|
||||
}
|
||||
auto wc1 = Util::Scale<double>(center - span / 2, minX, maxX, 0, 1);
|
||||
auto wc2 = Util::Scale<double>(center + span / 2, minX, maxX, 0, 1);
|
||||
|
||||
// create ideal filter coefficients
|
||||
for(unsigned int i=0;i<buf.size();i++) {
|
||||
int n = i - buf.size() / 2;
|
||||
if(n == 0) {
|
||||
buf[i] = wc2 - wc1;
|
||||
} else {
|
||||
buf[i] = (sin(M_PI * wc2 * n) - sin(M_PI * wc1 * n)) / (n * M_PI);
|
||||
}
|
||||
if(!bandpass) {
|
||||
if(n == 0) {
|
||||
buf[i] = 1.0 - buf[i];
|
||||
} else {
|
||||
buf[i] = sin(M_PI * n) / M_PI - buf[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.apply(buf);
|
||||
Fft::shift(buf, true);
|
||||
Fft::transform(buf, false);
|
||||
|
||||
filter.resize(buf.size());
|
||||
for(unsigned int i=0;i<buf.size() / 2;i++) {
|
||||
filter[i] = abs(buf[i]);
|
||||
}
|
||||
emit filterUpdated();
|
||||
|
||||
// needs to update output samples, pretend that input samples have changed
|
||||
inputSamplesChanged(0, input->rData().size());
|
||||
}
|
||||
|
||||
Math::TimeGateGraph::TimeGateGraph(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
gate = nullptr;
|
||||
grabbedStop = false;
|
||||
grabbedStart = false;
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
QPoint Math::TimeGateGraph::plotValueToPixel(double x, double y)
|
||||
{
|
||||
auto input = gate->getInput()->rData();
|
||||
auto minX = input.front().x;
|
||||
auto maxX = input.back().x;
|
||||
|
||||
int plotLeft = 0;
|
||||
int plotRight = size().width();
|
||||
int plotTop = 0;
|
||||
int plotBottom = size().height();
|
||||
|
||||
QPoint p;
|
||||
p.setX(Util::Scale<double>(x, minX, maxX, plotLeft, plotRight));
|
||||
p.setY(Util::Scale<double>(y, -120, 20, plotBottom, plotTop));
|
||||
return p;
|
||||
}
|
||||
|
||||
QPointF Math::TimeGateGraph::pixelToPlotValue(QPoint p)
|
||||
{
|
||||
auto input = gate->getInput()->rData();
|
||||
auto minX = input.front().x;
|
||||
auto maxX = input.back().x;
|
||||
|
||||
int plotLeft = 0;
|
||||
int plotRight = size().width();
|
||||
int plotTop = 0;
|
||||
int plotBottom = size().height();
|
||||
|
||||
QPointF ret;
|
||||
ret.setX(Util::Scale<double>(p.x(), plotLeft, plotRight, minX, maxX));
|
||||
ret.setY(Util::Scale<double>(p.y(), plotBottom, plotTop, -120, 20));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
if(!gate) {
|
||||
return;
|
||||
}
|
||||
// grab input data
|
||||
auto input = gate->getInput()->rData();
|
||||
|
||||
Q_UNUSED(event)
|
||||
auto pref = Preferences::getInstance();
|
||||
QPainter p(this);
|
||||
// fill background
|
||||
p.setBackground(QBrush(pref.General.graphColors.background));
|
||||
p.fillRect(0, 0, width(), height(), QBrush(pref.General.graphColors.background));
|
||||
|
||||
// plot trace
|
||||
auto pen = QPen(Qt::green, 1);
|
||||
pen.setCosmetic(true);
|
||||
pen.setStyle(Qt::SolidLine);
|
||||
p.setPen(pen);
|
||||
for(unsigned int i=1;i<input.size();i++) {
|
||||
auto last = input[i-1];
|
||||
auto now = input[i];
|
||||
|
||||
auto y_last = 20*log10(abs(last.y));
|
||||
auto y_now = 20*log10(abs(now.y));
|
||||
|
||||
if(std::isnan(y_last) || std::isnan(y_now) || std::isinf(y_last) || std::isinf(y_now)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// scale to plot coordinates
|
||||
auto p1 = plotValueToPixel(last.x, y_last);
|
||||
auto p2 = plotValueToPixel(now.x, y_now);
|
||||
// draw line
|
||||
p.drawLine(p1, p2);
|
||||
}
|
||||
// plot filter shape
|
||||
auto filter = gate->rFilter();
|
||||
pen = QPen(Qt::red, 1);
|
||||
p.setPen(pen);
|
||||
for(unsigned int i=1;i<filter.size();i++) {
|
||||
auto x_last = input[i-1].x;
|
||||
auto x_now = input[i].x;
|
||||
|
||||
auto f_last = 20*log10(filter[i-1]);
|
||||
auto f_now = 20*log10(filter[i]);
|
||||
|
||||
if(std::isnan(f_last) || std::isnan(f_now) || std::isinf(f_last) || std::isinf(f_now)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// scale to plot coordinates
|
||||
auto p1 = plotValueToPixel(x_last, f_last);
|
||||
auto p2 = plotValueToPixel(x_now, f_now);
|
||||
|
||||
// draw line
|
||||
p.drawLine(p1, p2);
|
||||
}
|
||||
}
|
||||
|
||||
void Math::TimeGateGraph::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if(!gate) {
|
||||
return;
|
||||
}
|
||||
grabbedStop = false;
|
||||
grabbedStart = false;
|
||||
auto startX = plotValueToPixel(gate->getStart(), 0).x();
|
||||
auto stopX = plotValueToPixel(gate->getStop(), 0).x();
|
||||
if(abs(event->pos().x() - startX) < catchDistance) {
|
||||
grabbedStart = true;
|
||||
} else if(abs(event->pos().x() - stopX) < catchDistance) {
|
||||
grabbedStop = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Math::TimeGateGraph::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
grabbedStop = false;
|
||||
grabbedStart = false;
|
||||
}
|
||||
|
||||
void Math::TimeGateGraph::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if(!gate) {
|
||||
return;
|
||||
}
|
||||
auto value = pixelToPlotValue(event->pos());
|
||||
if(grabbedStop) {
|
||||
gate->setStop(value.x());
|
||||
} else if(grabbedStart) {
|
||||
gate->setStart(value.x());
|
||||
} else {
|
||||
// nothing grabbed but might be above start/stop -> check and change cursor
|
||||
auto startX = plotValueToPixel(gate->getStart(), 0).x();
|
||||
auto stopX = plotValueToPixel(gate->getStop(), 0).x();
|
||||
if(abs(event->pos().x() - startX) < catchDistance || abs(event->pos().x() - stopX) < catchDistance) {
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
} else {
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
}
|
||||
}
|
86
Software/PC_Application/Traces/Math/timegate.h
Normal file
86
Software/PC_Application/Traces/Math/timegate.h
Normal file
@ -0,0 +1,86 @@
|
||||
#ifndef TIMEGATE_H
|
||||
#define TIMEGATE_H
|
||||
|
||||
#include "tracemath.h"
|
||||
#include "windowfunction.h"
|
||||
|
||||
namespace Math {
|
||||
|
||||
class TimeGate;
|
||||
|
||||
class TimeGateGraph : public QWidget {
|
||||
public:
|
||||
TimeGateGraph(QWidget *parent);
|
||||
|
||||
void setGate(TimeGate *gate) {
|
||||
this->gate = gate;
|
||||
}
|
||||
private:
|
||||
static constexpr int catchDistance = 5;
|
||||
|
||||
QPoint plotValueToPixel(double x, double y);
|
||||
QPointF pixelToPlotValue(QPoint p);
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
TimeGate *gate;
|
||||
bool grabbedStart;
|
||||
bool grabbedStop;
|
||||
};
|
||||
|
||||
class TimeGate : public TraceMath
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimeGate();
|
||||
|
||||
virtual DataType outputType(DataType inputType) override;
|
||||
virtual QString description() override;
|
||||
|
||||
virtual void edit() override;
|
||||
|
||||
static QWidget *createExplanationWidget();
|
||||
|
||||
virtual nlohmann::json toJSON() override;
|
||||
virtual void fromJSON(nlohmann::json j) override;
|
||||
Type getType() override {return Type::TimeGate;};
|
||||
|
||||
const std::vector<double> &rFilter() { return filter;};
|
||||
|
||||
double getStart();
|
||||
double getStop();
|
||||
|
||||
public slots:
|
||||
// a single value of the input data has changed, index determines which sample has changed
|
||||
virtual void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
||||
void setStart(double start);
|
||||
void setStop(double stop);
|
||||
void setCenter(double center);
|
||||
void setSpan(double span);
|
||||
|
||||
private slots:
|
||||
void updateFilter();
|
||||
signals:
|
||||
void filterUpdated();
|
||||
void startChanged(double newval);
|
||||
void stopChanged(double newval);
|
||||
void centerChanged(double newval);
|
||||
void spanChanged(double newval);
|
||||
private:
|
||||
enum class Filter {
|
||||
None,
|
||||
Hamming,
|
||||
Hann
|
||||
};
|
||||
|
||||
bool bandpass;
|
||||
double center, span;
|
||||
WindowFunction window;
|
||||
std::vector<double> filter;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMEGATE_H
|
163
Software/PC_Application/Traces/Math/timegatedialog.ui
Normal file
163
Software/PC_Application/Traces/Math/timegatedialog.ui
Normal file
@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TimeGateDialog</class>
|
||||
<widget class="QDialog" name="TimeGateDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>973</width>
|
||||
<height>317</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Time Gate</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Start:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SIUnitEdit" name="start"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Stop:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SIUnitEdit" name="stop"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Center:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="SIUnitEdit" name="center"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Span:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="SIUnitEdit" name="span"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bandpass</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Notch</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="windowBox">
|
||||
<property name="title">
|
||||
<string>Shapewindow:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Math::TimeGateGraph" name="graph" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<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>
|
||||
<customwidget>
|
||||
<class>Math::TimeGateGraph</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Traces/Math/timegate.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>TimeGateDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TimeGateDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TimeGateExplanationWidget</class>
|
||||
<widget class="QWidget" name="TimeGateExplanationWidget">
|
||||
<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;">Time Gate</span></p><p>A bandpass or notch filter in the time domain. Depending on the setup, this operation can isolate or remove a response at a certain distance in time.<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>
|
@ -4,7 +4,9 @@
|
||||
#include "tdr.h"
|
||||
#include "dft.h"
|
||||
#include "expression.h"
|
||||
#include "timegate.h"
|
||||
#include "Traces/trace.h"
|
||||
#include "ui_timedomaingatingexplanationwidget.h"
|
||||
|
||||
TraceMath::TraceMath()
|
||||
{
|
||||
@ -13,20 +15,34 @@ TraceMath::TraceMath()
|
||||
error("Invalid input");
|
||||
}
|
||||
|
||||
TraceMath *TraceMath::createMath(TraceMath::Type type)
|
||||
std::vector<TraceMath *> TraceMath::createMath(TraceMath::Type type)
|
||||
{
|
||||
std::vector<TraceMath*> ret;
|
||||
switch(type) {
|
||||
case Type::MedianFilter:
|
||||
return new Math::MedianFilter();
|
||||
ret.push_back(new Math::MedianFilter());
|
||||
break;
|
||||
case Type::TDR:
|
||||
return new Math::TDR();
|
||||
ret.push_back(new Math::TDR());
|
||||
break;
|
||||
case Type::DFT:
|
||||
return new Math::DFT();
|
||||
ret.push_back(new Math::DFT());
|
||||
break;
|
||||
case Type::Expression:
|
||||
return new Math::Expression();
|
||||
ret.push_back(new Math::Expression());
|
||||
break;
|
||||
case Type::TimeGate:
|
||||
ret.push_back(new Math::TimeGate());
|
||||
break;
|
||||
case Type::TimeDomainGating:
|
||||
ret.push_back(new Math::TDR());
|
||||
ret.push_back(new Math::TimeGate());
|
||||
ret.push_back(new Math::DFT());
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
|
||||
@ -49,6 +65,17 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
|
||||
ret.name = "Custom Expression";
|
||||
ret.explanationWidget = Math::Expression::createExplanationWidget();
|
||||
break;
|
||||
case Type::TimeGate:
|
||||
ret.name = "Time Gate";
|
||||
ret.explanationWidget = Math::TimeGate::createExplanationWidget();
|
||||
break;
|
||||
case Type::TimeDomainGating: {
|
||||
ret.name = "Time Domain Gating";
|
||||
ret.explanationWidget = new QWidget();
|
||||
auto ui = new Ui::TimeDomainGatingExplanationWidget;
|
||||
ui->setupUi(ret.explanationWidget);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -74,11 +74,13 @@ public:
|
||||
TDR,
|
||||
DFT,
|
||||
Expression,
|
||||
TimeGate,
|
||||
TimeDomainGating,
|
||||
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
|
||||
Last,
|
||||
};
|
||||
|
||||
static TraceMath *createMath(Type type);
|
||||
static std::vector<TraceMath*> createMath(Type type);
|
||||
class TypeInfo {
|
||||
public:
|
||||
QString name;
|
||||
|
@ -396,7 +396,7 @@ void Trace::fromJSON(nlohmann::json j)
|
||||
continue;
|
||||
}
|
||||
qDebug() << "Creating math operation of type:" << operation;
|
||||
auto op = TraceMath::createMath(type);
|
||||
auto op = TraceMath::createMath(type)[0];
|
||||
if(jm.contains("settings")) {
|
||||
op->fromJSON(jm["settings"]);
|
||||
}
|
||||
@ -731,6 +731,18 @@ void Trace::addMathOperation(TraceMath *math)
|
||||
updateLastMath(mathOps.rbegin());
|
||||
}
|
||||
|
||||
void Trace::addMathOperations(std::vector<TraceMath *> maths)
|
||||
{
|
||||
TraceMath *input = lastMath;
|
||||
for(auto m : maths) {
|
||||
MathInfo info = {.math = m, .enabled = true};
|
||||
m->assignInput(input);
|
||||
input = m;
|
||||
mathOps.push_back(info);
|
||||
}
|
||||
updateLastMath(mathOps.rbegin());
|
||||
}
|
||||
|
||||
void Trace::removeMathOperation(unsigned int index)
|
||||
{
|
||||
if(index < 1 || index >= mathOps.size()) {
|
||||
|
@ -99,7 +99,8 @@ public:
|
||||
bool hasMathOperations(); // check if math operations are set up (not necessarily enabled)
|
||||
void enableMath(bool enable);
|
||||
// Adds a new math operation at the end of the list and enables it
|
||||
void addMathOperation(TraceMath *mathOps);
|
||||
void addMathOperation(TraceMath *math);
|
||||
void addMathOperations(std::vector<TraceMath*> maths);
|
||||
// removes the math operation at the given index. Index 0 is invalid as this would be the trace itself
|
||||
void removeMathOperation(unsigned int index);
|
||||
// swaps the order of math operations at index and index+1. Does nothing if either index is invalid
|
||||
|
@ -141,10 +141,23 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
|
||||
|
||||
connect(ui->list, &QListWidget::doubleClicked, ui->buttonBox, &QDialogButtonBox::accepted);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
|
||||
auto newMath = TraceMath::createMath(static_cast<TraceMath::Type>(ui->list->currentRow()));
|
||||
if(newMath) {
|
||||
model->addOperation(newMath);
|
||||
}
|
||||
auto type = static_cast<TraceMath::Type>(ui->list->currentRow());
|
||||
auto newMath = TraceMath::createMath(type);
|
||||
model->addOperations(newMath);
|
||||
if(newMath.size() == 1) {
|
||||
// any normal math operation added, edit now
|
||||
newMath[0]->edit();
|
||||
} else {
|
||||
// composite operation added, check which one and edit the correct suboperation
|
||||
switch(type) {
|
||||
case TraceMath::Type::TimeDomainGating:
|
||||
// TDR/DFT can be left at default, edit the actual gate
|
||||
newMath[1]->edit();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
ui->list->setCurrentRow(0);
|
||||
ui->stack->setCurrentIndex(0);
|
||||
@ -305,8 +318,13 @@ void MathModel::addOperation(TraceMath *math)
|
||||
beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size());
|
||||
t.addMathOperation(math);
|
||||
endInsertRows();
|
||||
// open the editor for the newly added operation
|
||||
math->edit();
|
||||
}
|
||||
|
||||
void MathModel::addOperations(std::vector<TraceMath *> maths)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size() + maths.size() - 1);
|
||||
t.addMathOperations(maths);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void MathModel::deleteRow(unsigned int row)
|
||||
|
@ -29,6 +29,7 @@ public:
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
void addOperation(TraceMath *math);
|
||||
void addOperations(std::vector<TraceMath*> maths);
|
||||
void deleteRow(unsigned int row);
|
||||
|
||||
private:
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <QDebug>
|
||||
#include "tracemarkermodel.h"
|
||||
#include "unit.h"
|
||||
#include <QMenu>
|
||||
#include <QActionGroup>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -19,13 +21,16 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *paren
|
||||
data(0),
|
||||
type(Type::Manual),
|
||||
description(descr),
|
||||
contextmenu(nullptr),
|
||||
delta(nullptr),
|
||||
parent(parent),
|
||||
cutoffAmplitude(-3.0),
|
||||
peakThreshold(-40.0),
|
||||
offset(10000)
|
||||
{
|
||||
|
||||
connect(this, &TraceMarker::traceChanged, this, &TraceMarker::updateContextmenu);
|
||||
connect(this, &TraceMarker::typeChanged, this, &TraceMarker::updateContextmenu);
|
||||
updateContextmenu();
|
||||
}
|
||||
|
||||
TraceMarker::~TraceMarker()
|
||||
@ -363,6 +368,29 @@ void TraceMarker::deltaDeleted()
|
||||
update();
|
||||
}
|
||||
|
||||
void TraceMarker::updateContextmenu()
|
||||
{
|
||||
if(contextmenu) {
|
||||
delete contextmenu;
|
||||
}
|
||||
contextmenu = new QMenu();
|
||||
auto typemenu = new QMenu("Type");
|
||||
auto typegroup = new QActionGroup(contextmenu);
|
||||
for(auto t : getSupportedTypes()) {
|
||||
auto setTypeAction = new QAction(typeToString(t));
|
||||
setTypeAction->setCheckable(true);
|
||||
if(t == type) {
|
||||
setTypeAction->setChecked(true);
|
||||
}
|
||||
connect(setTypeAction, &QAction::triggered, [=](){
|
||||
setType(t);
|
||||
});
|
||||
typegroup->addAction(setTypeAction);
|
||||
typemenu->addAction(setTypeAction);
|
||||
}
|
||||
contextmenu->addMenu(typemenu);
|
||||
}
|
||||
|
||||
std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
|
||||
{
|
||||
set<TraceMarker::Type> supported;
|
||||
|
@ -57,6 +57,8 @@ public:
|
||||
SIUnitEdit* getSettingsEditor();
|
||||
void adjustSettings(double value);
|
||||
|
||||
QMenu *getContextMenu() { return contextmenu;}
|
||||
|
||||
// Updates marker position and data on automatic markers. Should be called whenever the tracedata is complete
|
||||
void update();
|
||||
TraceMarker *getParent() const;
|
||||
@ -93,6 +95,7 @@ private slots:
|
||||
void updateSymbol();
|
||||
void checkDeltaMarker();
|
||||
void deltaDeleted();
|
||||
void updateContextmenu();
|
||||
signals:
|
||||
void rawDataChanged();
|
||||
void domainChanged();
|
||||
@ -133,6 +136,8 @@ private:
|
||||
QString suffix;
|
||||
QString description;
|
||||
|
||||
QMenu *contextmenu;
|
||||
|
||||
TraceMarker *delta;
|
||||
std::vector<TraceMarker*> helperMarkers;
|
||||
TraceMarker *parent;
|
||||
|
@ -92,7 +92,16 @@ void TracePlot::initializeTraceInfo()
|
||||
|
||||
void TracePlot::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
contextmenu->exec(event->globalPos());
|
||||
auto m = markerAtPosition(event->pos());
|
||||
QMenu *menu;
|
||||
if(m) {
|
||||
// right click on marker, execute its contextmenu
|
||||
menu = m->getContextMenu();
|
||||
} else {
|
||||
// no marker, contextmenu of graph
|
||||
menu = contextmenu;
|
||||
}
|
||||
menu->exec(event->globalPos());
|
||||
if(markedForDeletion) {
|
||||
emit deleted(this);
|
||||
delete this;
|
||||
@ -138,38 +147,7 @@ void TracePlot::paintEvent(QPaintEvent *event)
|
||||
|
||||
void TracePlot::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
auto clickPoint = event->pos() - QPoint(marginLeft, marginTop);
|
||||
// check if click was near a marker
|
||||
unsigned int closestDistance = numeric_limits<unsigned int>::max();
|
||||
TraceMarker *closestMarker = nullptr;
|
||||
for(auto t : traces) {
|
||||
if(!t.second) {
|
||||
// this trace is disabled, skip
|
||||
continue;
|
||||
}
|
||||
auto markers = t.first->getMarkers();
|
||||
for(auto m : markers) {
|
||||
if(!m->isMovable()) {
|
||||
continue;
|
||||
}
|
||||
auto markerPoint = markerToPixel(m);
|
||||
if(markerPoint.isNull()) {
|
||||
// invalid, skip
|
||||
continue;
|
||||
}
|
||||
auto diff = markerPoint - clickPoint;
|
||||
unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y();
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestMarker = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(closestDistance <= 400) {
|
||||
selectedMarker = closestMarker;
|
||||
} else {
|
||||
selectedMarker = nullptr;
|
||||
}
|
||||
selectedMarker = markerAtPosition(event->pos(), true);
|
||||
}
|
||||
|
||||
void TracePlot::mouseReleaseEvent(QMouseEvent *event)
|
||||
@ -205,6 +183,46 @@ void TracePlot::leaveEvent(QEvent *event)
|
||||
selectedMarker = nullptr;
|
||||
}
|
||||
|
||||
TraceMarker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable)
|
||||
{
|
||||
auto clickPoint = p - QPoint(marginLeft, marginTop);
|
||||
// check if click was near a marker
|
||||
unsigned int closestDistance = numeric_limits<unsigned int>::max();
|
||||
TraceMarker *closestMarker = nullptr;
|
||||
for(auto t : traces) {
|
||||
if(!t.second) {
|
||||
// this trace is disabled, skip
|
||||
continue;
|
||||
}
|
||||
auto markers = t.first->getMarkers();
|
||||
for(auto m : markers) {
|
||||
if(!m->isMovable() && onlyMovable) {
|
||||
continue;
|
||||
}
|
||||
auto markerPoint = markerToPixel(m);
|
||||
if(markerPoint.isNull()) {
|
||||
// invalid, skip
|
||||
continue;
|
||||
}
|
||||
auto diff = markerPoint - clickPoint;
|
||||
unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y();
|
||||
if(distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
if(m->getParent()) {
|
||||
closestMarker = m->getParent();
|
||||
} else {
|
||||
closestMarker = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(closestDistance <= 400) {
|
||||
return closestMarker;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasFormat("trace/pointer")) {
|
||||
|
@ -55,6 +55,8 @@ protected:
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
TraceMarker *markerAtPosition(QPoint p, bool onlyMovable = false);
|
||||
|
||||
// handle trace drops
|
||||
virtual bool dropSupported(Trace *t) = 0;
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
|
@ -859,7 +859,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo
|
||||
ret.setY(real(data.y));
|
||||
break;
|
||||
case YAxisType::ImpulseMag:
|
||||
ret.setY(abs(data.y));
|
||||
ret.setY(20*log10(abs(data.y)));
|
||||
break;
|
||||
case YAxisType::Step:
|
||||
ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real());
|
||||
@ -945,7 +945,7 @@ void TraceXYPlot::traceDropped(Trace *t, QPoint position)
|
||||
InformationBox::ShowMessage("X Axis Domain Change", "You dropped a time domain trace but the graph is still set up for the frequency domain."
|
||||
" All current traces will be removed and the graph changed to time domain.");
|
||||
setXAxis(XAxisType::Time, XAxisMode::FitTraces, 0, 1, 0.1);
|
||||
setYAxis(0, YAxisType::ImpulseReal, false, true, 0, 1, 1.0);
|
||||
setYAxis(0, YAxisType::ImpulseMag, false, true, 0, 1, 1.0);
|
||||
setYAxis(1, YAxisType::Disabled, false, true, 0, 1, 1.0);
|
||||
}
|
||||
|
||||
@ -1002,7 +1002,7 @@ QString TraceXYPlot::AxisUnit(TraceXYPlot::YAxisType type)
|
||||
case TraceXYPlot::YAxisType::Phase: return "°"; break;
|
||||
case TraceXYPlot::YAxisType::VSWR: return ""; break;
|
||||
case TraceXYPlot::YAxisType::ImpulseReal: return ""; break;
|
||||
case TraceXYPlot::YAxisType::ImpulseMag: return ""; break;
|
||||
case TraceXYPlot::YAxisType::ImpulseMag: return "db"; break;
|
||||
case TraceXYPlot::YAxisType::Step: return ""; break;
|
||||
case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break;
|
||||
default: return ""; break;
|
||||
|
Loading…
Reference in New Issue
Block a user