some experiments with TDR math operations
This commit is contained in:
parent
8e661aecd6
commit
a7ff3d60fb
@ -26,6 +26,8 @@ HEADERS += \
|
||||
Tools/eseries.h \
|
||||
Tools/impedancematchdialog.h \
|
||||
Traces/Math/medianfilter.h \
|
||||
Traces/Math/tdrbandpass.h \
|
||||
Traces/Math/tdrlowpass.h \
|
||||
Traces/Math/tracemath.h \
|
||||
Traces/Math/tracematheditdialog.h \
|
||||
Traces/fftcomplex.h \
|
||||
@ -81,6 +83,8 @@ SOURCES += \
|
||||
Tools/eseries.cpp \
|
||||
Tools/impedancematchdialog.cpp \
|
||||
Traces/Math/medianfilter.cpp \
|
||||
Traces/Math/tdrbandpass.cpp \
|
||||
Traces/Math/tdrlowpass.cpp \
|
||||
Traces/Math/tracemath.cpp \
|
||||
Traces/Math/tracematheditdialog.cpp \
|
||||
Traces/fftcomplex.cpp \
|
||||
@ -127,7 +131,9 @@ FORMS += \
|
||||
Device/manualcontroldialog.ui \
|
||||
Generator/signalgenwidget.ui \
|
||||
Tools/impedancematchdialog.ui \
|
||||
Traces/Math/medianexplanationwidget.ui \
|
||||
Traces/Math/medianfilterdialog.ui \
|
||||
Traces/Math/newtracemathdialog.ui \
|
||||
Traces/Math/tracematheditdialog.ui \
|
||||
Traces/markerwidget.ui \
|
||||
Traces/smithchartdialog.ui \
|
||||
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MedianFilterExplanationWidget</class>
|
||||
<widget class="QWidget" name="MedianFilterExplanationWidget">
|
||||
<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;">Median Filter</span></p><p>The median filter looks at a number of input samples, sorts them and uses the one in the middle as the output sample. This filters the data and especially eliminates outliers.</p><p>Parameters: </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;">Kernel Size: the amount of input samples used for each output sample. Higher values result in more aggressive filtering</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Sorting method: Since the input data is complex, there are several options for sorting:</li></ul><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;"><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Absolute value</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Phase</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Real</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Imaginary</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>
|
@ -1,15 +1,12 @@
|
||||
#include "medianfilter.h"
|
||||
#include "ui_medianfilterdialog.h"
|
||||
#include "ui_medianexplanationwidget.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
using namespace Math;
|
||||
using namespace std;
|
||||
|
||||
namespace Ui {
|
||||
class MedianFilterDialog;
|
||||
}
|
||||
|
||||
MedianFilter::MedianFilter()
|
||||
{
|
||||
kernelSize = 3;
|
||||
@ -50,18 +47,26 @@ void MedianFilter::edit()
|
||||
d->show();
|
||||
}
|
||||
|
||||
QWidget *MedianFilter::createExplanationWidget()
|
||||
{
|
||||
auto w = new QWidget();
|
||||
auto ui = new Ui::MedianFilterExplanationWidget;
|
||||
ui->setupUi(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
|
||||
if(data.size() != input->rData().size()) {
|
||||
data.resize(input->rData().size());
|
||||
}
|
||||
if(data.size() > 0) {
|
||||
auto kernelOffset = (kernelSize-1)/2;
|
||||
int start = (int) begin - kernelOffset;
|
||||
unsigned int stop = (int) end + kernelOffset;
|
||||
int start = (int) begin - (int) kernelOffset;
|
||||
unsigned int stop = end + kernelOffset;
|
||||
if(start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if(stop >= input->rData().size()) {
|
||||
if(stop > input->rData().size()) {
|
||||
stop = input->rData().size();
|
||||
}
|
||||
|
||||
@ -71,6 +76,7 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
|
||||
case Order::Phase: return arg(a) < arg(b);
|
||||
case Order::Real: return real(a) < real(b);
|
||||
case Order::Imag: return imag(a) < imag(b);
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -82,16 +88,13 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
|
||||
unsigned int inputSample;
|
||||
if(kernelOffset > in + out) {
|
||||
inputSample = 0;
|
||||
} else if(in + kernelOffset + out >= input->rData().size()) {
|
||||
} else if(in + out >= input->rData().size() + kernelOffset) {
|
||||
inputSample = input->rData().size() - 1;
|
||||
} else {
|
||||
inputSample = in + out - kernelOffset;
|
||||
}
|
||||
auto sample = input->rData().at(inputSample).y;
|
||||
if(out == (unsigned int) start) {
|
||||
// this is the first sample to update, fill initial kernel
|
||||
kernel[in] = sample;
|
||||
}
|
||||
kernel[in] = sample;
|
||||
}
|
||||
// sort initial kernel
|
||||
sort(kernel.begin(), kernel.end(), comp);
|
||||
@ -131,5 +134,6 @@ QString MedianFilter::orderToString(MedianFilter::Order o)
|
||||
case Order::Phase: return "Phase";
|
||||
case Order::Real: return "Real";
|
||||
case Order::Imag: return "Imag";
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ public:
|
||||
|
||||
virtual void edit() override;
|
||||
|
||||
static QWidget *createExplanationWidget();
|
||||
|
||||
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;
|
||||
|
@ -17,7 +17,7 @@
|
||||
<string>Median Filter</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
|
84
Software/PC_Application/Traces/Math/newtracemathdialog.ui
Normal file
84
Software/PC_Application/Traces/Math/newtracemathdialog.ui
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NewTraceMathDialog</class>
|
||||
<widget class="QDialog" name="NewTraceMathDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>840</width>
|
||||
<height>596</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Available math operations</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QListWidget" name="list"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>NewTraceMathDialog</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>NewTraceMathDialog</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>
|
85
Software/PC_Application/Traces/Math/tdrbandpass.cpp
Normal file
85
Software/PC_Application/Traces/Math/tdrbandpass.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "tdrbandpass.h"
|
||||
|
||||
#include "Traces/fftcomplex.h"
|
||||
|
||||
|
||||
#include <QLabel>
|
||||
using namespace Math;
|
||||
using namespace std;
|
||||
|
||||
TDRBandpass::TDRBandpass()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TraceMath::DataType TDRBandpass::outputType(TraceMath::DataType inputType)
|
||||
{
|
||||
if(inputType == DataType::Frequency) {
|
||||
return DataType::Time;
|
||||
} else {
|
||||
return DataType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QString TDRBandpass::description()
|
||||
{
|
||||
return "TDR (bandpass mode)";
|
||||
}
|
||||
|
||||
void TDRBandpass::edit()
|
||||
{
|
||||
// nothing to do for now
|
||||
}
|
||||
|
||||
QWidget *TDRBandpass::createExplanationWidget()
|
||||
{
|
||||
return new QLabel("Test");
|
||||
}
|
||||
|
||||
void TDRBandpass::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
{
|
||||
Q_UNUSED(begin);
|
||||
const double PI = 3.141592653589793238463;
|
||||
if(input->rData().size() >= 2) {
|
||||
// TDR is computationally expensive, only update at the end of sweep
|
||||
if(end != input->rData().size()) {
|
||||
// not the end, do nothing
|
||||
return;
|
||||
}
|
||||
// create vector for frequency data
|
||||
vector<complex<double>> dft(end);
|
||||
// window data and perform FFTshift ("zero"-bin expected at index 0 but is in the middle of input data)
|
||||
for(unsigned int i=0;i<end;i++) {
|
||||
// TODO implement other windows as well
|
||||
constexpr double alpha0 = 0.54;
|
||||
auto hamming = alpha0 - (1.0 - alpha0) * cos(2 * PI * i / end);
|
||||
auto windowed = input->rData()[i].y * hamming;
|
||||
// swap upper and lower half
|
||||
if(i < end/2) {
|
||||
dft[i + (end+1)/2] = windowed;
|
||||
} else {
|
||||
dft[i - end/2] = windowed;
|
||||
}
|
||||
}
|
||||
// perform IFT operation on data
|
||||
Fft::transform(dft, true);
|
||||
|
||||
// calculate sample distance
|
||||
auto freqStep = input->rData()[1].x - input->rData()[0].x;
|
||||
auto timeStep = 1.0 / (freqStep * end);
|
||||
|
||||
// copy DFT data to output data
|
||||
data.resize(dft.size());
|
||||
for(unsigned int i=0;i<dft.size();i++) {
|
||||
data[i].y = dft[i];
|
||||
data[i].x = i * timeStep;
|
||||
}
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
success();
|
||||
} else {
|
||||
// not enough input data
|
||||
data.clear();
|
||||
emit outputSamplesChanged(0, 0);
|
||||
warning("Not enough input samples");
|
||||
}
|
||||
}
|
25
Software/PC_Application/Traces/Math/tdrbandpass.h
Normal file
25
Software/PC_Application/Traces/Math/tdrbandpass.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef TDRBANDPASS_H
|
||||
#define TDRBANDPASS_H
|
||||
|
||||
#include "tracemath.h"
|
||||
|
||||
namespace Math {
|
||||
|
||||
class TDRBandpass : public TraceMath
|
||||
{
|
||||
public:
|
||||
TDRBandpass();
|
||||
|
||||
DataType outputType(DataType inputType) override;
|
||||
QString description() override;
|
||||
void edit() override;
|
||||
|
||||
static QWidget* createExplanationWidget();
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TDRBANDPASS_H
|
96
Software/PC_Application/Traces/Math/tdrlowpass.cpp
Normal file
96
Software/PC_Application/Traces/Math/tdrlowpass.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "tdrlowpass.h"
|
||||
|
||||
#include "Traces/fftcomplex.h"
|
||||
|
||||
|
||||
#include <QLabel>
|
||||
using namespace Math;
|
||||
using namespace std;
|
||||
|
||||
TDRLowpass::TDRLowpass()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
TraceMath::DataType TDRLowpass::outputType(TraceMath::DataType inputType)
|
||||
{
|
||||
if(inputType == DataType::Frequency) {
|
||||
return DataType::Time;
|
||||
} else {
|
||||
return DataType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QString TDRLowpass::description()
|
||||
{
|
||||
return "TDR (bandpass mode)";
|
||||
}
|
||||
|
||||
void TDRLowpass::edit()
|
||||
{
|
||||
// nothing to do for now
|
||||
}
|
||||
|
||||
QWidget *TDRLowpass::createExplanationWidget()
|
||||
{
|
||||
return new QLabel("Test");
|
||||
}
|
||||
|
||||
void TDRLowpass::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
{
|
||||
Q_UNUSED(begin);
|
||||
if(input->rData().size() >= 2) {
|
||||
// TDR is computationally expensive, only update at the end of sweep
|
||||
if(end != input->rData().size()) {
|
||||
// not the end, do nothing
|
||||
return;
|
||||
}
|
||||
auto steps = input->rData().size();
|
||||
auto firstStep = input->rData().front().x;
|
||||
if(firstStep == 0) {
|
||||
// zero as first step would result in infinite number of points, skip and start with second
|
||||
firstStep = input->rData()[1].x;
|
||||
steps--;
|
||||
}
|
||||
if(firstStep * steps != input->rData().back().x) {
|
||||
// data is not available with correct frequency spacing, calculate required steps
|
||||
steps = input->rData().back().x / firstStep;
|
||||
}
|
||||
const double PI = 3.141592653589793238463;
|
||||
// reserve vector for negative frequenies and DC as well
|
||||
vector<complex<double>> frequencyDomain(2*steps + 1);
|
||||
// copy frequencies, use the flipped conjugate for negative part
|
||||
for(unsigned int i = 1;i<=steps;i++) {
|
||||
auto S = input->getInterpolatedSample(firstStep * i).y;
|
||||
constexpr double alpha0 = 0.54;
|
||||
auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps);
|
||||
S *= hamming;
|
||||
frequencyDomain[2 * steps - i + 1] = conj(S);
|
||||
frequencyDomain[i] = S;
|
||||
}
|
||||
// use simple extrapolation from lowest two points to extract DC value
|
||||
auto abs_DC = 2.0 * abs(frequencyDomain[1]) - abs(frequencyDomain[2]);
|
||||
auto phase_DC = 2.0 * arg(frequencyDomain[1]) - arg(frequencyDomain[2]);
|
||||
frequencyDomain[0] = polar(abs_DC, phase_DC);
|
||||
|
||||
auto fft_bins = frequencyDomain.size();
|
||||
const double fs = 1.0 / (firstStep * fft_bins);
|
||||
|
||||
Fft::transform(frequencyDomain, true);
|
||||
|
||||
data.clear();
|
||||
data.resize(fft_bins);
|
||||
|
||||
for(unsigned int i = 0;i<fft_bins;i++) {
|
||||
data[i].x = fs * i;
|
||||
data[i].y = frequencyDomain[i] / (double) fft_bins;
|
||||
}
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
success();
|
||||
} else {
|
||||
// not enough input data
|
||||
data.clear();
|
||||
emit outputSamplesChanged(0, 0);
|
||||
warning("Not enough input samples");
|
||||
}
|
||||
}
|
25
Software/PC_Application/Traces/Math/tdrlowpass.h
Normal file
25
Software/PC_Application/Traces/Math/tdrlowpass.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef TDRLOWPASS_H
|
||||
#define TDRLOWPASS_H
|
||||
|
||||
#include "tracemath.h"
|
||||
|
||||
namespace Math {
|
||||
|
||||
class TDRLowpass : public TraceMath
|
||||
{
|
||||
public:
|
||||
TDRLowpass();
|
||||
|
||||
DataType outputType(DataType inputType) override;
|
||||
QString description() override;
|
||||
void edit() override;
|
||||
|
||||
static QWidget* createExplanationWidget();
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TDRBANDPASS_H
|
@ -1,5 +1,9 @@
|
||||
#include "tracemath.h"
|
||||
|
||||
#include "medianfilter.h"
|
||||
#include "tdrbandpass.h"
|
||||
#include "tdrlowpass.h"
|
||||
|
||||
TraceMath::TraceMath()
|
||||
{
|
||||
input = nullptr;
|
||||
@ -7,11 +11,73 @@ TraceMath::TraceMath()
|
||||
error("Invalid input");
|
||||
}
|
||||
|
||||
TraceMath *TraceMath::createMath(TraceMath::Type type)
|
||||
{
|
||||
switch(type) {
|
||||
case Type::MedianFilter:
|
||||
return new Math::MedianFilter();
|
||||
case Type::TDRlowpass:
|
||||
return new Math::TDRLowpass();
|
||||
case Type::TDRbandpass:
|
||||
return new Math::TDRBandpass();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
|
||||
{
|
||||
TypeInfo ret = {};
|
||||
switch(type) {
|
||||
case Type::MedianFilter:
|
||||
ret.name = "Median filter";
|
||||
ret.explanationWidget = Math::MedianFilter::createExplanationWidget();
|
||||
break;
|
||||
case Type::TDRlowpass:
|
||||
ret.name = "TDR (lowpass)";
|
||||
ret.explanationWidget = Math::TDRLowpass::createExplanationWidget();
|
||||
break;
|
||||
case Type::TDRbandpass:
|
||||
ret.name = "TDR (bandpass)";
|
||||
ret.explanationWidget = Math::TDRBandpass::createExplanationWidget();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TraceMath::Data TraceMath::getSample(unsigned int index)
|
||||
{
|
||||
return data.at(index);
|
||||
}
|
||||
|
||||
TraceMath::Data TraceMath::getInterpolatedSample(double x)
|
||||
{
|
||||
Data ret;
|
||||
|
||||
if(data.size() == 0 || x < data.front().x || x > data.back().x) {
|
||||
ret.y = std::numeric_limits<std::complex<double>>::quiet_NaN();
|
||||
ret.x = std::numeric_limits<double>::quiet_NaN();
|
||||
} else {
|
||||
auto it = lower_bound(data.begin(), data.end(), x, [](const Data &lhs, const double x) -> bool {
|
||||
return lhs.x < x;
|
||||
});
|
||||
if(it->x == x) {
|
||||
ret = *it;
|
||||
} else {
|
||||
// no exact match, needs to interpolate
|
||||
auto high = *it;
|
||||
it--;
|
||||
auto low = *it;
|
||||
double alpha = (x - low.x) / (high.x - low.x);
|
||||
ret.y = low.y * (1 - alpha) + high.y * alpha;
|
||||
ret.x = x;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned int TraceMath::numSamples()
|
||||
{
|
||||
return data.size();
|
||||
|
@ -5,6 +5,42 @@
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
/*
|
||||
* How to implement a new type of math operation:
|
||||
* 1. Create your new math operation class by deriving from this class. Put the new class in the namespace
|
||||
* "Math" to avoid name collisions.
|
||||
* 2. Implement required virtual functions:
|
||||
* a. outputType(DataType inputType):
|
||||
* Indicates what kind of result your math operation creates, depending on the type of its input.
|
||||
* If the input type is not applicable for your operation (e.g. frequency domain data as input for
|
||||
* a (forward) DFT, you can return DataType::Invalid
|
||||
* b. description():
|
||||
* Return a short, user-readable description how the operation is set up. This will be displayed
|
||||
* in the math edit dialog.
|
||||
* c. edit():
|
||||
* Optional. If your operation has customizable parameters, calling this function should start a
|
||||
* dialog that allows the user to change these parameters.
|
||||
* d. inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
* This slot gets called whenever the input data has changed. Override it and implement your math
|
||||
* operation in it. Parameters begin and end indicate which input samples have changed: If, for
|
||||
* example, only the 2nd and third input values have changed, they are set like this: begin=1 end=3
|
||||
* CAUTION: the size of the input vector may have changed, check before accessing it.
|
||||
*
|
||||
* Emit the signal outputSamplesChanged(unsigned int begin, unsigned int end) after your operation is
|
||||
* finished. Also call either success(), warning() or error() at the end of this slot, depending on
|
||||
* whether the operation succeeded:
|
||||
* success(): everything went well, output data contains valid values
|
||||
* warning(): something might be wrong (e.g. not enough input samples to create meaningful data, ...).
|
||||
* Provide a hint by passing a short description string
|
||||
* error(): something went wrong (called with wrong type of data, mathematical error, ...).
|
||||
* Provide a hint by passing a short description string
|
||||
* 3. Add a new type to the Type enum for your operation
|
||||
* 4. Extend the createMath(Type type) factory function to create an instance of your operation
|
||||
* 5. Add a static function "createExplanationWidget" which returns a QWidget explaining what your operation does.
|
||||
* This will be displayed when the user chooses to add a new math operation.
|
||||
* 6. Extend the function getInfo(Type type) to set a name and create the explanation widget for your operation
|
||||
*/
|
||||
|
||||
class TraceMath : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@ -28,7 +64,24 @@ public:
|
||||
Error,
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
MedianFilter,
|
||||
TDRlowpass,
|
||||
TDRbandpass,
|
||||
// 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);
|
||||
class TypeInfo {
|
||||
public:
|
||||
QString name;
|
||||
QWidget *explanationWidget;
|
||||
};
|
||||
static TypeInfo getInfo(Type type);
|
||||
|
||||
Data getSample(unsigned int index);
|
||||
Data getInterpolatedSample(double x);
|
||||
unsigned int numSamples();
|
||||
|
||||
// indicate whether this function produces time or frequency domain data
|
||||
|
@ -2,6 +2,11 @@
|
||||
#include "ui_tracematheditdialog.h"
|
||||
#include <QHeaderView>
|
||||
|
||||
#include "ui_newtracemathdialog.h"
|
||||
namespace Ui {
|
||||
class NewTraceMathDialog;
|
||||
}
|
||||
|
||||
#include "medianfilter.h"
|
||||
|
||||
TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
|
||||
@ -18,6 +23,7 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
|
||||
headerView->setSectionResizeMode(MathModel::ColIndexDomain, QHeaderView::ResizeToContents);
|
||||
|
||||
connect(ui->view->selectionModel(), &QItemSelectionModel::currentRowChanged, [=](const QModelIndex ¤t, const QModelIndex &previous){
|
||||
Q_UNUSED(previous)
|
||||
if(!current.isValid()) {
|
||||
ui->bDelete->setEnabled(false);
|
||||
ui->bMoveUp->setEnabled(false);
|
||||
@ -37,7 +43,30 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
|
||||
});
|
||||
|
||||
connect(ui->bAdd, &QPushButton::clicked, [=](){
|
||||
model->addOperation(new Math::MedianFilter);
|
||||
auto d = new QDialog();
|
||||
auto ui = new Ui::NewTraceMathDialog();
|
||||
ui->setupUi(d);
|
||||
for(int i = 0; i < (int) TraceMath::Type::Last;i++) {
|
||||
auto info = TraceMath::getInfo(static_cast<TraceMath::Type>(i));
|
||||
ui->list->addItem(info.name);
|
||||
if(!info.explanationWidget) {
|
||||
info.explanationWidget = new QWidget();
|
||||
}
|
||||
ui->stack->addWidget(info.explanationWidget);
|
||||
}
|
||||
// always show the widget for the selected function
|
||||
connect(ui->list, &QListWidget::currentRowChanged, ui->stack, &QStackedWidget::setCurrentIndex);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
|
||||
auto newMath = TraceMath::createMath(static_cast<TraceMath::Type>(ui->list->currentRow()));
|
||||
if(newMath) {
|
||||
model->addOperation(newMath);
|
||||
}
|
||||
});
|
||||
ui->list->setCurrentRow(0);
|
||||
ui->stack->setCurrentIndex(0);
|
||||
|
||||
d->show();
|
||||
});
|
||||
connect(ui->bDelete, &QPushButton::clicked, [=](){
|
||||
model->deleteRow(ui->view->currentIndex().row());
|
||||
@ -45,13 +74,11 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
|
||||
connect(ui->bMoveUp, &QPushButton::clicked, [&](){
|
||||
auto index = ui->view->currentIndex();
|
||||
t.swapMathOrder(index.row() - 1);
|
||||
model->rowsSwapped(index.row() - 1);
|
||||
ui->view->setCurrentIndex(index.sibling(index.row() - 1, 0));
|
||||
});
|
||||
connect(ui->bMoveDown, &QPushButton::clicked, [&](){
|
||||
auto index = ui->view->currentIndex();
|
||||
t.swapMathOrder(index.row());
|
||||
model->rowsSwapped(index.row());
|
||||
ui->view->setCurrentIndex(index.sibling(index.row() + 1, 0));
|
||||
});
|
||||
}
|
||||
@ -70,11 +97,13 @@ MathModel::MathModel(Trace &t, QObject *parent)
|
||||
|
||||
int MathModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return t.getMathOperations().size();
|
||||
}
|
||||
|
||||
int MathModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return ColIndexLast;
|
||||
}
|
||||
|
||||
@ -168,7 +197,3 @@ void MathModel::deleteRow(unsigned int row)
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void MathModel::rowsSwapped(unsigned int top)
|
||||
{
|
||||
// emit dataChanged(createIndex(top, 0), createIndex(top+1, ColIndexLast - 1));
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ public:
|
||||
|
||||
void addOperation(TraceMath *math);
|
||||
void deleteRow(unsigned int row);
|
||||
void rowsSwapped(unsigned int top);
|
||||
|
||||
private:
|
||||
Trace &t;
|
||||
|
@ -17,7 +17,7 @@
|
||||
<string>Math functions</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
|
@ -263,6 +263,7 @@ void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
|
||||
lastMath = newLast;
|
||||
// relay signals of end of math chain
|
||||
connect(lastMath, &TraceMath::outputSamplesChanged, this, &Trace::dataChanged);
|
||||
emit outputSamplesChanged(0, data.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
<string>Edit Trace</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
|
@ -9,13 +9,13 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="17.410528mm"
|
||||
height="9.36625mm"
|
||||
viewBox="0 0 17.410529 9.3662499"
|
||||
width="9.5429888mm"
|
||||
height="8.6545076mm"
|
||||
viewBox="0 0 9.5429888 8.6545077"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="math_disabled.svg">
|
||||
sodipodi:docname="math_disabled.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
@ -25,9 +25,9 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="43.034455"
|
||||
inkscape:cy="-5.145459"
|
||||
inkscape:zoom="7.9195959"
|
||||
inkscape:cx="23.599766"
|
||||
inkscape:cy="6.7003366"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
@ -52,33 +52,29 @@
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-77.99312,-144.11468)">
|
||||
transform="translate(-84.809019,-129.3552)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="74.839287"
|
||||
y="158.66071"
|
||||
x="85.422623"
|
||||
y="136.7381"
|
||||
id="text817"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan815"
|
||||
x="74.839287"
|
||||
y="168.51379"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';stroke-width:0.26458332px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="78.808037"
|
||||
y="151.29018"
|
||||
id="text825"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan823"
|
||||
x="78.808037"
|
||||
y="151.29018"
|
||||
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:FreeSerif;-inkscape-font-specification:'FreeSerif Italic';fill:#969696;fill-opacity:1;stroke-width:0.26458332px">f(x)</tspan></text>
|
||||
x="85.422623"
|
||||
y="136.7381"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Padauk Book';-inkscape-font-specification:'Padauk Book, ';stroke-width:0.26458332px">M</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#969696;stroke-width:0.271;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 78.040368,148.85072 h 17.36328"
|
||||
id="path867"
|
||||
inkscape:connector-curvature="0" />
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 85.314335,137.50494 6.93903,-6.21735 0.812743,-0.72822 0.780583,-0.6994"
|
||||
id="path819"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 85.314335,137.50494 8.532356,-7.64497"
|
||||
id="path819-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.2 KiB |
@ -9,13 +9,13 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16.50322mm"
|
||||
height="9.36625mm"
|
||||
viewBox="0 0 16.50322 9.36625"
|
||||
width="9.5429888mm"
|
||||
height="8.6545076mm"
|
||||
viewBox="0 0 9.5429888 8.6545077"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="math_enabled.svg">
|
||||
sodipodi:docname="math_enabled.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
@ -25,9 +25,9 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="43.1742"
|
||||
inkscape:cy="5.0657458"
|
||||
inkscape:zoom="7.9195959"
|
||||
inkscape:cx="23.599766"
|
||||
inkscape:cy="-28.655003"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
@ -52,28 +52,29 @@
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-77.99312,-144.11468)">
|
||||
transform="translate(-84.809019,-129.3552)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="74.839287"
|
||||
y="158.66071"
|
||||
x="85.422623"
|
||||
y="136.7381"
|
||||
id="text817"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan815"
|
||||
x="74.839287"
|
||||
y="168.51379"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';stroke-width:0.26458332px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="78.808037"
|
||||
y="151.29018"
|
||||
id="text825"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan823"
|
||||
x="78.808037"
|
||||
y="151.29018"
|
||||
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:FreeSerif;-inkscape-font-specification:'FreeSerif Italic';stroke-width:0.26458332px">f(x)</tspan></text>
|
||||
x="85.422623"
|
||||
y="136.7381"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Padauk Book';-inkscape-font-specification:'Padauk Book, ';stroke-width:0.26458332px">M</tspan></text>
|
||||
<path
|
||||
style="opacity:0;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 85.314335,137.50494 6.93903,-6.21735 0.812743,-0.72822 0.780583,-0.6994"
|
||||
id="path819"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:0;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 85.314335,137.50494 8.532356,-7.64497"
|
||||
id="path819-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in New Issue
Block a user