add mutex for trace data (fixes DFT/TDR thread crashes)

This commit is contained in:
Jan Käberich 2024-12-17 10:48:52 +01:00
parent acb79fa3bc
commit 70ac53aa75
17 changed files with 315 additions and 141 deletions

View file

@ -147,10 +147,9 @@ void Math::DFT::inputSamplesChanged(unsigned int begin, unsigned int end)
{
Q_UNUSED(begin);
Q_UNUSED(end);
if(input->rData().size() < 2) {
if(input->getData().size() < 2) {
// not enough input data
data.clear();
emit outputSamplesChanged(0, 0);
clearOutput();
warning("Not enough input samples");
return;
}
@ -162,10 +161,18 @@ void Math::DFT::inputSamplesChanged(unsigned int begin, unsigned int end)
void Math::DFT::updateDFT()
{
if(dataType != DataType::Invalid) {
inputSamplesChanged(0, input->rData().size());
inputSamplesChanged(0, input->getData().size());
}
}
void Math::DFT::clearOutput()
{
dataMutex.lock();
data.clear();
dataMutex.unlock();
emit outputSamplesChanged(0, 0);
}
Math::DFTThread::DFTThread(Math::DFT &dft)
: dft(dft)
{
@ -186,11 +193,18 @@ void Math::DFTThread::run()
qDebug() << "DFT thread exiting";
return;
}
// qDebug() << "DFT thread calculating";
// qDebug() << "DFT thread calculating";
if(!dft.input) {
// not connected, skip calculation
continue;
}
auto inputData = dft.input->getData();
if(!inputData.size()) {
dft.clearOutput();
dft.warning("Not enough input samples");
continue;
}
double DC = dft.DCfreq;
TDR *tdr = nullptr;
// find the last TDR operation
@ -213,14 +227,15 @@ void Math::DFTThread::run()
DC = tdr->getInput()->getSample(tdr->getInput()->numSamples()/2).x;
}
}
auto samples = dft.input->rData().size();
auto timeSpacing = dft.input->rData()[1].x - dft.input->rData()[0].x;
auto samples = inputData.size();
auto timeSpacing = inputData[1].x - inputData[0].x;
vector<complex<double>> timeDomain(samples);
for(unsigned int i=0;i<samples;i++) {
timeDomain.at(i) = dft.input->rData()[i].y;
timeDomain.at(i) = inputData[i].y;
}
dft.window.apply(timeDomain);
Fft::shift(timeDomain, true);
Fft::transform(timeDomain, false);
// shift DC bin into the middle
Fft::shift(timeDomain, false);
@ -229,7 +244,10 @@ void Math::DFTThread::run()
if(tdr) {
// split in padding and actual data sections
unsigned int padding = timeDomain.size() - tdr->getUnpaddedInputSize();
unsigned int padding = 0;
if(timeDomain.size() > tdr->getUnpaddedInputSize()) {
padding = timeDomain.size() - tdr->getUnpaddedInputSize();
}
std::vector<std::complex<double>> pad_front(timeDomain.begin(), timeDomain.begin()+padding/2);
std::vector<std::complex<double>> data(timeDomain.begin()+padding/2, timeDomain.end()-padding/2);
std::vector<std::complex<double>> pad_back(timeDomain.end()-padding/2, timeDomain.end());
@ -248,8 +266,8 @@ void Math::DFTThread::run()
}
}
dft.data.clear();
int DCbin = timeDomain.size() / 2, startBin = 0;
dft.dataMutex.lock();
if(DC > 0) {
dft.data.resize(timeDomain.size(), TraceMath::Data());
} else {
@ -262,7 +280,9 @@ void Math::DFTThread::run()
dft.data[i - startBin].x = round(freq);
dft.data[i - startBin].y = timeDomain.at(i);
}
emit dft.outputSamplesChanged(0, dft.data.size());
auto size = dft.data.size();
dft.dataMutex.unlock();
emit dft.outputSamplesChanged(0, size);
// limit update rate if configured in preferences
auto &p = Preferences::getInstance();

View file

@ -45,6 +45,7 @@ public slots:
private:
void updateDFT();
void clearOutput();
bool automaticDC;
double DCfreq;
WindowFunction window;

View file

@ -85,7 +85,11 @@ void Math::Expression::fromJSON(nlohmann::json j)
void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
{
auto in = input->rData();
std::vector<Data> in;
if(input) {
in = input->getData();
}
dataMutex.lock();
data.resize(in.size());
try {
for(unsigned int i=begin;i<end;i++) {
@ -100,10 +104,11 @@ void Math::Expression::inputSamplesChanged(unsigned int begin, unsigned int end)
data[i].y = res.GetComplex();
}
success();
emit outputSamplesChanged(begin, end);
} catch (const ParserError &e) {
error(QString::fromStdString(e.GetMsg()));
}
dataMutex.unlock();
emit outputSamplesChanged(begin, end);
}
void Math::Expression::expressionChanged()
@ -137,6 +142,6 @@ void Math::Expression::expressionChanged()
break;
}
if(input) {
inputSamplesChanged(0, input->rData().size());
inputSamplesChanged(0, input->getData().size());
}
}

View file

@ -79,8 +79,14 @@ void MedianFilter::fromJSON(nlohmann::json j)
}
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(data.size() != input->rData().size()) {
data.resize(input->rData().size());
std::vector<Data> inputData;
if(input) {
inputData = input->getData();
}
if(data.size() != inputData.size()) {
dataMutex.lock();
data.resize(inputData.size());
dataMutex.unlock();
}
if(data.size() > 0) {
auto kernelOffset = (kernelSize-1)/2;
@ -89,8 +95,8 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(start < 0) {
start = 0;
}
if(stop > input->rData().size()) {
stop = input->rData().size();
if(stop > inputData.size()) {
stop = inputData.size();
}
auto comp = [=](const complex<double>&a, const complex<double>&b){
@ -104,6 +110,7 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
};
vector<complex<double>> kernel(kernelSize);
dataMutex.lock();
for(unsigned int out=start;out<stop;out++) {
if(out == (unsigned int) start) {
// this is the first sample to update, fill initial kernel
@ -111,12 +118,12 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
unsigned int inputSample;
if(kernelOffset > in + out) {
inputSample = 0;
} else if(in + out >= input->rData().size() + kernelOffset) {
inputSample = input->rData().size() - 1;
} else if(in + out >= inputData.size() + kernelOffset) {
inputSample = inputData.size() - 1;
} else {
inputSample = in + out - kernelOffset;
}
auto sample = input->rData().at(inputSample).y;
auto sample = inputData.at(inputSample).y;
kernel[in] = sample;
}
// sort initial kernel
@ -129,20 +136,21 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(toRemove < 0) {
toRemove = 0;
}
if(toAdd >= input->rData().size()) {
toAdd = input->rData().size() - 1;
if(toAdd >= inputData.size()) {
toAdd = inputData.size() - 1;
}
auto sampleToRemove = input->rData().at(toRemove).y;
auto sampleToRemove = inputData.at(toRemove).y;
auto remove_iterator = lower_bound(kernel.begin(), kernel.end(), sampleToRemove, comp);
kernel.erase(remove_iterator);
auto sampleToAdd = input->rData().at(toAdd).y;
auto sampleToAdd = inputData.at(toAdd).y;
// insert sample at correct position in vector
kernel.insert(upper_bound(kernel.begin(), kernel.end(), sampleToAdd, comp), sampleToAdd);
}
data.at(out).y = kernel[kernelOffset];
data.at(out).x = input->rData().at(out).x;
data.at(out).x = inputData.at(out).x;
}
dataMutex.unlock();
emit outputSamplesChanged(start, stop);
success();
} else {

View file

@ -205,7 +205,7 @@ void TDR::setMode(Mode m)
}
mode = m;
if(input) {
inputSamplesChanged(0, input->rData().size());
inputSamplesChanged(0, input->getData().size());
}
}
@ -213,15 +213,13 @@ void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
{
Q_UNUSED(begin);
Q_UNUSED(end);
if(input->rData().size() >= 2) {
if(input->getData().size() >= 2) {
// trigger calculation in thread
semphr.release();
success();
} else {
// not enough input data
data.clear();
updateStepResponse(false);
emit outputSamplesChanged(0, 0);
clearOutput();
warning("Not enough input samples");
}
}
@ -229,10 +227,19 @@ void TDR::inputSamplesChanged(unsigned int begin, unsigned int end)
void TDR::updateTDR()
{
if(dataType != DataType::Invalid) {
inputSamplesChanged(0, input->rData().size());
inputSamplesChanged(0, input->getData().size());
}
}
void TDR::clearOutput()
{
dataMutex.lock();
data.clear();
dataMutex.unlock();
updateStepResponse(false);
emit outputSamplesChanged(0, 0);
}
unsigned int TDR::getUnpaddedInputSize() const
{
return unpaddedInputSize;
@ -268,67 +275,56 @@ void TDRThread::run()
qDebug() << "TDR thread exiting";
return;
}
// qDebug() << "TDR thread calculating";
// qDebug() << "TDR thread calculating";
// perform calculation
if(!tdr.input) {
// not connected, skip calculation
continue;
}
auto inputData = tdr.input->getData();
if(!inputData.size()) {
// empty input data, clear output data
tdr.clearOutput();
tdr.warning("Not enough input samples");
continue;
}
vector<complex<double>> frequencyDomain;
auto stepSize = (tdr.input->rData().back().x - tdr.input->rData().front().x) / (tdr.input->rData().size() - 1);
auto stepSize = (inputData.back().x - inputData.front().x) / (inputData.size() - 1);
if(tdr.mode == TDR::Mode::Lowpass) {
if(tdr.stepResponse) {
auto steps = tdr.input->rData().size();
auto firstStep = tdr.input->rData().front().x;
// frequency points need to be evenly spaced all the way to DC
if(firstStep == 0) {
// zero as first step would result in infinite number of points, skip and start with second
firstStep = tdr.input->rData()[1].x;
steps--;
}
if(firstStep * steps != tdr.input->rData().back().x) {
// data is not available with correct frequency spacing, calculate required steps
steps = tdr.input->rData().back().x / firstStep;
stepSize = firstStep;
}
frequencyDomain.resize(2 * steps + 1);
// copy frequencies, use the flipped conjugate for negative part
for(unsigned int i = 1;i<=steps;i++) {
auto S = tdr.input->getInterpolatedSample(stepSize * i).y;
frequencyDomain[steps - i] = conj(S);
frequencyDomain[steps + i] = S;
}
if(tdr.automaticDC) {
// use simple extrapolation from lowest two points to extract DC value
auto abs_DC = 2.0 * abs(frequencyDomain[steps + 1]) - abs(frequencyDomain[steps + 2]);
auto phase_DC = 2.0 * arg(frequencyDomain[steps + 1]) - arg(frequencyDomain[steps + 2]);
frequencyDomain[steps] = polar(abs_DC, phase_DC);
} else {
frequencyDomain[steps] = tdr.manualDC;
}
auto steps = inputData.size();
auto firstStep = inputData.front().x;
// frequency points need to be evenly spaced all the way to DC
if(firstStep == 0) {
// zero as first step would result in infinite number of points, skip and start with second
firstStep = inputData[1].x;
steps--;
}
if(firstStep * steps != inputData.back().x) {
// data is not available with correct frequency spacing, calculate required steps
steps = inputData.back().x / firstStep;
stepSize = firstStep;
}
frequencyDomain.resize(2 * steps + 1);
// copy frequencies, use the flipped conjugate for negative part
for(unsigned int i = 1;i<=steps;i++) {
auto S = tdr.input->getInterpolatedSample(stepSize * i).y;
frequencyDomain[steps - i] = conj(S);
frequencyDomain[steps + i] = S;
}
if(tdr.automaticDC) {
// use simple extrapolation from lowest two points to extract DC value
auto abs_DC = 2.0 * abs(frequencyDomain[steps + 1]) - abs(frequencyDomain[steps + 2]);
auto phase_DC = 2.0 * arg(frequencyDomain[steps + 1]) - arg(frequencyDomain[steps + 2]);
frequencyDomain[steps] = polar(abs_DC, phase_DC);
} else {
auto steps = tdr.input->rData().size();
unsigned int offset = 0;
if(tdr.input->rData().front().x == 0) {
// DC measurement is inaccurate, skip
steps--;
offset++;
}
// no step response required, can use frequency values as they are. No extra extrapolated DC value here -> 2 values less than with step response
frequencyDomain.resize(2 * steps - 1);
frequencyDomain[steps - 1] = tdr.input->rData()[offset].y;
for(unsigned int i = 1;i<steps;i++) {
auto S = tdr.input->rData()[i + offset].y;
frequencyDomain[steps - i - 1] = conj(S);
frequencyDomain[steps + i - 1] = S;
}
frequencyDomain[steps] = tdr.manualDC;
}
} else {
// bandpass mode
// Can use input data directly, no need to extend with complex conjugate
frequencyDomain.resize(tdr.input->rData().size());
for(unsigned int i=0;i<tdr.input->rData().size();i++) {
frequencyDomain[i] = tdr.input->rData()[i].y;
frequencyDomain.resize(inputData.size());
for(unsigned int i=0;i<inputData.size();i++) {
frequencyDomain[i] = inputData[i].y;
}
}
@ -347,18 +343,20 @@ void TDRThread::run()
Fft::transform(frequencyDomain, true);
Fft::shift(frequencyDomain, false);
tdr.dataMutex.lock();
tdr.data.resize(fft_bins, TraceMath::Data());
for(int i = 0;i<fft_bins;i++) {
tdr.data[i].x = fs * (i - fft_bins / 2);
tdr.data[i].y = frequencyDomain[i] / (double) fft_bins;
}
auto size = tdr.data.size();
tdr.dataMutex.unlock();
if(tdr.stepResponse && tdr.mode == TDR::Mode::Lowpass) {
tdr.updateStepResponse(true);
} else {
tdr.updateStepResponse(false);
}
emit tdr.outputSamplesChanged(0, tdr.data.size());
emit tdr.outputSamplesChanged(0, size);
// limit update rate if configured in preferences
auto &p = Preferences::getInstance();

View file

@ -55,6 +55,7 @@ public slots:
private:
void updateTDR();
void clearOutput();
Mode mode;
WindowFunction window;
unsigned int padding;

View file

@ -53,7 +53,7 @@
<item row="1" column="1">
<widget class="QSpinBox" name="padding">
<property name="maximum">
<number>100000</number>
<number>10000000</number>
</property>
<property name="singleStep">
<number>100</number>

View file

@ -148,8 +148,12 @@ void Math::TimeGate::fromJSON(nlohmann::json j)
void Math::TimeGate::setStart(double start)
{
if(input && input->rData().size() > 0 && start < input->rData().front().x) {
start = input->rData().back().x;
if(!input) {
return;
}
auto inputData = input->getData();
if(inputData.size() > 0 && start < inputData.front().x) {
start = inputData.back().x;
}
double stop = center + span / 2;
@ -165,8 +169,12 @@ void Math::TimeGate::setStart(double start)
void Math::TimeGate::setStop(double stop)
{
if(input && input->rData().size() > 0 && stop > input->rData().back().x) {
stop = input->rData().back().x;
if(!input) {
return;
}
auto inputData = input->getData();
if(inputData.size() > 0 && stop > inputData.back().x) {
stop = inputData.back().x;
}
double start = center - span / 2;
@ -210,16 +218,24 @@ double Math::TimeGate::getStop()
void Math::TimeGate::inputSamplesChanged(unsigned int begin, unsigned int end)
{
if(data.size() != input->rData().size()) {
data.resize(input->rData().size());
std::vector<Data> inputData;
if(input) {
inputData = input->getData();
}
if(data.size() != inputData.size()) {
dataMutex.lock();
data.resize(inputData.size());
dataMutex.unlock();
updateFilter();
}
dataMutex.lock();
for(auto i = begin;i<end;i++) {
data[i] = input->rData()[i];
data[i] = inputData[i];
data[i].y *= filter[i];
}
dataMutex.unlock();
emit outputSamplesChanged(begin, end);
if(input->rData().size() > 0) {
if(inputData.size() > 0) {
success();
} else {
warning("No input data");
@ -231,14 +247,15 @@ void Math::TimeGate::updateFilter()
if(!input) {
return;
}
auto inputData = input->getData();
std::vector<std::complex<double>> buf;
filter.clear();
buf.resize(input->rData().size() * 2);
buf.resize(inputData.size() * 2);
if(!buf.size()) {
return;
}
auto maxX = input->rData().back().x;
auto minX = input->rData().front().x;
auto maxX = inputData.back().x;
auto minX = inputData.front().x;
auto wc1 = Util::Scale<double>(center - span / 2, minX, maxX, 0, 1);
auto wc2 = Util::Scale<double>(center + span / 2, minX, maxX, 0, 1);
@ -271,7 +288,7 @@ void Math::TimeGate::updateFilter()
emit filterUpdated();
// needs to update output samples, pretend that input samples have changed
inputSamplesChanged(0, input->rData().size());
inputSamplesChanged(0, inputData.size());
}
Math::TimeGateGraph::TimeGateGraph(QWidget *parent)
@ -285,13 +302,18 @@ Math::TimeGateGraph::TimeGateGraph(QWidget *parent)
QPoint Math::TimeGateGraph::plotValueToPixel(double x, double y)
{
if(!gate->getInput() || !gate->getInput()->rData().size()) {
if(!gate->getInput()) {
return QPoint(0, 0);
}
auto input = gate->getInput()->rData();
auto minX = input.front().x;
auto maxX = input.back().x;
auto inputData = gate->getInput()->getData();
if(!inputData.size()) {
return QPoint(0, 0);
}
auto minX = inputData.front().x;
auto maxX = inputData.back().x;
int plotLeft = 0;
int plotRight = size().width();
@ -306,13 +328,18 @@ QPoint Math::TimeGateGraph::plotValueToPixel(double x, double y)
QPointF Math::TimeGateGraph::pixelToPlotValue(QPoint p)
{
if(!gate->getInput() || !gate->getInput()->rData().size()) {
if(!gate->getInput()) {
return QPointF(0.0, 0.0);
}
auto input = gate->getInput()->rData();
auto minX = input.front().x;
auto maxX = input.back().x;
auto inputData = gate->getInput()->getData();
if(!inputData.size()) {
return QPointF(0.0, 0.0);
}
auto minX = inputData.front().x;
auto maxX = inputData.back().x;
int plotLeft = 0;
int plotRight = size().width();
@ -327,11 +354,11 @@ QPointF Math::TimeGateGraph::pixelToPlotValue(QPoint p)
void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
{
if(!gate) {
if(!gate || !gate->getInput()) {
return;
}
// grab input data
auto input = gate->getInput()->rData();
auto inputData = gate->getInput()->getData();
Q_UNUSED(event)
auto& pref = Preferences::getInstance();
@ -340,7 +367,7 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
p.setBackground(QBrush(pref.Graphs.Color.background));
p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background));
if(!gate->getInput() || !gate->getInput()->rData().size()) {
if(!inputData.size()) {
// no data yet, nothing to plot
return;
}
@ -351,8 +378,8 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
pen.setStyle(Qt::SolidLine);
p.setPen(pen);
auto minX = input.front().x;
auto maxX = input.back().x;
auto minX = inputData.front().x;
auto maxX = inputData.back().x;
int plotLeft = 0;
int plotRight = size().width();
@ -362,14 +389,14 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
QPoint p1, p2;
// limit amount of displayed points to keep GUI snappy
auto increment = input.size() / 500;
auto increment = inputData.size() / 500;
if(!increment) {
increment = 1;
}
for(unsigned int i=increment;i<input.size();i+=increment) {
auto last = input[i-increment];
auto now = input[i];
for(unsigned int i=increment;i<inputData.size();i+=increment) {
auto last = inputData[i-increment];
auto now = inputData[i];
auto y_last = Util::SparamTodB(last.y);
auto y_now = Util::SparamTodB(now.y);
@ -390,9 +417,9 @@ void Math::TimeGateGraph::paintEvent(QPaintEvent *event)
auto filter = gate->rFilter();
pen = QPen(Qt::red, 1);
p.setPen(pen);
for(unsigned int i=increment;i<filter.size() && i<input.size();i+=increment) {
auto x_last = input[i-increment].x;
auto x_now = input[i].x;
for(unsigned int i=increment;i<filter.size() && i<inputData.size();i+=increment) {
auto x_last = inputData[i-increment].x;
auto x_now = inputData[i].x;
auto f_last = Util::SparamTodB(filter[i-increment]);
auto f_now = Util::SparamTodB(filter[i]);

View file

@ -8,6 +8,8 @@
#include "Traces/trace.h"
#include "ui_timedomaingatingexplanationwidget.h"
#include <QMutexLocker>
TraceMath::TraceMath()
{
input = nullptr;
@ -87,14 +89,13 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
TraceMath::Data TraceMath::getSample(unsigned int index)
{
TraceMath::Data d;
dataMutex.lock();
if(index < data.size()) {
return data[index];
} else {
TraceMath::Data d;
d.x = 0;
d.y = 0;
return d;
d = data[index];
}
dataMutex.unlock();
return d;
}
double TraceMath::getStepResponse(unsigned int index)
@ -108,6 +109,7 @@ double TraceMath::getStepResponse(unsigned int index)
double TraceMath::getInterpolatedStepResponse(double x)
{
QMutexLocker locker(&dataMutex);
if(stepResponse.size() != data.size()) {
// make sure all the step response data is available
return std::numeric_limits<double>::quiet_NaN();
@ -140,7 +142,7 @@ double TraceMath::getInterpolatedStepResponse(double x)
TraceMath::Data TraceMath::getInterpolatedSample(double x)
{
Data ret;
QMutexLocker locker(&dataMutex);
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();
@ -165,7 +167,10 @@ TraceMath::Data TraceMath::getInterpolatedSample(double x)
unsigned int TraceMath::numSamples()
{
return data.size();
dataMutex.lock();
auto size = data.size();
dataMutex.unlock();
return size;
}
QString TraceMath::dataTypeToString(TraceMath::DataType type)
@ -201,7 +206,9 @@ void TraceMath::removeInput()
// disconnect everything from the input
disconnect(input, nullptr, this, nullptr);
input = nullptr;
dataMutex.lock();
data.clear();
dataMutex.unlock();
dataType = DataType::Invalid;
emit outputTypeChanged(dataType);
}
@ -222,14 +229,19 @@ void TraceMath::inputTypeChanged(TraceMath::DataType type)
{
auto newType = outputType(type);
dataType = newType;
dataMutex.lock();
data.clear();
dataMutex.unlock();
if(dataType == DataType::Invalid) {
error("Invalid input data");
disconnect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged);
updateStepResponse(false);
} else {
connect(input, &TraceMath::outputSamplesChanged, this, &TraceMath::inputSamplesChanged, Qt::UniqueConnection);
inputSamplesChanged(0, input->data.size());
input->dataMutex.lock();
auto inputSize = input->data.size();
input->dataMutex.unlock();
inputSamplesChanged(0, inputSize);
}
emit outputTypeChanged(dataType);
}
@ -256,8 +268,14 @@ void TraceMath::success()
}
}
QMutex& TraceMath::mutex()
{
return dataMutex;
}
void TraceMath::updateStepResponse(bool valid)
{
QMutexLocker locker(&dataMutex);
if(valid) {
stepResponse.resize(data.size());
double accumulate = 0.0;
@ -298,3 +316,11 @@ TraceMath::DataType TraceMath::getDataType() const
{
return dataType;
}
std::vector<TraceMath::Data> TraceMath::getData()
{
dataMutex.lock();
auto ret = data;
dataMutex.unlock();
return ret;
}

View file

@ -4,6 +4,7 @@
#include "savable.h"
#include <QObject>
#include <QMutex>
#include <vector>
#include <complex>
/*
@ -110,7 +111,7 @@ public:
void assignInput(TraceMath *input);
DataType getDataType() const;
virtual std::vector<Data>& rData() { return data;}
virtual std::vector<Data> getData();
Status getStatus() const;
QString getStatusDescription() const;
virtual Type getType() = 0;
@ -120,6 +121,8 @@ public:
TraceMath *getInput() const;
QMutex& mutex();
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)}
@ -137,6 +140,7 @@ protected:
void warning(QString warn);
void error(QString err);
void success();
QMutex dataMutex;
std::vector<Data> data;
// buffer for time domain step response data. This makes it possible to access an arbitrary sample of the step response without having to
// integrate the impulse response every time. Call updateStepResponse in your derived class, if step response data is valid after updating

View file

@ -1355,7 +1355,7 @@ void Trace::clearDeembedding()
double Trace::minX()
{
if(lastMath->numSamples() > 0) {
return lastMath->rData().front().x;
return lastMath->getData().front().x;
} else {
return numeric_limits<double>::max();
}
@ -1364,7 +1364,7 @@ double Trace::minX()
double Trace::maxX()
{
if(lastMath->numSamples() > 0) {
return lastMath->rData().back().x;
return lastMath->getData().back().x;
} else {
return numeric_limits<double>::lowest();
}
@ -1374,7 +1374,7 @@ double Trace::findExtremum(bool max, double xmin, double xmax)
{
double compare = max ? numeric_limits<double>::min() : numeric_limits<double>::max();
double freq = 0.0;
for(auto sample : lastMath->rData()) {
for(auto sample : lastMath->getData()) {
if(sample.x < xmin || sample.x > xmax) {
continue;
}
@ -1405,7 +1405,7 @@ std::vector<double> Trace::findPeakFrequencies(unsigned int maxPeaks, double min
double frequency = 0.0;
double max_dbm = -200.0;
double min_dbm = 200.0;
for(auto d : lastMath->rData()) {
for(auto d : lastMath->getData()) {
if(d.x < xmin || d.x > xmax) {
continue;
}
@ -1517,12 +1517,12 @@ unsigned int Trace::numSamples()
}
}
std::vector<Trace::Data> &Trace::rData()
std::vector<Trace::Data> Trace::getData()
{
if(deembeddingActive && deembeddingAvailable()) {
return deembeddingData;
} else {
return TraceMath::rData();
return TraceMath::getData();
}
}
@ -1620,12 +1620,12 @@ double Trace::getGroupDelay(double frequency)
int Trace::index(double x)
{
auto lower = lower_bound(lastMath->rData().begin(), lastMath->rData().end(), x, [](const Data &lhs, const double x) -> bool {
auto lower = lower_bound(lastMath->getData().begin(), lastMath->getData().end(), x, [](const Data &lhs, const double x) -> bool {
return lhs.x < x;
});
if(lower == lastMath->rData().end()) {
if(lower == lastMath->getData().end()) {
// actually beyond the last sample, return the index of the last anyway to avoid access past data
return lastMath->rData().size() - 1;
return lastMath->getData().size() - 1;
}
return lower - lastMath->rData().begin();
return lower - lastMath->getData().begin();
}

View file

@ -95,7 +95,7 @@ public:
virtual Data getSample(unsigned int index) override;
virtual Data getInterpolatedSample(double x) override;
virtual unsigned int numSamples() override;
virtual std::vector<Data>& rData() override;
virtual std::vector<Data> getData() override;
double getUnwrappedPhase(unsigned int index);
// returns a (possibly interpolated sample) at a specified frequency/time/power

View file

@ -283,13 +283,14 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
} else {
// composite operation added, check which one and edit the correct suboperation
switch(type) {
case TraceMath::Type::TimeDomainGating:
case TraceMath::Type::TimeDomainGating: {
auto inputData = newMath[0]->getInput()->getData();
// Automatically select bandpass/lowpass TDR, depending on selected span
if(newMath[0]->getInput()->rData().size() > 0) {
if(inputData.size() > 0) {
// Automatically select bandpass/lowpass TDR, depending on selected span
auto tdr = (Math::TDR*) newMath[0];
auto fstart = tdr->getInput()->rData().front().x;
auto fstop = tdr->getInput()->rData().back().x;
auto fstart = inputData.front().x;
auto fstop = inputData.back().x;
if(fstart < fstop / 100.0) {
tdr->setMode(Math::TDR::Mode::Lowpass);
@ -301,6 +302,7 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
// TDR/DFT can be left at default, edit the actual gate
newMath[1]->edit();
}
break;
default:
break;

View file

@ -151,6 +151,7 @@ SOURCES += \
../LibreVNA-GUI/streamingserver.cpp \
../LibreVNA-GUI/touchstone.cpp \
../LibreVNA-GUI/unit.cpp \
ffttests.cpp \
main.cpp \
parametertests.cpp \
portextensiontests.cpp \
@ -344,6 +345,7 @@ HEADERS += \
../LibreVNA-GUI/streamingserver.h \
../LibreVNA-GUI/touchstone.h \
../LibreVNA-GUI/unit.h \
ffttests.h \
parametertests.h \
portextensiontests.h \
utiltests.h

View file

@ -0,0 +1,59 @@
#include "ffttests.h"
#include "Traces/fftcomplex.h"
using namespace std;
fftTests::fftTests() {}
static bool compareComplexVectors(const vector<complex<double>> &v1, const vector<complex<double>> &v2) {
if(v1.size() != v2.size()) {
return false;
}
for(unsigned int i=0;i<v1.size();i++) {
if(abs(v1[i].real() - v2[i].real()) > 1e-14) {
return false;
}
if(abs(v1[i].imag() - v2[i].imag()) > 1e-14) {
return false;
}
}
return true;
}
void fftTests::fft()
{
vector<complex<double>> data{1, 2, 3, 4, 5};
Fft::transform(data, false);
vector<complex<double>> expectedResult{15, complex(-2.5, 3.440954801177934), complex(-2.5, 0.812299240582265), complex(-2.5, -0.812299240582265), complex(-2.5, -3.440954801177934)};
QVERIFY(compareComplexVectors(data, expectedResult));
}
void fftTests::fftAndIfft()
{
vector<complex<double>> data{1, 2, 3, 4, 5};
vector<complex<double>> expectedResult{1, 2, 3, 4, 5};
Fft::transform(data, false);
Fft::transform(data, true);
for(auto &d : data) {
d /= data.size();
}
QVERIFY(compareComplexVectors(data, expectedResult));
}
void fftTests::ifftAndFft()
{
vector<complex<double>> data{1, 2, 3, 4, 5};
vector<complex<double>> expectedResult{1, 2, 3, 4, 5};
Fft::transform(data, true);
for(auto &d : data) {
d /= data.size();
}
Fft::transform(data, false);
QVERIFY(compareComplexVectors(data, expectedResult));
}
void fftTests::fftAndIfftWithShift()
{
}

View file

@ -0,0 +1,19 @@
#ifndef FFTTESTS_H
#define FFTTESTS_H
#include <QtTest>
class fftTests : public QObject
{
Q_OBJECT
public:
fftTests();
private slots:
void fft();
void fftAndIfft();
void ifftAndFft();
void fftAndIfftWithShift();
};
#endif // FFTTESTS_H

View file

@ -1,6 +1,7 @@
#include "utiltests.h"
#include "portextensiontests.h"
#include "parametertests.h"
#include "ffttests.h"
#include <QtTest>
@ -12,6 +13,7 @@ int main(int argc, char *argv[])
status |= QTest::qExec(new UtilTests, argc, argv);
status |= QTest::qExec(new PortExtensionTests, argc, argv);
status |= QTest::qExec(new ParameterTests, argc, argv);
status |= QTest::qExec(new fftTests, argc, argv);
return status;
}