diff options
Diffstat (limited to 'gr-qtgui/src/lib/WaterfallDisplayPlot.cc')
-rw-r--r-- | gr-qtgui/src/lib/WaterfallDisplayPlot.cc | 483 |
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 */ |