WIP: TDR/DFT improvements

This commit is contained in:
Jan Käberich 2024-12-16 22:21:33 +01:00
parent 4725942727
commit acb79fa3bc
6 changed files with 147 additions and 51 deletions

View file

@ -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();
}
}
}

View file

@ -48,6 +48,8 @@ private:
bool automaticDC;
double DCfreq;
WindowFunction window;
bool removePaddingFromTDR;
bool revertWindowFromTDR;
DFTThread *thread;
bool destructing;
QSemaphore semphr;

View file

@ -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>

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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>