Add reference spectrum to equalization plots and more plotting enhancements

Use  a header  format for  polynomial coefficients  that includes  the
valid  X  range  in  scaled  terms  and  a  count  of  the  number  of
coefficients.

Use double  precision consistently  for polynomial  coefficients. This
includes formatting with sufficient DPs when writing to files.

Many changes to the equalization plots, more to come.

Add  error   handling  for   reading  coefficient,  plot   and  filter
files.  This  includes  being  backward  compatible  for  old  format
refspec.dat files with no header.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7578 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
Bill Somerville 2017-02-23 16:21:26 +00:00
parent b7637d9740
commit 9f3a50531c
9 changed files with 217 additions and 119 deletions

View File

@ -8,7 +8,7 @@
#include <QDir>
#include <QVector>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
@ -63,8 +63,8 @@ namespace
{
return plot_data_loader<QCPGraphData, decltype (adjust)> {plot, index, adjust};
}
// identity adjust function when none is needed in the above
// instantiation helper
// identity adjust function when no adjustment is needed with the
// above instantiation helper
QCPGraphData adjust_identity (QCustomPlot *, int, QCPGraphData const& v) {return v;}
// a plot_data_loader adjustment function that wraps Y values of
@ -219,16 +219,6 @@ namespace
auto pi_scaling = [] (float v) -> float {return v / PI;};
}
// read a phase point line from a stream (pcoeff file)
std::istream& operator >> (std::istream& is, QCPGraphData& v)
{
float pp, sigmay; // discard these
is >> v.key >> pp >> v.value >> sigmay;
v.key = 1500. + 1000. * v.key; // scale frequency to Hz
v.value /= PI; // scale to units of Pi
return is;
}
class PhaseEqualizationDialog::impl final
: public QDialog
{
@ -236,7 +226,7 @@ class PhaseEqualizationDialog::impl final
public:
explicit impl (PhaseEqualizationDialog * self, QSettings * settings
, QDir const& data_directory, QVector<float> const& coefficients
, QDir const& data_directory, QVector<double> const& coefficients
, QWidget * parent);
~impl () {save_window_state ();}
@ -255,14 +245,18 @@ private:
}
void plot_current ();
void plot ();
void plot_phase ();
void plot_amplitude ();
PhaseEqualizationDialog * self_;
QSettings * settings_;
QDir data_directory_;
QVBoxLayout layout_;
QVector<float> current_coefficients_;
QVector<float> new_coefficients_;
QHBoxLayout layout_;
QVector<double> current_coefficients_;
QVector<double> new_coefficients_;
unsigned amp_poly_low_;
unsigned amp_poly_high_;
QVector<double> amp_coefficients_;
QCustomPlot plot_;
QDialogButtonBox button_box_;
};
@ -271,7 +265,7 @@ private:
PhaseEqualizationDialog::PhaseEqualizationDialog (QSettings * settings
, QDir const& data_directory
, QVector<float> const& coefficients
, QVector<double> const& coefficients
, QWidget * parent)
: m_ {this, settings, data_directory, coefficients, parent}
{
@ -285,15 +279,18 @@ void PhaseEqualizationDialog::show ()
PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self
, QSettings * settings
, QDir const& data_directory
, QVector<float> const& coefficients
, QVector<double> const& coefficients
, QWidget * parent)
: QDialog {parent}
, self_ {self}
, settings_ {settings}
, data_directory_ {data_directory}
, current_coefficients_ {coefficients}
, amp_poly_low_ {0}
, amp_poly_high_ {6000}
, button_box_ {QDialogButtonBox::Discard | QDialogButtonBox::Apply
| QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Close}
| QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Close
, Qt::Vertical}
{
setWindowTitle (windowTitle () + ' ' + tr (title));
resize (500, 600);
@ -301,7 +298,11 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self
SettingsGroup g {settings_, title};
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
}
layout_.addWidget (&plot_);
auto legend_title = new QCPTextElement {&plot_, tr ("Phase"), QFont {"sans", 9, QFont::Bold}};
legend_title->setLayer (plot_.legend->layer ());
plot_.legend->addElement (0, 0, legend_title);
plot_.legend->setVisible (true);
plot_.xAxis->setLabel (tr ("Freq (Hz)"));
plot_.xAxis->setRange (500, 2500);
@ -311,41 +312,68 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self
plot_.axisRect ()->setRangeDrag (Qt::Vertical);
plot_.axisRect ()->setRangeZoom (Qt::Vertical);
plot_.yAxis2->setVisible (true);
plot_.axisRect ()->setRangeDragAxes (0, plot_.yAxis2);
plot_.axisRect ()->setRangeZoomAxes (0, plot_.yAxis2);
plot_.axisRect ()->setRangeDragAxes (nullptr, plot_.yAxis2);
plot_.axisRect ()->setRangeZoomAxes (nullptr, plot_.yAxis2);
plot_.axisRect ()->insetLayout ()->setInsetAlignment (0, Qt::AlignBottom|Qt::AlignRight);
plot_.legend->setVisible (true);
plot_.setInteractions (QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
plot_.addGraph ();
plot_.graph ()->setName (tr ("Measured"));
plot_.addGraph ()->setName (tr ("Measured"));
plot_.graph ()->setPen (QPen {Qt::blue});
plot_.graph ()->setVisible (false);
plot_.graph ()->removeFromLegend ();
plot_.addGraph ();
plot_.graph ()->setName (tr ("Proposed"));
plot_.addGraph ()->setName (tr ("Proposed"));
plot_.graph ()->setPen (QPen {Qt::red});
plot_.graph ()->setVisible (false);
plot_.graph ()->removeFromLegend ();
plot_.addGraph ();
plot_.graph ()->setName (tr ("Current"));
plot_.addGraph ()->setName (tr ("Current"));
plot_.graph ()->setPen (QPen {Qt::green});
plot_.addGraph (plot_.xAxis, plot_.yAxis2);
plot_.graph ()->setName (tr ("Group Delay"));
plot_.addGraph (plot_.xAxis, plot_.yAxis2)->setName (tr ("Group Delay"));
plot_.graph ()->setPen (QPen {Qt::darkGreen});
auto load_button = button_box_.addButton (tr ("Load ..."), QDialogButtonBox::ActionRole);
plot_.plotLayout ()->addElement (new QCPAxisRect {&plot_});
plot_.plotLayout ()->setRowStretchFactor (1, 0.5);
auto amp_legend = new QCPLegend;
plot_.axisRect (1)->insetLayout ()->addElement (amp_legend, Qt::AlignTop | Qt::AlignRight);
plot_.axisRect (1)->insetLayout ()->setMargins (QMargins {12, 12, 12, 12});
amp_legend->setVisible (true);
amp_legend->setLayer (QLatin1String {"legend"});
legend_title = new QCPTextElement {&plot_, tr ("Amplitude"), QFont {"sans", 9, QFont::Bold}};
legend_title->setLayer (amp_legend->layer ());
amp_legend->addElement (0, 0, legend_title);
plot_.axisRect (1)->axis (QCPAxis::atBottom)->setLabel (tr ("Freq (Hz)"));
plot_.axisRect (1)->axis (QCPAxis::atBottom)->setRange (0, 6000);
plot_.axisRect (1)->axis (QCPAxis::atLeft)->setLabel (tr ("Relative Power (dB)"));
plot_.axisRect (1)->axis (QCPAxis::atLeft)->setRangeLower (0);
plot_.axisRect (1)->setRangeDragAxes (nullptr, nullptr);
plot_.axisRect (1)->setRangeZoomAxes (nullptr, nullptr);
plot_.addGraph (plot_.axisRect (1)->axis (QCPAxis::atBottom)
, plot_.axisRect (1)->axis (QCPAxis::atLeft))->setName (tr ("Reference"));
plot_.graph ()->setPen (QPen {Qt::blue});
plot_.graph ()->removeFromLegend ();
plot_.graph ()->addToLegend (amp_legend);
layout_.addWidget (&plot_);
auto load_phase_button = button_box_.addButton (tr ("Phase ..."), QDialogButtonBox::ActionRole);
auto refresh_button = button_box_.addButton (tr ("Refresh"), QDialogButtonBox::ActionRole);
layout_.addWidget (&button_box_);
setLayout (&layout_);
connect (&button_box_, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect (&button_box_, &QDialogButtonBox::clicked, [this, load_button] (QAbstractButton * button) {
if (button == load_button)
connect (&button_box_, &QDialogButtonBox::clicked, [=] (QAbstractButton * button) {
if (button == load_phase_button)
{
plot ();
plot_phase ();
}
else if (button == refresh_button)
{
plot_current ();
}
else if (button == button_box_.button (QDialogButtonBox::Apply))
{
@ -358,13 +386,13 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self
}
else if (button == button_box_.button (QDialogButtonBox::RestoreDefaults))
{
current_coefficients_ = QVector<float> {0., 0., 0., 0., 0.};
current_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};
Q_EMIT self_->phase_equalization_changed (current_coefficients_);
plot_current ();
}
else if (button == button_box_.button (QDialogButtonBox::Discard))
{
new_coefficients_ = QVector<float> {0., 0., 0., 0., 0.};
new_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};
plot_.graph (0)->data ()->clear ();
plot_.graph (0)->setVisible (false);
@ -381,68 +409,136 @@ PhaseEqualizationDialog::impl::impl (PhaseEqualizationDialog * self
plot_current ();
}
struct PowerSpectrumPoint
{
operator QCPGraphData () const
{
return QCPGraphData {freq_, power_};
}
float freq_;
float power_;
};
// read an amplitude point line from a stream (refspec.dat)
std::istream& operator >> (std::istream& is, PowerSpectrumPoint& r)
{
float y1, y3, y4; // discard these
is >> r.freq_ >> y1 >> r.power_ >> y3 >> y4;
return is;
}
void PhaseEqualizationDialog::impl::plot_current ()
{
auto phase_graph = make_plot_data_loader (&plot_, 2, wrap_pi);
plot_.graph (2)->data ()->clear ();
plot_.graph (3)->data ()->clear ();
{
// plot the current polynomial
auto graph = make_plot_data_loader (&plot_, 2, wrap_pi);
std::generate_n (std::back_inserter (graph), intervals + 1
std::generate_n (std::back_inserter (phase_graph), intervals + 1
, make_graph_generator (make_polynomial (current_coefficients_), freq_scaling, pi_scaling));
}
{
// plot the group delay for the current polynomial
auto graph = make_plot_data_loader (&plot_, 3, adjust_identity);
std::generate_n (std::back_inserter (graph), intervals + 1
, make_graph_generator (make_group_delay (current_coefficients_), freq_scaling, identity<float>));
auto group_delay_graph = make_plot_data_loader (&plot_, 3, adjust_identity);
plot_.graph (3)->data ()->clear ();
std::generate_n (std::back_inserter (group_delay_graph), intervals + 1
, make_graph_generator (make_group_delay (current_coefficients_), freq_scaling, identity<double>));
plot_.graph (3)->rescaleValueAxis ();
QFileInfo refspec_file_info {data_directory_.absoluteFilePath ("refspec.dat")};
std::ifstream refspec_file (refspec_file_info.absoluteFilePath ().toLatin1 ().constData (), std::ifstream::in);
unsigned n;
if (refspec_file >> amp_poly_low_ >> amp_poly_high_ >> n)
{
std::istream_iterator<double> isi {refspec_file};
amp_coefficients_.clear ();
std::copy_n (isi, n, std::back_inserter (amp_coefficients_));
}
else
{
// may be old format refspec.dat with no header so rewind
refspec_file.clear ();
refspec_file.seekg (0);
}
auto reference_spectrum_graph = make_plot_data_loader (&plot_, 4, adjust_identity);
plot_.graph (4)->data ()->clear ();
std::copy (std::istream_iterator<PowerSpectrumPoint> {refspec_file},
std::istream_iterator<PowerSpectrumPoint> {},
std::back_inserter (reference_spectrum_graph));
plot_.graph (4)->rescaleValueAxis (true);
plot_.replot ();
}
void PhaseEqualizationDialog::impl::plot ()
struct PhasePoint
{
auto const& name = QFileDialog::getOpenFileName (this
operator QCPGraphData () const
{
return QCPGraphData {freq_, phase_};
}
double freq_;
double phase_;
};
// read a phase point line from a stream (pcoeff file)
std::istream& operator >> (std::istream& is, PhasePoint& c)
{
double pp, sigmay; // discard these
if (is >> c.freq_ >> pp >> c.phase_ >> sigmay)
{
c.freq_ = 1500. + 1000. * c.freq_; // scale frequency to Hz
c.phase_ /= PI; // scale to units of Pi
}
return is;
}
void PhaseEqualizationDialog::impl::plot_phase ()
{
auto const& phase_file_name = QFileDialog::getOpenFileName (this
, "Select Phase Response Coefficients"
, data_directory_.absolutePath ()
, "Phase Coefficient Files (*.pcoeff)");
if (name.size ())
{
std::ifstream coeffs_source (name.toLatin1 ().constData (), std::ifstream::in);
if (!phase_file_name.size ()) return;
std::ifstream phase_file (phase_file_name.toLatin1 ().constData (), std::ifstream::in);
int n;
float chi;
float rmsdiff;
unsigned freq_low;
unsigned freq_high;
unsigned terms;
// read header information
coeffs_source >> n >> chi >> rmsdiff;
if (phase_file >> n >> chi >> rmsdiff >> freq_low >> freq_high >> terms)
{
std::istream_iterator<float> isi {coeffs_source};
std::istream_iterator<double> isi {phase_file};
new_coefficients_.clear ();
std::copy_n (isi, 5, std::back_inserter (new_coefficients_));
}
std::copy_n (isi, terms, std::back_inserter (new_coefficients_));
if (phase_file)
{
plot_.graph (0)->data ()->clear ();
plot_.graph (1)->data ()->clear ();
{
// read the phase data to plot into graph 0
// read the phase data and plot as graph 0
auto graph = make_plot_data_loader (&plot_, 0, adjust_identity);
std::istream_iterator<QCPGraphData> start {coeffs_source};
std::copy_n (start, intervals + 1, std::back_inserter (graph));
}
std::copy_n (std::istream_iterator<PhasePoint> {phase_file},
intervals + 1, std::back_inserter (graph));
if (phase_file)
{
// generate the proposed polynomial plot in graph 1
auto graph = make_plot_data_loader (&plot_, 1, wrap_pi);
std::generate_n (std::back_inserter (graph), intervals + 1
, make_graph_generator (make_polynomial (new_coefficients_), freq_scaling, pi_scaling));
}
plot_.graph (0)->setVisible (true);
plot_.graph (0)->addToLegend ();
// generate the proposed polynomial plot as graph 1
auto graph = make_plot_data_loader (&plot_, 1, wrap_pi);
std::generate_n (std::back_inserter (graph), intervals + 1
, make_graph_generator (make_polynomial (new_coefficients_)
, freq_scaling, pi_scaling));
plot_.graph (1)->setVisible (true);
plot_.graph (1)->addToLegend ();
}
plot_.replot ();
}
}
}
#include "moc_PhaseEqualizationDialog.cpp"

View File

@ -17,11 +17,11 @@ class PhaseEqualizationDialog
public:
explicit PhaseEqualizationDialog (QSettings *
, QDir const& data_directory
, QVector<float> const& coefficients
, QVector<double> const& coefficients
, QWidget * = nullptr);
Q_SLOT void show ();
Q_SIGNAL void phase_equalization_changed (QVector<float> const&);
Q_SIGNAL void phase_equalization_changed (QVector<double> const&);
private:
class impl;

View File

@ -6,9 +6,9 @@ subroutine analytic(d,npts,nfft,c,pc,beq)
real d(npts) ! passband signal
real h(NFFTMAX/2) ! real BPF magnitude
real pc(5),pclast(5) ! static phase coeffs
real ac(5),aclast(5) ! amp coeffs
real fp
real*8 pc(5),pclast(5) ! static phase coeffs
real*8 ac(5),aclast(5) ! amp coeffs
real*8 fp
complex corr(NFFTMAX/2) ! complex frequency-dependent correction
complex c(NFFTMAX) ! analytic signal

View File

@ -27,7 +27,7 @@ subroutine hspec(id2,k,nutc0,ntrpdepth,nrxfreq,ntol,bmsk144,bcontest, &
real green(0:JZ-1)
real s(0:63,0:JZ-1)
real x(512)
real pcoeffs(5)
real*8 pcoeffs(5)
complex cx(0:256)
data rms/999.0/,k0/99999999/
equivalence (x,cx)

View File

@ -36,8 +36,9 @@ subroutine msk144signalquality(cframe,snr,freq,t0,softbits,msg,dxcall, &
real phase(864)
real twopi,freq,phi,dphi0,dphi1,dphi
real*8 x(145),y(145),pp(145),sigmay(145),a(5),chisqr
real pcoeffs(5)
real*8 pcoeffs(5)
parameter (NFREQLOW=500,NFREQHIGH=2500)
data first/.true./
save cross_avg,wt_avg,first,currently_training, &
navg,tlast,training_dxcall,trained_dxcall
@ -202,7 +203,7 @@ write(*,*) 'training ',navg,sqrt(chisqr),rmsdiff
pcoeff_filename=datadir(1:l1+1)//trim(pcoeff_filename)
write(*,*) 'trained - writing coefficients to: ',pcoeff_filename
open(17,file=pcoeff_filename,status='new')
write(17,'(i4,2f10.2,5f10.4)') navg,sqrt(chisqr),rmsdiff,a(1),a(2),a(3),a(4),a(5)
write(17,'(i4,2f10.2,3i5,5e25.16)') navg,sqrt(chisqr),rmsdiff,NFREQLOW,NFREQHIGH,nterms,a
do i=1, 145
write(17,*) x(i),pp(i),y(i),sigmay(i)
enddo

View File

@ -37,7 +37,7 @@ subroutine mskrtd(id2,nutc0,tsec,ntol,nrxfreq,ndepth,mycall,mygrid,hiscall, &
real pow(8)
real softbits(144)
real xmc(NPATTERNS)
real pcoeffs(5)
real*8 pcoeffs(5)
logical*1 bshmsg,bcontest,btrain,bswl
logical*1 first

View File

@ -4,9 +4,9 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
! id2 i*2 Raw 16-bit integer data, 12000 Hz sample rate
! brefspec logical True when accumulating a reference spectrum
parameter (NFFT=6912,NH=NFFT/2)
parameter (NFFT=6912,NH=NFFT/2,NPOLYLOW=400,NPOLYHIGH=2600)
integer*2 id2(NFFT)
logical*1 bclear,brefspec,buseref
logical*1 bclear,brefspec,buseref,blastuse
real x0(0:NH-1) !Input samples
real x1(0:NH-1) !Output samples (delayed by one block)
@ -17,12 +17,12 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
real*4 s(0:NH) !Average spectrum
real*4 fil(0:NH)
real*8 xfit(1500),yfit(1500),sigmay(1500),a(5),chisqr !Polyfit arrays
logical first,firstuse
logical first
complex cx(0:NH) !Complex frequency-domain work array
character*(*) fname
common/spectra/syellow(6827),ref(0:NH),filter(0:NH)
equivalence(x,cx)
data first/.true./,firstuse/.true./
data first/.true./,blastuse/.false./
save
if(first) then
@ -98,8 +98,8 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
if(s(i).gt.0.0) filter(i)=20.0*log10(fil(i))
enddo
il=nint(400.0/df)
ih=nint(2600.0/df)
il=nint(NPOLYLOW/df)
ih=nint(NPOLYHIGH/df)
nfit=ih-il+1
mode=0
nterms=5
@ -111,8 +111,8 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
call polyfit(xfit,yfit,sigmay,nfit,nterms,mode,a,chisqr)
open(16,file=fname,status='unknown')
write(16,1003) a
1003 format(5f10.4)
write(16,1003) NPOLYLOW,NPOLYHIGH,nterms,a
1003 format(3i5,5e25.16)
do i=1,NH
freq=i*df
ref(i)=db(s(i)/avemid)
@ -125,17 +125,18 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
endif
if(buseref) then
if(firstuse) then
if(blastuse.neqv.buseref) then !just enabled so read filter
fil=1.0
open(16,file=fname,status='old',err=10)
read(16,1003,err=10,end=10) a
do i=1,NH
read(16,1005,err=10,end=10) freq,s(i),ref(i),fil(i),filter(i)
open(16,file=fname,status='old',err=110)
read(16,1003,err=20,end=100) ndummy,ndummy,nterms,a
goto 30
20 rewind(16) !allow for old style refspec.dat with no header
30 do i=1,NH
read(16,1005,err=100,end=100) freq,s(i),ref(i),fil(i),filter(i)
enddo
10 close(16)
firstuse=.false.
100 close(16)
110 continue
endif
! x0=id2(NH+1:NFFT)
x0=id2(1:NH)
x(0:NH-1)=x0s !Previous 2nd half to new 1st half
x(NH:NFFT-1)=x0 !New 2nd half
@ -148,6 +149,6 @@ subroutine refspectrum(id2,bclear,brefspec,buseref,fname)
id2(1:NH)=nint(x1)
x1s=x(NH:NFFT-1) !Save the new 2nd half
endif
blastuse=buseref
return
end subroutine refspectrum

View File

@ -69,7 +69,7 @@ extern "C" {
int* minw, float* px, float s[], float* df3, int* nhsym, int* npts8);
void hspec_(short int d2[], int* k, int* nutc0, int* ntrperiod, int* nrxfreq, int* ntol,
bool* bmsk144, bool* bcontest, bool* btrain, float const pcoeffs[], int* ingain,
bool* bmsk144, bool* bcontest, bool* btrain, double const pcoeffs[], int* ingain,
char mycall[], char hiscall[], bool* bshmsg, bool* bswl, char ddir[], float green[],
float s[], int* jh, char line[], char mygrid[],
int len1, int len2, int len3, int len4, int len5);
@ -515,7 +515,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
{
m_phaseEqualizationDialog.reset (new PhaseEqualizationDialog {m_settings, m_dataDir, m_phaseEqCoefficients, this});
connect (m_phaseEqualizationDialog.data (), &PhaseEqualizationDialog::phase_equalization_changed,
[this] (QVector<float> const& coeffs) {
[this] (QVector<double> const& coeffs) {
m_phaseEqCoefficients = coeffs;
});
}
@ -986,7 +986,7 @@ void MainWindow::writeSettings()
QList<QVariant> coeffs; // suitable for QSettings
for (auto const& coeff : m_phaseEqCoefficients)
{
coeffs << static_cast<double> (coeff);
coeffs << coeff;
}
m_settings->setValue ("PhaseEqualizationCoefficients", QVariant {coeffs});
}

View File

@ -542,7 +542,7 @@ private:
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
QHash<QString, QVariant> m_pwrBandTuneMemory; // Remembers power level by band for tuning
QByteArray m_geometryNoControls;
QVector<float> m_phaseEqCoefficients;
QVector<double> m_phaseEqCoefficients;
//---------------------------------------------------- private functions
void readSettings();