diff options
Diffstat (limited to 'gr-qtgui/lib/WaterfallDisplayPlot.cc')
-rw-r--r-- | gr-qtgui/lib/WaterfallDisplayPlot.cc | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/gr-qtgui/lib/WaterfallDisplayPlot.cc b/gr-qtgui/lib/WaterfallDisplayPlot.cc new file mode 100644 index 000000000..52381a9f6 --- /dev/null +++ b/gr-qtgui/lib/WaterfallDisplayPlot.cc @@ -0,0 +1,583 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008,2009,2010,2011 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#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> + +#include <boost/date_time/posix_time/posix_time.hpp> +namespace pt = boost::posix_time; + +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, 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 + { + timespec lineTime = timespec_add(GetZeroTime(), (-value) * GetSecondsPerLine()); + std::string time_str = pt::to_simple_string(pt::from_time_t(lineTime.tv_sec)); + + // lops off the YYYY-mmm-DD part of the string + size_t ind = time_str.find(" "); + if(ind != std::string::npos) + time_str = time_str.substr(ind); + return QwtText(QString("").sprintf("%s.%03ld", time_str.c_str(), lineTime.tv_nsec/1000000)); + } + + 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(); + } + + void SetUnitType(const std::string &type) + { + _unitType = type; + } + +protected: + using QwtPlotZoomer::trackerText; + virtual QwtText trackerText( const QwtDoublePoint& p ) const + { + timespec lineTime = timespec_add(GetZeroTime(), (-p.y()) * GetSecondsPerLine()); + std::string time_str = pt::to_simple_string(pt::from_time_t(lineTime.tv_sec)); + + // lops off the YYYY-mmm-DD part of the string + size_t ind = time_str.find(" "); + if(ind != std::string::npos) + time_str = time_str.substr(ind); + QString yLabel(QString("").sprintf("%s.%03ld", time_str.c_str(), lineTime.tv_nsec/1000000)); + + QwtText t(QString("%1 %2, %3"). + arg(p.x(), 0, 'f', GetFrequencyPrecision()). + arg(_unitType.c_str()).arg(yLabel)); + return t; + } + +private: + std::string _unitType; +}; + + +WaterfallDisplayPlot::WaterfallDisplayPlot(QWidget* parent) + : QwtPlot(parent) +{ + _zoomer = NULL; + _startFrequency = 0; + _stopFrequency = 4000; + + resize(parent->width(), parent->height()); + _numPoints = 1024; + + _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); + _zoomer->setSelectionFlags(QwtPicker::RectSelection | QwtPicker::DragSelection); + _zoomer->setMousePattern(QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier); + _zoomer->setMousePattern(QwtEventPattern::MouseSelect3, + Qt::RightButton); + + _panner = new QwtPlotPanner(canvas()); + _panner->setAxisEnabled(QwtPlot::yRight, false); + _panner->setMouseButton(Qt::MidButton); + + // emit the position of clicks on widget + _picker = new QwtDblClickPlotPicker(canvas()); + connect(_picker, SIGNAL(selected(const QwtDoublePoint &)), + this, SLOT(OnPickerPointSelected(const QwtDoublePoint &))); + + // 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(); + + _xAxisMultiplier = 1; +} + +WaterfallDisplayPlot::~WaterfallDisplayPlot() +{ + delete _waterfallData; + delete d_spectrogram; +} + +void +WaterfallDisplayPlot::Reset() +{ + _waterfallData->ResizeData(_startFrequency, _stopFrequency, _numPoints); + _waterfallData->Reset(); + + setAxisScale(QwtPlot::xBottom, _startFrequency, _stopFrequency); + + // 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 constStartFreq, + const double constStopFreq, + const double constCenterFreq, + const bool useCenterFrequencyFlag, + const double units, const std::string &strunits) +{ + double startFreq = constStartFreq / units; + double stopFreq = constStopFreq / units; + double centerFreq = constCenterFreq / units; + + _xAxisMultiplier = units; + + _useCenterFrequencyFlag = useCenterFrequencyFlag; + + if(_useCenterFrequencyFlag){ + startFreq = (startFreq + centerFreq); + stopFreq = (stopFreq + centerFreq); + } + + bool reset = false; + if((startFreq != _startFrequency) || (stopFreq != _stopFrequency)) + reset = true; + + if(stopFreq > startFreq) { + _startFrequency = startFreq; + _stopFrequency = stopFreq; + + if((axisScaleDraw(QwtPlot::xBottom) != NULL) && (_zoomer != NULL)){ + double display_units = ceil(log10(units)/2.0); + setAxisScaleDraw(QwtPlot::xBottom, new WaterfallFreqDisplayScaleDraw(display_units)); + setAxisTitle(QwtPlot::xBottom, QString("Frequency (%1)").arg(strunits.c_str())); + + if(reset) { + Reset(); + } + + ((WaterfallZoomer*)_zoomer)->SetFrequencyPrecision(display_units); + ((WaterfallZoomer*)_zoomer)->SetUnitType(strunits); + } + } +} + + +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(); + } + + if(diff_timespec(get_highres_clock(), _lastReplot) > timePerFFT) { + //FIXME: We may want to average the data between these updates to smooth display + _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); + + d_spectrogram->invalidateCache(); + d_spectrogram->itemChanged(); + + 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() +{ + 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(); +} + +void +WaterfallDisplayPlot::resizeSlot( QSize *s ) +{ + resize(s->width(), s->height()); +} + +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(); +} + +void +WaterfallDisplayPlot::OnPickerPointSelected(const QwtDoublePoint & p) +{ + QPointF point = p; + //fprintf(stderr,"OnPickerPointSelected %f %f %d\n", point.x(), point.y(), _xAxisMultiplier); + point.setX(point.x() * _xAxisMultiplier); + emit plotPointSelected(point); +} + +#endif /* WATERFALL_DISPLAY_PLOT_C */ |