summaryrefslogtreecommitdiff
path: root/gr-qtgui/src/lib/WaterfallDisplayPlot.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gr-qtgui/src/lib/WaterfallDisplayPlot.cc')
-rw-r--r--gr-qtgui/src/lib/WaterfallDisplayPlot.cc483
1 files changed, 483 insertions, 0 deletions
diff --git a/gr-qtgui/src/lib/WaterfallDisplayPlot.cc b/gr-qtgui/src/lib/WaterfallDisplayPlot.cc
new file mode 100644
index 0000000000..0f15d95fd7
--- /dev/null
+++ b/gr-qtgui/src/lib/WaterfallDisplayPlot.cc
@@ -0,0 +1,483 @@
+#ifndef WATERFALL_DISPLAY_PLOT_C
+#define WATERFALL_DISPLAY_PLOT_C
+
+#include <WaterfallDisplayPlot.h>
+
+#include <qwt_color_map.h>
+#include <qwt_scale_widget.h>
+#include <qwt_scale_draw.h>
+#include <qwt_plot_zoomer.h>
+#include <qwt_plot_panner.h>
+#include <qwt_plot_layout.h>
+
+#include <qapplication.h>
+
+class FreqOffsetAndPrecisionClass
+{
+public:
+ FreqOffsetAndPrecisionClass(const int freqPrecision){
+ _frequencyPrecision = freqPrecision;
+ _centerFrequency = 0;
+ }
+
+ virtual ~FreqOffsetAndPrecisionClass(){
+
+ }
+
+ virtual unsigned int GetFrequencyPrecision()const{
+ return _frequencyPrecision;
+ }
+
+ virtual void SetFrequencyPrecision(const unsigned int newPrecision){
+ _frequencyPrecision = newPrecision;
+ }
+
+ virtual double GetCenterFrequency()const{
+ return _centerFrequency;
+ }
+
+ virtual void SetCenterFrequency(const double newFreq){
+ _centerFrequency = newFreq;
+ }
+
+protected:
+ unsigned int _frequencyPrecision;
+ double _centerFrequency;
+
+private:
+
+};
+
+class WaterfallFreqDisplayScaleDraw: public QwtScaleDraw, public FreqOffsetAndPrecisionClass{
+public:
+ WaterfallFreqDisplayScaleDraw(const unsigned int precision):QwtScaleDraw(), FreqOffsetAndPrecisionClass(precision){
+
+ }
+
+ virtual ~WaterfallFreqDisplayScaleDraw(){
+
+ }
+
+ QwtText label(double value)const{
+ return QString("%1").arg((value + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision());
+ }
+
+ virtual void initiateUpdate(){
+ invalidateCache();
+ }
+
+protected:
+
+private:
+
+};
+
+class TimeScaleData
+{
+public:
+ TimeScaleData(){
+ timespec_reset(&_zeroTime);
+ _secondsPerLine = 1.0;
+
+ }
+
+ virtual ~TimeScaleData(){
+
+ }
+
+ virtual timespec GetZeroTime()const{
+ return _zeroTime;
+ }
+
+ virtual void SetZeroTime(const timespec newTime){
+ _zeroTime = newTime;
+ }
+
+ virtual void SetSecondsPerLine(const double newTime){
+ _secondsPerLine = newTime;
+ }
+
+ virtual double GetSecondsPerLine()const{
+ return _secondsPerLine;
+ }
+
+
+protected:
+ timespec _zeroTime;
+ double _secondsPerLine;
+
+private:
+
+};
+
+class QwtTimeScaleDraw: public QwtScaleDraw, public TimeScaleData
+{
+public:
+ QwtTimeScaleDraw():QwtScaleDraw(),TimeScaleData(){
+
+ }
+
+ virtual ~QwtTimeScaleDraw(){
+
+ }
+
+ virtual QwtText label(double value)const{
+ QwtText returnLabel("");
+
+ timespec lineTime = timespec_add(GetZeroTime(), (-value) * GetSecondsPerLine());
+ struct tm timeTm;
+ gmtime_r(&lineTime.tv_sec, &timeTm);
+ returnLabel = (QString("").sprintf("%04d/%02d/%02d\n%02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
+
+ return returnLabel;
+ }
+
+ virtual void initiateUpdate(){
+ // Do this in one call rather than when zeroTime and secondsPerLine updates is to prevent the display from being updated too often...
+ invalidateCache();
+ }
+
+protected:
+
+private:
+
+};
+
+class WaterfallZoomer: public QwtPlotZoomer, public TimeScaleData, public FreqOffsetAndPrecisionClass
+{
+public:
+ WaterfallZoomer(QwtPlotCanvas* canvas, const unsigned int freqPrecision):QwtPlotZoomer(canvas), TimeScaleData(), FreqOffsetAndPrecisionClass(freqPrecision)
+ {
+ setTrackerMode(QwtPicker::AlwaysOn);
+ }
+
+ virtual ~WaterfallZoomer(){
+
+ }
+
+ virtual void updateTrackerText(){
+ updateDisplay();
+ }
+
+protected:
+ virtual QwtText trackerText( const QwtDoublePoint& p ) const
+ {
+ QString yLabel("");
+
+ timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine());
+ struct tm timeTm;
+ gmtime_r(&lineTime.tv_sec, &timeTm);
+ yLabel = (QString("").sprintf("%04d/%02d/%02d %02d:%02d:%02d.%03ld", timeTm.tm_year+1900, timeTm.tm_mon+1, timeTm.tm_mday, timeTm.tm_hour, timeTm.tm_min, timeTm.tm_sec, lineTime.tv_nsec/1000000));
+
+ QwtText t(QString("%1 %2, %3").arg((p.x() + GetCenterFrequency()) / ((GetFrequencyPrecision() == 0) ? 1.0 : 1000.0), 0, 'f', GetFrequencyPrecision()).arg( (GetFrequencyPrecision() == 0) ? "Hz" : "kHz").arg(yLabel));
+
+ return t;
+ }
+};
+
+
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_WHITE_HOT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_BLACK_HOT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_INCANDESCENT;
+const int WaterfallDisplayPlot::INTENSITY_COLOR_MAP_TYPE_USER_DEFINED;
+
+WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent):QwtPlot(parent){
+ _zoomer = NULL;
+ _startFrequency = 0;
+ _stopFrequency = 4000;
+
+ resize(parent->width(), parent->height());
+ _numPoints = 1024;
+
+ _displayIntervalTime = (1.0/5.0); // 1/5 of a second between updates
+
+ _waterfallData = new WaterfallData(_startFrequency, _stopFrequency, _numPoints, 200);
+
+ QPalette palette;
+ palette.setColor(canvas()->backgroundRole(), QColor("white"));
+ canvas()->setPalette(palette);
+
+ setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
+ setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(0));
+
+ setAxisTitle(QwtPlot::yLeft, "Time");
+ setAxisScaleDraw(QwtPlot::yLeft, new QwtTimeScaleDraw());
+
+ timespec_reset(&_lastReplot);
+
+ d_spectrogram = new PlotWaterfall(_waterfallData, "Waterfall Display");
+
+ _intensityColorMapType = INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR;
+
+ QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
+ colorMap.addColorStop(0.25, Qt::cyan);
+ colorMap.addColorStop(0.5, Qt::yellow);
+ colorMap.addColorStop(0.75, Qt::red);
+
+ d_spectrogram->setColorMap(colorMap);
+
+ d_spectrogram->attach(this);
+
+ // LeftButton for the zooming
+ // MidButton for the panning
+ // RightButton: zoom out by 1
+ // Ctrl+RighButton: zoom out to full size
+
+ _zoomer = new WaterfallZoomer(canvas(), 0);
+#if QT_VERSION < 0x040000
+ _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
+ Qt::RightButton, Qt::ControlModifier);
+#else
+ _zoomer->setMousePattern(QwtEventPattern::MouseSelect2,
+ Qt::RightButton, Qt::ControlModifier);
+#endif
+ _zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
+ Qt::RightButton);
+
+ _panner = new QwtPlotPanner(canvas());
+ _panner->setAxisEnabled(QwtPlot::yRight, false);
+ _panner->setMouseButton(Qt::MidButton);
+
+ // Avoid jumping when labels with more/less digits
+ // appear/disappear when scrolling vertically
+
+ const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font());
+ QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft);
+ sd->setMinimumExtent( fm.width("100.00") );
+
+ const QColor c(Qt::white);
+ _zoomer->setRubberBandPen(c);
+ _zoomer->setTrackerPen(c);
+
+ _UpdateIntensityRangeDisplay();
+}
+
+WaterfallDisplayPlot::~WaterfallDisplayPlot(){
+ delete _waterfallData;
+}
+
+void WaterfallDisplayPlot::Reset(){
+ _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints);
+ _waterfallData->Reset();
+
+ // Load up the new base zoom settings
+ QwtDoubleRect newSize = _zoomer->zoomBase();
+ newSize.setLeft(_startFrequency);
+ newSize.setWidth(_stopFrequency-_startFrequency);
+ _zoomer->zoom(newSize);
+ _zoomer->setZoomBase(newSize);
+ _zoomer->zoom(0);
+}
+
+void WaterfallDisplayPlot::SetFrequencyRange(const double startFreq, const double stopFreq, const double centerFreq, const bool useCenterFrequencyFlag){
+ if((stopFreq > 0) && (stopFreq > startFreq)){
+ _startFrequency = startFreq;
+ _stopFrequency = stopFreq;
+
+ if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){
+ WaterfallFreqDisplayScaleDraw* freqScale = ((WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom));
+ freqScale->SetCenterFrequency(centerFreq);
+ ((WaterfallZoomer*)_zoomer)->SetCenterFrequency(centerFreq);
+ if(useCenterFrequencyFlag){
+ freqScale->SetFrequencyPrecision( 3 );
+ ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 3 );
+ setAxisTitle(QwtPlot::xBottom, "Frequency (kHz)");
+ }
+ else{
+ freqScale->SetFrequencyPrecision( 0 );
+ ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision( 0 );
+ setAxisTitle(QwtPlot::xBottom, "Frequency (Hz)");
+ }
+ }
+
+ Reset();
+
+ // Only replot if screen is visible
+ if(isVisible()){
+ replot();
+ }
+ }
+}
+
+
+double WaterfallDisplayPlot::GetStartFrequency()const{
+ return _startFrequency;
+}
+
+double WaterfallDisplayPlot::GetStopFrequency()const{
+ return _stopFrequency;
+}
+
+void WaterfallDisplayPlot::PlotNewData(const double* dataPoints, const int64_t numDataPoints, const double timePerFFT, const timespec timestamp, const int droppedFrames){
+ if(numDataPoints > 0){
+ if(numDataPoints != _numPoints){
+ _numPoints = numDataPoints;
+
+ Reset();
+
+ d_spectrogram->invalidateCache();
+ d_spectrogram->itemChanged();
+
+ if(isVisible()){
+ replot();
+ }
+
+ _lastReplot = get_highres_clock();
+ }
+
+ _waterfallData->addFFTData(dataPoints, numDataPoints, droppedFrames);
+ _waterfallData->IncrementNumLinesToUpdate();
+
+ QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
+ timeScale->SetSecondsPerLine(timePerFFT);
+ timeScale->SetZeroTime(timestamp);
+
+ ((WaterfallZoomer*)_zoomer)->SetSecondsPerLine(timePerFFT);
+ ((WaterfallZoomer*)_zoomer)->SetZeroTime(timestamp);
+ }
+
+ // Allow at least a 50% duty cycle
+ if(diff_timespec(get_highres_clock(), _lastReplot) > _displayIntervalTime){
+
+ d_spectrogram->invalidateCache();
+ d_spectrogram->itemChanged();
+
+ // Only update when window is visible
+ if(isVisible()){
+ replot();
+ }
+
+ _lastReplot = get_highres_clock();
+ }
+}
+
+void WaterfallDisplayPlot::SetIntensityRange(const double minIntensity, const double maxIntensity){
+ _waterfallData->setRange(QwtDoubleInterval(minIntensity, maxIntensity));
+
+ emit UpdatedLowerIntensityLevel(minIntensity);
+ emit UpdatedUpperIntensityLevel(maxIntensity);
+
+ _UpdateIntensityRangeDisplay();
+}
+
+void WaterfallDisplayPlot::replot(){
+ const timespec startTime = get_highres_clock();
+
+ QwtTimeScaleDraw* timeScale = (QwtTimeScaleDraw*)axisScaleDraw(QwtPlot::yLeft);
+ timeScale->initiateUpdate();
+
+ WaterfallFreqDisplayScaleDraw* freqScale = (WaterfallFreqDisplayScaleDraw*)axisScaleDraw(QwtPlot::xBottom);
+ freqScale->initiateUpdate();
+
+ // Update the time axis display
+ if(axisWidget(QwtPlot::yLeft) != NULL){
+ axisWidget(QwtPlot::yLeft)->update();
+ }
+
+ // Update the Frequency Offset Display
+ if(axisWidget(QwtPlot::xBottom) != NULL){
+ axisWidget(QwtPlot::xBottom)->update();
+ }
+
+ if(_zoomer != NULL){
+ ((WaterfallZoomer*)_zoomer)->updateTrackerText();
+ }
+
+ QwtPlot::replot();
+
+ double differenceTime = (diff_timespec(get_highres_clock(), startTime));
+
+ // Require at least a 5% duty cycle
+ differenceTime *= 19.0;
+ if(differenceTime > (1.0/5.0)){
+ _displayIntervalTime = differenceTime;
+ }
+}
+
+int WaterfallDisplayPlot::GetIntensityColorMapType()const{
+ return _intensityColorMapType;
+}
+
+void WaterfallDisplayPlot::SetIntensityColorMapType(const int newType, const QColor lowColor, const QColor highColor){
+ if((_intensityColorMapType != newType) ||
+ ((newType == INTENSITY_COLOR_MAP_TYPE_USER_DEFINED) &&
+ (lowColor.isValid() && highColor.isValid()))){
+ switch(newType){
+ case INTENSITY_COLOR_MAP_TYPE_MULTI_COLOR:{
+ _intensityColorMapType = newType;
+ QwtLinearColorMap colorMap(Qt::darkCyan, Qt::white);
+ colorMap.addColorStop(0.25, Qt::cyan);
+ colorMap.addColorStop(0.5, Qt::yellow);
+ colorMap.addColorStop(0.75, Qt::red);
+ d_spectrogram->setColorMap(colorMap);
+ break;
+ }
+ case INTENSITY_COLOR_MAP_TYPE_WHITE_HOT:{
+ _intensityColorMapType = newType;
+ QwtLinearColorMap colorMap(Qt::black, Qt::white);
+ d_spectrogram->setColorMap(colorMap);
+ break;
+ }
+ case INTENSITY_COLOR_MAP_TYPE_BLACK_HOT:{
+ _intensityColorMapType = newType;
+ QwtLinearColorMap colorMap(Qt::white, Qt::black);
+ d_spectrogram->setColorMap(colorMap);
+ break;
+ }
+ case INTENSITY_COLOR_MAP_TYPE_INCANDESCENT:{
+ _intensityColorMapType = newType;
+ QwtLinearColorMap colorMap(Qt::black, Qt::white);
+ colorMap.addColorStop(0.5, Qt::darkRed);
+ d_spectrogram->setColorMap(colorMap);
+ break;
+ }
+ case INTENSITY_COLOR_MAP_TYPE_USER_DEFINED:{
+ _userDefinedLowIntensityColor = lowColor;
+ _userDefinedHighIntensityColor = highColor;
+ _intensityColorMapType = newType;
+ QwtLinearColorMap colorMap(_userDefinedLowIntensityColor, _userDefinedHighIntensityColor);
+ d_spectrogram->setColorMap(colorMap);
+ break;
+ }
+ default: break;
+ }
+
+ _UpdateIntensityRangeDisplay();
+ }
+}
+
+const QColor WaterfallDisplayPlot::GetUserDefinedLowIntensityColor()const{
+ return _userDefinedLowIntensityColor;
+}
+
+const QColor WaterfallDisplayPlot::GetUserDefinedHighIntensityColor()const{
+ return _userDefinedHighIntensityColor;
+}
+
+void WaterfallDisplayPlot::_UpdateIntensityRangeDisplay(){
+ QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight);
+ rightAxis->setTitle("Intensity (dB)");
+ rightAxis->setColorBarEnabled(true);
+ rightAxis->setColorMap(d_spectrogram->data()->range(),
+ d_spectrogram->colorMap());
+
+ setAxisScale(QwtPlot::yRight,
+ d_spectrogram->data()->range().minValue(),
+ d_spectrogram->data()->range().maxValue() );
+ enableAxis(QwtPlot::yRight);
+
+ plotLayout()->setAlignCanvasToScales(true);
+
+ // Tell the display to redraw everything
+ d_spectrogram->invalidateCache();
+ d_spectrogram->itemChanged();
+
+ // Draw again
+ replot();
+
+ // Update the last replot timer
+ _lastReplot = get_highres_clock();
+}
+
+#endif /* WATERFALL_DISPLAY_PLOT_C */