mirror of
https://github.com/jankae/LibreVNA.git
synced 2026-01-29 11:54:19 +01:00
add mutex for trace data (fixes DFT/TDR thread crashes)
This commit is contained in:
parent
acb79fa3bc
commit
70ac53aa75
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public slots:
|
|||
|
||||
private:
|
||||
void updateDFT();
|
||||
void clearOutput();
|
||||
bool automaticDC;
|
||||
double DCfreq;
|
||||
WindowFunction window;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public slots:
|
|||
|
||||
private:
|
||||
void updateTDR();
|
||||
void clearOutput();
|
||||
Mode mode;
|
||||
WindowFunction window;
|
||||
unsigned int padding;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
59
Software/PC_Application/LibreVNA-Test/ffttests.cpp
Normal file
59
Software/PC_Application/LibreVNA-Test/ffttests.cpp
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
19
Software/PC_Application/LibreVNA-Test/ffttests.h
Normal file
19
Software/PC_Application/LibreVNA-Test/ffttests.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue