mirror of
https://github.com/jankae/LibreVNA.git
synced 2026-01-21 08:00:23 +01:00
WIP: TDR/DFT improvements
This commit is contained in:
parent
4725942727
commit
acb79fa3bc
|
|
@ -17,6 +17,8 @@ using namespace std;
|
|||
Math::DFT::DFT()
|
||||
{
|
||||
automaticDC = true;
|
||||
removePaddingFromTDR = true;
|
||||
revertWindowFromTDR = true;
|
||||
DCfreq = 1000000000.0;
|
||||
|
||||
destructing = false;
|
||||
|
|
@ -67,12 +69,23 @@ void Math::DFT::edit()
|
|||
ui->windowBox->setLayout(new QVBoxLayout);
|
||||
ui->windowBox->layout()->addWidget(window.createEditor());
|
||||
|
||||
connect(ui->DCautomatic, &QRadioButton::toggled, [=](bool automatic){
|
||||
connect(ui->removePadding, &QCheckBox::toggled, this, [=](bool remove){
|
||||
removePaddingFromTDR = remove;
|
||||
});
|
||||
|
||||
connect(ui->revertWindow, &QCheckBox::toggled, this, [=](bool revert){
|
||||
revertWindowFromTDR = revert;
|
||||
});
|
||||
|
||||
connect(ui->DCautomatic, &QRadioButton::toggled, this, [=](bool automatic){
|
||||
automaticDC = automatic;
|
||||
ui->freq->setEnabled(!automatic);
|
||||
updateDFT();
|
||||
});
|
||||
|
||||
ui->removePadding->setChecked(removePaddingFromTDR);
|
||||
ui->revertWindow->setChecked(revertWindowFromTDR);
|
||||
|
||||
if(automaticDC) {
|
||||
ui->DCautomatic->setChecked(true);
|
||||
} else {
|
||||
|
|
@ -84,7 +97,7 @@ void Math::DFT::edit()
|
|||
ui->freq->setPrefixes(" kMG");
|
||||
ui->freq->setValue(DCfreq);
|
||||
|
||||
connect(ui->freq, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
connect(ui->freq, &SIUnitEdit::valueChanged, this, [=](double newval){
|
||||
DCfreq = newval;
|
||||
updateDFT();
|
||||
});
|
||||
|
|
@ -111,6 +124,8 @@ nlohmann::json Math::DFT::toJSON()
|
|||
nlohmann::json j;
|
||||
j["automatic_DC"] = automaticDC;
|
||||
j["window"] = window.toJSON();
|
||||
j["removePadding"] = removePaddingFromTDR;
|
||||
j["revertWindow"] = revertWindowFromTDR;
|
||||
if(!automaticDC) {
|
||||
j["DC"] = DCfreq;
|
||||
}
|
||||
|
|
@ -124,6 +139,8 @@ void Math::DFT::fromJSON(nlohmann::json j)
|
|||
if(j.contains("window")) {
|
||||
window.fromJSON(j["window"]);
|
||||
}
|
||||
removePaddingFromTDR = j.value("removePadding", true);
|
||||
revertWindowFromTDR = j.value("revertWindow", true);
|
||||
}
|
||||
|
||||
void Math::DFT::inputSamplesChanged(unsigned int begin, unsigned int end)
|
||||
|
|
@ -169,38 +186,33 @@ void Math::DFTThread::run()
|
|||
qDebug() << "DFT thread exiting";
|
||||
return;
|
||||
}
|
||||
// limit update rate if configured in preferences
|
||||
auto &p = Preferences::getInstance();
|
||||
if(p.Acquisition.limitDFT) {
|
||||
std::this_thread::sleep_until(lastCalc + duration<double>(1.0 / p.Acquisition.maxDFTrate));
|
||||
lastCalc = system_clock::now();
|
||||
}
|
||||
// qDebug() << "DFT thread calculating";
|
||||
if(!dft.input) {
|
||||
// not connected, skip calculation
|
||||
continue;
|
||||
}
|
||||
double DC = dft.DCfreq;
|
||||
TDR *tdr = nullptr;
|
||||
if(dft.automaticDC) {
|
||||
// find the last operation that transformed from the frequency domain to the time domain
|
||||
auto in = dft.input;
|
||||
while(in->getInput()->getDataType() != DFT::DataType::Frequency) {
|
||||
in = dft.input->getInput();
|
||||
}
|
||||
switch(in->getType()) {
|
||||
case DFT::Type::TDR: {
|
||||
tdr = static_cast<TDR*>(in);
|
||||
if(tdr->getMode() == TDR::Mode::Lowpass) {
|
||||
DC = 0;
|
||||
} else {
|
||||
// bandpass mode, assume DC is in the middle of the frequency data
|
||||
DC = tdr->getInput()->getSample(tdr->getInput()->numSamples()/2).x;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// unknown, assume DC is in the middle of the frequency data
|
||||
DC = in->getInput()->getSample(in->getInput()->numSamples()/2).x;
|
||||
// find the last TDR operation
|
||||
auto in = dft.input;
|
||||
while(in->getType() != DFT::Type::TDR) {
|
||||
in = dft.input->getInput();
|
||||
if(!in) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(in) {
|
||||
tdr = static_cast<TDR*>(in);
|
||||
}
|
||||
|
||||
if(tdr && dft.automaticDC) {
|
||||
if(tdr->getMode() == TDR::Mode::Lowpass) {
|
||||
DC = 0;
|
||||
} else {
|
||||
// bandpass mode, assume DC is in the middle of the frequency data
|
||||
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;
|
||||
vector<complex<double>> timeDomain(samples);
|
||||
|
|
@ -208,14 +220,34 @@ void Math::DFTThread::run()
|
|||
timeDomain.at(i) = dft.input->rData()[i].y;
|
||||
}
|
||||
|
||||
Fft::shift(timeDomain, false);
|
||||
dft.window.apply(timeDomain);
|
||||
Fft::shift(timeDomain, true);
|
||||
Fft::transform(timeDomain, false);
|
||||
// shift DC bin into the middle
|
||||
Fft::shift(timeDomain, false);
|
||||
|
||||
double binSpacing = 1.0 / (timeSpacing * timeDomain.size());
|
||||
|
||||
if(tdr) {
|
||||
// split in padding and actual data sections
|
||||
unsigned int 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());
|
||||
|
||||
if(dft.revertWindowFromTDR) {
|
||||
tdr->getWindow().reverse(data);
|
||||
}
|
||||
|
||||
if(dft.removePaddingFromTDR) {
|
||||
timeDomain = data;
|
||||
} else {
|
||||
// include padding
|
||||
timeDomain = pad_front;
|
||||
copy(data.begin(), data.end(), back_inserter(timeDomain));
|
||||
copy(pad_back.begin(), pad_back.end(), back_inserter(timeDomain));
|
||||
}
|
||||
}
|
||||
|
||||
dft.data.clear();
|
||||
int DCbin = timeDomain.size() / 2, startBin = 0;
|
||||
if(DC > 0) {
|
||||
|
|
@ -225,16 +257,18 @@ void Math::DFTThread::run()
|
|||
dft.data.resize(timeDomain.size()/2, TraceMath::Data());
|
||||
}
|
||||
|
||||
// reverse effect of frequency domain window function from TDR (if available)
|
||||
if(tdr) {
|
||||
tdr->getWindow().reverse(timeDomain);
|
||||
}
|
||||
|
||||
for(int i = startBin;(unsigned int) i<timeDomain.size();i++) {
|
||||
auto freq = (i - DCbin) * binSpacing + DC;
|
||||
dft.data[i - startBin].x = round(freq);
|
||||
dft.data[i - startBin].y = timeDomain.at(i);
|
||||
}
|
||||
emit dft.outputSamplesChanged(0, dft.data.size());
|
||||
|
||||
// limit update rate if configured in preferences
|
||||
auto &p = Preferences::getInstance();
|
||||
if(p.Acquisition.limitDFT) {
|
||||
std::this_thread::sleep_until(lastCalc + duration<double>(1.0 / p.Acquisition.maxDFTrate));
|
||||
lastCalc = system_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ private:
|
|||
bool automaticDC;
|
||||
double DCfreq;
|
||||
WindowFunction window;
|
||||
bool removePaddingFromTDR;
|
||||
bool revertWindowFromTDR;
|
||||
DFTThread *thread;
|
||||
bool destructing;
|
||||
QSemaphore semphr;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<class>DFTDialog</class>
|
||||
<widget class="QDialog" name="DFTDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
<enum>Qt::WindowModality::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
|
|
@ -19,7 +19,21 @@
|
|||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,1,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="removePadding">
|
||||
<property name="text">
|
||||
<string>Remove possible padding from last TDR</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="revertWindow">
|
||||
<property name="text">
|
||||
<string>Revert window function from last TDR</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
|
|
@ -83,7 +97,7 @@
|
|||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ TDR::TDR()
|
|||
manualDC = 1.0;
|
||||
stepResponse = true;
|
||||
mode = Mode::Lowpass;
|
||||
padding = 0;
|
||||
|
||||
destructing = false;
|
||||
thread = new TDRThread(*this);
|
||||
|
|
@ -86,19 +87,23 @@ void TDR::edit()
|
|||
ui->manualMag->setEnabled(enable);
|
||||
};
|
||||
|
||||
connect(ui->mode, qOverload<int>(&QComboBox::currentIndexChanged), [=](int index){
|
||||
connect(ui->mode, qOverload<int>(&QComboBox::currentIndexChanged), this, [=](int index){
|
||||
mode = (Mode) index;
|
||||
updateEnabledWidgets();
|
||||
updateTDR();
|
||||
});
|
||||
|
||||
connect(ui->computeStepResponse, &QCheckBox::toggled, [=](bool computeStep) {
|
||||
connect(ui->padding, &QSpinBox::valueChanged, this, [=](int value) {
|
||||
padding = value;
|
||||
});
|
||||
|
||||
connect(ui->computeStepResponse, &QCheckBox::toggled, this, [=](bool computeStep) {
|
||||
stepResponse = computeStep;
|
||||
updateEnabledWidgets();
|
||||
updateTDR();
|
||||
});
|
||||
|
||||
connect(ui->DCmanual, &QRadioButton::toggled, [=](bool manual) {
|
||||
connect(ui->DCmanual, &QRadioButton::toggled, this, [=](bool manual) {
|
||||
automaticDC = !manual;
|
||||
updateEnabledWidgets();
|
||||
updateTDR();
|
||||
|
|
@ -114,6 +119,8 @@ void TDR::edit()
|
|||
ui->mode->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
ui->padding->setValue(padding);
|
||||
|
||||
ui->manualMag->setUnit("dBm");
|
||||
ui->manualMag->setPrecision(3);
|
||||
ui->manualMag->setValue(Util::SparamTodB(manualDC));
|
||||
|
|
@ -121,11 +128,11 @@ void TDR::edit()
|
|||
ui->manualPhase->setPrecision(4);
|
||||
ui->manualPhase->setValue(180.0/M_PI * arg(manualDC));
|
||||
|
||||
connect(ui->manualMag, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
connect(ui->manualMag, &SIUnitEdit::valueChanged, this, [=](double newval){
|
||||
manualDC = polar(pow(10, newval / 20.0), arg(manualDC));
|
||||
updateTDR();
|
||||
});
|
||||
connect(ui->manualPhase, &SIUnitEdit::valueChanged, [=](double newval){
|
||||
connect(ui->manualPhase, &SIUnitEdit::valueChanged, this, [=](double newval){
|
||||
manualDC = polar(abs(manualDC), newval * M_PI / 180.0);
|
||||
updateTDR();
|
||||
});
|
||||
|
|
@ -152,6 +159,7 @@ nlohmann::json TDR::toJSON()
|
|||
nlohmann::json j;
|
||||
j["bandpass_mode"] = mode == Mode::Bandpass;
|
||||
j["window"] = window.toJSON();
|
||||
j["padding"] = padding;
|
||||
if(mode == Mode::Lowpass) {
|
||||
j["step_response"] = stepResponse;
|
||||
if(stepResponse) {
|
||||
|
|
@ -170,6 +178,7 @@ void TDR::fromJSON(nlohmann::json j)
|
|||
if(j.contains("window")) {
|
||||
window.fromJSON(j["window"]);
|
||||
}
|
||||
padding = j.value("padding", 0);
|
||||
if(j.value("bandpass_mode", true)) {
|
||||
mode = Mode::Bandpass;
|
||||
} else {
|
||||
|
|
@ -224,6 +233,11 @@ void TDR::updateTDR()
|
|||
}
|
||||
}
|
||||
|
||||
unsigned int TDR::getUnpaddedInputSize() const
|
||||
{
|
||||
return unpaddedInputSize;
|
||||
}
|
||||
|
||||
const WindowFunction& TDR::getWindow() const
|
||||
{
|
||||
return window;
|
||||
|
|
@ -254,14 +268,12 @@ void TDRThread::run()
|
|||
qDebug() << "TDR thread exiting";
|
||||
return;
|
||||
}
|
||||
// limit update rate if configured in preferences
|
||||
auto &p = Preferences::getInstance();
|
||||
if(p.Acquisition.limitDFT) {
|
||||
std::this_thread::sleep_until(lastCalc + duration<double>(1.0 / p.Acquisition.maxDFTrate));
|
||||
lastCalc = system_clock::now();
|
||||
}
|
||||
// qDebug() << "TDR thread calculating";
|
||||
// perform calculation
|
||||
if(!tdr.input) {
|
||||
// not connected, skip calculation
|
||||
continue;
|
||||
}
|
||||
vector<complex<double>> frequencyDomain;
|
||||
auto stepSize = (tdr.input->rData().back().x - tdr.input->rData().front().x) / (tdr.input->rData().size() - 1);
|
||||
if(tdr.mode == TDR::Mode::Lowpass) {
|
||||
|
|
@ -321,6 +333,12 @@ void TDRThread::run()
|
|||
}
|
||||
|
||||
tdr.window.apply(frequencyDomain);
|
||||
tdr.unpaddedInputSize = frequencyDomain.size();
|
||||
if(frequencyDomain.size() < tdr.padding) {
|
||||
auto missing = tdr.padding - frequencyDomain.size();
|
||||
frequencyDomain.insert(frequencyDomain.begin(), missing/2, 0);
|
||||
frequencyDomain.insert(frequencyDomain.end(), missing/2, 0);
|
||||
}
|
||||
Fft::shift(frequencyDomain, true);
|
||||
|
||||
int fft_bins = frequencyDomain.size();
|
||||
|
|
@ -341,5 +359,12 @@ void TDRThread::run()
|
|||
tdr.updateStepResponse(false);
|
||||
}
|
||||
emit tdr.outputSamplesChanged(0, tdr.data.size());
|
||||
|
||||
// limit update rate if configured in preferences
|
||||
auto &p = Preferences::getInstance();
|
||||
if(p.Acquisition.limitDFT) {
|
||||
std::this_thread::sleep_until(lastCalc + duration<double>(1.0 / p.Acquisition.maxDFTrate));
|
||||
lastCalc = system_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ public:
|
|||
Mode getMode() const;
|
||||
const WindowFunction& getWindow() const;
|
||||
|
||||
unsigned int getUnpaddedInputSize() const;
|
||||
|
||||
public slots:
|
||||
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
|
||||
|
||||
|
|
@ -55,6 +57,8 @@ private:
|
|||
void updateTDR();
|
||||
Mode mode;
|
||||
WindowFunction window;
|
||||
unsigned int padding;
|
||||
unsigned int unpaddedInputSize;
|
||||
bool stepResponse;
|
||||
bool automaticDC;
|
||||
std::complex<double> manualDC;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<class>TDRDialog</class>
|
||||
<widget class="QDialog" name="TDRDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
<enum>Qt::WindowModality::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
|
|
@ -43,6 +43,23 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Padding:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="padding">
|
||||
<property name="maximum">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
@ -129,7 +146,7 @@
|
|||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
Loading…
Reference in a new issue