minimal median filter implementation for math testing

This commit is contained in:
Jan Käberich 2020-11-27 16:31:05 +01:00
parent 692bb85b5d
commit a168e81cca
9 changed files with 131 additions and 27 deletions

View File

@ -25,6 +25,7 @@ HEADERS += \
SpectrumAnalyzer/tracewidgetsa.h \
Tools/eseries.h \
Tools/impedancematchdialog.h \
Traces/Math/medianfilter.h \
Traces/Math/tracemath.h \
Traces/Math/tracematheditdialog.h \
Traces/fftcomplex.h \
@ -79,6 +80,7 @@ SOURCES += \
SpectrumAnalyzer/tracewidgetsa.cpp \
Tools/eseries.cpp \
Tools/impedancematchdialog.cpp \
Traces/Math/medianfilter.cpp \
Traces/Math/tracemath.cpp \
Traces/Math/tracematheditdialog.cpp \
Traces/fftcomplex.cpp \

View File

@ -0,0 +1,65 @@
#include "medianfilter.h"
using namespace Math;
using namespace std;
MedianFilter::MedianFilter()
{
kernelSize = 3;
order = Order::AbsoluteValue;
}
TraceMath::DataType MedianFilter::outputType(TraceMath::DataType inputType)
{
// domain stays the same
return inputType;
}
QString MedianFilter::description()
{
return "Median filter";
}
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(data.size() != input->rData().size()) {
data.resize(input->rData().size());
}
int start = (int) begin - (kernelSize-1)/2;
unsigned int stop = (int) end + (kernelSize-1)/2;
if(start < 0) {
start = 0;
}
if(stop >= input->rData().size()) {
stop = input->rData().size();
}
for(unsigned int i=start;i<stop;i++) {
updateSample(i);
}
emit outputSamplesChanged(start, stop);
}
void MedianFilter::updateSample(int index)
{
vector<complex<double>> values;
for(int i=index - (kernelSize-1)/2;i<=index+(kernelSize-1)/2;i++) {
unsigned int inputSample;
if(i<0) {
inputSample = 0;
} else if(i>=(int) input->rData().size()) {
inputSample = input->rData().size() - 1;
} else {
inputSample = i;
}
values.push_back(input->rData().at(inputSample).y);
}
sort(values.begin(), values.end(), [=](const complex<double>&a, const complex<double>&b){
switch(order) {
case Order::AbsoluteValue: return abs(a) > abs(b);
case Order::Phase: return arg(a) > arg(b);
case Order::Real: return real(a) > real(b);
case Order::Imag: return imag(a) > imag(b);
}
});
data.at(index).y = values[(kernelSize-1)/2];
data.at(index).x = input->rData().at(index).x;
}

View File

@ -0,0 +1,33 @@
#ifndef MEDIANFILTER_H
#define MEDIANFILTER_H
#include "tracemath.h"
namespace Math {
class MedianFilter : public TraceMath
{
public:
MedianFilter();
virtual DataType outputType(DataType inputType) override;
virtual QString description() override;
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;
private:
void updateSample(int index);
int kernelSize;
enum class Order {
AbsoluteValue,
Phase,
Real,
Imag,
} order;
};
}
#endif // MEDIANFILTER_H

View File

@ -36,10 +36,9 @@ void TraceMath::assignInput(TraceMath *input)
this->input = input;
inputTypeChanged(input->dataType);
// do initial calculation
inputDataChanged();
inputSamplesChanged(0, input->data.size());
// connect to input
connect(input, &TraceMath::outputDataChanged, this, &TraceMath::inputDataChanged);
connect(input, &TraceMath::outputSampleChanged, this, &TraceMath::inputSampleChanged);
connect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged);
connect(input, &TraceMath::outputTypeChanged, this, &TraceMath::inputTypeChanged);
}
}
@ -50,7 +49,7 @@ void TraceMath::inputTypeChanged(TraceMath::DataType type)
if(newType != dataType) {
dataType = newType;
data.clear();
inputDataChanged();
inputSamplesChanged(0, input->data.size());
emit outputTypeChanged(dataType);
}
}

View File

@ -27,8 +27,8 @@ public:
// indicate whether this function produces time or frequency domain data
virtual DataType outputType(DataType inputType) = 0;
virtual QString description() = 0;
virtual void edit(){};
void removeInput();
void assignInput(TraceMath *input);
@ -37,17 +37,14 @@ public:
std::vector<Data>& rData() { return data;};
public slots:
// a single value of the input data has changed, index determines which sample has changed
virtual void inputSampleChanged(unsigned int index){Q_UNUSED(index)};
// the complete input data has changed (e.g. cleared or all data modified by some operation)
virtual void inputDataChanged(){};
// 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)};
void inputTypeChanged(DataType type);
signals:
// emit this whenever a sample changed (alternatively, if all samples are about to change, emit outputDataChanged after they have changed)
void outputSampleChanged(unsigned int index);
void outputDataChanged();
void outputSamplesChanged(unsigned int begin, unsigned int end);
// emit when the output type changed
void outputTypeChanged(DataType type);

View File

@ -1,6 +1,8 @@
#include "tracematheditdialog.h"
#include "ui_tracematheditdialog.h"
#include "medianfilter.h"
TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceMathEditDialog)
@ -21,6 +23,9 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
}
});
connect(ui->bAdd, &QPushButton::clicked, [=](){
model->addOperation(new Math::MedianFilter);
});
connect(ui->bDelete, &QPushButton::clicked, [=](){
model->deleteRow(ui->view->currentIndex().row());
});
@ -110,6 +115,13 @@ Qt::ItemFlags MathModel::flags(const QModelIndex &index) const
return (Qt::ItemFlags) flags;
}
void MathModel::addOperation(TraceMath *math)
{
beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size());
t.addMathOperation(math);
endInsertRows();
}
void MathModel::deleteRow(unsigned int row)
{
beginRemoveRows(QModelIndex(), row, row);

View File

@ -27,6 +27,7 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
void addOperation(TraceMath *math);
void deleteRow(unsigned int row);
void rowsSwapped(unsigned int top);

View File

@ -22,9 +22,6 @@ Trace::Trace(QString name, QColor color, LiveParameter live)
updateLastMath(mathOps.rbegin());
self.enabled = false;
mathOps.push_back(self);
mathOps.push_back(self);
mathOps.push_back(self);
}
Trace::~Trace()
@ -39,7 +36,7 @@ void Trace::clear() {
data.clear();
settings.valid = false;
emit cleared(this);
emit outputDataChanged();
emit outputSamplesChanged(0, 0);
}
void Trace::addData(const Trace::Data& d) {
@ -74,7 +71,7 @@ void Trace::addData(const Trace::Data& d) {
// insert at this position
data.insert(lower, d);
}
emit outputSampleChanged(lower - data.begin());
emit outputSamplesChanged(lower - data.begin(), lower - data.begin() + 1);
if(lower == data.begin()) {
// received the first point, which means the last sweep just finished
if(tdr_users) {
@ -173,7 +170,7 @@ void Trace::removeMarker(TraceMarker *m)
void Trace::updateTimeDomainData()
{
if(_data.size() < 2) {
if(data.size() < 2) {
// can't compute anything
timeDomain.clear();
return;
@ -258,13 +255,11 @@ void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
Q_ASSERT(newLast != nullptr);
if(newLast != lastMath) {
if(lastMath != nullptr) {
disconnect(lastMath, &TraceMath::outputDataChanged, this, nullptr);
disconnect(lastMath, &TraceMath::outputSampleChanged, this, nullptr);
disconnect(lastMath, &TraceMath::outputSamplesChanged, this, nullptr);
}
lastMath = newLast;
// relay signals of end of math chain
connect(lastMath, &TraceMath::outputDataChanged, this, &Trace::dataChanged);
connect(lastMath, &TraceMath::outputSampleChanged, this, &Trace::dataChanged);
connect(lastMath, &TraceMath::outputSamplesChanged, this, &Trace::dataChanged);
}
}
@ -419,7 +414,7 @@ bool Trace::hasMathOperations()
void Trace::enableMath(bool enable)
{
auto start = enable ? mathOps.rbegin() : make_reverse_iterator(mathOps.begin());
auto start = enable ? mathOps.rbegin() : make_reverse_iterator(mathOps.begin() + 1);
updateLastMath(start);
}

View File

@ -666,8 +666,8 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo
case YAxisType::Phase:
case YAxisType::VSWR: {
auto d = t->sample(sample);
ret.setY(traceToCoordinate(d.S, type));
ret.setX(d.frequency);
ret.setY(traceToCoordinate(d.y, type));
ret.setX(d.x);
}
break;
case YAxisType::Impulse:
@ -714,12 +714,12 @@ unsigned int TraceXYPlot::numTraceSamples(Trace *t)
QPoint TraceXYPlot::dataToPixel(Trace::Data d)
{
if(d.frequency < XAxis.rangeMin || d.frequency > XAxis.rangeMax) {
if(d.x < XAxis.rangeMin || d.x > XAxis.rangeMax) {
return QPoint();
}
auto y = traceToCoordinate(d.S, YAxis[0].type);
auto y = traceToCoordinate(d.y, YAxis[0].type);
QPoint p;
p.setX(Util::Scale<double>(d.frequency, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth));
p.setX(Util::Scale<double>(d.x, XAxis.rangeMin, XAxis.rangeMax, plotAreaLeft, plotAreaLeft + plotAreaWidth));
p.setY(Util::Scale<double>(y, YAxis[0].rangeMin, YAxis[0].rangeMax, plotAreaBottom, 0));
return p;
}